models.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. """
  2. Trace 和 Step 数据模型
  3. Trace: 一次完整的 LLM 交互(单次调用或 Agent 任务)
  4. Step: Trace 中的一个原子操作,形成树结构
  5. """
  6. from dataclasses import dataclass, field
  7. from datetime import datetime
  8. from typing import Dict, Any, List, Optional, Literal
  9. import uuid
  10. # Step 类型
  11. StepType = Literal[
  12. # 计划相关
  13. "goal", # 目标/计划项(可以有子 steps)
  14. # LLM 输出
  15. "thought", # 思考/分析(中间过程)
  16. "evaluation", # 评估总结(需要 summary)
  17. "response", # 最终回复
  18. # 工具相关(数据结构上分开以保留描述能力,可视化时候可能合并)
  19. "action", # 工具调用(tool_call)
  20. "result", # 工具结果(tool_result)
  21. # 系统相关
  22. "memory_read", # 读取记忆(经验/技能)
  23. "memory_write", # 写入记忆
  24. ]
  25. # Step 状态
  26. StepStatus = Literal[
  27. "planned", # 计划中(未执行)
  28. "in_progress", # 执行中
  29. "awaiting_approval", # 等待用户确认
  30. "completed", # 已完成
  31. "failed", # 失败
  32. "skipped", # 跳过
  33. ]
  34. @dataclass
  35. class Trace:
  36. """
  37. 执行轨迹 - 一次完整的 LLM 交互
  38. 单次调用: mode="call", 只有 1 个 Step
  39. Agent 模式: mode="agent", 多个 Steps 形成树结构
  40. """
  41. trace_id: str
  42. mode: Literal["call", "agent"]
  43. # Prompt 标识(可选)
  44. prompt_name: Optional[str] = None
  45. # Agent 模式特有
  46. task: Optional[str] = None
  47. agent_type: Optional[str] = None
  48. # 状态
  49. status: Literal["running", "completed", "failed"] = "running"
  50. # 统计
  51. total_steps: int = 0
  52. total_tokens: int = 0
  53. total_cost: float = 0.0
  54. total_duration_ms: int = 0 # 总耗时(毫秒)
  55. # 进度追踪(head)
  56. last_sequence: int = 0 # 最新 step 的 sequence
  57. last_event_id: int = 0 # 最新事件 ID(用于 WS 续传)
  58. # 上下文
  59. uid: Optional[str] = None
  60. context: Dict[str, Any] = field(default_factory=dict)
  61. # 当前焦点 goal(用于 step 工具)
  62. current_goal_id: Optional[str] = None
  63. # 时间
  64. created_at: datetime = field(default_factory=datetime.now)
  65. completed_at: Optional[datetime] = None
  66. @classmethod
  67. def create(
  68. cls,
  69. mode: Literal["call", "agent"],
  70. **kwargs
  71. ) -> "Trace":
  72. """创建新的 Trace"""
  73. return cls(
  74. trace_id=str(uuid.uuid4()),
  75. mode=mode,
  76. **kwargs
  77. )
  78. def to_dict(self) -> Dict[str, Any]:
  79. """转换为字典"""
  80. return {
  81. "trace_id": self.trace_id,
  82. "mode": self.mode,
  83. "prompt_name": self.prompt_name,
  84. "task": self.task,
  85. "agent_type": self.agent_type,
  86. "status": self.status,
  87. "total_steps": self.total_steps,
  88. "total_tokens": self.total_tokens,
  89. "total_cost": self.total_cost,
  90. "total_duration_ms": self.total_duration_ms,
  91. "last_sequence": self.last_sequence,
  92. "last_event_id": self.last_event_id,
  93. "uid": self.uid,
  94. "context": self.context,
  95. "current_goal_id": self.current_goal_id,
  96. "created_at": self.created_at.isoformat() if self.created_at else None,
  97. "completed_at": self.completed_at.isoformat() if self.completed_at else None,
  98. }
  99. @dataclass
  100. class Step:
  101. """
  102. 执行步骤 - Trace 中的一个原子操作
  103. Step 之间通过 parent_id 形成树结构(单父节点)
  104. ## 字段设计规则
  105. **顶层字段**(Step 类属性):
  106. - 所有(或大部分)step 都有的字段
  107. - 需要筛选/排序/索引的字段(如 tokens, cost, duration_ms)
  108. - 结构化、类型明确的字段
  109. **data 字段**(Dict):
  110. - step_type 特定的字段(不同类型有不同 schema)
  111. - 详细的业务数据(如 messages, content, arguments, output)
  112. - 可能很大的字段
  113. - 半结构化、动态的字段
  114. ## data 字段 schema(按 step_type)
  115. - thought/response: model, messages, content, tool_calls
  116. - action: tool_name, arguments
  117. - result: tool_name, output, error
  118. - memory_read: experiences_count, skills_count
  119. - goal: 自定义(根据具体目标)
  120. """
  121. step_id: str
  122. trace_id: str
  123. step_type: StepType
  124. status: StepStatus
  125. sequence: int # 在 Trace 中的顺序
  126. # 树结构(单父节点)
  127. parent_id: Optional[str] = None
  128. # 内容
  129. description: str = "" # 所有节点都有,系统自动提取
  130. # 类型相关数据
  131. data: Dict[str, Any] = field(default_factory=dict)
  132. # 仅 evaluation 类型需要
  133. summary: Optional[str] = None
  134. # UI 优化字段
  135. has_children: bool = False # 是否有子节点
  136. children_count: int = 0 # 子节点数量
  137. # 执行指标
  138. duration_ms: Optional[int] = None
  139. tokens: Optional[int] = None
  140. cost: Optional[float] = None
  141. # 时间
  142. created_at: datetime = field(default_factory=datetime.now)
  143. @classmethod
  144. def create(
  145. cls,
  146. trace_id: str,
  147. step_type: StepType,
  148. sequence: int,
  149. status: StepStatus = "completed",
  150. description: str = "",
  151. data: Dict[str, Any] = None,
  152. parent_id: Optional[str] = None,
  153. summary: Optional[str] = None,
  154. duration_ms: Optional[int] = None,
  155. tokens: Optional[int] = None,
  156. cost: Optional[float] = None,
  157. ) -> "Step":
  158. """创建新的 Step"""
  159. return cls(
  160. step_id=str(uuid.uuid4()),
  161. trace_id=trace_id,
  162. step_type=step_type,
  163. status=status,
  164. sequence=sequence,
  165. parent_id=parent_id,
  166. description=description,
  167. data=data or {},
  168. summary=summary,
  169. duration_ms=duration_ms,
  170. tokens=tokens,
  171. cost=cost,
  172. )
  173. def to_dict(self, view: str = "full") -> Dict[str, Any]:
  174. """
  175. 转换为字典
  176. Args:
  177. view: "compact" - 不返回大字段
  178. "full" - 返回完整数据
  179. """
  180. result = {
  181. "step_id": self.step_id,
  182. "trace_id": self.trace_id,
  183. "step_type": self.step_type,
  184. "status": self.status,
  185. "sequence": self.sequence,
  186. "parent_id": self.parent_id,
  187. "description": self.description,
  188. "summary": self.summary,
  189. "has_children": self.has_children,
  190. "children_count": self.children_count,
  191. "duration_ms": self.duration_ms,
  192. "tokens": self.tokens,
  193. "cost": self.cost,
  194. "created_at": self.created_at.isoformat() if self.created_at else None,
  195. }
  196. # 处理 data 字段
  197. if view == "compact":
  198. # compact 模式:移除 data 中的大字段
  199. data_copy = self.data.copy()
  200. # 移除可能的大字段(如 output, content 等)
  201. for key in ["output", "content", "full_output", "full_content"]:
  202. data_copy.pop(key, None)
  203. result["data"] = data_copy
  204. else:
  205. # full 模式:返回完整 data
  206. result["data"] = self.data
  207. return result
  208. # Step.data 结构说明
  209. #
  210. # goal:
  211. # {
  212. # "description": "探索代码库",
  213. # }
  214. #
  215. # thought:
  216. # {
  217. # "content": "需要先了解项目结构...",
  218. # }
  219. #
  220. # action:
  221. # {
  222. # "tool_name": "glob_files",
  223. # "arguments": {"pattern": "**/*.py"},
  224. # }
  225. #
  226. # result:
  227. # {
  228. # "tool_name": "glob_files",
  229. # "output": ["src/main.py", ...],
  230. # "title": "找到 15 个文件",
  231. # }
  232. #
  233. # evaluation:
  234. # {
  235. # "content": "分析完成...",
  236. # }
  237. # # summary 字段存储简短总结
  238. #
  239. # response:
  240. # {
  241. # "content": "任务已完成...",
  242. # "is_final": True,
  243. # }
  244. #
  245. # memory_read:
  246. # {
  247. # "skills": [...],
  248. # "experiences": [...],
  249. # "skills_count": 3,
  250. # "experiences_count": 5
  251. # }
  252. #
  253. # memory_write:
  254. # {
  255. # "experience_id": "...",
  256. # "condition": "...",
  257. # "rule": "..."
  258. # }