|
@@ -106,8 +106,9 @@ class RunConfig:
|
|
|
tools: Optional[List[str]] = None # None = 全部已注册工具
|
|
tools: Optional[List[str]] = None # None = 全部已注册工具
|
|
|
side_branch_max_turns: int = 5 # 侧分支最大轮次(压缩/反思)
|
|
side_branch_max_turns: int = 5 # 侧分支最大轮次(压缩/反思)
|
|
|
|
|
|
|
|
- # --- 强制侧分支(用于 API 手动触发)---
|
|
|
|
|
- force_side_branch: Optional[Literal["compression", "reflection"]] = None
|
|
|
|
|
|
|
+ # --- 强制侧分支(用于 API 手动触发或自动压缩流程)---
|
|
|
|
|
+ # 使用列表作为侧分支队列,每次完成一个侧分支后 pop(0) 取下一个
|
|
|
|
|
+ force_side_branch: Optional[List[Literal["compression", "reflection"]]] = None
|
|
|
|
|
|
|
|
# --- 框架层参数 ---
|
|
# --- 框架层参数 ---
|
|
|
agent_type: str = "default"
|
|
agent_type: str = "default"
|
|
@@ -157,6 +158,7 @@ BUILTIN_TOOLS = [
|
|
|
"goal",
|
|
"goal",
|
|
|
"agent",
|
|
"agent",
|
|
|
"evaluate",
|
|
"evaluate",
|
|
|
|
|
+ "get_current_context",
|
|
|
|
|
|
|
|
# 搜索工具
|
|
# 搜索工具
|
|
|
"search_posts",
|
|
"search_posts",
|
|
@@ -780,8 +782,9 @@ class AgentRunner:
|
|
|
return history, head_seq, sequence, False
|
|
return history, head_seq, sequence, False
|
|
|
|
|
|
|
|
# 知识提取:在任何压缩发生前,用完整 history 做反思(进入反思侧分支)
|
|
# 知识提取:在任何压缩发生前,用完整 history 做反思(进入反思侧分支)
|
|
|
- if config.knowledge.enable_extraction:
|
|
|
|
|
- # 返回标志,让主循环进入反思侧分支
|
|
|
|
|
|
|
+ if config.knowledge.enable_extraction and not config.force_side_branch:
|
|
|
|
|
+ # 设置侧分支队列:先反思,再压缩
|
|
|
|
|
+ config.force_side_branch = ["reflection", "compression"]
|
|
|
return history, head_seq, sequence, True
|
|
return history, head_seq, sequence, True
|
|
|
|
|
|
|
|
# Level 1 压缩:GoalTree 过滤
|
|
# Level 1 压缩:GoalTree 过滤
|
|
@@ -823,7 +826,10 @@ class AgentRunner:
|
|
|
"Level 1 后仍超阈值 (消息数=%d/%d, token=%d/%d),需要进入压缩侧分支",
|
|
"Level 1 后仍超阈值 (消息数=%d/%d, token=%d/%d),需要进入压缩侧分支",
|
|
|
msg_count_after, compression_config.max_messages, token_count_after, max_tokens,
|
|
msg_count_after, compression_config.max_messages, token_count_after, max_tokens,
|
|
|
)
|
|
)
|
|
|
- # 返回标志,让主循环进入压缩侧分支
|
|
|
|
|
|
|
+ # 如果还没有设置侧分支(说明没有启用知识提取),直接进入压缩
|
|
|
|
|
+ if not config.force_side_branch:
|
|
|
|
|
+ config.force_side_branch = ["compression"]
|
|
|
|
|
+ # 返回标志,让主循环进入侧分支
|
|
|
return history, head_seq, sequence, True
|
|
return history, head_seq, sequence, True
|
|
|
|
|
|
|
|
# 压缩完成后,输出最终发给模型的消息列表
|
|
# 压缩完成后,输出最终发给模型的消息列表
|
|
@@ -887,13 +893,14 @@ class AgentRunner:
|
|
|
return history, start_head_seq, sequence
|
|
return history, start_head_seq, sequence
|
|
|
|
|
|
|
|
# 创建 summary 消息
|
|
# 创建 summary 消息
|
|
|
|
|
+ from agent.core.prompts import build_summary_header
|
|
|
summary_msg = Message.create(
|
|
summary_msg = Message.create(
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
role="user",
|
|
role="user",
|
|
|
sequence=sequence,
|
|
sequence=sequence,
|
|
|
parent_sequence=start_head_seq,
|
|
parent_sequence=start_head_seq,
|
|
|
branch_type=None, # 主路径
|
|
branch_type=None, # 主路径
|
|
|
- content=f"[压缩总结 - Fallback]\n{summary_text}",
|
|
|
|
|
|
|
+ content=build_summary_header(summary_text),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
@@ -1009,12 +1016,12 @@ class AgentRunner:
|
|
|
|
|
|
|
|
# 进入侧分支
|
|
# 进入侧分支
|
|
|
if needs_enter_side_branch and not side_branch_ctx:
|
|
if needs_enter_side_branch and not side_branch_ctx:
|
|
|
- # 判断侧分支类型
|
|
|
|
|
- if config.force_side_branch:
|
|
|
|
|
- # API 强制触发
|
|
|
|
|
- branch_type = config.force_side_branch
|
|
|
|
|
|
|
+ # 从队列中取出第一个侧分支类型
|
|
|
|
|
+ if config.force_side_branch and isinstance(config.force_side_branch, list) and len(config.force_side_branch) > 0:
|
|
|
|
|
+ branch_type = config.force_side_branch.pop(0)
|
|
|
|
|
+ logger.info(f"从队列取出侧分支: {branch_type}, 剩余队列: {config.force_side_branch}")
|
|
|
elif config.knowledge.enable_extraction:
|
|
elif config.knowledge.enable_extraction:
|
|
|
- # 自动触发:反思
|
|
|
|
|
|
|
+ # 兼容旧的单值模式(如果 force_side_branch 是字符串)
|
|
|
branch_type = "reflection"
|
|
branch_type = "reflection"
|
|
|
else:
|
|
else:
|
|
|
# 自动触发:压缩
|
|
# 自动触发:压缩
|
|
@@ -1086,31 +1093,6 @@ class AgentRunner:
|
|
|
config.enable_prompt_caching
|
|
config.enable_prompt_caching
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # 周期性注入 GoalTree + Collaborators(动态内容追加在缓存点之后)
|
|
|
|
|
- # 仅在主路径执行
|
|
|
|
|
- if not side_branch_ctx and iteration % CONTEXT_INJECTION_INTERVAL == 0:
|
|
|
|
|
- context_injection = self._build_context_injection(trace, goal_tree)
|
|
|
|
|
- if context_injection:
|
|
|
|
|
- system_msg = {"role": "system", "content": context_injection}
|
|
|
|
|
- llm_messages.append(system_msg)
|
|
|
|
|
-
|
|
|
|
|
- # 持久化上下文注入消息
|
|
|
|
|
- if self.trace_store:
|
|
|
|
|
- current_goal_id = goal_tree.current_id if (goal_tree and goal_tree.current_id) else None
|
|
|
|
|
- system_message = Message.create(
|
|
|
|
|
- trace_id=trace_id,
|
|
|
|
|
- role="system",
|
|
|
|
|
- sequence=sequence,
|
|
|
|
|
- goal_id=current_goal_id,
|
|
|
|
|
- parent_sequence=head_seq if head_seq > 0 else None,
|
|
|
|
|
- content=f"[上下文注入]\n{context_injection}",
|
|
|
|
|
- )
|
|
|
|
|
- await self.trace_store.add_message(system_message)
|
|
|
|
|
- history.append(system_msg)
|
|
|
|
|
- head_seq = sequence
|
|
|
|
|
- sequence += 1
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
# 调用 LLM(等待完成后再检查 cancel 信号,不中断正在进行的调用)
|
|
# 调用 LLM(等待完成后再检查 cancel 信号,不中断正在进行的调用)
|
|
|
result = await self.llm_call(
|
|
result = await self.llm_call(
|
|
|
messages=llm_messages,
|
|
messages=llm_messages,
|
|
@@ -1129,6 +1111,30 @@ class AgentRunner:
|
|
|
cache_creation_tokens = result.get("cache_creation_tokens")
|
|
cache_creation_tokens = result.get("cache_creation_tokens")
|
|
|
cache_read_tokens = result.get("cache_read_tokens")
|
|
cache_read_tokens = result.get("cache_read_tokens")
|
|
|
|
|
|
|
|
|
|
+ # 周期性自动注入上下文(仅主路径)
|
|
|
|
|
+ if not side_branch_ctx and iteration % CONTEXT_INJECTION_INTERVAL == 0:
|
|
|
|
|
+ # 检查是否已经调用了 get_current_context
|
|
|
|
|
+ if tool_calls:
|
|
|
|
|
+ has_context_call = any(
|
|
|
|
|
+ tc.get("function", {}).get("name") == "get_current_context"
|
|
|
|
|
+ for tc in tool_calls
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ has_context_call = False
|
|
|
|
|
+ tool_calls = []
|
|
|
|
|
+
|
|
|
|
|
+ if not has_context_call:
|
|
|
|
|
+ # 手动添加 get_current_context 工具调用
|
|
|
|
|
+ import uuid
|
|
|
|
|
+ context_call_id = f"call_context_{uuid.uuid4().hex[:8]}"
|
|
|
|
|
+ tool_calls.append({
|
|
|
|
|
+ "id": context_call_id,
|
|
|
|
|
+ "type": "function",
|
|
|
|
|
+ "function": {"name": "get_current_context", "arguments": "{}"}
|
|
|
|
|
+ })
|
|
|
|
|
+ logger.info(f"[周期性注入] 自动添加 get_current_context 工具调用 (iteration={iteration})")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# 按需自动创建 root goal(仅主路径)
|
|
# 按需自动创建 root goal(仅主路径)
|
|
|
if not side_branch_ctx and goal_tree and not goal_tree.goals and tool_calls:
|
|
if not side_branch_ctx and goal_tree and not goal_tree.goals and tool_calls:
|
|
|
has_goal_call = any(
|
|
has_goal_call = any(
|
|
@@ -1239,6 +1245,12 @@ class AgentRunner:
|
|
|
|
|
|
|
|
# 清除侧分支状态
|
|
# 清除侧分支状态
|
|
|
trace.context.pop("active_side_branch", None)
|
|
trace.context.pop("active_side_branch", None)
|
|
|
|
|
+
|
|
|
|
|
+ # 队列中如果还有侧分支,保持 force_side_branch;否则清空
|
|
|
|
|
+ if not config.force_side_branch or len(config.force_side_branch) == 0:
|
|
|
|
|
+ config.force_side_branch = None
|
|
|
|
|
+ logger.info("反思超时,队列为空")
|
|
|
|
|
+
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
|
await self.trace_store.update_trace(
|
|
await self.trace_store.update_trace(
|
|
|
trace_id, context=trace.context
|
|
trace_id, context=trace.context
|
|
@@ -1286,14 +1298,22 @@ class AgentRunner:
|
|
|
logger.warning("侧分支未生成有效 summary,使用默认")
|
|
logger.warning("侧分支未生成有效 summary,使用默认")
|
|
|
summary_text = "压缩完成"
|
|
summary_text = "压缩完成"
|
|
|
|
|
|
|
|
- # 创建主路径的 summary 消息
|
|
|
|
|
|
|
+ # 创建主路径的 summary 消息(末尾追加详细 GoalTree)
|
|
|
|
|
+ from agent.core.prompts import build_summary_header
|
|
|
|
|
+ summary_content = build_summary_header(summary_text)
|
|
|
|
|
+
|
|
|
|
|
+ # 追加详细 GoalTree(压缩后立即注入)
|
|
|
|
|
+ if goal_tree and goal_tree.goals:
|
|
|
|
|
+ goal_tree_detail = goal_tree.to_prompt(include_summary=True)
|
|
|
|
|
+ summary_content += f"\n\n## Current Plan\n\n{goal_tree_detail}"
|
|
|
|
|
+
|
|
|
summary_msg = Message.create(
|
|
summary_msg = Message.create(
|
|
|
trace_id=trace_id,
|
|
trace_id=trace_id,
|
|
|
role="user",
|
|
role="user",
|
|
|
sequence=sequence,
|
|
sequence=sequence,
|
|
|
parent_sequence=side_branch_ctx.start_head_seq,
|
|
parent_sequence=side_branch_ctx.start_head_seq,
|
|
|
branch_type=None, # 回到主路径
|
|
branch_type=None, # 回到主路径
|
|
|
- content=f"[压缩总结]\n{summary_text}",
|
|
|
|
|
|
|
+ content=summary_content,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
@@ -1312,6 +1332,9 @@ class AgentRunner:
|
|
|
|
|
|
|
|
logger.info(f"压缩侧分支完成,history 长度: {len(history)}")
|
|
logger.info(f"压缩侧分支完成,history 长度: {len(history)}")
|
|
|
|
|
|
|
|
|
|
+ # 清除侧分支队列
|
|
|
|
|
+ config.force_side_branch = None
|
|
|
|
|
+
|
|
|
elif side_branch_ctx.type == "reflection":
|
|
elif side_branch_ctx.type == "reflection":
|
|
|
# 反思侧分支:直接恢复主路径
|
|
# 反思侧分支:直接恢复主路径
|
|
|
logger.info("反思侧分支完成")
|
|
logger.info("反思侧分支完成")
|
|
@@ -1323,6 +1346,11 @@ class AgentRunner:
|
|
|
history = [m.to_llm_dict() for m in main_path_messages]
|
|
history = [m.to_llm_dict() for m in main_path_messages]
|
|
|
head_seq = side_branch_ctx.start_head_seq
|
|
head_seq = side_branch_ctx.start_head_seq
|
|
|
|
|
|
|
|
|
|
+ # 队列中如果还有侧分支,保持 force_side_branch;否则清空
|
|
|
|
|
+ if not config.force_side_branch or len(config.force_side_branch) == 0:
|
|
|
|
|
+ config.force_side_branch = None
|
|
|
|
|
+ logger.info("反思完成,队列为空")
|
|
|
|
|
+
|
|
|
# 清除侧分支状态
|
|
# 清除侧分支状态
|
|
|
trace.context.pop("active_side_branch", None)
|
|
trace.context.pop("active_side_branch", None)
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
@@ -1332,8 +1360,8 @@ class AgentRunner:
|
|
|
head_sequence=head_seq,
|
|
head_sequence=head_seq,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # 清除强制侧分支配置(避免影响后续续跑)
|
|
|
|
|
- config.force_side_branch = None
|
|
|
|
|
|
|
+ # 注意:不在这里清除 force_side_branch,因为反思侧分支可能已经设置了下一个侧分支
|
|
|
|
|
+ # force_side_branch 的清除由各个分支类型自己处理
|
|
|
|
|
|
|
|
side_branch_ctx = None
|
|
side_branch_ctx = None
|
|
|
continue
|
|
continue
|