|
|
@@ -1,9 +1,8 @@
|
|
|
-"""Single-process resource budgets for a Recursive Agent tree.
|
|
|
+"""Recursive Agent 任务树的单进程资源预算。
|
|
|
|
|
|
-The environment is read once when a root Trace is created. Callers persist
|
|
|
-the resulting :class:`ResourceBudget` snapshot on that root Trace and pass the
|
|
|
-same snapshot to every descendant operation. Dynamic usage is stored through
|
|
|
-``TraceStore`` and guarded by one in-process lock per root Trace.
|
|
|
+Runner 创建根 Trace 时只读取一次环境配置,将 ResourceBudget 快照固化在根 Trace;
|
|
|
+Runner 的模型调用、Sub-Agent 创建和 Validator 沿用同一快照。动态用量由 TraceStore
|
|
|
+单独持久化,ResourceBudgetController 以每个根 Trace 一把进程内异步锁保护。
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
@@ -94,7 +93,10 @@ def _positive_float(
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
class ResourceBudget:
|
|
|
- """Immutable limits captured for one Recursive root Trace."""
|
|
|
+ """一棵 Recursive 任务树不可变的资源上限快照。
|
|
|
+
|
|
|
+ `AgentRunner._prepare_new_trace` 从环境创建它并写入根 Trace,子孙执行始终从根 Trace 恢复。
|
|
|
+ """
|
|
|
|
|
|
enabled: bool = True
|
|
|
max_total_agents: int = DEFAULT_MAX_TOTAL_AGENTS
|
|
|
@@ -132,6 +134,10 @@ class ResourceBudget:
|
|
|
cls,
|
|
|
environ: Mapping[str, str] | None = None,
|
|
|
) -> "ResourceBudget":
|
|
|
+ """在新建 Recursive 根 Trace 时解析并校验预算环境变量。
|
|
|
+
|
|
|
+ 只由 Runner 的根 Trace 初始化阶段调用,Legacy 和既有 Trace 不会重新读取。
|
|
|
+ """
|
|
|
values = os.environ if environ is None else environ
|
|
|
return cls(
|
|
|
enabled=_strict_bool(values, RESOURCE_BUDGET_ENABLED_ENV, True),
|
|
|
@@ -157,6 +163,10 @@ class ResourceBudget:
|
|
|
|
|
|
@classmethod
|
|
|
def from_dict(cls, value: Mapping[str, Any]) -> "ResourceBudget":
|
|
|
+ """从根 Trace context 恢复已固化的预算快照。
|
|
|
+
|
|
|
+ Runner 在每次 Recursive 模型调用或子 Agent 预留前调用,字段缺失或多余都会失败。
|
|
|
+ """
|
|
|
if not isinstance(value, Mapping):
|
|
|
raise ValueError("resource budget snapshot must be an object")
|
|
|
expected = {field.name for field in cls.__dataclass_fields__.values()}
|
|
|
@@ -176,7 +186,10 @@ class ResourceBudget:
|
|
|
|
|
|
@dataclass
|
|
|
class ResourceUsage:
|
|
|
- """Persisted, cumulative usage for one Recursive Agent tree."""
|
|
|
+ """一棵 Recursive 任务树已持久化的累计用量。
|
|
|
+
|
|
|
+ ResourceBudgetController 每次预留或记账时从 TraceStore 读取、校验并原子替换该快照。
|
|
|
+ """
|
|
|
|
|
|
total_agents: int
|
|
|
llm_calls: int
|
|
|
@@ -277,11 +290,11 @@ class ResourceUsage:
|
|
|
|
|
|
|
|
|
class ResourceBudgetStateError(RuntimeError):
|
|
|
- """The persisted tree usage is missing or invalid, so execution must stop."""
|
|
|
+ """任务树持久化用量缺失或损坏,执行必须失败关闭。"""
|
|
|
|
|
|
|
|
|
class ResourceBudgetExceeded(RuntimeError):
|
|
|
- """A tree resource limit was denied or exceeded."""
|
|
|
+ """某项任务树资源预留被拒绝,或响应记账后已超限。"""
|
|
|
|
|
|
def __init__(
|
|
|
self,
|
|
|
@@ -303,7 +316,10 @@ _ROOT_LOCKS: WeakValueDictionary[
|
|
|
|
|
|
|
|
|
class ResourceBudgetController:
|
|
|
- """Atomic, single-process checks over a root Trace's persisted usage."""
|
|
|
+ """根据持久化用量执行单进程原子预留和记账。
|
|
|
+
|
|
|
+ AgentRunner 持有该控制器:模型/Validator 调用由 Runner 记账,子 Agent 数由 `agent()` 创建阶段预留。
|
|
|
+ """
|
|
|
|
|
|
def __init__(
|
|
|
self,
|
|
|
@@ -329,6 +345,10 @@ class ResourceBudgetController:
|
|
|
*,
|
|
|
initial_agents: int = 1,
|
|
|
) -> ResourceUsage:
|
|
|
+ """为新 Recursive 根 Trace 初始化用量,并将根 Agent 计入总数。
|
|
|
+
|
|
|
+ `AgentRunner._prepare_new_trace` 在根 Trace 和预算快照落库后调用;已有用量时幂等返回。
|
|
|
+ """
|
|
|
if (
|
|
|
isinstance(initial_agents, bool)
|
|
|
or not isinstance(initial_agents, int)
|
|
|
@@ -354,6 +374,10 @@ class ResourceBudgetController:
|
|
|
return usage
|
|
|
|
|
|
async def get_usage(self, root_trace_id: str) -> ResourceUsage:
|
|
|
+ """读取并严格校验根 Trace 的当前累计用量。
|
|
|
+
|
|
|
+ 所有预留、记账与运行状态查询共用该入口,文件缺失或损坏时 fail closed。
|
|
|
+ """
|
|
|
try:
|
|
|
raw = await self.trace_store.get_resource_usage(root_trace_id)
|
|
|
except Exception as exc:
|
|
|
@@ -372,6 +396,10 @@ class ResourceBudgetController:
|
|
|
budget: ResourceBudget,
|
|
|
count: int,
|
|
|
) -> ResourceUsage:
|
|
|
+ """在创建一批本地业务子 Trace 前原子预留 Agent 数。
|
|
|
+
|
|
|
+ Sub-Agent `_run_agents` 在六孩子临界区内调用,超限时整批拒绝且不创建 Trace。
|
|
|
+ """
|
|
|
if isinstance(count, bool) or not isinstance(count, int) or count <= 0:
|
|
|
raise ValueError("count must be a positive integer")
|
|
|
async with self._lock(root_trace_id):
|
|
|
@@ -397,10 +425,9 @@ class ResourceBudgetController:
|
|
|
budget: ResourceBudget,
|
|
|
count: int,
|
|
|
) -> ResourceUsage:
|
|
|
- """Roll back a reservation for children whose Trace was never created.
|
|
|
+ """回滚批次预留中尚未创建 Trace 的 Agent 数。
|
|
|
|
|
|
- This is not a refund mechanism for stopped, failed, or rewound Agents.
|
|
|
- Callers may only release the uncreated remainder of a batch reservation.
|
|
|
+ 仅由 Sub-Agent 初始化失败时调用;已创建、停止、失败或回溯的 Agent 都不退还。
|
|
|
"""
|
|
|
del budget # The persisted count is rolled back even after another limit expires.
|
|
|
if isinstance(count, bool) or not isinstance(count, int) or count <= 0:
|
|
|
@@ -423,6 +450,10 @@ class ResourceBudgetController:
|
|
|
*,
|
|
|
purpose: LLMCallPurpose = "ordinary",
|
|
|
) -> ResourceUsage:
|
|
|
+ """在模型请求发出前预留一次调用,必要时保留根验收槽位。
|
|
|
+
|
|
|
+ `AgentRunner.call_recursive_llm` 为普通 Agent、子 Validator 和根 Validator 统一调用该入口。
|
|
|
+ """
|
|
|
if purpose not in {"ordinary", "root_validator"}:
|
|
|
raise ValueError("purpose must be 'ordinary' or 'root_validator'")
|
|
|
async with self._lock(root_trace_id):
|
|
|
@@ -454,6 +485,10 @@ class ResourceBudgetController:
|
|
|
completion_tokens: int,
|
|
|
cost_usd: float,
|
|
|
) -> ResourceUsage:
|
|
|
+ """在框架模型响应返回后登记 Token 和成本。
|
|
|
+
|
|
|
+ Runner 在已预留调用次数后使用;响应造成超限时会先持久化实际用量再报错。
|
|
|
+ """
|
|
|
self._validate_reported_usage(prompt_tokens, completion_tokens, cost_usd)
|
|
|
async with self._lock(root_trace_id):
|
|
|
usage = await self.get_usage(root_trace_id)
|
|
|
@@ -476,10 +511,9 @@ class ResourceBudgetController:
|
|
|
completion_tokens: int,
|
|
|
cost_usd: float,
|
|
|
) -> ResourceUsage:
|
|
|
- """Record a tool-internal LLM call that could not be pre-reserved.
|
|
|
+ """事后登记工具内部不能预留的模型调用。
|
|
|
|
|
|
- Actual external usage is always persisted. If it pushes any dimension
|
|
|
- over its limit, the method raises only after the atomic replacement.
|
|
|
+ Runner 仅在工具返回 `tool_usage` 时调用;总调用数、Token 和成本都会先落库再检查超限。
|
|
|
"""
|
|
|
self._validate_reported_usage(prompt_tokens, completion_tokens, cost_usd)
|
|
|
async with self._lock(root_trace_id):
|
|
|
@@ -618,10 +652,9 @@ class ResourceBudgetController:
|
|
|
|
|
|
@staticmethod
|
|
|
def _raise_if_terminal(usage: ResourceUsage, budget: ResourceBudget) -> None:
|
|
|
- """Reject new work after a response makes the tree irrecoverably over budget.
|
|
|
+ """响应已造成 Token、成本或时长超限后,拒绝新工作。
|
|
|
|
|
|
- Agent-count denials only prevent more children. An ordinary-call denial
|
|
|
- also leaves the explicitly reserved root-validator slot usable.
|
|
|
+ Agent 数超限只禁止新建孩子;普通调用耗尽仍保留明确预留的根 Validator 槽位。
|
|
|
"""
|
|
|
prefix = "budget_exhausted:"
|
|
|
reason = usage.exhausted_reason or ""
|