trace.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. StepType = Literal[
  11. "llm_call", # LLM 调用
  12. "tool_call", # 工具调用
  13. "tool_result", # 工具结果
  14. "conclusion", # 中间/最终结论
  15. "feedback", # 人工反馈
  16. "memory_read", # 读取记忆(经验/技能)
  17. "memory_write", # 写入记忆
  18. ]
  19. @dataclass
  20. class Trace:
  21. """
  22. 执行轨迹 - 一次完整的 LLM 交互
  23. 单次调用: mode="call", 只有 1 个 Step
  24. Agent 模式: mode="agent", 多个 Steps 形成 DAG
  25. """
  26. trace_id: str
  27. mode: Literal["call", "agent"]
  28. # Prompt 标识(可选)
  29. prompt_name: Optional[str] = None
  30. # Agent 模式特有
  31. task: Optional[str] = None
  32. agent_type: Optional[str] = None
  33. # 状态
  34. status: Literal["running", "completed", "failed"] = "running"
  35. # 统计
  36. total_steps: int = 0
  37. total_tokens: int = 0
  38. total_cost: float = 0.0
  39. # 上下文
  40. uid: Optional[str] = None
  41. context: Dict[str, Any] = field(default_factory=dict)
  42. # 时间
  43. created_at: datetime = field(default_factory=datetime.now)
  44. completed_at: Optional[datetime] = None
  45. @classmethod
  46. def create(
  47. cls,
  48. mode: Literal["call", "agent"],
  49. **kwargs
  50. ) -> "Trace":
  51. """创建新的 Trace"""
  52. return cls(
  53. trace_id=str(uuid.uuid4()),
  54. mode=mode,
  55. **kwargs
  56. )
  57. def to_dict(self) -> Dict[str, Any]:
  58. """转换为字典"""
  59. return {
  60. "trace_id": self.trace_id,
  61. "mode": self.mode,
  62. "prompt_name": self.prompt_name,
  63. "task": self.task,
  64. "agent_type": self.agent_type,
  65. "status": self.status,
  66. "total_steps": self.total_steps,
  67. "total_tokens": self.total_tokens,
  68. "total_cost": self.total_cost,
  69. "uid": self.uid,
  70. "context": self.context,
  71. "created_at": self.created_at.isoformat() if self.created_at else None,
  72. "completed_at": self.completed_at.isoformat() if self.completed_at else None,
  73. }
  74. @dataclass
  75. class Step:
  76. """
  77. 执行步骤 - Trace 中的一个原子操作
  78. Step 之间通过 parent_ids 形成 DAG 结构
  79. """
  80. step_id: str
  81. trace_id: str
  82. step_type: StepType
  83. sequence: int # 在 Trace 中的顺序
  84. # DAG 结构(支持多父节点)
  85. parent_ids: List[str] = field(default_factory=list)
  86. # 类型相关数据
  87. data: Dict[str, Any] = field(default_factory=dict)
  88. # 时间
  89. created_at: datetime = field(default_factory=datetime.now)
  90. @classmethod
  91. def create(
  92. cls,
  93. trace_id: str,
  94. step_type: StepType,
  95. sequence: int,
  96. data: Dict[str, Any] = None,
  97. parent_ids: List[str] = None,
  98. ) -> "Step":
  99. """创建新的 Step"""
  100. return cls(
  101. step_id=str(uuid.uuid4()),
  102. trace_id=trace_id,
  103. step_type=step_type,
  104. sequence=sequence,
  105. parent_ids=parent_ids or [],
  106. data=data or {},
  107. )
  108. def to_dict(self) -> Dict[str, Any]:
  109. """转换为字典"""
  110. return {
  111. "step_id": self.step_id,
  112. "trace_id": self.trace_id,
  113. "step_type": self.step_type,
  114. "sequence": self.sequence,
  115. "parent_ids": self.parent_ids,
  116. "data": self.data,
  117. "created_at": self.created_at.isoformat() if self.created_at else None,
  118. }
  119. # Step.data 结构说明
  120. #
  121. # llm_call:
  122. # {
  123. # "messages": [...],
  124. # "response": "...",
  125. # "model": "gpt-4o",
  126. # "prompt_tokens": 100,
  127. # "completion_tokens": 50,
  128. # "cost": 0.01,
  129. # "tool_calls": [...] # 如果有
  130. # }
  131. #
  132. # tool_call:
  133. # {
  134. # "tool_name": "search_blocks",
  135. # "arguments": {...},
  136. # "llm_step_id": "..." # 哪个 LLM 调用触发的
  137. # }
  138. #
  139. # tool_result:
  140. # {
  141. # "tool_call_step_id": "...",
  142. # "result": "...",
  143. # "duration_ms": 123
  144. # }
  145. #
  146. # conclusion:
  147. # {
  148. # "content": "...",
  149. # "is_final": True/False
  150. # }
  151. #
  152. # feedback:
  153. # {
  154. # "target_step_id": "...",
  155. # "feedback_type": "positive" | "negative" | "correction",
  156. # "content": "..."
  157. # }
  158. #
  159. # memory_read:
  160. # {
  161. # "skills": [...],
  162. # "experiences": [...],
  163. # "skills_count": 3,
  164. # "experiences_count": 5
  165. # }
  166. #
  167. # memory_write:
  168. # {
  169. # "experience_id": "...",
  170. # "condition": "...",
  171. # "rule": "..."
  172. # }