agent_mode.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. """Agent delegation mode policy.
  2. The environment selects a mode only when a root Trace is created. The chosen
  3. mode is then persisted on the Trace and inherited by every local descendant so
  4. that a running tree cannot change behaviour after a process configuration
  5. change.
  6. """
  7. from __future__ import annotations
  8. from dataclasses import dataclass
  9. from enum import Enum
  10. import os
  11. from typing import Any, Mapping, MutableMapping
  12. AGENT_MODE_ENV = "AGENT_MODE"
  13. REMOVED_RECURSION_ENV = "AGENT_RECURSION_ENABLED"
  14. AGENT_MODE_CONTEXT_KEY = "agent_mode"
  15. AGENT_MODE_REVISION_CONTEXT_KEY = "agent_mode_revision"
  16. # Runner 注入给本地 agent 工具的内部上下文。它们不来自 LLM 参数,也不持久化
  17. # 到 Trace;Recursive 子级只能继承调用方在本次运行中实际拥有的能力与调度策略。
  18. RECURSIVE_CAPABILITY_TOOLS_CONTEXT_KEY = "_recursive_capability_tool_names"
  19. RECURSIVE_CHILD_EXECUTION_MODE_CONTEXT_KEY = "_recursive_child_execution_mode"
  20. RECURSIVE_MAX_PARALLEL_CHILDREN_CONTEXT_KEY = "_recursive_max_parallel_children"
  21. MAX_PARALLEL_RECURSIVE_CHILDREN = 2
  22. class AgentMode(str, Enum):
  23. LEGACY = "legacy"
  24. RECURSIVE = "recursive"
  25. @dataclass(frozen=True)
  26. class AgentPolicy:
  27. mode: AgentMode
  28. revision: int
  29. max_depth: int
  30. max_children_per_parent: int | None
  31. accumulate_sub_trace_ids: bool
  32. show_sub_trace_shortcuts: bool
  33. @property
  34. def requires_task_protocol(self) -> bool:
  35. return self.mode is AgentMode.RECURSIVE and self.revision >= 2
  36. def to_context(self) -> dict[str, Any]:
  37. return {
  38. AGENT_MODE_CONTEXT_KEY: self.mode.value,
  39. AGENT_MODE_REVISION_CONTEXT_KEY: self.revision,
  40. }
  41. def _policy(mode: AgentMode, revision: int = 1) -> AgentPolicy:
  42. if mode is AgentMode.LEGACY:
  43. return AgentPolicy(
  44. mode=mode,
  45. revision=1,
  46. max_depth=1,
  47. max_children_per_parent=None,
  48. accumulate_sub_trace_ids=False,
  49. show_sub_trace_shortcuts=False,
  50. )
  51. return AgentPolicy(
  52. mode=mode,
  53. revision=revision,
  54. max_depth=5,
  55. max_children_per_parent=6,
  56. accumulate_sub_trace_ids=True,
  57. show_sub_trace_shortcuts=True,
  58. )
  59. def assert_removed_config_absent() -> None:
  60. if REMOVED_RECURSION_ENV in os.environ:
  61. raise ValueError(
  62. f"{REMOVED_RECURSION_ENV} has been removed; "
  63. f"set {AGENT_MODE_ENV}=legacy or {AGENT_MODE_ENV}=recursive instead"
  64. )
  65. def policy_from_environment(*, recursive_revision: int = 1) -> AgentPolicy:
  66. """Resolve the policy for a newly-created root Trace."""
  67. assert_removed_config_absent()
  68. raw_mode = os.getenv(AGENT_MODE_ENV, AgentMode.LEGACY.value).strip().lower()
  69. try:
  70. mode = AgentMode(raw_mode)
  71. except ValueError as exc:
  72. allowed = ", ".join(mode.value for mode in AgentMode)
  73. raise ValueError(
  74. f"Invalid {AGENT_MODE_ENV}={raw_mode!r}; expected one of: {allowed}"
  75. ) from exc
  76. revision = recursive_revision if mode is AgentMode.RECURSIVE else 1
  77. return _policy(mode, revision)
  78. def policy_from_context(context: Mapping[str, Any] | None) -> AgentPolicy:
  79. """Resolve a persisted Trace policy; missing metadata is legacy."""
  80. context = context or {}
  81. raw_mode = context.get(AGENT_MODE_CONTEXT_KEY, AgentMode.LEGACY.value)
  82. try:
  83. mode = AgentMode(str(raw_mode).strip().lower())
  84. except ValueError as exc:
  85. raise ValueError(f"Invalid persisted agent mode: {raw_mode!r}") from exc
  86. raw_revision = context.get(AGENT_MODE_REVISION_CONTEXT_KEY, 1)
  87. if not isinstance(raw_revision, int) or raw_revision < 1:
  88. raise ValueError(f"Invalid persisted agent mode revision: {raw_revision!r}")
  89. return _policy(mode, raw_revision)
  90. def apply_policy_to_context(
  91. context: MutableMapping[str, Any] | None,
  92. policy: AgentPolicy,
  93. ) -> dict[str, Any]:
  94. merged = dict(context or {})
  95. merged.update(policy.to_context())
  96. return merged
  97. def validate_recursive_child_execution(
  98. mode: Any,
  99. max_parallel_children: Any,
  100. ) -> tuple[str, int]:
  101. """校验单批 Recursive 子 Agent 的执行策略。"""
  102. if mode not in {"sequential", "parallel"}:
  103. raise ValueError(
  104. "child_execution_mode must be 'sequential' or 'parallel'"
  105. )
  106. if (
  107. isinstance(max_parallel_children, bool)
  108. or not isinstance(max_parallel_children, int)
  109. or not 1 <= max_parallel_children <= MAX_PARALLEL_RECURSIVE_CHILDREN
  110. ):
  111. raise ValueError(
  112. "max_parallel_children must be an integer between 1 and "
  113. f"{MAX_PARALLEL_RECURSIVE_CHILDREN}"
  114. )
  115. return mode, max_parallel_children