|
@@ -14,8 +14,10 @@ Agent Runner - Agent 执行引擎
|
|
|
- Messages: OpenAI SDK 格式的任务消息
|
|
- Messages: OpenAI SDK 格式的任务消息
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
+import asyncio
|
|
|
import json
|
|
import json
|
|
|
import logging
|
|
import logging
|
|
|
|
|
+import os
|
|
|
import uuid
|
|
import uuid
|
|
|
from dataclasses import dataclass, field
|
|
from dataclasses import dataclass, field
|
|
|
from datetime import datetime
|
|
from datetime import datetime
|
|
@@ -24,7 +26,7 @@ from typing import AsyncIterator, Optional, Dict, Any, List, Callable, Literal,
|
|
|
from agent.trace.models import Trace, Message
|
|
from agent.trace.models import Trace, Message
|
|
|
from agent.trace.protocols import TraceStore
|
|
from agent.trace.protocols import TraceStore
|
|
|
from agent.trace.goal_models import GoalTree
|
|
from agent.trace.goal_models import GoalTree
|
|
|
-from agent.memory.models import Experience, Skill
|
|
|
|
|
|
|
+from agent.memory.models import Skill
|
|
|
from agent.memory.protocols import MemoryStore, StateStore
|
|
from agent.memory.protocols import MemoryStore, StateStore
|
|
|
from agent.memory.skill_loader import load_skills_from_dir
|
|
from agent.memory.skill_loader import load_skills_from_dir
|
|
|
from agent.tools import ToolRegistry, get_tool_registry
|
|
from agent.tools import ToolRegistry, get_tool_registry
|
|
@@ -170,6 +172,7 @@ class AgentRunner:
|
|
|
utility_llm_call: Optional[Callable] = None,
|
|
utility_llm_call: Optional[Callable] = None,
|
|
|
config: Optional[AgentConfig] = None,
|
|
config: Optional[AgentConfig] = None,
|
|
|
skills_dir: Optional[str] = None,
|
|
skills_dir: Optional[str] = None,
|
|
|
|
|
+ experiences_path: Optional[str] = "./cache/experiences.md",
|
|
|
goal_tree: Optional[GoalTree] = None,
|
|
goal_tree: Optional[GoalTree] = None,
|
|
|
debug: bool = False,
|
|
debug: bool = False,
|
|
|
):
|
|
):
|
|
@@ -185,6 +188,7 @@ class AgentRunner:
|
|
|
utility_llm_call: 轻量 LLM(用于生成任务标题等),可选
|
|
utility_llm_call: 轻量 LLM(用于生成任务标题等),可选
|
|
|
config: [向后兼容] AgentConfig
|
|
config: [向后兼容] AgentConfig
|
|
|
skills_dir: Skills 目录路径
|
|
skills_dir: Skills 目录路径
|
|
|
|
|
+ experiences_path: 经验文件路径(默认 ./cache/experiences.md)
|
|
|
goal_tree: 初始 GoalTree(可选)
|
|
goal_tree: 初始 GoalTree(可选)
|
|
|
debug: 保留参数(已废弃)
|
|
debug: 保留参数(已废弃)
|
|
|
"""
|
|
"""
|
|
@@ -196,8 +200,10 @@ class AgentRunner:
|
|
|
self.utility_llm_call = utility_llm_call
|
|
self.utility_llm_call = utility_llm_call
|
|
|
self.config = config or AgentConfig()
|
|
self.config = config or AgentConfig()
|
|
|
self.skills_dir = skills_dir
|
|
self.skills_dir = skills_dir
|
|
|
|
|
+ self.experiences_path = experiences_path
|
|
|
self.goal_tree = goal_tree
|
|
self.goal_tree = goal_tree
|
|
|
self.debug = debug
|
|
self.debug = debug
|
|
|
|
|
+ self._cancel_events: Dict[str, asyncio.Event] = {} # trace_id → cancel event
|
|
|
|
|
|
|
|
# ===== 核心公开方法 =====
|
|
# ===== 核心公开方法 =====
|
|
|
|
|
|
|
@@ -228,12 +234,16 @@ class AgentRunner:
|
|
|
try:
|
|
try:
|
|
|
# Phase 1: PREPARE TRACE
|
|
# Phase 1: PREPARE TRACE
|
|
|
trace, goal_tree, sequence = await self._prepare_trace(messages, config)
|
|
trace, goal_tree, sequence = await self._prepare_trace(messages, config)
|
|
|
|
|
+ # 注册取消事件
|
|
|
|
|
+ self._cancel_events[trace.trace_id] = asyncio.Event()
|
|
|
yield trace
|
|
yield trace
|
|
|
|
|
|
|
|
# Phase 2: BUILD HISTORY
|
|
# Phase 2: BUILD HISTORY
|
|
|
- history, sequence, created_messages = await self._build_history(
|
|
|
|
|
|
|
+ history, sequence, created_messages, head_seq = await self._build_history(
|
|
|
trace.trace_id, messages, goal_tree, config, sequence
|
|
trace.trace_id, messages, goal_tree, config, sequence
|
|
|
)
|
|
)
|
|
|
|
|
+ # Update trace's head_sequence in memory
|
|
|
|
|
+ trace.head_sequence = head_seq
|
|
|
for msg in created_messages:
|
|
for msg in created_messages:
|
|
|
yield msg
|
|
yield msg
|
|
|
|
|
|
|
@@ -255,6 +265,10 @@ class AgentRunner:
|
|
|
if trace_obj:
|
|
if trace_obj:
|
|
|
yield trace_obj
|
|
yield trace_obj
|
|
|
raise
|
|
raise
|
|
|
|
|
+ finally:
|
|
|
|
|
+ # 清理取消事件
|
|
|
|
|
+ if trace:
|
|
|
|
|
+ self._cancel_events.pop(trace.trace_id, None)
|
|
|
|
|
|
|
|
async def run_result(
|
|
async def run_result(
|
|
|
self,
|
|
self,
|
|
@@ -306,6 +320,22 @@ class AgentRunner:
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ async def stop(self, trace_id: str) -> bool:
|
|
|
|
|
+ """
|
|
|
|
|
+ 停止运行中的 Trace
|
|
|
|
|
+
|
|
|
|
|
+ 设置取消信号,agent loop 在下一个 LLM 调用前检查并退出。
|
|
|
|
|
+ Trace 状态置为 "stopped"。
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ True 如果成功发送停止信号,False 如果该 trace 不在运行中
|
|
|
|
|
+ """
|
|
|
|
|
+ cancel_event = self._cancel_events.get(trace_id)
|
|
|
|
|
+ if cancel_event is None:
|
|
|
|
|
+ return False
|
|
|
|
|
+ cancel_event.set()
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
# ===== 单次调用(保留)=====
|
|
# ===== 单次调用(保留)=====
|
|
|
|
|
|
|
|
async def call(
|
|
async def call(
|
|
@@ -432,11 +462,8 @@ class AgentRunner:
|
|
|
# 回溯模式
|
|
# 回溯模式
|
|
|
sequence = await self._rewind(config.trace_id, config.insert_after, goal_tree)
|
|
sequence = await self._rewind(config.trace_id, config.insert_after, goal_tree)
|
|
|
else:
|
|
else:
|
|
|
- # 续跑模式:从最大 sequence + 1 开始
|
|
|
|
|
- all_messages = await self.trace_store.get_trace_messages(
|
|
|
|
|
- config.trace_id, include_abandoned=True
|
|
|
|
|
- )
|
|
|
|
|
- sequence = max((m.sequence for m in all_messages), default=0) + 1
|
|
|
|
|
|
|
+ # 续跑模式:从 last_sequence + 1 开始
|
|
|
|
|
+ sequence = trace_obj.last_sequence + 1
|
|
|
|
|
|
|
|
# 状态置为 running
|
|
# 状态置为 running
|
|
|
await self.trace_store.update_trace(
|
|
await self.trace_store.update_trace(
|
|
@@ -461,21 +488,30 @@ class AgentRunner:
|
|
|
"""
|
|
"""
|
|
|
构建完整的 LLM 消息历史
|
|
构建完整的 LLM 消息历史
|
|
|
|
|
|
|
|
- 1. 加载已有 active messages(续跑/回溯场景)
|
|
|
|
|
- 2. 构建 system prompt(新建时注入 skills/experiences)
|
|
|
|
|
- 3. 追加 input messages
|
|
|
|
|
|
|
+ 1. 从 head_sequence 沿 parent chain 加载主路径消息(续跑/回溯场景)
|
|
|
|
|
+ 2. 构建 system prompt(新建时注入 skills)
|
|
|
|
|
+ 3. 新建时:在第一条 user message 末尾注入当前经验
|
|
|
|
|
+ 4. 追加 input messages(设置 parent_sequence 链接到当前 head)
|
|
|
|
|
|
|
|
Returns:
|
|
Returns:
|
|
|
- (history, next_sequence, created_messages)
|
|
|
|
|
|
|
+ (history, next_sequence, created_messages, head_sequence)
|
|
|
created_messages: 本次新创建并持久化的 Message 列表,供 run() yield 给调用方
|
|
created_messages: 本次新创建并持久化的 Message 列表,供 run() yield 给调用方
|
|
|
|
|
+ head_sequence: 当前主路径头节点的 sequence
|
|
|
"""
|
|
"""
|
|
|
history: List[Dict] = []
|
|
history: List[Dict] = []
|
|
|
created_messages: List[Message] = []
|
|
created_messages: List[Message] = []
|
|
|
|
|
+ head_seq: Optional[int] = None # 当前主路径的头节点 sequence
|
|
|
|
|
|
|
|
- # 1. 加载已有 messages
|
|
|
|
|
|
|
+ # 1. 加载已有 messages(通过主路径遍历)
|
|
|
if config.trace_id and self.trace_store:
|
|
if config.trace_id and self.trace_store:
|
|
|
- existing_messages = await self.trace_store.get_trace_messages(trace_id)
|
|
|
|
|
- history = [msg.to_llm_dict() for msg in existing_messages]
|
|
|
|
|
|
|
+ trace_obj = await self.trace_store.get_trace(trace_id)
|
|
|
|
|
+ if trace_obj and trace_obj.head_sequence > 0:
|
|
|
|
|
+ main_path = await self.trace_store.get_main_path_messages(
|
|
|
|
|
+ trace_id, trace_obj.head_sequence
|
|
|
|
|
+ )
|
|
|
|
|
+ history = [msg.to_llm_dict() for msg in main_path]
|
|
|
|
|
+ if main_path:
|
|
|
|
|
+ head_seq = main_path[-1].sequence
|
|
|
|
|
|
|
|
# 2. 构建 system prompt(如果历史中没有 system message)
|
|
# 2. 构建 system prompt(如果历史中没有 system message)
|
|
|
has_system = any(m.get("role") == "system" for m in history)
|
|
has_system = any(m.get("role") == "system" for m in history)
|
|
@@ -490,24 +526,41 @@ class AgentRunner:
|
|
|
system_msg = Message.create(
|
|
system_msg = Message.create(
|
|
|
trace_id=trace_id, role="system", sequence=sequence,
|
|
trace_id=trace_id, role="system", sequence=sequence,
|
|
|
goal_id=None, content=system_prompt,
|
|
goal_id=None, content=system_prompt,
|
|
|
|
|
+ parent_sequence=None, # system message 是 root
|
|
|
)
|
|
)
|
|
|
await self.trace_store.add_message(system_msg)
|
|
await self.trace_store.add_message(system_msg)
|
|
|
created_messages.append(system_msg)
|
|
created_messages.append(system_msg)
|
|
|
|
|
+ head_seq = sequence
|
|
|
sequence += 1
|
|
sequence += 1
|
|
|
|
|
|
|
|
- # 3. 追加新 messages
|
|
|
|
|
|
|
+ # 3. 新建时:在第一条 user message 末尾注入当前经验
|
|
|
|
|
+ if not config.trace_id: # 新建模式
|
|
|
|
|
+ experiences_text = self._load_experiences()
|
|
|
|
|
+ if experiences_text:
|
|
|
|
|
+ for msg in new_messages:
|
|
|
|
|
+ if msg.get("role") == "user" and isinstance(msg.get("content"), str):
|
|
|
|
|
+ msg["content"] += f"\n\n## 参考经验\n\n{experiences_text}"
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ # 4. 追加新 messages(设置 parent_sequence 链接到当前 head)
|
|
|
for msg_dict in new_messages:
|
|
for msg_dict in new_messages:
|
|
|
history.append(msg_dict)
|
|
history.append(msg_dict)
|
|
|
|
|
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
|
stored_msg = Message.from_llm_dict(
|
|
stored_msg = Message.from_llm_dict(
|
|
|
- msg_dict, trace_id=trace_id, sequence=sequence, goal_id=None
|
|
|
|
|
|
|
+ msg_dict, trace_id=trace_id, sequence=sequence,
|
|
|
|
|
+ goal_id=None, parent_sequence=head_seq,
|
|
|
)
|
|
)
|
|
|
await self.trace_store.add_message(stored_msg)
|
|
await self.trace_store.add_message(stored_msg)
|
|
|
created_messages.append(stored_msg)
|
|
created_messages.append(stored_msg)
|
|
|
|
|
+ head_seq = sequence
|
|
|
sequence += 1
|
|
sequence += 1
|
|
|
|
|
|
|
|
- return history, sequence, created_messages
|
|
|
|
|
|
|
+ # 5. 更新 trace 的 head_sequence
|
|
|
|
|
+ if self.trace_store and head_seq is not None:
|
|
|
|
|
+ await self.trace_store.update_trace(trace_id, head_sequence=head_seq)
|
|
|
|
|
+
|
|
|
|
|
+ return history, sequence, created_messages, head_seq or 0
|
|
|
|
|
|
|
|
# ===== Phase 3: AGENT LOOP =====
|
|
# ===== Phase 3: AGENT LOOP =====
|
|
|
|
|
|
|
@@ -523,12 +576,30 @@ class AgentRunner:
|
|
|
trace_id = trace.trace_id
|
|
trace_id = trace.trace_id
|
|
|
tool_schemas = self._get_tool_schemas(config.tools)
|
|
tool_schemas = self._get_tool_schemas(config.tools)
|
|
|
|
|
|
|
|
|
|
+ # 当前主路径头节点的 sequence(用于设置 parent_sequence)
|
|
|
|
|
+ head_seq = trace.head_sequence
|
|
|
|
|
+
|
|
|
# 设置 goal_tree 到 goal 工具
|
|
# 设置 goal_tree 到 goal 工具
|
|
|
if goal_tree and self.trace_store:
|
|
if goal_tree and self.trace_store:
|
|
|
from agent.trace.goal_tool import set_goal_tree
|
|
from agent.trace.goal_tool import set_goal_tree
|
|
|
set_goal_tree(goal_tree)
|
|
set_goal_tree(goal_tree)
|
|
|
|
|
|
|
|
for iteration in range(config.max_iterations):
|
|
for iteration in range(config.max_iterations):
|
|
|
|
|
+ # 检查取消信号
|
|
|
|
|
+ cancel_event = self._cancel_events.get(trace_id)
|
|
|
|
|
+ if cancel_event and cancel_event.is_set():
|
|
|
|
|
+ logger.info(f"Trace {trace_id} stopped by user")
|
|
|
|
|
+ if self.trace_store:
|
|
|
|
|
+ await self.trace_store.update_trace(
|
|
|
|
|
+ trace_id,
|
|
|
|
|
+ status="stopped",
|
|
|
|
|
+ completed_at=datetime.now(),
|
|
|
|
|
+ )
|
|
|
|
|
+ trace_obj = await self.trace_store.get_trace(trace_id)
|
|
|
|
|
+ if trace_obj:
|
|
|
|
|
+ yield trace_obj
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
# 构建 LLM messages(注入上下文)
|
|
# 构建 LLM messages(注入上下文)
|
|
|
llm_messages = list(history)
|
|
llm_messages = list(history)
|
|
|
|
|
|
|
@@ -577,12 +648,13 @@ class AgentRunner:
|
|
|
# 获取当前 goal_id
|
|
# 获取当前 goal_id
|
|
|
current_goal_id = goal_tree.current_id if (goal_tree and goal_tree.current_id) else None
|
|
current_goal_id = goal_tree.current_id if (goal_tree and goal_tree.current_id) else None
|
|
|
|
|
|
|
|
- # 记录 assistant Message
|
|
|
|
|
|
|
+ # 记录 assistant Message(parent_sequence 指向当前 head)
|
|
|
assistant_msg = Message.create(
|
|
assistant_msg = Message.create(
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
role="assistant",
|
|
role="assistant",
|
|
|
sequence=sequence,
|
|
sequence=sequence,
|
|
|
goal_id=current_goal_id,
|
|
goal_id=current_goal_id,
|
|
|
|
|
+ parent_sequence=head_seq if head_seq > 0 else None,
|
|
|
content={"text": response_content, "tool_calls": tool_calls},
|
|
content={"text": response_content, "tool_calls": tool_calls},
|
|
|
prompt_tokens=prompt_tokens,
|
|
prompt_tokens=prompt_tokens,
|
|
|
completion_tokens=completion_tokens,
|
|
completion_tokens=completion_tokens,
|
|
@@ -594,6 +666,7 @@ class AgentRunner:
|
|
|
await self.trace_store.add_message(assistant_msg)
|
|
await self.trace_store.add_message(assistant_msg)
|
|
|
|
|
|
|
|
yield assistant_msg
|
|
yield assistant_msg
|
|
|
|
|
+ head_seq = sequence
|
|
|
sequence += 1
|
|
sequence += 1
|
|
|
|
|
|
|
|
# 处理工具调用
|
|
# 处理工具调用
|
|
@@ -632,6 +705,7 @@ class AgentRunner:
|
|
|
role="tool",
|
|
role="tool",
|
|
|
sequence=sequence,
|
|
sequence=sequence,
|
|
|
goal_id=current_goal_id,
|
|
goal_id=current_goal_id,
|
|
|
|
|
+ parent_sequence=head_seq,
|
|
|
tool_call_id=tc["id"],
|
|
tool_call_id=tc["id"],
|
|
|
content={"tool_name": tool_name, "result": tool_result},
|
|
content={"tool_name": tool_name, "result": tool_result},
|
|
|
)
|
|
)
|
|
@@ -640,6 +714,7 @@ class AgentRunner:
|
|
|
await self.trace_store.add_message(tool_msg)
|
|
await self.trace_store.add_message(tool_msg)
|
|
|
|
|
|
|
|
yield tool_msg
|
|
yield tool_msg
|
|
|
|
|
+ head_seq = sequence
|
|
|
sequence += 1
|
|
sequence += 1
|
|
|
|
|
|
|
|
history.append({
|
|
history.append({
|
|
@@ -654,11 +729,12 @@ class AgentRunner:
|
|
|
# 无工具调用,任务完成
|
|
# 无工具调用,任务完成
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
- # 完成 Trace
|
|
|
|
|
|
|
+ # 更新 head_sequence 并完成 Trace
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
|
await self.trace_store.update_trace(
|
|
await self.trace_store.update_trace(
|
|
|
trace_id,
|
|
trace_id,
|
|
|
status="completed",
|
|
status="completed",
|
|
|
|
|
+ head_sequence=head_seq,
|
|
|
completed_at=datetime.now(),
|
|
completed_at=datetime.now(),
|
|
|
)
|
|
)
|
|
|
trace_obj = await self.trace_store.get_trace(trace_id)
|
|
trace_obj = await self.trace_store.get_trace(trace_id)
|
|
@@ -674,7 +750,9 @@ class AgentRunner:
|
|
|
goal_tree: Optional[GoalTree],
|
|
goal_tree: Optional[GoalTree],
|
|
|
) -> int:
|
|
) -> int:
|
|
|
"""
|
|
"""
|
|
|
- 执行回溯:标记 insert_after 之后的 messages 和 goals 为 abandoned
|
|
|
|
|
|
|
+ 执行回溯:快照 GoalTree,重建干净树,设置 head_sequence
|
|
|
|
|
+
|
|
|
|
|
+ 新消息的 parent_sequence 将指向 rewind 点,旧消息通过树结构自然脱离主路径。
|
|
|
|
|
|
|
|
Returns:
|
|
Returns:
|
|
|
下一个可用的 sequence 号
|
|
下一个可用的 sequence 号
|
|
@@ -682,7 +760,7 @@ class AgentRunner:
|
|
|
if not self.trace_store:
|
|
if not self.trace_store:
|
|
|
raise ValueError("trace_store required for rewind")
|
|
raise ValueError("trace_store required for rewind")
|
|
|
|
|
|
|
|
- # 1. 加载所有 messages(含已 abandoned 的)
|
|
|
|
|
|
|
+ # 1. 加载所有 messages
|
|
|
all_messages = await self.trace_store.get_trace_messages(
|
|
all_messages = await self.trace_store.get_trace_messages(
|
|
|
trace_id, include_abandoned=True
|
|
trace_id, include_abandoned=True
|
|
|
)
|
|
)
|
|
@@ -693,40 +771,37 @@ class AgentRunner:
|
|
|
# 2. 找到安全截断点(确保不截断在 tool_call 和 tool response 之间)
|
|
# 2. 找到安全截断点(确保不截断在 tool_call 和 tool response 之间)
|
|
|
cutoff = self._find_safe_cutoff(all_messages, insert_after)
|
|
cutoff = self._find_safe_cutoff(all_messages, insert_after)
|
|
|
|
|
|
|
|
- # 3. 批量标记 messages 为 abandoned
|
|
|
|
|
- abandoned_ids = await self.trace_store.abandon_messages_after(trace_id, cutoff)
|
|
|
|
|
-
|
|
|
|
|
- # 4. 处理 Goals
|
|
|
|
|
|
|
+ # 3. 快照并重建 GoalTree
|
|
|
if goal_tree:
|
|
if goal_tree:
|
|
|
- active_messages = [m for m in all_messages if m.sequence <= cutoff]
|
|
|
|
|
- active_goal_ids = {m.goal_id for m in active_messages if m.goal_id}
|
|
|
|
|
-
|
|
|
|
|
|
|
+ # 找出 rewind 点之前已完成的 goal IDs
|
|
|
|
|
+ # 通过主路径消息来判断:cutoff 之前的消息引用的 completed goals
|
|
|
|
|
+ messages_before = [m for m in all_messages if m.sequence <= cutoff]
|
|
|
|
|
+ completed_goal_ids = set()
|
|
|
for goal in goal_tree.goals:
|
|
for goal in goal_tree.goals:
|
|
|
- if goal.status == "abandoned":
|
|
|
|
|
- continue # 已 abandoned,跳过
|
|
|
|
|
- if goal.status == "completed" and goal.id in active_goal_ids:
|
|
|
|
|
- continue # 已完成且有截断点之前的 messages → 保留
|
|
|
|
|
- # 其余全部 abandon(含无 active messages 的 completed goal)
|
|
|
|
|
- goal.status = "abandoned"
|
|
|
|
|
- goal.summary = "回溯导致放弃"
|
|
|
|
|
-
|
|
|
|
|
- # 重置 current_id
|
|
|
|
|
- goal_tree._current_id = None
|
|
|
|
|
-
|
|
|
|
|
- await self.trace_store.update_goal_tree(trace_id, goal_tree)
|
|
|
|
|
-
|
|
|
|
|
- # 5. 记录 rewind 事件
|
|
|
|
|
- abandoned_sequences = [
|
|
|
|
|
- m.sequence for m in all_messages
|
|
|
|
|
- if m.sequence > cutoff and m.status != "abandoned" # 本次新 abandon 的
|
|
|
|
|
- ]
|
|
|
|
|
- await self.trace_store.append_event(trace_id, "rewind", {
|
|
|
|
|
- "insert_after_sequence": cutoff,
|
|
|
|
|
- "abandoned_message_count": len(abandoned_ids),
|
|
|
|
|
- "abandoned_sequences": abandoned_sequences[:20], # 只记前 20 条
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- # 6. 返回 next sequence
|
|
|
|
|
|
|
+ if goal.status == "completed":
|
|
|
|
|
+ # 检查该 goal 是否在 rewind 点之前就已完成(有关联消息在 cutoff 之前)
|
|
|
|
|
+ goal_msgs = [m for m in messages_before if m.goal_id == goal.id]
|
|
|
|
|
+ if goal_msgs:
|
|
|
|
|
+ completed_goal_ids.add(goal.id)
|
|
|
|
|
+
|
|
|
|
|
+ # 快照到 events
|
|
|
|
|
+ await self.trace_store.append_event(trace_id, "rewind", {
|
|
|
|
|
+ "insert_after_sequence": cutoff,
|
|
|
|
|
+ "goal_tree_snapshot": goal_tree.to_dict(),
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ # 重建干净的 GoalTree
|
|
|
|
|
+ new_tree = goal_tree.rebuild_for_rewind(completed_goal_ids)
|
|
|
|
|
+ await self.trace_store.update_goal_tree(trace_id, new_tree)
|
|
|
|
|
+
|
|
|
|
|
+ # 更新内存中的引用
|
|
|
|
|
+ goal_tree.goals = new_tree.goals
|
|
|
|
|
+ goal_tree.current_id = new_tree.current_id
|
|
|
|
|
+
|
|
|
|
|
+ # 4. 更新 head_sequence 到 rewind 点
|
|
|
|
|
+ await self.trace_store.update_trace(trace_id, head_sequence=cutoff)
|
|
|
|
|
+
|
|
|
|
|
+ # 5. 返回 next sequence(全局递增,不复用)
|
|
|
max_seq = max((m.sequence for m in all_messages), default=0)
|
|
max_seq = max((m.sequence for m in all_messages), default=0)
|
|
|
return max_seq + 1
|
|
return max_seq + 1
|
|
|
|
|
|
|
@@ -806,7 +881,7 @@ class AgentRunner:
|
|
|
return self.tools.get_schemas(tool_names)
|
|
return self.tools.get_schemas(tool_names)
|
|
|
|
|
|
|
|
async def _build_system_prompt(self, config: RunConfig) -> Optional[str]:
|
|
async def _build_system_prompt(self, config: RunConfig) -> Optional[str]:
|
|
|
- """构建 system prompt(注入 skills 和 experiences)"""
|
|
|
|
|
|
|
+ """构建 system prompt(注入 skills)"""
|
|
|
system_prompt = config.system_prompt
|
|
system_prompt = config.system_prompt
|
|
|
|
|
|
|
|
# 加载 Skills
|
|
# 加载 Skills
|
|
@@ -815,27 +890,12 @@ class AgentRunner:
|
|
|
if skills:
|
|
if skills:
|
|
|
skills_text = self._format_skills(skills)
|
|
skills_text = self._format_skills(skills)
|
|
|
|
|
|
|
|
- # 加载 Experiences
|
|
|
|
|
- experiences_text = ""
|
|
|
|
|
- if config.enable_memory and self.memory_store:
|
|
|
|
|
- scope = f"agent:{config.agent_type}"
|
|
|
|
|
- # 从 messages 提取文本作为查询
|
|
|
|
|
- experiences = await self.memory_store.search_experiences(scope, system_prompt or "")
|
|
|
|
|
- experiences_text = self._format_experiences(experiences)
|
|
|
|
|
-
|
|
|
|
|
# 拼装
|
|
# 拼装
|
|
|
if system_prompt:
|
|
if system_prompt:
|
|
|
if skills_text:
|
|
if skills_text:
|
|
|
system_prompt += f"\n\n## Skills\n{skills_text}"
|
|
system_prompt += f"\n\n## Skills\n{skills_text}"
|
|
|
- if experiences_text:
|
|
|
|
|
- system_prompt += f"\n\n## 相关经验\n{experiences_text}"
|
|
|
|
|
- elif skills_text or experiences_text:
|
|
|
|
|
- parts = []
|
|
|
|
|
- if skills_text:
|
|
|
|
|
- parts.append(f"## Skills\n{skills_text}")
|
|
|
|
|
- if experiences_text:
|
|
|
|
|
- parts.append(f"## 相关经验\n{experiences_text}")
|
|
|
|
|
- system_prompt = "\n\n".join(parts)
|
|
|
|
|
|
|
+ elif skills_text:
|
|
|
|
|
+ system_prompt = f"## Skills\n{skills_text}"
|
|
|
|
|
|
|
|
return system_prompt
|
|
return system_prompt
|
|
|
|
|
|
|
@@ -880,7 +940,14 @@ class AgentRunner:
|
|
|
return ""
|
|
return ""
|
|
|
return "\n\n".join(s.to_prompt_text() for s in skills)
|
|
return "\n\n".join(s.to_prompt_text() for s in skills)
|
|
|
|
|
|
|
|
- def _format_experiences(self, experiences: List[Experience]) -> str:
|
|
|
|
|
- if not experiences:
|
|
|
|
|
|
|
+ def _load_experiences(self) -> str:
|
|
|
|
|
+ """从文件加载经验(./cache/experiences.md)"""
|
|
|
|
|
+ if not self.experiences_path:
|
|
|
return ""
|
|
return ""
|
|
|
- return "\n".join(f"- {e.to_prompt_text()}" for e in experiences)
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ if os.path.exists(self.experiences_path):
|
|
|
|
|
+ with open(self.experiences_path, "r", encoding="utf-8") as f:
|
|
|
|
|
+ return f.read().strip()
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning(f"Failed to load experiences from {self.experiences_path}: {e}")
|
|
|
|
|
+ return ""
|