| """ |
| TRJ 数据模型定义 |
| |
| 定义标准的轨迹数据结构,支持: |
| 1. ReAct 模式:Context -> Thought -> Action -> Observation |
| 2. Workflow 模式:State_In -> Node Processing -> State_Update |
| """ |
|
|
| from dataclasses import dataclass, field |
| from datetime import datetime |
| from enum import Enum |
| from typing import Any, Dict, List, Optional |
| import uuid |
|
|
|
|
| class TrajectoryMode(str, Enum): |
| """轨迹模式""" |
| REACT = "react" |
| WORKFLOW = "workflow" |
| HYBRID = "hybrid" |
|
|
|
|
| class StepRole(str, Enum): |
| """步骤角色""" |
| AGENT = "agent" |
| ENVIRONMENT = "environment" |
| SYSTEM_NODE = "system_node" |
| TOOL = "tool" |
| USER = "user" |
|
|
|
|
| class ActionType(str, Enum): |
| """动作类型""" |
| TOOL_CALL = "tool_call" |
| RESPONSE = "response" |
| STATE_UPDATE = "state_update" |
| LLM_CALL = "llm_call" |
| MULTIMODAL = "multimodal" |
|
|
|
|
| @dataclass |
| class ToolCallRecord: |
| """工具调用记录""" |
| tool_name: str |
| tool_args: Dict[str, Any] |
| tool_result: Any |
| timestamp: str |
| duration_ms: Optional[float] = None |
| error: Optional[str] = None |
|
|
|
|
| @dataclass |
| class LLMCallRecord: |
| """LLM 调用记录""" |
| model: str |
| messages_in: List[Dict[str, Any]] |
| response: str |
| timestamp: str |
| duration_ms: Optional[float] = None |
| token_usage: Optional[Dict[str, int]] = None |
| temperature: Optional[float] = None |
|
|
|
|
| @dataclass |
| class MultimodalData: |
| """多模态数据""" |
| type: str |
| path: Optional[str] = None |
| url: Optional[str] = None |
| base64: Optional[str] = None |
| metadata: Dict[str, Any] = field(default_factory=dict) |
|
|
|
|
| @dataclass |
| class TrajectoryStep: |
| """ |
| 单个执行步骤 |
| |
| 对于 ReAct 模式: |
| - input_context: Agent 看到的上下文 |
| - thought: Agent 的思考过程 |
| - action_type: 动作类型(tool_call/response) |
| - action_payload: 动作内容 |
| - observation: 环境反馈 |
| |
| 对于 Workflow 模式: |
| - input_context: 节点输入状态 |
| - node_output: 节点输出/状态更新 |
| """ |
| step_index: int |
| node_name: str |
| role: str |
| timestamp: str |
| |
| |
| input_context: Dict[str, Any] = field(default_factory=dict) |
| |
| |
| thought: Optional[str] = None |
| action_type: Optional[str] = None |
| action_payload: Optional[Dict[str, Any]] = None |
| observation: Optional[str] = None |
| |
| |
| node_output: Optional[Dict[str, Any]] = None |
| |
| |
| llm_calls: List[LLMCallRecord] = field(default_factory=list) |
| tool_calls: List[ToolCallRecord] = field(default_factory=list) |
| |
| |
| multimodal_input: Optional[MultimodalData] = None |
| multimodal_output: Optional[MultimodalData] = None |
| |
| |
| error: Optional[str] = None |
| |
| |
| duration_ms: Optional[float] = None |
| |
| |
| metadata: Dict[str, Any] = field(default_factory=dict) |
|
|
| def to_dict(self) -> Dict[str, Any]: |
| """转换为字典""" |
| result = { |
| "step_index": self.step_index, |
| "node_name": self.node_name, |
| "role": self.role, |
| "timestamp": self.timestamp, |
| } |
| |
| |
| if self.input_context: |
| result["input_context"] = self.input_context |
| if self.thought: |
| result["thought"] = self.thought |
| if self.action_type: |
| result["action_type"] = self.action_type |
| if self.action_payload: |
| result["action_payload"] = self.action_payload |
| if self.observation: |
| result["observation"] = self.observation |
| if self.node_output: |
| result["node_output"] = self.node_output |
| if self.llm_calls: |
| result["llm_calls"] = [self._llm_call_to_dict(c) for c in self.llm_calls] |
| if self.tool_calls: |
| result["tool_calls"] = [self._tool_call_to_dict(c) for c in self.tool_calls] |
| if self.multimodal_input: |
| result["multimodal_input"] = self._multimodal_to_dict(self.multimodal_input) |
| if self.multimodal_output: |
| result["multimodal_output"] = self._multimodal_to_dict(self.multimodal_output) |
| if self.error: |
| result["error"] = self.error |
| if self.duration_ms is not None: |
| result["duration_ms"] = self.duration_ms |
| if self.metadata: |
| result["metadata"] = self.metadata |
| |
| return result |
| |
| @staticmethod |
| def _llm_call_to_dict(call: LLMCallRecord) -> Dict[str, Any]: |
| return { |
| "model": call.model, |
| "messages_in": call.messages_in, |
| "response": call.response, |
| "timestamp": call.timestamp, |
| "duration_ms": call.duration_ms, |
| "token_usage": call.token_usage, |
| "temperature": call.temperature, |
| } |
| |
| @staticmethod |
| def _tool_call_to_dict(call: ToolCallRecord) -> Dict[str, Any]: |
| return { |
| "tool_name": call.tool_name, |
| "tool_args": call.tool_args, |
| "tool_result": call.tool_result, |
| "timestamp": call.timestamp, |
| "duration_ms": call.duration_ms, |
| "error": call.error, |
| } |
| |
| @staticmethod |
| def _multimodal_to_dict(data: MultimodalData) -> Dict[str, Any]: |
| result = {"type": data.type} |
| if data.path: |
| result["path"] = data.path |
| if data.url: |
| result["url"] = data.url |
| if data.metadata: |
| result["metadata"] = data.metadata |
| |
| return result |
|
|
|
|
| @dataclass |
| class TrajectoryFeedback: |
| """用户反馈""" |
| score: Optional[int] = None |
| comment: Optional[str] = None |
| edited_response: Optional[str] = None |
| labels: List[str] = field(default_factory=list) |
| timestamp: Optional[str] = None |
|
|
|
|
| @dataclass |
| class Trajectory: |
| """ |
| 完整的执行轨迹 |
| |
| 包含三个核心部分: |
| 1. Metadata: 元数据 |
| 2. Steps: 执行步骤列表 |
| 3. Outcome: 最终结果和反馈 |
| """ |
| |
| trace_id: str |
| workflow_name: str |
| timestamp: str |
| status: str |
| mode: str |
| |
| |
| user_id: Optional[str] = None |
| session_id: Optional[str] = None |
| version: str = "1.0" |
| |
| |
| inputs: Dict[str, Any] = field(default_factory=dict) |
| |
| |
| steps: List[TrajectoryStep] = field(default_factory=list) |
| |
| |
| final_output: Any = None |
| |
| |
| feedback: Optional[TrajectoryFeedback] = None |
| |
| |
| total_duration_ms: Optional[float] = None |
| total_llm_calls: int = 0 |
| total_tool_calls: int = 0 |
| total_tokens: Optional[Dict[str, int]] = None |
| |
| |
| metadata: Dict[str, Any] = field(default_factory=dict) |
|
|
| @staticmethod |
| def generate_trace_id() -> str: |
| """生成唯一的 trace_id""" |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| unique_id = uuid.uuid4().hex[:8] |
| return f"trj_{timestamp}_{unique_id}" |
|
|
| def add_step(self, step: TrajectoryStep): |
| """添加执行步骤""" |
| self.steps.append(step) |
| |
| self.total_llm_calls += len(step.llm_calls) |
| self.total_tool_calls += len(step.tool_calls) |
|
|
| def set_feedback(self, score: int = None, comment: str = None, |
| edited_response: str = None, labels: List[str] = None): |
| """设置用户反馈""" |
| self.feedback = TrajectoryFeedback( |
| score=score, |
| comment=comment, |
| edited_response=edited_response, |
| labels=labels or [], |
| timestamp=datetime.now().isoformat() |
| ) |
|
|
| def to_dict(self) -> Dict[str, Any]: |
| """转换为字典(用于 JSON 导出)""" |
| result = { |
| |
| "trace_id": self.trace_id, |
| "workflow_name": self.workflow_name, |
| "timestamp": self.timestamp, |
| "status": self.status, |
| "mode": self.mode, |
| "version": self.version, |
| |
| |
| "inputs": self.inputs, |
| |
| |
| "steps": [step.to_dict() for step in self.steps], |
| |
| |
| "final_output": self.final_output, |
| |
| |
| "statistics": { |
| "total_steps": len(self.steps), |
| "total_llm_calls": self.total_llm_calls, |
| "total_tool_calls": self.total_tool_calls, |
| "total_duration_ms": self.total_duration_ms, |
| "total_tokens": self.total_tokens, |
| } |
| } |
| |
| |
| if self.user_id: |
| result["user_id"] = self.user_id |
| if self.session_id: |
| result["session_id"] = self.session_id |
| if self.feedback: |
| result["feedback"] = { |
| "score": self.feedback.score, |
| "comment": self.feedback.comment, |
| "edited_response": self.feedback.edited_response, |
| "labels": self.feedback.labels, |
| "timestamp": self.feedback.timestamp, |
| } |
| if self.metadata: |
| result["metadata"] = self.metadata |
| |
| return result |
|
|
| def to_sft_format(self) -> List[Dict[str, str]]: |
| """ |
| 转换为 SFT 训练格式(OpenAI messages 格式) |
| |
| Returns: |
| [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}] |
| """ |
| messages = [] |
| |
| for step in self.steps: |
| if step.role == StepRole.AGENT.value: |
| |
| content = "" |
| if step.thought: |
| content += f"<thought>{step.thought}</thought>\n" |
| if step.action_type == ActionType.TOOL_CALL.value and step.action_payload: |
| tool_name = step.action_payload.get("tool_name", "") |
| tool_args = step.action_payload.get("tool_args", "") |
| content += f"<call>{tool_name}({tool_args})</call>" |
| elif step.node_output: |
| content += str(step.node_output) |
| |
| if content: |
| messages.append({"role": "assistant", "content": content}) |
| |
| elif step.role in [StepRole.ENVIRONMENT.value, StepRole.TOOL.value]: |
| |
| if step.observation: |
| messages.append({"role": "tool", "content": step.observation}) |
| |
| elif step.role == StepRole.USER.value: |
| |
| if step.input_context: |
| content = step.input_context.get("query", str(step.input_context)) |
| messages.append({"role": "user", "content": content}) |
| |
| return messages |
|
|
| def to_dpo_format(self) -> Dict[str, Any]: |
| """ |
| 转换为 DPO 训练格式 |
| |
| Returns: |
| {"prompt": "...", "chosen": [...], "rejected": [...]} |
| """ |
| |
| prompt = self.inputs.get("query", self.inputs.get("target", "")) |
| |
| |
| trajectory_steps = self.to_sft_format() |
| |
| return { |
| "prompt": prompt, |
| "trajectory": trajectory_steps, |
| "score": self.feedback.score if self.feedback else None, |
| "status": self.status, |
| } |
|
|