models.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """
  2. Tool Models - 工具系统核心数据模型
  3. 定义:
  4. 1. ToolResult: 工具执行结果(支持双层记忆管理)
  5. 2. ToolContext: 工具执行上下文(依赖注入)
  6. """
  7. from dataclasses import dataclass, field
  8. from typing import Any, Dict, List, Optional, Protocol
  9. @dataclass
  10. class ToolResult:
  11. """
  12. 工具执行结果
  13. 支持双层记忆管理(参考 Browser-Use 的 ActionResult):
  14. - output: 主要输出,可能很长,可以配置只给 LLM 看一次
  15. - long_term_memory: 简短摘要,永久保存在对话历史中
  16. 这种设计避免了大量临时内容占用 context。
  17. """
  18. # 主要输出(临时内容)
  19. title: str # 简短标题,用于展示
  20. output: str # 主要输出内容
  21. # 记忆管理
  22. long_term_memory: Optional[str] = None # 永久记忆(简短摘要)
  23. include_output_only_once: bool = False # output 是否只给 LLM 看一次
  24. # 元数据
  25. metadata: Dict[str, Any] = field(default_factory=dict)
  26. # 状态标志
  27. truncated: bool = False # 输出是否被截断
  28. error: Optional[str] = None # 错误信息(如果执行失败)
  29. # 附件支持(用于浏览器自动化等场景)
  30. attachments: List[str] = field(default_factory=list) # 文件路径列表
  31. images: List[Dict[str, Any]] = field(default_factory=list) # 图片列表
  32. # Token追踪(用于工具内部LLM调用)
  33. tool_usage: Optional[Dict[str, Any]] = None # 格式:{"model": "...", "prompt_tokens": 100, "completion_tokens": 50, "cost": 0.0}
  34. def to_llm_message(self, first_time: bool = True) -> str:
  35. """
  36. 转换为给 LLM 的消息
  37. Args:
  38. first_time: 是否第一次展示(影响 include_output_only_once 的行为)
  39. Returns:
  40. 给 LLM 的消息字符串
  41. """
  42. # 有错误: 错误摘要 + 详细输出一起返回。output 常含命令 stdout/stderr 的真实报错
  43. # (如 wf-patch 的「✗ 某字段 不合法」), 不能丢 —— 否则 LLM 只看到 "exit code 1"
  44. # 无从修复, 只能瞎重试到死。
  45. if self.error:
  46. if self.output and self.output.strip() and self.output.strip() != "(命令无输出)":
  47. return f"Error: {self.error}\n\n{self.output}"
  48. return f"Error: {self.error}"
  49. # 构建消息
  50. parts = []
  51. # 标题
  52. if self.title:
  53. parts.append(f"# {self.title}")
  54. # 主要输出
  55. if first_time or not self.include_output_only_once:
  56. if self.output:
  57. parts.append(self.output)
  58. if self.truncated:
  59. parts.append("(Output truncated)")
  60. # 长期记忆(永远包含)
  61. if self.long_term_memory:
  62. parts.append(f"\nSummary: {self.long_term_memory}")
  63. # 附件信息
  64. if self.attachments:
  65. parts.append(f"\nAttachments: {', '.join(self.attachments)}")
  66. return "\n\n".join(parts)
  67. class ToolContext(Protocol):
  68. """
  69. 工具执行上下文(依赖注入)
  70. 工具函数可以声明需要哪些上下文字段,框架自动注入。
  71. 使用 Protocol 允许不同实现提供不同的上下文字段。
  72. """
  73. # 基础字段(所有工具都可用)
  74. trace_id: str
  75. step_id: str
  76. uid: Optional[str]
  77. # 浏览器相关(Browser-Use 集成)
  78. browser_session: Optional[Any] # BrowserSession 实例
  79. page_url: Optional[str] # 当前页面 URL
  80. file_system: Optional[Any] # FileSystem 实例
  81. sensitive_data: Optional[Dict[str, Any]] # 敏感数据字典
  82. # 其他可扩展字段
  83. context: Optional[Dict[str, Any]] # 额外上下文数据
  84. @dataclass
  85. class ToolContextImpl:
  86. """ToolContext 的默认实现"""
  87. # 基础字段
  88. trace_id: str
  89. step_id: str
  90. uid: Optional[str] = None
  91. # 浏览器相关
  92. browser_session: Optional[Any] = None
  93. page_url: Optional[str] = None
  94. file_system: Optional[Any] = None
  95. sensitive_data: Optional[Dict[str, Any]] = None
  96. # 额外上下文
  97. context: Optional[Dict[str, Any]] = None