agent_mode.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. """Agent 委托模式及其统一运行策略。
  2. Runner 仅在创建根 Trace 时读取环境模式,随后将策略持久化并传给所有本地子孙。
  3. Runner、Sub-Agent 工具和 Goal 工具共同使用该策略,保证运行中的任务树不被环境变更改变。
  4. """
  5. from __future__ import annotations
  6. from dataclasses import dataclass
  7. from enum import Enum
  8. import os
  9. from typing import Any, Mapping, MutableMapping
  10. AGENT_MODE_ENV = "AGENT_MODE"
  11. REMOVED_RECURSION_ENV = "AGENT_RECURSION_ENABLED"
  12. AGENT_MODE_CONTEXT_KEY = "agent_mode"
  13. AGENT_MODE_REVISION_CONTEXT_KEY = "agent_mode_revision"
  14. # Runner 注入给本地 agent 工具的内部上下文。它们不来自 LLM 参数,也不持久化
  15. # 到 Trace;Recursive 子级只能继承调用方在本次运行中实际拥有的能力与调度策略。
  16. RECURSIVE_CAPABILITY_TOOLS_CONTEXT_KEY = "_recursive_capability_tool_names"
  17. RECURSIVE_CHILD_EXECUTION_MODE_CONTEXT_KEY = "_recursive_child_execution_mode"
  18. RECURSIVE_MAX_PARALLEL_CHILDREN_CONTEXT_KEY = "_recursive_max_parallel_children"
  19. MAX_PARALLEL_RECURSIVE_CHILDREN = 2
  20. RECURSIVE_REVISION_READ_ONLY_ERROR = "recursive_revision_2_read_only"
  21. CURRENT_RECURSIVE_REVISION = 3
  22. SUPPORTED_RECURSIVE_REVISIONS = frozenset({1, 2, CURRENT_RECURSIVE_REVISION})
  23. UNSUPPORTED_AGENT_MODE_REVISION_ERROR = "unsupported_agent_mode_revision"
  24. class AgentMode(str, Enum):
  25. """Agent 对外暴露的两种运行模式。
  26. `legacy` 保留旧的一层委托,`recursive` 启用递归任务协议与资源边界。
  27. """
  28. LEGACY = "legacy"
  29. RECURSIVE = "recursive"
  30. @dataclass(frozen=True)
  31. class AgentPolicy:
  32. """某棵 Trace 树不可变的模式策略快照。
  33. Runner 将其写入根 Trace,Sub-Agent 创建子 Trace 时继承,各运行门禁据此判断能力。
  34. """
  35. mode: AgentMode
  36. revision: int
  37. max_depth: int
  38. max_children_per_parent: int | None
  39. accumulate_sub_trace_ids: bool
  40. show_sub_trace_shortcuts: bool
  41. @property
  42. def requires_task_protocol(self) -> bool:
  43. """Recursive revision 2+ 是否使用结构化任务协议。"""
  44. return self.mode is AgentMode.RECURSIVE and self.revision >= 2
  45. @property
  46. def requires_task_progress(self) -> bool:
  47. """Recursive revision 3 是否强制使用 TaskProgress。"""
  48. return (
  49. self.mode is AgentMode.RECURSIVE
  50. and self.revision == CURRENT_RECURSIVE_REVISION
  51. )
  52. @property
  53. def is_read_only(self) -> bool:
  54. """历史 Recursive revision 2 只保留读取和导出。"""
  55. return self.mode is AgentMode.RECURSIVE and self.revision == 2
  56. def to_context(self) -> dict[str, Any]:
  57. return {
  58. AGENT_MODE_CONTEXT_KEY: self.mode.value,
  59. AGENT_MODE_REVISION_CONTEXT_KEY: self.revision,
  60. }
  61. def _policy(mode: AgentMode, revision: int = 1) -> AgentPolicy:
  62. if mode is AgentMode.LEGACY:
  63. return AgentPolicy(
  64. mode=mode,
  65. revision=1,
  66. max_depth=1,
  67. max_children_per_parent=None,
  68. accumulate_sub_trace_ids=False,
  69. show_sub_trace_shortcuts=False,
  70. )
  71. return AgentPolicy(
  72. mode=mode,
  73. revision=revision,
  74. max_depth=5,
  75. max_children_per_parent=6,
  76. accumulate_sub_trace_ids=True,
  77. show_sub_trace_shortcuts=True,
  78. )
  79. def assert_removed_config_absent() -> None:
  80. """拒绝已删除的递归开关,避免运行时继续使用含糊配置。
  81. Runner 准备 Trace 和 ``agent`` 工具入口都会调用,新根 Trace 解析模式时也会间接调用。
  82. """
  83. if REMOVED_RECURSION_ENV in os.environ:
  84. raise ValueError(
  85. f"{REMOVED_RECURSION_ENV} has been removed; "
  86. f"set {AGENT_MODE_ENV}=legacy or {AGENT_MODE_ENV}=recursive instead"
  87. )
  88. def policy_from_environment(*, recursive_revision: int = 1) -> AgentPolicy:
  89. """为新建根 Trace 解析一次环境模式。
  90. `AgentRunner._prepare_new_trace` 在任何 Trace 落库前调用,返回的策略随后被持久化。
  91. """
  92. assert_removed_config_absent()
  93. raw_mode = os.getenv(AGENT_MODE_ENV, AgentMode.LEGACY.value).strip().lower()
  94. try:
  95. mode = AgentMode(raw_mode)
  96. except ValueError as exc:
  97. allowed = ", ".join(mode.value for mode in AgentMode)
  98. raise ValueError(
  99. f"Invalid {AGENT_MODE_ENV}={raw_mode!r}; expected one of: {allowed}"
  100. ) from exc
  101. revision = recursive_revision if mode is AgentMode.RECURSIVE else 1
  102. return _policy(mode, revision)
  103. def policy_from_context(context: Mapping[str, Any] | None) -> AgentPolicy:
  104. """从 Trace context 恢复已持久化的模式策略。
  105. Runner、Sub-Agent 和 Goal 工具在恢复或执行时调用;无模式字段的历史 Trace 按 Legacy 处理。
  106. """
  107. context = context or {}
  108. raw_mode = context.get(AGENT_MODE_CONTEXT_KEY, AgentMode.LEGACY.value)
  109. try:
  110. mode = AgentMode(str(raw_mode).strip().lower())
  111. except ValueError as exc:
  112. raise ValueError(f"Invalid persisted agent mode: {raw_mode!r}") from exc
  113. raw_revision = context.get(AGENT_MODE_REVISION_CONTEXT_KEY, 1)
  114. if not isinstance(raw_revision, int) or raw_revision < 1:
  115. raise ValueError(f"Invalid persisted agent mode revision: {raw_revision!r}")
  116. if mode is AgentMode.LEGACY and raw_revision != 1:
  117. raise ValueError(
  118. f"{UNSUPPORTED_AGENT_MODE_REVISION_ERROR}: "
  119. f"legacy revision {raw_revision}"
  120. )
  121. if (
  122. mode is AgentMode.RECURSIVE
  123. and raw_revision not in SUPPORTED_RECURSIVE_REVISIONS
  124. ):
  125. raise ValueError(
  126. f"{UNSUPPORTED_AGENT_MODE_REVISION_ERROR}: "
  127. f"recursive revision {raw_revision}"
  128. )
  129. return _policy(mode, raw_revision)
  130. def apply_policy_to_context(
  131. context: MutableMapping[str, Any] | None,
  132. policy: AgentPolicy,
  133. ) -> dict[str, Any]:
  134. """将模式和 revision 覆盖写入一份 Trace context 副本。
  135. Runner 创建根 Trace、Sub-Agent 创建或续跑子 Trace 时调用,保证子孙继承同一策略。
  136. """
  137. merged = dict(context or {})
  138. merged.update(policy.to_context())
  139. return merged
  140. def require_mutable_trace_policy(context: Mapping[str, Any] | None) -> AgentPolicy:
  141. """返回可写 Trace 的持久化策略,并统一拒绝已退役的 revision 2。"""
  142. policy = policy_from_context(context)
  143. if policy.is_read_only:
  144. raise ValueError(RECURSIVE_REVISION_READ_ONLY_ERROR)
  145. return policy
  146. def validate_recursive_child_execution(
  147. mode: Any,
  148. max_parallel_children: Any,
  149. ) -> tuple[str, int]:
  150. """校验单批 Recursive 子 Agent 的串并行策略和硬上限。
  151. Runner 创建/恢复根 Trace 以及 `agent()` 调度子任务前调用,防止模型或 context 提高并发。
  152. """
  153. if mode not in {"sequential", "parallel"}:
  154. raise ValueError(
  155. "child_execution_mode must be 'sequential' or 'parallel'"
  156. )
  157. if (
  158. isinstance(max_parallel_children, bool)
  159. or not isinstance(max_parallel_children, int)
  160. or not 1 <= max_parallel_children <= MAX_PARALLEL_RECURSIVE_CHILDREN
  161. ):
  162. raise ValueError(
  163. "max_parallel_children must be an integer between 1 and "
  164. f"{MAX_PARALLEL_RECURSIVE_CHILDREN}"
  165. )
  166. return mode, max_parallel_children