|
@@ -969,6 +969,8 @@ class AgentRunner:
|
|
|
# 重新计算 start_history_length
|
|
# 重新计算 start_history_length
|
|
|
side_branch_ctx.start_history_length = len(history) - len(side_messages)
|
|
side_branch_ctx.start_history_length = len(history) - len(side_messages)
|
|
|
|
|
|
|
|
|
|
+ break_after_side_branch = False # 侧分支退出后是否 break 主循环
|
|
|
|
|
+
|
|
|
for iteration in range(config.max_iterations):
|
|
for iteration in range(config.max_iterations):
|
|
|
# 更新活动时间(表明trace正在活跃运行)
|
|
# 更新活动时间(表明trace正在活跃运行)
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
@@ -1002,7 +1004,11 @@ class AgentRunner:
|
|
|
# Context 管理(仅主路径)
|
|
# Context 管理(仅主路径)
|
|
|
needs_enter_side_branch = False
|
|
needs_enter_side_branch = False
|
|
|
if not side_branch_ctx:
|
|
if not side_branch_ctx:
|
|
|
- # 检查是否强制进入侧分支(API 手动触发)
|
|
|
|
|
|
|
+ # 侧分支退出后需要 break 主循环
|
|
|
|
|
+ if break_after_side_branch and not config.force_side_branch:
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ # 检查是否强制进入侧分支(API 手动触发或自动压缩流程)
|
|
|
if config.force_side_branch:
|
|
if config.force_side_branch:
|
|
|
needs_enter_side_branch = True
|
|
needs_enter_side_branch = True
|
|
|
logger.info(f"强制进入侧分支: {config.force_side_branch}")
|
|
logger.info(f"强制进入侧分支: {config.force_side_branch}")
|
|
@@ -1521,12 +1527,16 @@ class AgentRunner:
|
|
|
|
|
|
|
|
continue # 继续循环
|
|
continue # 继续循环
|
|
|
|
|
|
|
|
- # 无工具调用,任务完成
|
|
|
|
|
- break
|
|
|
|
|
|
|
+ # 无工具调用
|
|
|
|
|
+ # 如果在侧分支中,已经在上面处理过了(不会走到这里)
|
|
|
|
|
+ # 主路径无工具调用 → 任务完成,检查是否需要完成后反思
|
|
|
|
|
+ if not side_branch_ctx and config.knowledge.enable_completion_extraction and not break_after_side_branch:
|
|
|
|
|
+ config.force_side_branch = ["reflection"]
|
|
|
|
|
+ break_after_side_branch = True
|
|
|
|
|
+ logger.info("任务完成,进入完成后反思侧分支")
|
|
|
|
|
+ continue
|
|
|
|
|
|
|
|
- # 任务完成后复盘提取
|
|
|
|
|
- if config.knowledge.enable_completion_extraction:
|
|
|
|
|
- await self._extract_knowledge_on_completion(trace_id, history, config)
|
|
|
|
|
|
|
+ break
|
|
|
|
|
|
|
|
# 清理 trace 相关的跟踪数据
|
|
# 清理 trace 相关的跟踪数据
|
|
|
self._context_warned.pop(trace_id, None)
|
|
self._context_warned.pop(trace_id, None)
|
|
@@ -1595,95 +1605,6 @@ class AgentRunner:
|
|
|
|
|
|
|
|
return new_history
|
|
return new_history
|
|
|
|
|
|
|
|
- async def _run_reflect(
|
|
|
|
|
- self,
|
|
|
|
|
- trace_id: str,
|
|
|
|
|
- history: List[Dict],
|
|
|
|
|
- config: RunConfig,
|
|
|
|
|
- reflect_prompt: str,
|
|
|
|
|
- source_name: str,
|
|
|
|
|
- ) -> None:
|
|
|
|
|
- """
|
|
|
|
|
- 执行反思提取:LLM 对历史消息进行反思,直接调用 knowledge_save 工具保存经验。
|
|
|
|
|
-
|
|
|
|
|
- Args:
|
|
|
|
|
- trace_id: Trace ID(作为知识的 message_id)
|
|
|
|
|
- history: 当前对话历史
|
|
|
|
|
- config: 运行配置
|
|
|
|
|
- reflect_prompt: 反思 prompt
|
|
|
|
|
- source_name: 来源名称(用于区分压缩时/完成时)
|
|
|
|
|
- """
|
|
|
|
|
- try:
|
|
|
|
|
- reflect_messages = list(history) + [{"role": "user", "content": reflect_prompt}]
|
|
|
|
|
- reflect_messages = self._add_cache_control(
|
|
|
|
|
- reflect_messages, config.model, config.enable_prompt_caching
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 只暴露 knowledge_save 工具,让 LLM 直接调用
|
|
|
|
|
- knowledge_save_schema = self._get_tool_schemas(["knowledge_save"])
|
|
|
|
|
-
|
|
|
|
|
- reflect_result = await self.llm_call(
|
|
|
|
|
- messages=reflect_messages,
|
|
|
|
|
- model=config.model,
|
|
|
|
|
- tools=knowledge_save_schema,
|
|
|
|
|
- temperature=0.2,
|
|
|
|
|
- **config.extra_llm_params,
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- tool_calls = reflect_result.get("tool_calls") or []
|
|
|
|
|
- if not tool_calls:
|
|
|
|
|
- logger.info("反思阶段无经验保存 (source=%s)", source_name)
|
|
|
|
|
- return
|
|
|
|
|
-
|
|
|
|
|
- saved_count = 0
|
|
|
|
|
- for tc in tool_calls:
|
|
|
|
|
- tool_name = tc.get("function", {}).get("name")
|
|
|
|
|
- if tool_name != "knowledge_save":
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- tool_args = tc.get("function", {}).get("arguments") or {}
|
|
|
|
|
- if isinstance(tool_args, str):
|
|
|
|
|
- tool_args = json.loads(tool_args) if tool_args.strip() else {}
|
|
|
|
|
-
|
|
|
|
|
- # 注入来源信息(LLM 不需要填写这些字段)
|
|
|
|
|
- tool_args.setdefault("source_name", source_name)
|
|
|
|
|
- tool_args.setdefault("source_category", "exp")
|
|
|
|
|
- tool_args.setdefault("message_id", trace_id)
|
|
|
|
|
-
|
|
|
|
|
- try:
|
|
|
|
|
- await self.tools.execute(
|
|
|
|
|
- "knowledge_save",
|
|
|
|
|
- tool_args,
|
|
|
|
|
- uid=config.uid or "",
|
|
|
|
|
- context={
|
|
|
|
|
- "store": self.trace_store,
|
|
|
|
|
- "trace_id": trace_id,
|
|
|
|
|
- "knowledge_config": config.knowledge,
|
|
|
|
|
- },
|
|
|
|
|
- )
|
|
|
|
|
- saved_count += 1
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- logger.warning("保存经验失败: %s", e)
|
|
|
|
|
-
|
|
|
|
|
- logger.info("已提取并保存 %d 条经验 (source=%s)", saved_count, source_name)
|
|
|
|
|
-
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- logger.error("知识反思提取失败 (source=%s): %s", source_name, e)
|
|
|
|
|
-
|
|
|
|
|
- async def _extract_knowledge_on_completion(
|
|
|
|
|
- self,
|
|
|
|
|
- trace_id: str,
|
|
|
|
|
- history: List[Dict],
|
|
|
|
|
- config: RunConfig,
|
|
|
|
|
- ) -> None:
|
|
|
|
|
- """任务完成后执行全局复盘,提取经验保存到知识库。"""
|
|
|
|
|
- logger.info("任务完成后复盘提取: trace=%s", trace_id)
|
|
|
|
|
- await self._run_reflect(
|
|
|
|
|
- trace_id, history, config,
|
|
|
|
|
- reflect_prompt=config.knowledge.get_completion_reflect_prompt(),
|
|
|
|
|
- source_name="completion_reflection",
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
# ===== 回溯(Rewind)=====
|
|
# ===== 回溯(Rewind)=====
|
|
|
|
|
|
|
|
async def _rewind(
|
|
async def _rewind(
|