|
@@ -19,6 +19,7 @@ from agent.models.memory import Experience, Skill
|
|
|
from agent.storage.protocols import TraceStore, MemoryStore, StateStore
|
|
from agent.storage.protocols import TraceStore, MemoryStore, StateStore
|
|
|
from agent.storage.skill_loader import load_skills_from_dir
|
|
from agent.storage.skill_loader import load_skills_from_dir
|
|
|
from agent.tools import ToolRegistry, get_tool_registry
|
|
from agent.tools import ToolRegistry, get_tool_registry
|
|
|
|
|
+from agent.debug import dump_tree
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
@@ -60,6 +61,7 @@ class AgentRunner:
|
|
|
tool_registry: Optional[ToolRegistry] = None,
|
|
tool_registry: Optional[ToolRegistry] = None,
|
|
|
llm_call: Optional[Callable] = None,
|
|
llm_call: Optional[Callable] = None,
|
|
|
config: Optional[AgentConfig] = None,
|
|
config: Optional[AgentConfig] = None,
|
|
|
|
|
+ debug: bool = False,
|
|
|
):
|
|
):
|
|
|
"""
|
|
"""
|
|
|
初始化 AgentRunner
|
|
初始化 AgentRunner
|
|
@@ -71,6 +73,7 @@ class AgentRunner:
|
|
|
tool_registry: 工具注册表(可选,默认使用全局注册表)
|
|
tool_registry: 工具注册表(可选,默认使用全局注册表)
|
|
|
llm_call: LLM 调用函数(必须提供,用于实际调用 LLM)
|
|
llm_call: LLM 调用函数(必须提供,用于实际调用 LLM)
|
|
|
config: Agent 配置
|
|
config: Agent 配置
|
|
|
|
|
+ debug: 是否启用 debug 模式(输出 step tree 到 .trace/tree.txt)
|
|
|
"""
|
|
"""
|
|
|
self.trace_store = trace_store
|
|
self.trace_store = trace_store
|
|
|
self.memory_store = memory_store
|
|
self.memory_store = memory_store
|
|
@@ -78,12 +81,21 @@ class AgentRunner:
|
|
|
self.tools = tool_registry or get_tool_registry()
|
|
self.tools = tool_registry or get_tool_registry()
|
|
|
self.llm_call = llm_call
|
|
self.llm_call = llm_call
|
|
|
self.config = config or AgentConfig()
|
|
self.config = config or AgentConfig()
|
|
|
|
|
+ self.debug = debug
|
|
|
|
|
|
|
|
def _generate_id(self) -> str:
|
|
def _generate_id(self) -> str:
|
|
|
"""生成唯一 ID"""
|
|
"""生成唯一 ID"""
|
|
|
import uuid
|
|
import uuid
|
|
|
return str(uuid.uuid4())
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
|
|
|
|
+ async def _dump_debug(self, trace_id: str) -> None:
|
|
|
|
|
+ """Debug 模式下输出 step tree"""
|
|
|
|
|
+ if not self.debug or not self.trace_store:
|
|
|
|
|
+ return
|
|
|
|
|
+ trace = await self.trace_store.get_trace(trace_id)
|
|
|
|
|
+ steps = await self.trace_store.get_trace_steps(trace_id)
|
|
|
|
|
+ dump_tree(trace, steps)
|
|
|
|
|
+
|
|
|
# ===== 单次调用 =====
|
|
# ===== 单次调用 =====
|
|
|
|
|
|
|
|
async def call(
|
|
async def call(
|
|
@@ -141,19 +153,21 @@ class AgentRunner:
|
|
|
if trace and self.trace_store and trace_id:
|
|
if trace and self.trace_store and trace_id:
|
|
|
step = Step.create(
|
|
step = Step.create(
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
- step_type="llm_call",
|
|
|
|
|
|
|
+ step_type="thought",
|
|
|
sequence=0,
|
|
sequence=0,
|
|
|
|
|
+ status="completed",
|
|
|
|
|
+ description=f"LLM 调用 ({model})",
|
|
|
data={
|
|
data={
|
|
|
"messages": messages,
|
|
"messages": messages,
|
|
|
"response": result.get("content", ""),
|
|
"response": result.get("content", ""),
|
|
|
"model": model,
|
|
"model": model,
|
|
|
"tool_calls": result.get("tool_calls"),
|
|
"tool_calls": result.get("tool_calls"),
|
|
|
- "prompt_tokens": result.get("prompt_tokens", 0),
|
|
|
|
|
- "completion_tokens": result.get("completion_tokens", 0),
|
|
|
|
|
- "cost": result.get("cost", 0),
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ tokens=result.get("prompt_tokens", 0) + result.get("completion_tokens", 0),
|
|
|
|
|
+ cost=result.get("cost", 0),
|
|
|
)
|
|
)
|
|
|
step_id = await self.trace_store.add_step(step)
|
|
step_id = await self.trace_store.add_step(step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
|
|
|
# 完成 Trace
|
|
# 完成 Trace
|
|
|
await self.trace_store.update_trace(
|
|
await self.trace_store.update_trace(
|
|
@@ -254,12 +268,15 @@ class AgentRunner:
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
step_type="memory_read",
|
|
step_type="memory_read",
|
|
|
sequence=0,
|
|
sequence=0,
|
|
|
|
|
+ status="completed",
|
|
|
|
|
+ description=f"加载 {len(experiences)} 条经验",
|
|
|
data={
|
|
data={
|
|
|
"experiences_count": len(experiences),
|
|
"experiences_count": len(experiences),
|
|
|
"experiences": [e.to_dict() for e in experiences],
|
|
"experiences": [e.to_dict() for e in experiences],
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
await self.trace_store.add_step(mem_step)
|
|
await self.trace_store.add_step(mem_step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
|
|
|
yield AgentEvent("memory_loaded", {
|
|
yield AgentEvent("memory_loaded", {
|
|
|
"experiences_count": len(experiences)
|
|
"experiences_count": len(experiences)
|
|
@@ -286,7 +303,7 @@ class AgentRunner:
|
|
|
tool_schemas = self.tools.get_schemas(tools)
|
|
tool_schemas = self.tools.get_schemas(tools)
|
|
|
|
|
|
|
|
# 执行循环
|
|
# 执行循环
|
|
|
- parent_step_ids = []
|
|
|
|
|
|
|
+ current_goal_id = None # 当前焦点 goal
|
|
|
sequence = 1
|
|
sequence = 1
|
|
|
total_tokens = 0
|
|
total_tokens = 0
|
|
|
total_cost = 0.0
|
|
total_cost = 0.0
|
|
@@ -294,7 +311,7 @@ class AgentRunner:
|
|
|
for iteration in range(max_iterations):
|
|
for iteration in range(max_iterations):
|
|
|
yield AgentEvent("step_started", {
|
|
yield AgentEvent("step_started", {
|
|
|
"iteration": iteration,
|
|
"iteration": iteration,
|
|
|
- "step_type": "llm_call"
|
|
|
|
|
|
|
+ "step_type": "thought"
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
# 调用 LLM
|
|
# 调用 LLM
|
|
@@ -307,42 +324,49 @@ class AgentRunner:
|
|
|
|
|
|
|
|
response_content = result.get("content", "")
|
|
response_content = result.get("content", "")
|
|
|
tool_calls = result.get("tool_calls")
|
|
tool_calls = result.get("tool_calls")
|
|
|
- tokens = result.get("prompt_tokens", 0) + result.get("completion_tokens", 0)
|
|
|
|
|
- cost = result.get("cost", 0)
|
|
|
|
|
|
|
+ step_tokens = result.get("prompt_tokens", 0) + result.get("completion_tokens", 0)
|
|
|
|
|
+ step_cost = result.get("cost", 0)
|
|
|
|
|
|
|
|
- total_tokens += tokens
|
|
|
|
|
- total_cost += cost
|
|
|
|
|
|
|
+ total_tokens += step_tokens
|
|
|
|
|
+ total_cost += step_cost
|
|
|
|
|
|
|
|
# 记录 LLM 调用 Step
|
|
# 记录 LLM 调用 Step
|
|
|
llm_step_id = self._generate_id()
|
|
llm_step_id = self._generate_id()
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
|
|
|
+ # 推断 step_type
|
|
|
|
|
+ step_type = "thought"
|
|
|
|
|
+ if tool_calls:
|
|
|
|
|
+ step_type = "thought" # 有工具调用的思考
|
|
|
|
|
+ elif not tool_calls and iteration > 0:
|
|
|
|
|
+ step_type = "response" # 无工具调用,可能是最终回复
|
|
|
|
|
+
|
|
|
llm_step = Step(
|
|
llm_step = Step(
|
|
|
step_id=llm_step_id,
|
|
step_id=llm_step_id,
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
- step_type="llm_call",
|
|
|
|
|
|
|
+ step_type=step_type,
|
|
|
|
|
+ status="completed",
|
|
|
sequence=sequence,
|
|
sequence=sequence,
|
|
|
- parent_ids=parent_step_ids,
|
|
|
|
|
|
|
+ parent_id=current_goal_id,
|
|
|
|
|
+ description=response_content[:100] + "..." if len(response_content) > 100 else response_content,
|
|
|
data={
|
|
data={
|
|
|
- "messages": messages,
|
|
|
|
|
- "response": response_content,
|
|
|
|
|
|
|
+ "content": response_content,
|
|
|
"model": model,
|
|
"model": model,
|
|
|
"tool_calls": tool_calls,
|
|
"tool_calls": tool_calls,
|
|
|
- "prompt_tokens": result.get("prompt_tokens", 0),
|
|
|
|
|
- "completion_tokens": result.get("completion_tokens", 0),
|
|
|
|
|
- "cost": cost,
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ tokens=step_tokens,
|
|
|
|
|
+ cost=step_cost,
|
|
|
)
|
|
)
|
|
|
await self.trace_store.add_step(llm_step)
|
|
await self.trace_store.add_step(llm_step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
|
|
|
sequence += 1
|
|
sequence += 1
|
|
|
- parent_step_ids = [llm_step_id]
|
|
|
|
|
|
|
|
|
|
yield AgentEvent("llm_call_completed", {
|
|
yield AgentEvent("llm_call_completed", {
|
|
|
"step_id": llm_step_id,
|
|
"step_id": llm_step_id,
|
|
|
"content": response_content,
|
|
"content": response_content,
|
|
|
"tool_calls": tool_calls,
|
|
"tool_calls": tool_calls,
|
|
|
- "tokens": tokens,
|
|
|
|
|
- "cost": cost
|
|
|
|
|
|
|
+ "tokens": step_tokens,
|
|
|
|
|
+ "cost": step_cost
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
# 处理工具调用
|
|
# 处理工具调用
|
|
@@ -379,28 +403,50 @@ class AgentRunner:
|
|
|
uid=uid or ""
|
|
uid=uid or ""
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # 记录 tool_call Step
|
|
|
|
|
- tool_step_id = self._generate_id()
|
|
|
|
|
|
|
+ # 记录 action Step
|
|
|
|
|
+ action_step_id = self._generate_id()
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
|
- tool_step = Step(
|
|
|
|
|
- step_id=tool_step_id,
|
|
|
|
|
|
|
+ action_step = Step(
|
|
|
|
|
+ step_id=action_step_id,
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
- step_type="tool_call",
|
|
|
|
|
|
|
+ step_type="action",
|
|
|
|
|
+ status="completed",
|
|
|
sequence=sequence,
|
|
sequence=sequence,
|
|
|
- parent_ids=[llm_step_id],
|
|
|
|
|
|
|
+ parent_id=llm_step_id,
|
|
|
|
|
+ description=f"{tool_name}({', '.join(f'{k}={v}' for k, v in list(tool_args.items())[:2])})",
|
|
|
data={
|
|
data={
|
|
|
"tool_name": tool_name,
|
|
"tool_name": tool_name,
|
|
|
"arguments": tool_args,
|
|
"arguments": tool_args,
|
|
|
- "result": tool_result,
|
|
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
- await self.trace_store.add_step(tool_step)
|
|
|
|
|
|
|
+ await self.trace_store.add_step(action_step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
+
|
|
|
|
|
+ sequence += 1
|
|
|
|
|
+
|
|
|
|
|
+ # 记录 result Step
|
|
|
|
|
+ result_step_id = self._generate_id()
|
|
|
|
|
+ if self.trace_store:
|
|
|
|
|
+ result_step = Step(
|
|
|
|
|
+ step_id=result_step_id,
|
|
|
|
|
+ trace_id=trace_id,
|
|
|
|
|
+ step_type="result",
|
|
|
|
|
+ status="completed",
|
|
|
|
|
+ sequence=sequence,
|
|
|
|
|
+ parent_id=action_step_id,
|
|
|
|
|
+ description=str(tool_result)[:100] if tool_result else "",
|
|
|
|
|
+ data={
|
|
|
|
|
+ "tool_name": tool_name,
|
|
|
|
|
+ "output": tool_result,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.trace_store.add_step(result_step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
|
|
|
sequence += 1
|
|
sequence += 1
|
|
|
- parent_step_ids.append(tool_step_id)
|
|
|
|
|
|
|
|
|
|
yield AgentEvent("tool_result", {
|
|
yield AgentEvent("tool_result", {
|
|
|
- "step_id": tool_step_id,
|
|
|
|
|
|
|
+ "step_id": result_step_id,
|
|
|
"tool_name": tool_name,
|
|
"tool_name": tool_name,
|
|
|
"result": tool_result
|
|
"result": tool_result
|
|
|
})
|
|
})
|
|
@@ -416,24 +462,27 @@ class AgentRunner:
|
|
|
continue # 继续循环
|
|
continue # 继续循环
|
|
|
|
|
|
|
|
# 无工具调用,任务完成
|
|
# 无工具调用,任务完成
|
|
|
- # 记录 conclusion Step
|
|
|
|
|
- conclusion_step_id = self._generate_id()
|
|
|
|
|
|
|
+ # 记录 response Step
|
|
|
|
|
+ response_step_id = self._generate_id()
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
|
- conclusion_step = Step(
|
|
|
|
|
- step_id=conclusion_step_id,
|
|
|
|
|
|
|
+ response_step = Step(
|
|
|
|
|
+ step_id=response_step_id,
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
- step_type="conclusion",
|
|
|
|
|
|
|
+ step_type="response",
|
|
|
|
|
+ status="completed",
|
|
|
sequence=sequence,
|
|
sequence=sequence,
|
|
|
- parent_ids=parent_step_ids,
|
|
|
|
|
|
|
+ parent_id=current_goal_id,
|
|
|
|
|
+ description=response_content[:100] + "..." if len(response_content) > 100 else response_content,
|
|
|
data={
|
|
data={
|
|
|
"content": response_content,
|
|
"content": response_content,
|
|
|
"is_final": True
|
|
"is_final": True
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
- await self.trace_store.add_step(conclusion_step)
|
|
|
|
|
|
|
+ await self.trace_store.add_step(response_step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
|
|
|
yield AgentEvent("conclusion", {
|
|
yield AgentEvent("conclusion", {
|
|
|
- "step_id": conclusion_step_id,
|
|
|
|
|
|
|
+ "step_id": response_step_id,
|
|
|
"content": response_content,
|
|
"content": response_content,
|
|
|
"is_final": True
|
|
"is_final": True
|
|
|
})
|
|
})
|
|
@@ -511,7 +560,9 @@ class AgentRunner:
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
step_type="feedback",
|
|
step_type="feedback",
|
|
|
sequence=max_seq + 1,
|
|
sequence=max_seq + 1,
|
|
|
- parent_ids=[target_step_id],
|
|
|
|
|
|
|
+ status="completed",
|
|
|
|
|
+ description=f"{feedback_type}: {content[:50]}...",
|
|
|
|
|
+ parent_id=target_step_id,
|
|
|
data={
|
|
data={
|
|
|
"target_step_id": target_step_id,
|
|
"target_step_id": target_step_id,
|
|
|
"feedback_type": feedback_type,
|
|
"feedback_type": feedback_type,
|
|
@@ -519,6 +570,7 @@ class AgentRunner:
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
await self.trace_store.add_step(feedback_step)
|
|
await self.trace_store.add_step(feedback_step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
|
|
|
# 提取经验
|
|
# 提取经验
|
|
|
exp_id = None
|
|
exp_id = None
|
|
@@ -538,7 +590,9 @@ class AgentRunner:
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
step_type="memory_write",
|
|
step_type="memory_write",
|
|
|
sequence=max_seq + 2,
|
|
sequence=max_seq + 2,
|
|
|
- parent_ids=[feedback_step.step_id],
|
|
|
|
|
|
|
+ status="completed",
|
|
|
|
|
+ description=f"保存经验: {exp.condition[:30]}...",
|
|
|
|
|
+ parent_id=feedback_step.step_id,
|
|
|
data={
|
|
data={
|
|
|
"experience_id": exp_id,
|
|
"experience_id": exp_id,
|
|
|
"condition": exp.condition,
|
|
"condition": exp.condition,
|
|
@@ -546,6 +600,7 @@ class AgentRunner:
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
await self.trace_store.add_step(mem_step)
|
|
await self.trace_store.add_step(mem_step)
|
|
|
|
|
+ await self._dump_debug(trace_id)
|
|
|
|
|
|
|
|
return exp_id
|
|
return exp_id
|
|
|
|
|
|