|
@@ -41,6 +41,8 @@ from cyber_agent.tools.builtin.knowledge import KnowledgeConfig
|
|
|
from cyber_agent.core.memory import MemoryConfig
|
|
from cyber_agent.core.memory import MemoryConfig
|
|
|
from cyber_agent.core.agent_mode import (
|
|
from cyber_agent.core.agent_mode import (
|
|
|
AGENT_MODE_CONTEXT_KEY,
|
|
AGENT_MODE_CONTEXT_KEY,
|
|
|
|
|
+ AgentMode,
|
|
|
|
|
+ RECURSIVE_CAPABILITY_TOOLS_CONTEXT_KEY,
|
|
|
apply_policy_to_context,
|
|
apply_policy_to_context,
|
|
|
assert_removed_config_absent,
|
|
assert_removed_config_absent,
|
|
|
policy_from_context,
|
|
policy_from_context,
|
|
@@ -1083,7 +1085,12 @@ class AgentRunner:
|
|
|
) -> AsyncIterator[Union[Trace, Message]]:
|
|
) -> AsyncIterator[Union[Trace, Message]]:
|
|
|
"""ReAct 循环"""
|
|
"""ReAct 循环"""
|
|
|
trace_id = trace.trace_id
|
|
trace_id = trace.trace_id
|
|
|
- tool_schemas = self._get_runtime_tool_schemas(config, trace)
|
|
|
|
|
|
|
+ runtime_tool_names = self._get_runtime_tool_names(config, trace)
|
|
|
|
|
+ tool_schemas = self._get_runtime_tool_schemas(
|
|
|
|
|
+ config,
|
|
|
|
|
+ trace,
|
|
|
|
|
+ runtime_tool_names=runtime_tool_names,
|
|
|
|
|
+ )
|
|
|
completion_status = "completed"
|
|
completion_status = "completed"
|
|
|
|
|
|
|
|
# 当前主路径头节点的 sequence(用于设置 parent_sequence)
|
|
# 当前主路径头节点的 sequence(用于设置 parent_sequence)
|
|
@@ -1284,10 +1291,22 @@ class AgentRunner:
|
|
|
fresh_trace = await self.trace_store.get_trace(trace_id)
|
|
fresh_trace = await self.trace_store.get_trace(trace_id)
|
|
|
if fresh_trace:
|
|
if fresh_trace:
|
|
|
trace = fresh_trace
|
|
trace = fresh_trace
|
|
|
|
|
+ runtime_policy = policy_from_context(trace.context)
|
|
|
|
|
+ runtime_tool_names = self._get_runtime_tool_names(
|
|
|
|
|
+ config,
|
|
|
|
|
+ trace,
|
|
|
|
|
+ in_side_branch=side_branch_ctx is not None,
|
|
|
|
|
+ )
|
|
|
tool_schemas = self._get_runtime_tool_schemas(
|
|
tool_schemas = self._get_runtime_tool_schemas(
|
|
|
config,
|
|
config,
|
|
|
trace,
|
|
trace,
|
|
|
in_side_branch=side_branch_ctx is not None,
|
|
in_side_branch=side_branch_ctx is not None,
|
|
|
|
|
+ runtime_tool_names=runtime_tool_names,
|
|
|
|
|
+ )
|
|
|
|
|
+ dispatch_allowlist = (
|
|
|
|
|
+ runtime_tool_names
|
|
|
|
|
+ if runtime_policy.mode is AgentMode.RECURSIVE
|
|
|
|
|
+ else None
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# 构建 LLM messages(注入上下文,移除内部字段)
|
|
# 构建 LLM messages(注入上下文,移除内部字段)
|
|
@@ -1327,7 +1346,6 @@ class AgentRunner:
|
|
|
1 for tc in (tool_calls or [])
|
|
1 for tc in (tool_calls or [])
|
|
|
if tc.get("function", {}).get("name") in lifecycle_tools
|
|
if tc.get("function", {}).get("name") in lifecycle_tools
|
|
|
)
|
|
)
|
|
|
- runtime_policy = policy_from_context(trace.context)
|
|
|
|
|
protocol_batch_error = None
|
|
protocol_batch_error = None
|
|
|
if (
|
|
if (
|
|
|
runtime_policy.requires_task_protocol
|
|
runtime_policy.requires_task_protocol
|
|
@@ -1366,6 +1384,10 @@ class AgentRunner:
|
|
|
not has_context_call
|
|
not has_context_call
|
|
|
and not lifecycle_call_count
|
|
and not lifecycle_call_count
|
|
|
and not protocol_lifecycle_required
|
|
and not protocol_lifecycle_required
|
|
|
|
|
+ and (
|
|
|
|
|
+ runtime_policy.mode is not AgentMode.RECURSIVE
|
|
|
|
|
+ or "get_current_context" in runtime_tool_names
|
|
|
|
|
+ )
|
|
|
):
|
|
):
|
|
|
# 手动添加 get_current_context 工具调用
|
|
# 手动添加 get_current_context 工具调用
|
|
|
context_call_id = f"call_context_{uuid.uuid4().hex[:8]}"
|
|
context_call_id = f"call_context_{uuid.uuid4().hex[:8]}"
|
|
@@ -1383,6 +1405,10 @@ class AgentRunner:
|
|
|
and iteration == 0
|
|
and iteration == 0
|
|
|
and not lifecycle_call_count
|
|
and not lifecycle_call_count
|
|
|
and not protocol_lifecycle_required
|
|
and not protocol_lifecycle_required
|
|
|
|
|
+ and (
|
|
|
|
|
+ runtime_policy.mode is not AgentMode.RECURSIVE
|
|
|
|
|
+ or "skill" in runtime_tool_names
|
|
|
|
|
+ )
|
|
|
):
|
|
):
|
|
|
skills_to_inject = self._check_skills_need_injection(
|
|
skills_to_inject = self._check_skills_need_injection(
|
|
|
trace, inject_skills, history, skill_recency_threshold
|
|
trace, inject_skills, history, skill_recency_threshold
|
|
@@ -1757,8 +1783,20 @@ class AgentRunner:
|
|
|
pass
|
|
pass
|
|
|
try:
|
|
try:
|
|
|
tool_result = await self.tools.execute(
|
|
tool_result = await self.tools.execute(
|
|
|
- tool_name, tool_args, uid=config.uid or "",
|
|
|
|
|
- context={"store": self.trace_store, "trace_id": trace_id, "goal_id": current_goal_id, "runner": self, "goal_tree": goal_tree, "knowledge_config": config.knowledge, "sequence": sequence, "side_branch": {"type": side_branch_ctx.type, "branch_id": side_branch_ctx.branch_id, "is_side_branch": True, "max_turns": side_branch_ctx.max_turns, "trigger_event": trigger_event_for_tool} if side_branch_ctx else None, **(config.context or {})}
|
|
|
|
|
|
|
+ tool_name,
|
|
|
|
|
+ tool_args,
|
|
|
|
|
+ uid=config.uid or "",
|
|
|
|
|
+ context=self._build_tool_context(
|
|
|
|
|
+ config=config,
|
|
|
|
|
+ trace=trace,
|
|
|
|
|
+ trace_id=trace_id,
|
|
|
|
|
+ goal_id=current_goal_id,
|
|
|
|
|
+ goal_tree=goal_tree,
|
|
|
|
|
+ sequence=sequence,
|
|
|
|
|
+ side_branch_ctx=side_branch_ctx,
|
|
|
|
|
+ trigger_event=trigger_event_for_tool,
|
|
|
|
|
+ ),
|
|
|
|
|
+ allowed_tool_names=dispatch_allowlist,
|
|
|
)
|
|
)
|
|
|
return (tc, tool_args, tool_result)
|
|
return (tc, tool_args, tool_result)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
@@ -1888,25 +1926,17 @@ class AgentRunner:
|
|
|
tool_name,
|
|
tool_name,
|
|
|
tool_args,
|
|
tool_args,
|
|
|
uid=config.uid or "",
|
|
uid=config.uid or "",
|
|
|
- context={
|
|
|
|
|
- "store": self.trace_store,
|
|
|
|
|
- "trace_id": trace_id,
|
|
|
|
|
- "goal_id": current_goal_id,
|
|
|
|
|
- "runner": self,
|
|
|
|
|
- "goal_tree": goal_tree,
|
|
|
|
|
- "knowledge_config": config.knowledge,
|
|
|
|
|
- "sequence": sequence, # 添加sequence用于知识注入记录
|
|
|
|
|
- # 新增:侧分支信息
|
|
|
|
|
- "side_branch": {
|
|
|
|
|
- "type": side_branch_ctx.type,
|
|
|
|
|
- "branch_id": side_branch_ctx.branch_id,
|
|
|
|
|
- "is_side_branch": True,
|
|
|
|
|
- "max_turns": side_branch_ctx.max_turns,
|
|
|
|
|
- "trigger_event": trigger_event_for_tool,
|
|
|
|
|
- } if side_branch_ctx else None,
|
|
|
|
|
- # 合并用户自定义 context(RunConfig.context)
|
|
|
|
|
- **(config.context or {}),
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ context=self._build_tool_context(
|
|
|
|
|
+ config=config,
|
|
|
|
|
+ trace=trace,
|
|
|
|
|
+ trace_id=trace_id,
|
|
|
|
|
+ goal_id=current_goal_id,
|
|
|
|
|
+ goal_tree=goal_tree,
|
|
|
|
|
+ sequence=sequence,
|
|
|
|
|
+ side_branch_ctx=side_branch_ctx,
|
|
|
|
|
+ trigger_event=trigger_event_for_tool,
|
|
|
|
|
+ ),
|
|
|
|
|
+ allowed_tool_names=dispatch_allowlist,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# 如果是 goal 工具,记录执行后的状态
|
|
# 如果是 goal 工具,记录执行后的状态
|
|
@@ -3212,22 +3242,13 @@ class AgentRunner:
|
|
|
)
|
|
)
|
|
|
return messages
|
|
return messages
|
|
|
|
|
|
|
|
- def _get_tool_schemas(
|
|
|
|
|
|
|
+ def _get_configured_tool_names(
|
|
|
self,
|
|
self,
|
|
|
tools: Optional[List[str]] = None,
|
|
tools: Optional[List[str]] = None,
|
|
|
tool_groups: Optional[List[str]] = None,
|
|
tool_groups: Optional[List[str]] = None,
|
|
|
exclude_tools: Optional[List[str]] = None,
|
|
exclude_tools: Optional[List[str]] = None,
|
|
|
- ) -> List[Dict]:
|
|
|
|
|
- """
|
|
|
|
|
- 获取工具 Schema
|
|
|
|
|
-
|
|
|
|
|
- 合并策略(取并集):
|
|
|
|
|
- - tool_groups 非空: 按分组白名单过滤得到基础工具集
|
|
|
|
|
- - tools 非空: 追加指定的工具名(与 tool_groups 结果取并集)
|
|
|
|
|
- - 两者都为 None: 返回所有已注册工具
|
|
|
|
|
-
|
|
|
|
|
- 最后再用 exclude_tools 减去禁用的工具(如远程 agent 禁止 agent/evaluate)。
|
|
|
|
|
- """
|
|
|
|
|
|
|
+ ) -> set[str]:
|
|
|
|
|
+ """解析 RunConfig 的基础工具能力集合。"""
|
|
|
if tool_groups is not None:
|
|
if tool_groups is not None:
|
|
|
tool_names = set(self.tools.get_tool_names(groups=tool_groups))
|
|
tool_names = set(self.tools.get_tool_names(groups=tool_groups))
|
|
|
else:
|
|
else:
|
|
@@ -3236,54 +3257,72 @@ class AgentRunner:
|
|
|
tool_names |= set(tools)
|
|
tool_names |= set(tools)
|
|
|
if exclude_tools:
|
|
if exclude_tools:
|
|
|
tool_names -= set(exclude_tools)
|
|
tool_names -= set(exclude_tools)
|
|
|
|
|
+ return tool_names
|
|
|
|
|
+
|
|
|
|
|
+ def _get_tool_schemas(
|
|
|
|
|
+ self,
|
|
|
|
|
+ tools: Optional[List[str]] = None,
|
|
|
|
|
+ tool_groups: Optional[List[str]] = None,
|
|
|
|
|
+ exclude_tools: Optional[List[str]] = None,
|
|
|
|
|
+ ) -> List[Dict]:
|
|
|
|
|
+ """获取 RunConfig 基础能力对应的工具 Schema。"""
|
|
|
|
|
+ tool_names = self._get_configured_tool_names(
|
|
|
|
|
+ tools,
|
|
|
|
|
+ tool_groups,
|
|
|
|
|
+ exclude_tools,
|
|
|
|
|
+ )
|
|
|
return self.tools.get_schemas(list(tool_names))
|
|
return self.tools.get_schemas(list(tool_names))
|
|
|
|
|
|
|
|
- def _get_runtime_tool_schemas(
|
|
|
|
|
|
|
+ def _get_runtime_tool_names(
|
|
|
self,
|
|
self,
|
|
|
config: RunConfig,
|
|
config: RunConfig,
|
|
|
trace: Trace,
|
|
trace: Trace,
|
|
|
*,
|
|
*,
|
|
|
in_side_branch: bool = False,
|
|
in_side_branch: bool = False,
|
|
|
- ) -> List[Dict]:
|
|
|
|
|
- """按 Trace 持久化模式和当前协议状态过滤工具 Schema。"""
|
|
|
|
|
- schemas = deepcopy(self._get_tool_schemas(
|
|
|
|
|
|
|
+ ) -> set[str]:
|
|
|
|
|
+ """在基础能力上应用 Recursive 协议状态门禁。"""
|
|
|
|
|
+ tool_names = self._get_configured_tool_names(
|
|
|
config.tools,
|
|
config.tools,
|
|
|
config.tool_groups,
|
|
config.tool_groups,
|
|
|
config.exclude_tools,
|
|
config.exclude_tools,
|
|
|
- ))
|
|
|
|
|
|
|
+ )
|
|
|
policy = policy_from_context(trace.context)
|
|
policy = policy_from_context(trace.context)
|
|
|
protocol_tools = {"submit_task_report", "review_task_result"}
|
|
protocol_tools = {"submit_task_report", "review_task_result"}
|
|
|
|
|
|
|
|
if not policy.requires_task_protocol or in_side_branch:
|
|
if not policy.requires_task_protocol or in_side_branch:
|
|
|
- schemas = [
|
|
|
|
|
- schema for schema in schemas
|
|
|
|
|
- if schema.get("function", {}).get("name") not in protocol_tools
|
|
|
|
|
- ]
|
|
|
|
|
- else:
|
|
|
|
|
- state = ensure_task_protocol(trace.context)
|
|
|
|
|
- if state["pending_reviews"]:
|
|
|
|
|
- schemas = [
|
|
|
|
|
- schema for schema in schemas
|
|
|
|
|
- if schema.get("function", {}).get("name") == "review_task_result"
|
|
|
|
|
- ]
|
|
|
|
|
- elif state["next_actions"]:
|
|
|
|
|
- schemas = [
|
|
|
|
|
- schema for schema in schemas
|
|
|
|
|
- if schema.get("function", {}).get("name") == "agent"
|
|
|
|
|
- ]
|
|
|
|
|
- else:
|
|
|
|
|
- is_child = bool(trace.parent_trace_id)
|
|
|
|
|
- has_report = state.get("task_report") is not None
|
|
|
|
|
- schemas = [
|
|
|
|
|
- schema for schema in schemas
|
|
|
|
|
- if not (
|
|
|
|
|
- schema.get("function", {}).get("name") == "review_task_result"
|
|
|
|
|
- or (
|
|
|
|
|
- schema.get("function", {}).get("name") == "submit_task_report"
|
|
|
|
|
- and (not is_child or has_report)
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ return tool_names - protocol_tools
|
|
|
|
|
+
|
|
|
|
|
+ state = ensure_task_protocol(trace.context)
|
|
|
|
|
+ if state["pending_reviews"]:
|
|
|
|
|
+ return tool_names & {"review_task_result"}
|
|
|
|
|
+ if state["next_actions"]:
|
|
|
|
|
+ return tool_names & {"agent"}
|
|
|
|
|
+
|
|
|
|
|
+ tool_names.discard("review_task_result")
|
|
|
|
|
+ if not trace.parent_trace_id or state.get("task_report") is not None:
|
|
|
|
|
+ tool_names.discard("submit_task_report")
|
|
|
|
|
+ return tool_names
|
|
|
|
|
+
|
|
|
|
|
+ def _get_runtime_tool_schemas(
|
|
|
|
|
+ self,
|
|
|
|
|
+ config: RunConfig,
|
|
|
|
|
+ trace: Trace,
|
|
|
|
|
+ *,
|
|
|
|
|
+ in_side_branch: bool = False,
|
|
|
|
|
+ runtime_tool_names: Optional[set[str]] = None,
|
|
|
|
|
+ ) -> List[Dict]:
|
|
|
|
|
+ """按 Trace 持久化模式和当前协议状态过滤工具 Schema。"""
|
|
|
|
|
+ tool_names = (
|
|
|
|
|
+ runtime_tool_names
|
|
|
|
|
+ if runtime_tool_names is not None
|
|
|
|
|
+ else self._get_runtime_tool_names(
|
|
|
|
|
+ config,
|
|
|
|
|
+ trace,
|
|
|
|
|
+ in_side_branch=in_side_branch,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ schemas = deepcopy(self.tools.get_schemas(list(tool_names)))
|
|
|
|
|
+ policy = policy_from_context(trace.context)
|
|
|
|
|
|
|
|
for schema in schemas:
|
|
for schema in schemas:
|
|
|
function = schema.get("function", {})
|
|
function = schema.get("function", {})
|
|
@@ -3309,6 +3348,48 @@ class AgentRunner:
|
|
|
required.append("task")
|
|
required.append("task")
|
|
|
return schemas
|
|
return schemas
|
|
|
|
|
|
|
|
|
|
+ def _build_tool_context(
|
|
|
|
|
+ self,
|
|
|
|
|
+ *,
|
|
|
|
|
+ config: RunConfig,
|
|
|
|
|
+ trace: Trace,
|
|
|
|
|
+ trace_id: str,
|
|
|
|
|
+ goal_id: Optional[str],
|
|
|
|
|
+ goal_tree: Optional[GoalTree],
|
|
|
|
|
+ sequence: int,
|
|
|
|
|
+ side_branch_ctx: Optional[SideBranchContext],
|
|
|
|
|
+ trigger_event: Optional[str],
|
|
|
|
|
+ ) -> Dict[str, Any]:
|
|
|
|
|
+ """构建工具上下文;Recursive 内部字段最后覆盖,不能由调用方伪造。"""
|
|
|
|
|
+ context = {
|
|
|
|
|
+ "store": self.trace_store,
|
|
|
|
|
+ "trace_id": trace_id,
|
|
|
|
|
+ "goal_id": goal_id,
|
|
|
|
|
+ "runner": self,
|
|
|
|
|
+ "goal_tree": goal_tree,
|
|
|
|
|
+ "knowledge_config": config.knowledge,
|
|
|
|
|
+ "sequence": sequence,
|
|
|
|
|
+ "side_branch": {
|
|
|
|
|
+ "type": side_branch_ctx.type,
|
|
|
|
|
+ "branch_id": side_branch_ctx.branch_id,
|
|
|
|
|
+ "is_side_branch": True,
|
|
|
|
|
+ "max_turns": side_branch_ctx.max_turns,
|
|
|
|
|
+ "trigger_event": trigger_event,
|
|
|
|
|
+ } if side_branch_ctx else None,
|
|
|
|
|
+ **(config.context or {}),
|
|
|
|
|
+ }
|
|
|
|
|
+ if policy_from_context(trace.context).mode is AgentMode.RECURSIVE:
|
|
|
|
|
+ context.update({
|
|
|
|
|
+ RECURSIVE_CAPABILITY_TOOLS_CONTEXT_KEY: sorted(
|
|
|
|
|
+ self._get_configured_tool_names(
|
|
|
|
|
+ config.tools,
|
|
|
|
|
+ config.tool_groups,
|
|
|
|
|
+ config.exclude_tools,
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ })
|
|
|
|
|
+ return context
|
|
|
|
|
+
|
|
|
# 默认 system prompt 前缀(当 config.system_prompt 和前端都未提供 system message 时使用)
|
|
# 默认 system prompt 前缀(当 config.system_prompt 和前端都未提供 system message 时使用)
|
|
|
# 注意:此常量已迁移到 cyber_agent.core.prompts,这里保留引用以保持向后兼容
|
|
# 注意:此常量已迁移到 cyber_agent.core.prompts,这里保留引用以保持向后兼容
|
|
|
|
|
|