|
|
@@ -628,6 +628,7 @@ class AgentRunner:
|
|
|
resolved_policy = self._resolve_run_policy(config)
|
|
|
tool_schemas = self._get_run_tool_schemas(config, resolved_policy)
|
|
|
|
|
|
+ is_legacy = resolved_policy.completion_policy == CompletionPolicy.LEGACY_AUTO
|
|
|
trace_obj = Trace(
|
|
|
trace_id=trace_id,
|
|
|
mode="agent",
|
|
|
@@ -635,7 +636,7 @@ class AgentRunner:
|
|
|
agent_type=config.agent_type,
|
|
|
agent_role=resolved_policy.role.value,
|
|
|
parent_trace_id=config.parent_trace_id,
|
|
|
- parent_goal_id=config.parent_goal_id,
|
|
|
+ parent_goal_id=config.parent_goal_id if is_legacy else None,
|
|
|
uid=config.uid,
|
|
|
model=config.model,
|
|
|
tools=tool_schemas,
|
|
|
@@ -644,11 +645,12 @@ class AgentRunner:
|
|
|
status="running",
|
|
|
)
|
|
|
|
|
|
- goal_tree = self.goal_tree or GoalTree(mission=task_name)
|
|
|
+ goal_tree = (self.goal_tree or GoalTree(mission=task_name)) if is_legacy else None
|
|
|
|
|
|
if self.trace_store:
|
|
|
await self.trace_store.create_trace(trace_obj)
|
|
|
- await self.trace_store.update_goal_tree(trace_id, goal_tree)
|
|
|
+ if goal_tree is not None:
|
|
|
+ await self.trace_store.update_goal_tree(trace_id, goal_tree)
|
|
|
|
|
|
return trace_obj, goal_tree, 1
|
|
|
|
|
|
@@ -664,11 +666,14 @@ class AgentRunner:
|
|
|
if not trace_obj:
|
|
|
raise ValueError(f"Trace not found: {config.trace_id}")
|
|
|
|
|
|
- goal_tree = await self.trace_store.get_goal_tree(config.trace_id)
|
|
|
- if goal_tree is None:
|
|
|
- # 防御性兜底:trace 存在但 goal.json 丢失时,创建空树
|
|
|
- goal_tree = GoalTree(mission=trace_obj.task or "Agent task")
|
|
|
- await self.trace_store.update_goal_tree(config.trace_id, goal_tree)
|
|
|
+ policy = self._resolve_run_policy(config, trace_obj).completion_policy
|
|
|
+ goal_tree = None
|
|
|
+ if policy == CompletionPolicy.LEGACY_AUTO:
|
|
|
+ goal_tree = await self.trace_store.get_goal_tree(config.trace_id)
|
|
|
+ if goal_tree is None:
|
|
|
+ # Legacy traces retain the historical defensive empty tree.
|
|
|
+ goal_tree = GoalTree(mission=trace_obj.task or "Agent task")
|
|
|
+ await self.trace_store.update_goal_tree(config.trace_id, goal_tree)
|
|
|
|
|
|
# 自动判断行为:after_sequence 为 None 或 == head → 续跑;< head → 回溯
|
|
|
after_seq = config.after_sequence
|
|
|
@@ -927,9 +932,16 @@ class AgentRunner:
|
|
|
config.force_side_branch = ["reflection", "compression"]
|
|
|
return history, head_seq, sequence, True
|
|
|
|
|
|
- # 以下为未启用反思、需要压缩的情况,直接进行level 1压缩,并检查是否需要进行level 2压缩(进入侧分支)
|
|
|
- # Level 1 压缩:Goal 完成压缩
|
|
|
- if config.goal_compression != "none" and self.trace_store and goal_tree:
|
|
|
+ # Level 1 是 legacy GoalTree 的确定性压缩;explicit 直接保留 Level 2。
|
|
|
+ is_legacy = (
|
|
|
+ CompletionPolicy(config.completion_policy) == CompletionPolicy.LEGACY_AUTO
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ is_legacy
|
|
|
+ and config.goal_compression != "none"
|
|
|
+ and self.trace_store
|
|
|
+ and goal_tree
|
|
|
+ ):
|
|
|
if head_seq > 0:
|
|
|
main_path_msgs = await self.trace_store.get_main_path_messages(
|
|
|
trace_id, head_seq
|
|
|
@@ -947,7 +959,7 @@ class AgentRunner:
|
|
|
"Level 1 压缩: 无可过滤消息 (%d 条全部保留)",
|
|
|
len(main_path_msgs),
|
|
|
)
|
|
|
- elif needs_compression:
|
|
|
+ elif is_legacy:
|
|
|
self.log.warning(
|
|
|
"Token 数 (%d) 超过阈值,但无法执行 Level 1 压缩(缺少 store 或 goal_tree,或 goal_compression=none)",
|
|
|
token_count,
|
|
|
@@ -960,8 +972,10 @@ class AgentRunner:
|
|
|
needs_level2 = token_count_after > max_tokens
|
|
|
|
|
|
if needs_level2:
|
|
|
+ source = "Level 1 后" if is_legacy else "explicit 模式"
|
|
|
self.log.info(
|
|
|
- "Level 1 后仍超阈值 (token=%d/%d),需要进入压缩侧分支",
|
|
|
+ "%s仍超阈值 (token=%d/%d),需要进入压缩侧分支",
|
|
|
+ source,
|
|
|
token_count_after,
|
|
|
max_tokens,
|
|
|
)
|
|
|
@@ -972,7 +986,7 @@ class AgentRunner:
|
|
|
return history, head_seq, sequence, True
|
|
|
|
|
|
# 压缩完成后,输出最终发给模型的消息列表
|
|
|
- self.log.info("Level 1 压缩完成,发送给模型的消息列表:")
|
|
|
+ self.log.info("上下文压缩检查完成,发送给模型的消息列表:")
|
|
|
for idx, msg in enumerate(history):
|
|
|
role = msg.get("role", "unknown")
|
|
|
content = msg.get("content", "")
|
|
|
@@ -1070,8 +1084,12 @@ class AgentRunner:
|
|
|
# 构建压缩 prompt(使用 SINGLE_TURN_PROMPT)
|
|
|
from agent.core.prompts import build_single_turn_prompt
|
|
|
|
|
|
- goal_prompt = goal_tree.to_prompt(include_summary=True) if goal_tree else ""
|
|
|
- compress_prompt = build_single_turn_prompt(goal_prompt)
|
|
|
+ execution_context = await self._current_execution_context(
|
|
|
+ trace_id,
|
|
|
+ goal_tree,
|
|
|
+ config,
|
|
|
+ )
|
|
|
+ compress_prompt = build_single_turn_prompt(execution_context)
|
|
|
compress_messages = list(history) + [
|
|
|
{"role": "user", "content": compress_prompt}
|
|
|
]
|
|
|
@@ -1393,7 +1411,12 @@ class AgentRunner:
|
|
|
else: # compression
|
|
|
from agent.trace.compaction import build_compression_prompt
|
|
|
|
|
|
- prompt = build_compression_prompt(goal_tree)
|
|
|
+ execution_context = await self._current_execution_context(
|
|
|
+ trace_id,
|
|
|
+ goal_tree,
|
|
|
+ config,
|
|
|
+ )
|
|
|
+ prompt = build_compression_prompt(execution_context)
|
|
|
|
|
|
branch_user_msg = Message.create(
|
|
|
trace_id=trace_id,
|
|
|
@@ -1720,11 +1743,13 @@ class AgentRunner:
|
|
|
|
|
|
summary_content = build_summary_header(summary_text)
|
|
|
|
|
|
- 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}"
|
|
|
- )
|
|
|
+ execution_context = await self._current_execution_context(
|
|
|
+ trace_id,
|
|
|
+ goal_tree,
|
|
|
+ config,
|
|
|
+ )
|
|
|
+ if execution_context:
|
|
|
+ summary_content += f"\n\n{execution_context}"
|
|
|
|
|
|
carried_systems: Optional[List[Dict[str, Any]]] = None
|
|
|
if self.trace_store:
|
|
|
@@ -3084,16 +3109,20 @@ class AgentRunner:
|
|
|
|
|
|
def _build_context_injection(
|
|
|
self,
|
|
|
- trace: Trace,
|
|
|
+ trace: Optional[Trace],
|
|
|
goal_tree: Optional[GoalTree],
|
|
|
+ *,
|
|
|
+ task_context: str = "",
|
|
|
) -> str:
|
|
|
- """构建周期性注入的上下文(GoalTree + Active Collaborators + Focus 提醒 + IM 消息通知)"""
|
|
|
+ """构建周期上下文;计划来源由运行模式决定。"""
|
|
|
from datetime import datetime
|
|
|
|
|
|
parts = [f"## Current Time\n\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"]
|
|
|
+ trace_context = trace.context if trace and trace.context else {}
|
|
|
|
|
|
- # GoalTree
|
|
|
- if goal_tree and goal_tree.goals:
|
|
|
+ if task_context:
|
|
|
+ parts.append(task_context)
|
|
|
+ elif goal_tree and goal_tree.goals:
|
|
|
parts.append(f"## Current Plan\n\n{goal_tree.to_prompt()}")
|
|
|
|
|
|
if goal_tree.current_id:
|
|
|
@@ -3117,7 +3146,7 @@ class AgentRunner:
|
|
|
)
|
|
|
|
|
|
# Active Collaborators
|
|
|
- collaborators = trace.context.get("collaborators", [])
|
|
|
+ collaborators = trace_context.get("collaborators", [])
|
|
|
if collaborators:
|
|
|
lines = ["## Active Collaborators"]
|
|
|
for c in collaborators:
|
|
|
@@ -3129,7 +3158,7 @@ class AgentRunner:
|
|
|
parts.append("\n".join(lines))
|
|
|
|
|
|
# IM 消息通知(Research Agent)
|
|
|
- im_config = trace.context.get("im_config")
|
|
|
+ im_config = trace_context.get("im_config")
|
|
|
if im_config:
|
|
|
contact_id = im_config.get("contact_id")
|
|
|
chat_id = im_config.get("chat_id")
|
|
|
@@ -3155,9 +3184,9 @@ class AgentRunner:
|
|
|
pass
|
|
|
|
|
|
# Knowledge Manager 队列状态
|
|
|
- km_queue_size = trace.context.get("km_queue_size")
|
|
|
+ km_queue_size = trace_context.get("km_queue_size")
|
|
|
if km_queue_size is not None:
|
|
|
- current_sender = trace.context.get("current_sender", "unknown")
|
|
|
+ current_sender = trace_context.get("current_sender", "unknown")
|
|
|
if km_queue_size > 0:
|
|
|
parts.append(
|
|
|
f"## 消息队列状态\n\n"
|
|
|
@@ -3173,6 +3202,29 @@ class AgentRunner:
|
|
|
|
|
|
return "\n\n".join(parts)
|
|
|
|
|
|
+ async def _build_task_context(self, trace: Optional[Trace]) -> str:
|
|
|
+ """从 explicit 模式的权威 TaskLedger 构建只读计划摘要。"""
|
|
|
+
|
|
|
+ if trace is None or self.task_coordinator is None:
|
|
|
+ return ""
|
|
|
+ root_trace_id = (trace.context or {}).get("root_trace_id", trace.trace_id)
|
|
|
+ return str(await self.task_coordinator.task_context(root_trace_id))
|
|
|
+
|
|
|
+ async def _current_execution_context(
|
|
|
+ self,
|
|
|
+ trace_id: str,
|
|
|
+ goal_tree: Optional[GoalTree],
|
|
|
+ config: RunConfig,
|
|
|
+ ) -> str:
|
|
|
+ """Return the active mode's plan read model for Level 2 summaries."""
|
|
|
+
|
|
|
+ if CompletionPolicy(config.completion_policy) == CompletionPolicy.LEGACY_AUTO:
|
|
|
+ if not goal_tree:
|
|
|
+ return ""
|
|
|
+ return f"## Current Plan\n\n{goal_tree.to_prompt(include_summary=True)}"
|
|
|
+ trace = await self.trace_store.get_trace(trace_id) if self.trace_store else None
|
|
|
+ return await self._build_task_context(trace)
|
|
|
+
|
|
|
# ===== 辅助方法 =====
|
|
|
|
|
|
async def _optimize_images(self, messages: List[Dict], model: str) -> List[Dict]:
|