适用于MongoDB Atlas Agent Engine SDK 的 OpenAI Agents SDK 适配器。
注意
状态:实验性的。 CLI和构建服务接受 framework: openai-agents;平台构建需要一个发布Python Lite 运行库的环境。
适配器通过 SDK 自己的 Runner 运行原生OpenAI 助手 Agent,而编排引擎则拥有持久性执行权。适配器不保留会话数据库,也从不使用 OpenAI 会话、对话或 previous_response_id 作为继续状态。
构建代理
要从工作项目开始,运行agentengine create 并选择 Hello World 或人机交互智能代理的 OpenAI Agents SDK 变体(--template hello-world-agent-openai 或 --template insurance-agent-openai)。
from agents import Agent, ModelSettingsfrom agent_engine_sdk_openai_agents import App
app = App(app_name="support")
app.tool()def lookup_order(order_id: str) -> str: """Look up an order.""" ...
app.entrypointdef build() -> Agent: return Agent( name="support", instructions="Help with orders.", model=app.llm("gpt-4.1", settings=ModelSettings(temperature=0)), tools=app.tools(), )使用 app.llm(...) 返回的模型和 app.tools() 返回的工具;在任何模型或工具工作之前,任何其他操作都失败。将配置的 agents.Model 传递给 app.llm 以使用自定义 OpenAI客户端。工具参数必须是可以按名称传递的JSON值(没有仅限位置的参数或 *args 参数,也没有名为 tool_call_id 的参数),因为远程工具会将其参数接收为命名的JSON值。使用 @app.tool(timeout=...) 设置工具的超时;已发布工具上的原生timeout_seconds 在回合中失败。非字符串工具结果(包括嵌套模型、数据类、枚举和日期时间)以JSON文本形式到达模型。文本是规范的,因此重放的回合会向模型发送相同的文本:对键进行排序,并将整数浮点数(例如 164.0)写入 164。工具结果中超过 2**53 的整数会使调用失败,并且在其参数中使回合失败,因为持久性记录无法精确保存该整数;将此类值作为字符串传递。
每次调用都会运行一个原生Runner 回合,并在返回结果之前将其提交给编排引擎。引发问题的工具会像 OpenAI Agents SDK默认一样向模型报告,并附带已安装 SDK 的 default_tool_error_function 文本,然后继续。 0.23 之前的版本在该文本中包含该工具的错误消息;更高版本会发送没有它的固定消息。编排引擎会记录失败,因此重放回合会向模型发送相同的文本。平台停止或失败的调用导致回合失败,并报告工具路径报告的错误:策略拒绝、调用取消或用户停止,或者平台故障(例如编排引擎未确认工具的结果)。平台故障永远不会作为故障工具报告给模型,因为该工具可能已经在运行。在这些情况下,不会执行任何操作。但仍存在一个例外情况:当编排引擎无法访问远程工具的 Tool Pod 时,它会将此次故障记录为该工具自身的故障,因此模型会被告知该工具已发生故障。当模型同时调用多个工具时,它们会同时运行。每次调用都会保留其调用 ID 的编排引擎活动,并且其结果会按照模型进行调用的顺序(以先完成者为准)到达模型。如果其中一个等待批准,则等待之前运行的调用将其输出保持在已批准调用的输出之前,正如 SDK 记录的那样。当一个调用在回合中失败时,一旦仍在其旁边运行的调用完成,该调用也会失败。目前尚不支持代理钩子、托管提示、动态指令和自定义 tool_use_behavior。代理自己的输入和输出护栏按照输入和输出护栏中的描述运行。项目的护栏策略应用于每个模型和工具调用;请参阅 Guardrail 策略。
MCP 工具
使用每个助手引擎框架使用的相同密钥,在 agent.yaml 中的 mcp.servers 下列出远程 MCP服务器。代理启动时会发现其工具,app.tools() 将这些工具返回到名为 <server>__<tool> 的已注册函数旁边:
mcp: servers: atlas: url: https://mcp.example.com/mcp allowed_tools: [find] auth: type: bearer_env token_env: ATLAS_MCP_TOKENauth 为保存令牌的环境变量命名;令牌本身属于工作区的密钥,或者属于 .env 以进行本地开发。
与其他工具活动一样,对其中一个工具的调用属于业务流程引擎工具活动,并在 Tool Pod 中运行,而 Tool Pod 会保存服务器的凭证和网络访问权限。 allowed_tools 限制代理获取的服务器工具。像任何失败的工具一样,失败的调用或 MCP 错误结果会报告给模型。如果结构化结果包含等于或大于 2**53 的整数,则持久性记录无法精确保留该整数,因此该结构化结果在回合中失败;让服务器返回一个字符串等值。
代理启动时无法访问的服务器会阻止代理启动; timeout_seconds 限制该发现的每个请求,就像限制调用一样。
只有代理运行时会在启动时列出服务器的工具;当调用到达时,Tool Pod 会找到用于来自 agent.yaml 的调用的服务器。工具 Pod 还运行入口点,以注册代理的模型,并且 app.tools() 仅返回注册的函数。将 MCP 工具传递给代理而不按名称查找,示例[tool for tool in app.tools() if tool.name.startswith("atlas__")],因此入口点会在两者中构建。
SDK 自己的 Agent.mcp_servers 被拒绝:SDK 将从工具 Pod 外部的代理进程调用服务器。 MCP 工具上的 needs_approval 尚不受支持。
切换
将 SDK 自己的 handoffs=[...] 与其他代理或 agents.handoff(...) 结合使用。运行时会读取入口点在每个回合之前返回的整个代理图表,并使每个可到达的代理遵守与第一个智能体相同的规则:其模型来自 app.llm(...)(为每个智能体的模型提供自己的 llm_id),其工具来自 app.tools(),每个代理都可以通过名称从中选择自己的智能体。代理名称必须是唯一的。
切换会更改处于活动状态的代理;转牌圈保持一次执行。已提交状态记录了活动代理,因此下一回合(也是在重新启动后)从结束上一回合的代理开始。已移交的代理请求的批准会在该代理中暂停和恢复。
agents.handoff(...) 可能会覆盖工具名称和描述。代理可以在一个周期中相互切换。在任何模型或工具工作之前,input_filter、nest_handoff_history、on_handoff回调、input_type、可调用的 is_enabled 和手动构建的 Handoff 在回合中失败。工具名称与另一次交接或该座席工具之一匹配的任何代理的交接也是如此:否则,SDK 将保留一个工具并删除另一个。
作为工具的代理
使用 SDK 自己的 Agent.as_tool(...),并将结果放入另一个代理的 tools 中。嵌套代理遵循与图表中所有其他代理相同的规则。每次调用都将嵌套代理作为自己的操作来运行:其模型和工具调用是编排引擎在以工具命名的路径下的活动,并且对同一代理工具的重复或并发调用按照模型调用的顺序保持不同。
调用代理的模型只能看到工具的回答,并且只有调用会话被提交;替换尝试会从其记录的活动中重放该嵌套运行。调用者的流在 subagent_start 和 subagent_end 事件之间承载嵌套代理的文本,除了调用代理的回复之外,还标有工具名称和调用 ID。失败的嵌套运行在回合中失败。
嵌套代理使用的工具可能需要批准,代理工具本身也可能需要批准。与任何其他批准一样,轮次暂停,并且等待时间记录在嵌套代理的路径下。在一个回合中,来自调用代理和嵌套代理的等待会同时得到应答。工具调用 ID 在一个回合内必须是唯一的。停止等待批准的嵌套代理不会发送 subagent_end;已恢复的流在发送其余文本之前再次使用 subagent_start 将其打开,并且不会再次发送调用者在等待之前看到的文本。
max_turns支持 custom_output_extractor 和结构化输入。替换尝试会再次调用提取器和结构化输入的 input_builder,因此两者必须为同一输入返回相同的值,并且没有副作用;当重放的模型调用与记录的模型调用不同时,OE 在回合中失败。
on_stream回调、hooks、run_config、session、提供商延续 ID、可调用的 needs_approval 或 is_enabled、工具护栏、嵌套代理或其传递的代理上的输入或输出护栏在任何模型或工具工作之前,原生timeout_seconds 和可以通过自己的代理工具到达自身的代理均会失败。
审批
向 @app.tool(needs_approval=True) 注册一个工具,让专人批准每个调用。该参数是 SDK 自己的 function_tool(needs_approval=...),传递给 app.tools() 返回的 FunctionTool;在该返回的工具上设置 needs_approval 也有效。当模型调用需要批准的工具时,回合会暂停:调用返回 status: suspended,每次等待批准的调用都有一个中断,按照模型调用的顺序排列,每次调用都会命名工具及其参数。同一响应中无需批准的调用会先运行。通过同一会话上的正常调用端点进行恢复,resume_map 会立即应答每个中断 ID:
{"resume_map": {"<interrupt id>": {"confirmed": true}}}interrupts 列出每个待批准的项目。对于读取单个有效负载的调用方,suspend_payload 仅包含第一个值。
{"confirmed": false, "rejection_message": "..."} 拒接来电;该模型看到的是拒绝消息,而不是工具结果,就像 SDK 自己的 reject(..., rejection_message=...) 一样。如果没有 rejection_message,则模型会看到 SDK 的默认拒绝文本;空值会被拒绝。批准不包含任何消息。编排引擎会记录等待时间及其回答。恢复会重放已记录的模型调用,通过 SDK 自己的 RunState 应用回答,并仅运行剩余的内容,因此工具在获得批准之前绝不会运行,不会重复前缀输出,并且在等待期间重新启动也不会丢失任何内容。拒绝任何未响应的中断的恢复; needs_approval 必须是 True 或 False,而不是可调用对象。
输入和输出护栏
在 SDK 文档中使用 input_guardrails 和 output_guardrails 声明代理上的护栏:
from agents import Agent, GuardrailFunctionOutput, Runner, input_guardrail
app.entrypointdef build() -> Agent: judge = Agent( name="topic judge", instructions="Answer yes if the message is about an order, else no.", model=app.llm("gpt-4.1-mini", llm_id="judge"), )
input_guardrail async def on_topic(context, agent, input) -> GuardrailFunctionOutput: verdict = await Runner.run(judge, input, context=context.context) return GuardrailFunctionOutput( output_info=None, tripwire_triggered=verdict.final_output.strip().lower() != "yes", )
return Agent( name="support", model=app.llm("gpt-4.1"), tools=app.tools(), input_guardrails=[on_topic], )绊倒的护栏在回合中失败会被视为策略拒绝:执行以 error_code policy_denied 和平台自己的该类消息结束。拒绝原因命名了护栏并省略了其output_info;它位于运行时的日志和实时流的错误事件的元数据中,而不是存储的执行中。该回合不提交任何内容,因此对话从前一回合继续。本轮已运行的工具不会撤消。
由于拒绝命名了护栏,因此每个护栏都需要一个名称。使用 @input_guardrail 或 @output_guardrail 声明的,或从函数构建的,接受函数的;从 functools.partial 或可调用对象构建的一个没有,因此传递 name=。在任何模型调用之前,没有名称的护栏都会被拒绝。
与 SDK 自带的 Runner 有三点不同:
输入护栏在第一次模型调用之前完成。 SDK 的默认
run_in_parallel=True在模型调用旁边启动它们,然后无论是否有护栏跳闸,都会进行模型调用并付款。run_in_parallel在这里不起作用。代理的护栏按照代理列出的顺序一次运行一个,并且在行程之后的不会运行。 SDK 会同时启动它们。
具有输出护栏的代理不会流其文本。 SDK 在输出护栏看到答案之前流式传输回答,并仅保留运行的最终值。一旦通过护栏,调用者就会在回合结果中收到完整的回答。没有输出护栏的代理会像以前一样流。
与 SDK 中一样,输入护栏属于启动轮流的代理,并使用新消息检查到目前为止的对话;输出护栏属于给出回合最终回答的代理。交接后,代理将开始接下来的轮次,因此其输入护栏会检查这些轮次。
替换尝试会从头开始再次运行转向,包括护栏,而等待批准的转向会在恢复时再次运行护栏。因此,guardrail 必须对相同的输入返回相同的判断,并且在进程之外不进行任何更改。询问模型的护栏通过一个代理来执行此操作,该代理的模型来自 app.llm(...),在入口点构建应用时调用,如上面的 judge 所示:法官的模型调用是项目策略下的业务流程引擎活动,并且重放会返回其记录的响应,而不是再次询问。运行需要 context=context.context,它包含回合。具有普通模型名称的判断在运行时没有提供商凭证,因此在该回合中失败。判断代理中的工具、批准和移交不合格。保存法官响应以供查看的项目策略无法回合:只有应用程序图表的代理可以等待审核者。不支持直接调用外部服务的护栏:在平台的网络策略之外,每次重放都会再次进行调用。
目前尚不支持工具护栏以及用作工具的代理上的护栏。
护栏策略
项目的护栏策略由编排引擎在每个模型和工具调用上强制执行,包括用作工具的代理,并且不需要应用程序代码。记录、编辑或阻止的策略仅更改调用返回的内容。
要求查看模型响应的策略会像批准一样暂停轮次:保留响应,调用返回带有中断的 status: suspended,中断的值在 guardrail_review 下命名查看。它会等待同一回合的任何工具批准,并由一个 resume_map 一起回答:
{"resume_map": {"<interrupt id>": {"decision": "approve", "reviewer_notes": "checked"}}}批准后会释放保留的响应(包括工具调用),而无需再次调用提供商。拒绝会导致本回合失败,并且不会释放任何内容。编排引擎保存响应并记录决策;在获得批准之前,适配器永远不会看到保留的文本。
为了停止亚军,适配器为每个代理提供一个名为 agent_engine_guardrail_review 的工具,但没有模型提供该工具。该停止不是会话内容,因此后续模型调用和提交的历史记录均不包含该停止。运行者自己的运行结果仍然会列出它,因此当审核该代理的响应时,用作工具的代理的 custom_output_extractor 会在该运行的项目中看到该调用及其空输出;他们命名的是查看,而不是保留的响应。每次查看都会占用一个运行的 max_turns。具有该名称的工具或切换,或调用该名称的模型响应,均未通过回合。
内存
通过 agent.yaml 中的 memory: true,工具可通过 app.memory 读取和写入项目的长期内存,LangGraph 和 ADK 应用使用的外观相同:
app.tool()def remember_preference(preference: str) -> str: """Remember how the customer likes to be contacted.""" result = app.memory.save_semantic(text=preference, label="contact_preference") return "saved" if result.acknowledged else "not saved"执行请求时进行的调用会使用该请求的用户。写入是实时的,而不是持久性的活动:如果在崩溃后重放工具调用,其写入会再次运行,因此优先选择可安全重复的写入。
持久状态
每个已提交的轮次都会在 __agent_engine_openai_agents__ 状态属性下存储亚军的完整输入列表,以及其所属的应用和会话,以及结束轮次的代理的名称。下一回合将由该代理从该列表继续。提交的消息是历史记录阅读器的显式字段投影;它们不会被读回。
编排引擎历史记录的读者会看到该投影:用户和助手消息、每个工具调用以及每个输出。切换和对用作工具的代理的调用与普通调用及其输出一样。嵌套代理自己的对话未提交,因此不在历史记录中;其模型和工具调用位于执行的活动记录中。
支持的项目包括文本消息、函数调用和文本函数输出。一个模型响应发出的调用会汇集在一起,然后是每个调用的一个输出。在新模型或工具开始工作之前,属于不同应用或会话的任何其他项目、任何状态以及应用不再具有的活动代理均会失败。
会话和分支
编排引擎的提交状态是唯一的会话存储。适配器使用该状态作为输入自行进行 Runner 调用,因此不使用 SDK Session 对象,并且提供程序托管的延续(previous_response_id、对话 ID 或托管提示,无论是传递给运行器、到 Agent.as_tool,或在已注册模型的 extra_body 或 extra_args 中)在任何模型起作用之前失败。
分支是从另一个会话的已提交状态开始的新会话。它的第一个回合继续 源 和活动代理的对话,在自己的会话下提交,之后不与源共享任何内容;批准在其中的工作方式与任何其他顺序一样。对话可以在已完成的轮次结束时产生分支,因为适配器每轮次提交一次。平台仅从已提交的步骤中产生分支,因此等待批准的回合在完成之前不提供任何可从中产生的分支。
创建分支是一项平台操作,而不是 SDK 操作。适配器未添加任何分支API,并且 SDK 自己的会话分支 (AdvancedSQLiteSession.create_branch_from_turn) 不可用,因此应用无法对自己的会话进行分支;平台的分支API会执行此操作。
模型调用
原生Runner 进行的每个模型调用都会成为一个业务流程引擎 LLM 活动。代理的模型根据记录的活动进行回答,模型本身在工具 Pod 中运行。替换尝试会重放记录的响应,并为 Runner 提供相同的输出项。
Only per-call settings the platform carries cross that boundary: max_tokens, top_p, frequency_penalty, presence_penalty, timeout, parallel_tool_calls, reasoning.effort, verbosity, and a named tool_choice. Provider settings such as temperature belong to the model you register, and the Tool Pod applies them. A registered model cannot carry a tool_choice or timeout (set them on the agent), or extra_body / extra_args fields that make the provider continue a stored conversation (previous_response_id, conversation, conversation_id, prompt) or that override what the adapter builds for each call (such as tools, input, instructions, or the output format), or that set reasoning, reasoning_effort, or verbosity as raw fields (use ModelSettings.reasoning and ModelSettings.verbosity). Native tracing, provider-hosted continuation, and stop sequences fail before the call is sent.
推理
在注册的模型、代理或两者上设置 reasoning=Reasoning(effort=...) 和 verbosity;每次调用值胜过注册。首选注册。 SDK 无法区分代理的显式设置与其自身的默认值:当 Agent(model_settings=...) 等于 SDK 的默认设置(当前为 Reasoning(effort="none") 和 verbosity="low")时,SDK 在构造时将其清除,并应用注册的值。要在每次调用时使用该键值对,请在构造 (agent.model_settings = ...) 之后对其进行分配。其他推理设置(例如 reasoning.summary)在发送调用之前失败。推理令牌计数在模型调用的使用情况中报告。提供商可能会按模型和API来限制这些设置:示例,聊天完成推理模型可以拒绝函数工具,除非工作量为 none。提供者的拒绝导致模型调用失败。
持久性运行不会将模型的隐藏推理从一个模型调用传递到下一个模型调用。提供程序的加密推理项会放入 Tool Pod 中并且从不存储,因此每次调用(包括工具循环的每个步骤)都仅从持久性会话中进行推理。如果代理需要一个在调用之间持续存在的计划,则应显式记录该计划,示例在自己的消息或内存中,而不是依赖模型的隐藏推理。
Tool Pod 会在任何输出流之前重试暂时性提供商程序故障。 SDK 自己的 ModelSettings.retry 被拒绝:在代理上,它会使 Runner 发出额外的模型活动,而在已注册的模型上,它没有影响。在未完成响应的情况下结束的模型流将导致调用失败,而不是记录为截断的回答。