尧图精选

openai-agents-python 中 Agent 定义、克隆、动态解析与 RunContext 所有权机制全解析

🕒 发布时间:2026/9/10 9:25:00 📁 来源:尧图网络
openai-agents-python 中 Agent 定义、克隆、动态解析与 RunContext 所有权机制全解析【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python本文围绕 openai-agents-python 中“Agent 定义与运行上下文”这一核心边界展开从公开Agent字段与浅拷贝克隆语义、每轮动态指令/工具/交接的解析时机到RunContextWrapper与ToolContext的所有权划分再到全 run 维度用量累加器的正确维护方式。读完本文你将能安全地构造、克隆和嵌套复用 Agent正确设计应用上下文类型并准确理解流式、重试、交接与嵌套运行场景下的用量与身份边界。一、公开定义与克隆位置兼容性与浅拷贝语义1.1Agent与AgentBasedataclass 字段顺序即兼容性边界在 src/agents/agent.py 中AgentBase是Agent与RealtimeAgent共享的基类承载name、handoff_description、tools、mcp_servers、mcp_config等字段Agent在其上追加instructions、prompt、handoffs、model、model_settings、input_guardrails、output_guardrails、output_type、hooks、tool_use_behavior、reset_tool_choice等字段src/agents/agent.py#L183-L431。关键约束导出的Agent/AgentBasedataclass 字段顺序是位置参数构造的兼容性边界。由于 dataclass 支持按位置传参如Agent(name, desc, [...])任何在中间插入新字段都会破坏旧的位置构造调用。因此演进原则是新字段尽可能追加在末尾且当字段顺序必须变化时应使用旧的位置式构造方式补齐测试。这也是为什么 src/agents/agent.py#L399-L430 的TYPE_CHECKING分支中完整声明了__init__的位置签名——它既是 IDE 类型提示也固化了位置参数契约。1.2__post_init__急切校验的边界Agent.__post_init__()src/agents/agent.py#L432-L546是非法字段类别的急切校验边界在对象构造完成时立即执行覆盖name必须是字符串tools、mcp_servers、mcp_config、handoffs、input_guardrails、output_guardrails必须是列表/字典instructions必须是字符串、可调用对象或Nonemodel必须是字符串、Model实现或Nonemodel_settings会被_coerce_model_settings统一规整接受ModelSettings实例或字典output_type必须是类型、AgentOutputSchemaBase或泛型 originhooks必须是AgentHooksBase实例tool_use_behavior必须是run_llm_again、stop_on_first_tool、StopAtTools字典或可调用对象reset_tool_choice必须是布尔值。注意其中的“急切”与“惰性”分工凡是结果依赖当前 run 的动态回调动态 instructions 函数、FunctionTool.is_enabled回调、Handoff.is_enabled回调等不会在构造时求值而是在每次模型轮次真正被调用时解析——因为它们的返回值依赖当时的上下文状态。__post_init__中还包含一条容易被忽略的隐式默认值逻辑src/agents/agent.py#L494-L495若model被显式指定而model_settings仍等于全局默认设置get_default_model_settings()则会被替换为该模型自身的隐式默认设置_initial_model_settings_for_model。这意味着“不同模型拥有各自不同的默认 temperature/top_p 等参数”这一行为在 Agent 构造时就已建立。1.3clone()dataclasses.replace的浅拷贝语义Agent.clone(**kwargs)src/agents/agent.py#L548-L581基于dataclasses.replace(self, **kwargs)实现其语义需要精确掌握浅拷贝tools、handoffs、mcp_servers、input_guardrails、output_guardrails等列表字段不会被复制。未传参的字段直接沿用原 Agent 持有的同一列表对象因此通过任一实例修改该列表如cloned.tools.append(...)都会影响另一个。传入即按原样使用传参的字段完全按你给定的值使用。agent.clone(toolsagent.tools)依然共享列表agent.clone(tools[other_tool])则不共享任何条目。独立列表的写法agent.clone(tools[*agent.tools, extra_tool])会构造新列表但其中的条目工具对象本身仍与原 Agent 共享。clone()还有一个专门的model_settings重算逻辑当仅传入model而未传入model_settings且原 Agent 的model_settings恰好等于旧模型的隐式默认值时_model_settings_match_implicit_model_defaults会自动为新模型重算隐式默认设置反之若调用者显式定制过model_settings则保留定制值而非静默重置src/agents/agent.py#L568-L580。这些语义在 tests/test_agent_clone_shallow_copy.py 中有一组针对性的测试test_agent_clone_keeps_list_attributes_it_is_not_given、test_agent_clone_uses_a_given_list_as_is、test_agent_clone_still_shares_when_given_the_original_list与test_agent_clone_shared_list_mutation_affects_both_agents分别验证了“未传参共享”“传入即持有”“显式传原列表仍共享”“跨实例可见的变更”四种情形。二、每轮解析动态指令、启用开关与单一工具视图2.1 动态指令每轮用“当前上下文 公开 Agent”求值Agent.instructions支持三种形态静态字符串、Callable[[RunContextWrapper[TContext], Agent[TContext]], MaybeAwaitable[str]]两参可调用函数或None。当使用函数形态时每个模型轮次都必须用当时的RunContextWrapper与公开Agent实例重新求值且必须遵守两参签名并await异步结果。这一规则保证了动态指令能反映本轮上下文如用户身份、会话状态的最新变化而不是缓存上一轮的过期字符串。2.2is_enabled可调用开关只在当前 run 上下文下求值FunctionTool.is_enabled与Handoff.is_enabled都支持“布尔值或接收RunContextWrapper的可调用对象”。解析逻辑集中在 src/agents/run_internal/turn_preparation.pyget_all_tools(agent, context_wrapper)src/agents/run_internal/turn_preparation.py#L119-L121委托给agent.get_all_tools(run_context)后者在 src/agents/agent.py#L272-L280 中逐工具判断is_enabledget_handoffs(agent, context_wrapper)src/agents/run_internal/turn_preparation.py#L96同样逐交接判断Handoff.is_enabled。最重要的纪律是不要把上一轮解析出的启用集合缓存在可复用的 Agent 对象上。因为is_enabled回调的结果取决于“当时的 run 上下文”同一个 Agent 在不同 run、不同轮次下的可用工具集合可能完全不同。缓存会直接导致模型暴露、保留名冲突检查、本地分发、追踪和 Realtime 会话更新使用过期集合。2.3 单一工具/交接视图模型暴露与执行必须同源运行循环src/agents/run_internal/run_loop.py与轮次解析src/agents/run_internal/turn_resolution.py中多次调用get_all_tools/get_handoffs/get_output_schema如 src/agents/run_internal/run_loop.py#L1552、src/agents/run_internal/turn_resolution.py#L1943-L1945。规则每轮应使用同一份解析出的工具与交接视图用于模型暴露、保留名与名称冲突检查、本地分发、追踪以及 Realtime 会话更新。如果这些表面各自独立重新解析就可能出现“模型看到 A 集合本地却执行 B 集合”的不一致——这是工具调用 ID 复用、审批绑定错乱等隐性 bug 的温床。RunContextWrapper内部为此维护了_tool_invocations规范调用记录见 src/agents/run_context.py#L45-L54对同一 call ID 的重复使用会抛出ModelBehaviorError。2.4 内部预备 Agent 与公开身份内部实现会基于公开 Agent 构造“prepared clone”可能追加绑定工具、指令或采样设置如强制 tool_choice、动态注入的指令。但必须守住一条边界hooks、ToolContext.agent、交接回调、公开结果中呈现的身份都应指向公开 Agent除非内部身份是显式契约的一部分有效的输出 schema 属于“产出该候选输出的 Agent 与模型调用”。一次 handoff 会改变最终输出类型因此解析/标注最终结果时不能假设起始 Agent 的 schema 仍然有效——src/agents/run_internal/turn_preparation.py#L124 的get_output_schema(agent)是按“当前执行 agent”解析的运行循环在最终输出阶段也会基于当时的执行 Agent 重新获取 schema如 src/agents/run_internal/run_loop.py#L2127。三、上下文所有权RunContextWrapper与ToolContext的边界3.1 本地上下文类型一致是硬约束RunContextWrapper[TContext]src/agents/run_context.py#L72-L86是 SDK 对“你传入Runner.run(..., context...)的应用对象”的包装。核心纪律docs/context.md你创建任意 Python 对象dataclass / Pydantic 模型均可传入各 run 方法所有工具函数、生命周期钩子、交接回调都收到RunContextWrapper[T]通过wrapper.context访问你的对象。同一 run 内的每个 Agent、工具、交接、guardrail 与生命周期钩子必须使用同一种 context 类型——这也是Agent[UserInfo]泛型标注的意义让类型检查器在编译期捕获“工具 context 类型与 Agent 不一致”的错误。最重要的事实context 对象永远不会被自动加入模型输入。它只是本地运行时状态要让 LLM 看到数据必须走 instructions、输入消息、FunctionTool 或检索/搜索工具等显式通道docs/context.md#agentllm-context。3.2 派生包装的共享与隔离ToolContext继承自RunContextWrapper并在 src/agents/tool_context.py#L41-L64 上追加工具调用级字段tool_name、tool_call_id、tool_arguments、tool_namespace、qualified_tool_name、agent、run_config等。其工厂方法ToolContext.from_agent_context()src/agents/tool_context.py#L230-L292的行为需要精确区分共享底层的应用 context 对象、usage 累加器_share_tool_state_with同时共享审批映射_approvals与调用记录_tool_invocations、agent 引用、run_config新增调用作用域tool_call_id、tool_name、tool_arguments、tool_namespace等本次调用专属字段。也就是说共享应用对象 ≠ 共享每个包装字段。当嵌套变异不安全时必须显式做应用级隔离同时不能因为嵌套调用的工具名或 call ID 看起来相似就复用父级的审批决定——审批记录带 approval scope 与规范调用身份跨作用域复用是明确禁止的相关判定逻辑见 src/agents/run_context.py#L263-L302 的_approved_tool_invocation_status与 sticky approval 机制。3.3 嵌套Agent.as_tool()独立 run 循环与共享应用状态Agent.as_tool()src/agents/agent.py#L583-L605把 Agent 包装成FunctionTool供其他 Agent 调用。它与 handoff 有两点本质区别handoff 中新 Agent 接收对话历史而 as_tool 中嵌套 Agent 接收生成的结构化输入handoff 中嵌套 Agent 接管对话而 as_tool 中调用方 Agent 继续对话。嵌套执行的关键语义嵌套 Agent 拥有独立的 run 循环、独立的审批作用域、独立的可恢复工具状态tool_input属于嵌套包装器不得覆盖父级的 scoped 值嵌套工具状态由 src/agents/agent_tool_state.py 的 scope 机制管理但在普通函数工具路径上它仍然共享应用 context 对象与 usage 累加器——ToolContext.from_agent_context的共享语义同样适用于嵌套 as_tool 运行。RunContextWrapper.tool_input字段src/agents/run_context.py#L95-L96即用于“当前 run 正处于Agent.as_tool()嵌套执行时”的结构化输入访问docs/context.md 也明确指出嵌套 as_tool run 可以挂不同的tool_input但默认不会获得应用状态的隔离副本。3.4 上下文序列化独立的持久化决策共享 ≠ 可序列化。是否把 context、审批、usage、嵌套工具输入持久化是与运行期共享完全独立的决策。若要在人类介入human-in-the-loop或可恢复任务中序列化RunState必须先阅读 .agents/references/runstate-schema.mdRunState schema 与 resume 边界。相关实现证据RunContextWrapper._copy_for_run_state()src/agents/run_context.py#L117-L131为“可独立恢复的检查点”深拷贝 usage、审批与调用记录并分配新的 agent tool state scope——正是因为共享的 usage 实例会让一个恢复检查点的 token 同时落到其他检查点上。四、用量核算run 级累加器的正确维护4.1RunContextWrapper.usage是唯一权威累加器usage是 run 级可变更累加器src/agents/run_context.py#L83-L86其类型为 src/agents/usage.py#L195-L229 的Usage包含字段含义requests对 LLM API 的总请求数input_tokens/output_tokens/total_tokens全部请求的聚合 token 数input_tokens_details/output_tokens_details缓存命中、缓存写入、推理 token 等明细request_usage_entries逐请求明细RequestUsage列表用于精确成本计算与上下文窗口管理纪律每个模型响应在流式、非流式、重试、嵌套运行、交接与 resume 路径中必须恰好累加一次。Usage.add()src/agents/usage.py#L257-L312负责聚合累加请求数与各级 token并自动合并request_usage_entries——若other已含逐请求明细则深拷贝合并若other是“单请求且有 token”则自动生成一条RequestUsage零 token 请求与多请求聚合对象不会凭空生成条目。这些行为在 tests/test_usage.py 中有系统性验证如test_usage_add_preserves_single_request、test_usage_add_ignores_zero_token_requests、test_usage_add_merges_existing_request_usage_entries、test_runner_run_carries_request_usage_entries。4.2 权威 per-request 记录优先于合成条目当 provider 或重试层已提供 request 级记录时必须保留权威的request_usage_entries禁止从聚合总数再合成第二条 per-request 条目。源码中_mark_requests_completed_without_usagesrc/agents/usage.py#L327-L331体现了另一种边界provider 完成响应但未上报 usage 时适配器显式登记物理请求数而不是合成零填充的 usage 载荷——请求数被计入但原始 provider usage 保持缺席避免伪造 token 数据。4.3 重试与流式一致性与未完成态重试核算失败尝试可能没有 token 总数。此时需保持“请求数、聚合 token、request 级条目、trace span usage”内部一致不虚构 provider token 数据——请求数可以计token 保持为 0/缺席而不是用估算值填充。流式流式 usage 在终止块与流驱动完成前是不完整的src/agents/run_context.py#L83-L86 的 docstring 明确提示“for streamed responses, the usage will be stale until the last chunk”。因此不能仅凭最后一段可见文本增量就终结计费、结果摘要或 usage 承载的 span。这与 trace 端到端数据src/agents/usage.py#L434-L485 的 span 序列化辅助函数的闭合时机直接相关。五、修改与审查清单若你正在修改 Agent 定义、上下文或用量相关代码请对照以下清单源自 .agents/references/agent-definition-and-run-context.md 的 Review Checklist构造与克隆测试直接构造与clone()行为确保不修改调用方共享的对象若改动字段顺序补测旧的位置构造方式。每轮解析同源动态指令、工具、交接必须通过“与分发所用相同的公开 Agent 当前上下文”解析确认is_enabled回调未被缓存。身份与 schema验证 handoff 与内部预备 Agent 路径暴露预期的公开身份与有效输出 schemahandoff 可改变最终输出类型。嵌套工具测试嵌套 Agent 工具的共享应用状态与隔离的 scoped 元数据tool_input、审批作用域、可恢复工具状态。用量对比流式、重试、交接、中断恢复与嵌套运行后的聚合用量与 per-request 用量确认每次模型响应恰好累加一次且未合成重复 request 条目。六、深入阅读仓库中的相关实现与测试定义与校验src/agents/agent.pyAgentBase/Agent、__post_init__、clone、as_tool、get_all_tools运行上下文src/agents/run_context.pyRunContextWrapper、审批记录、调用记录、run-state 复制工具上下文src/agents/tool_context.pyToolContext、from_agent_context、嵌套审批路由用量模型src/agents/usage.pyUsage/RequestUsage、add()、序列化与 span 数据每轮解析src/agents/run_internal/turn_preparation.py、src/agents/run_internal/run_loop.py官方文档docs/agents.md、docs/context.md、docs/results.md针对性测试tests/test_agent_clone_shallow_copy.py、tests/test_agent_config.py、tests/test_agent_as_tool.py、tests/test_usage.py【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →