Adaptador del SDK de agentes de OpenAI para el SDK del motor de agentes Atlas de MongoDB.
Nota
Estado: experimental. La CLI y el servicio de compilación aceptan framework: openai-agents; una compilación de plataforma necesita un entorno que publique la base del ejecutor Python Lite.
El adaptador ejecuta agentes nativos de OpenAI Agent a través del propio Runner del SDK, mientras que el motor de orquestación se encarga de la ejecución persistente. El adaptador no mantiene una base de datos de sesiones y nunca utiliza sesiones, conversaciones o previous_response_id de OpenAI como estado de continuación.
Construyendo un agente
Para comenzar con un proyecto funcional, ejecute agentengine create y seleccione la variante del SDK de agentes de OpenAI del agente Hello World o Human-in-the-Loop (--template hello-world-agent-openai o --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(), )Utilice el modelo devuelto por app.llm(...) y las herramientas devueltas por app.tools(); cualquier otra cosa hace que el turno falle antes de que funcione cualquier modelo o herramienta. Pase un agents.Model configurado a app.llm para usar un cliente OpenAI personalizado. Los parámetros de la herramienta deben ser valores JSON que se puedan pasar por nombre (no se permiten parámetros posicionales solamente o *args, y ninguno llamado tool_call_id), porque las herramientas remotas reciben sus argumentos como valores JSON con nombre. Establezca el tiempo de espera de una herramienta con @app.tool(timeout=...); un timeout_seconds nativo en una herramienta emitida hace que el turno falle. Un resultado de herramienta que no sea una cadena, incluidos modelos anidados, clases de datos, enumeraciones y fechas y horas, llega al modelo como texto JSON. El texto es canónico, por lo que un turno repetido envía al modelo el mismo texto: las claves están ordenadas y un número entero flotante como 164.0 se escribe 164. Un entero más allá de 2**53 en el resultado de una herramienta hace que la llamada falle, y en sus argumentos hace que el turno falle, porque el registro duradero no puede mantenerlo exactamente; pase dicho valor como una cadena.
Cada invocación ejecuta un turno nativo Runner y lo confirma en el Motor de Orquestación antes de devolver el resultado. Una herramienta que genera un error se informa al modelo como lo hace el SDK de OpenAI Agents por defecto, con el texto del default_tool_error_function del SDK instalado, y el turno continúa. Las versiones anteriores a 0.23 incluyen el mensaje de error de la herramienta en ese texto; las versiones posteriores envían un mensaje fijo sin él. El Motor de Orquestación registra el fallo, por lo que un turno repetido envía al modelo el mismo texto. Una llamada que la plataforma detiene o falla en su lugar hace que el turno falle con el error que informa su ruta de herramienta: una denegación de política, una llamada cancelada o detenida por el usuario, o un fallo de la plataforma como que el Motor de Orquestación no reconozca el resultado de una herramienta. Un fallo de la plataforma nunca se informa al modelo como una herramienta fallida, porque la herramienta puede que ya se haya ejecutado. No se confirma nada en estos casos. Queda una excepción: cuando el Motor de Orquestación no puede acceder al Tool Pod de una herramienta remota, registra el fallo como propio de la herramienta, por lo que se le indica al modelo que la herramienta ha fallado. Cuando el modelo llama a varias herramientas a la vez, se ejecutan simultáneamente. Cada llamada mantiene la actividad del Motor de Orquestación de su ID de llamada, y sus resultados llegan al modelo en el orden en que el modelo realizó las llamadas, la que termine primero. Si una de ellas espera aprobación, las llamadas que se ejecutaron antes de la espera mantienen sus salidas por delante de la llamada aprobada, ya que el SDK las registra. Cuando una llamada falla en su turno, falla una vez que las llamadas que aún se ejecutan junto a ella hayan finalizado. Los ganchos de agente, las indicaciones alojadas, las instrucciones dinámicas y un tool_use_behavior personalizado aún no son compatibles. Las propias reglas de entrada y salida de un agente se ejecutan como se describe en Reglas de entrada y salida. Las políticas de reglas de un proyecto se aplican a cada llamada de modelo y herramienta; consulte Políticas de reglas.
Herramientas MCP
Enumera un servidor MCP remoto bajo mcp.servers en agent.yaml, con las mismas claves que utiliza cada marco de trabajo de Agent Engine. Sus herramientas se descubren cuando se inicia el agente y app.tools() las devuelve junto con las funciones registradas, denominadas <server>__<tool>:
mcp: servers: atlas: url: https://mcp.example.com/mcp allowed_tools: [find] auth: type: bearer_env token_env: ATLAS_MCP_TOKENauth nombra la variable de entorno que contiene el token; el token en sí debe estar en los secretos del espacio de trabajo, o en .env para el desarrollo local.
Una llamada a una de estas herramientas es una actividad de herramienta del Motor de Orquestación como cualquier otra y se ejecuta en el Tool Pod, que contiene las credenciales del servidor y el acceso a la red. allowed_tools limita qué herramientas del servidor recibe el agente. Una llamada fallida o un resultado de error de MCP se informa al modelo como cualquier herramienta fallida. Un resultado estructurado que contiene un entero igual o superior a 2**53, que un registro duradero no puede mantener con exactitud, provoca un fallo en el turno; el servidor debe devolver dicho valor como una cadena.
Un servidor al que no se puede acceder cuando el agente se inicia impide que se inicie; timeout_seconds limita cada solicitud de ese descubrimiento, como limita una llamada.
Solo el entorno de ejecución del agente muestra las herramientas del servidor al iniciarse; el Tool Pod encuentra el servidor para una llamada desde agent.yaml cuando llega la llamada. El Tool Pod también ejecuta el punto de entrada para registrar los modelos del agente, y allí app.tools() devuelve solo las funciones registradas. Pase las herramientas MCP a un agente sin buscarlas por nombre, por ejemplo [tool for tool in app.tools() if tool.name.startswith("atlas__")], para que el punto de entrada las incluya ambas.
Se rechaza el Agent.mcp_servers del propio SDK: el SDK llamaría al servidor desde el proceso del agente, fuera del Tool Pod. El needs_approval en una herramienta MCP aún no es compatible.
Transferencias
Utilice el propio handoffs=[...] del SDK, con otros agentes o agents.handoff(...). El entorno de ejecución lee todo el grafo de agentes que devuelve el punto de entrada antes de cada turno y aplica a cada agente accesible las mismas reglas que al primero: su modelo proviene de app.llm(...) (asigne a cada agente su propio llm_id) y sus herramientas de app.tools(), de las cuales cada agente puede seleccionar la suya por nombre. Los nombres de los agentes deben ser únicos.
Una transferencia cambia el agente activo; el turno se mantiene en una ejecución. El estado confirmado registra el agente activo, por lo que el siguiente turno, incluso después de un reinicio, comienza con el agente que finalizó el anterior. Una aprobación solicitada por un agente transferido se suspende y se reanuda en ese agente.
agents.handoff(...) may override the tool name and description. Agents may hand off to each other in a cycle. An input_filter, nest_handoff_history, an on_handoff callback, an input_type, a callable is_enabled, and a hand-built Handoff fail the turn before any model or tool work. So does a handoff on any agent whose tool name matches another handoff's or one of that agent's tools: the SDK would otherwise keep one and drop the other.
Los agentes como herramientas
Utilice el Agent.as_tool(...) del SDK y coloque el resultado en el tools de otro agente. El agente anidado sigue las mismas reglas que todos los demás agentes del gráfico. Cada llamada ejecuta el agente anidado como una operación independiente: sus llamadas al modelo y a las herramientas son actividades del Motor de Orquestación bajo una ruta con el nombre de la herramienta, y las llamadas repetidas o concurrentes a la misma herramienta del agente permanecen distintas, en el orden en que el modelo las realizó.
El modelo del agente que realiza la llamada solo ve la respuesta de la herramienta, y solo se confirma la conversación de la llamada; un intento de reemplazo reproduce la ejecución anidada a partir de sus actividades registradas. El flujo del emisor transporta el texto del agente anidado entre los eventos subagent_start y subagent_end, etiquetado con el nombre de la herramienta y el ID de la llamada, además de la respuesta del agente que realiza la llamada. Una ejecución anidada que falla provoca que el turno falle.
Una herramienta que utiliza un agente anidado puede requerir aprobación, al igual que la propia herramienta del agente. El turno se suspende como en cualquier otra aprobación, y la espera se registra en la ruta del agente anidado. En un turno, las esperas del agente que realiza la llamada y de los agentes anidados se responden simultáneamente. Los identificadores de llamada de las herramientas deben ser únicos dentro de un turno. Un agente anidado que se detiene para una aprobación no envía subagent_end; el flujo reanudado lo abre de nuevo con subagent_start antes del resto del texto, y no vuelve a enviar el texto que el agente que realizó la llamada vio antes de la espera.
max_turnsSe admiten custom_output_extractor y entrada estructurada. Un intento de reemplazo llama de nuevo al extractor y a input_builder de una entrada estructurada, por lo que ambos deben devolver el mismo valor para la misma entrada y no tener efectos secundarios; OE falla el turno cuando una llamada al modelo reproducida difiere de la registrada.
Una devolución de llamada on_stream, hooks, un run_config, un session, identificadores de continuación del proveedor, un needs_approval o is_enabled invocable, límites de herramientas, límites de entrada o salida en el agente anidado o un agente al que se lo transfiere, un timeout_seconds nativo y un agente que puede alcanzarse a sí mismo a través de su propia herramienta de agente fallan el turno antes de que cualquier modelo o herramienta funcione.
Aprobaciones
Registre una herramienta con @app.tool(needs_approval=True) para que una persona apruebe cada llamada. El argumento es el function_tool(needs_approval=...) del SDK, que se pasa al FunctionTool que devuelve app.tools(); configurar needs_approval en esa herramienta devuelta también funciona. Cuando el modelo llama a una herramienta que necesita aprobación, el turno se suspende: la invocación devuelve status: suspended con una interrupción por llamada esperando aprobación, en el orden en que el modelo realizó las llamadas, nombrando cada una la herramienta y sus argumentos. Las llamadas en la misma respuesta que no necesitan aprobación se ejecutan primero. Reanude a través del punto final de invocación normal en la misma sesión, con resume_map respondiendo a cada ID de interrupción a la vez:
{"resume_map": {"<interrupt id>": {"confirmed": true}}}interrupts Enumera todas las aprobaciones pendientes. suspend_payload contiene solo el valor de la primera, para los llamadores que leen una única carga útil.
{"confirmed": false, "rejection_message": "..."} rechaza la llamada; el modelo ve el mensaje de rechazo en lugar de un resultado de la herramienta, como con el reject(..., rejection_message=...) del SDK. Sin un rejection_message el modelo ve el texto de rechazo predeterminado del SDK; uno vacío es rechazado. Una aprobación no lleva ningún mensaje. El Motor de Orquestación registra la espera y su respuesta. Reanudar reproduce el turno de las llamadas del modelo registradas, aplica la respuesta a través del RunState del SDK y ejecuta solo lo que queda, por lo que una herramienta nunca se ejecuta antes de su aprobación, la salida del prefijo no se repite y un reinicio mientras espera no pierde nada. Una reanudación que deja alguna interrupción sin respuesta es rechazada; needs_approval debe ser True o False, no una invocable.
Barreras de protección de entrada y salida
Declara límites de seguridad en un agente como se documenta en el SDK, con input_guardrails y 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], )Una barrera de seguridad que se activa provoca un fallo en el turno por denegación de política: la ejecución finaliza con error_code policy_denied y el mensaje propio de la plataforma para esa clase. El motivo de la denegación nombra la barrera de seguridad y omite su output_info; se encuentra en el registro de tiempo de ejecución y en los metadatos del evento de error de la transmisión en vivo, no en la ejecución almacenada. El turno no confirma nada, por lo que la conversación continúa desde el turno anterior. Las herramientas que el turno ya ejecutó no se deshacen.
Dado que la denegación nombra la barrera de seguridad, cada barrera necesita un nombre. Si se declara con @input_guardrail o @output_guardrail, o se construye a partir de una función, toma el nombre de la función; si se construye a partir de un functools.partial o un objeto invocable, no tiene nombre, por lo que se pasa name=. Una barrera de seguridad sin nombre se rechaza antes de cualquier llamada al modelo.
Hay tres cosas que difieren del Runner del propio SDK:
Las barreras de entrada finalizan antes de la primera llamada al modelo. El valor predeterminado del SDK,
run_in_parallel=True, las inicia junto con la llamada al modelo, que se realiza y se paga independientemente de si se activa o no una barrera. En este caso,run_in_parallelno tiene ningún efecto.Las barreras de seguridad de un agente se ejecutan de una en una, en el orden en que el agente las enumera, y las que siguen a una infracción no se ejecutan. El SDK las inicia simultáneamente.
Un agente con restricciones de salida no transmite su texto. El SDK transmite una respuesta antes de que las restricciones de salida la detecten y retiene únicamente el valor final de la ejecución. En este caso, quien realiza la llamada recibe la respuesta completa, en el resultado del turno, una vez que las restricciones se cumplen. Los agentes sin restricciones de salida transmiten como antes.
Al igual que en el SDK, las restricciones de entrada pertenecen al agente que inicia el turno y verifican la conversación hasta el momento con el nuevo mensaje; las restricciones de salida pertenecen al agente que da la respuesta final del turno. Después de una transferencia, el agente que recibe el turno inicia los siguientes turnos, por lo que sus restricciones de entrada verifican esos turnos.
Un intento de reemplazo ejecuta el turno nuevamente desde su inicio, incluyendo las medidas de seguridad, y un turno que espera una aprobación las ejecuta nuevamente cuando se reanuda. Por lo tanto, una medida de seguridad debe devolver el mismo veredicto para la misma entrada y no cambiar nada fuera del proceso. Una medida de seguridad que solicita un modelo lo hace a través de un agente cuyo modelo proviene de app.llm(...), llamado mientras el punto de entrada construye la aplicación, como judge arriba: la llamada al modelo del juez es entonces una actividad del Motor de Orquestación bajo las políticas del proyecto, y una repetición devuelve su respuesta registrada en lugar de volver a preguntar. La ejecución necesita context=context.context, que lleva el turno. Un juez con un nombre de modelo simple no tiene credenciales de proveedor en el tiempo de ejecución y falla el turno. Las herramientas, aprobaciones y transferencias en un agente juez no están calificadas. Una política de proyecto que mantiene la respuesta de un juez para revisión falla el turno: solo un agente del gráfico de la aplicación puede esperar a un revisor. No se admite una medida de seguridad que llame directamente a un servicio externo: la llamada se haría nuevamente en cada repetición, fuera de la política de red de la plataforma.
Las protecciones para herramientas, así como las protecciones en un agente utilizado como herramienta, aún no son compatibles.
Políticas de salvaguardia
Las políticas de control de un proyecto son aplicadas por el Motor de Orquestación en cada llamada a modelo y herramienta del turno, incluidas las de un agente utilizado como herramienta, y no requieren código de aplicación. Una política que registra, censura o bloquea solo modifica el resultado de la llamada.
Una política que requiere la revisión de una respuesta del modelo suspende el turno como lo hace una aprobación: la respuesta se retiene y la invocación devuelve status: suspended con una interrupción cuyo valor nombra la revisión bajo guardrail_review. Espera junto a cualquier aprobación de herramienta del mismo turno, y un resume_map las responde a todas juntas:
{"resume_map": {"<interrupt id>": {"decision": "approve", "reviewer_notes": "checked"}}}La aprobación libera la respuesta retenida, incluidas las llamadas a herramientas, sin necesidad de realizar otra llamada al proveedor. La denegación anula el turno y no libera nada. El motor de orquestación retiene la respuesta y registra la decisión; el adaptador nunca ve el texto retenido antes de su aprobación.
Para detener al Runner, el adaptador le da a cada agente una herramienta llamada agent_engine_guardrail_review, para la cual no se ofrece ningún modelo. La detención no es contenido de la conversación, por lo que ni una llamada posterior al modelo ni el historial confirmado la incluyen. El propio resultado del Runner para una ejecución aún la lista, por lo que un custom_output_extractor de un agente usado como herramienta ve la llamada y su salida vacía entre los elementos de esa ejecución cuando se revisó una respuesta de ese agente; nombran la revisión, nunca la respuesta retenida. Cada revisión toma uno de los max_turns de la ejecución. Una herramienta o transferencia con ese nombre, o una respuesta del modelo que la llama, falla el turno.
Memoria
Con memory: true en agent.yaml, las herramientas leen y escriben la memoria a largo plazo del proyecto a través de app.memory, la misma interfaz que utilizan las aplicaciones LangGraph y 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"Las llamadas realizadas mientras se ejecuta una solicitud utilizan el usuario de dicha solicitud. Una escritura es una actividad en tiempo real, no persistente: si se vuelve a ejecutar una llamada a una herramienta después de un fallo, la escritura se ejecuta de nuevo, por lo que se recomienda realizar escrituras que se puedan repetir de forma segura.
Estado duradero
Cada turno confirmado almacena la lista completa de entradas del Runner bajo la propiedad de estado __agent_engine_openai_agents__, junto con la aplicación y la sesión a la que pertenece y el nombre del agente que finalizó el turno. El siguiente turno continúa a partir de esa lista con ese agente. Los mensajes confirmados son una proyección de campo explícito para los lectores de historial; no se leen de vuelta.
Un lector del historial del motor de orquestación observa esa proyección: mensajes de usuario y asistente, cada llamada a herramienta y cada resultado. Una transferencia y una llamada a un agente utilizado como herramienta aparecen como llamadas ordinarias con sus respectivos resultados. La conversación de un agente anidado no se confirma, por lo que no se registra en el historial; su modelo y las llamadas a herramientas se encuentran en los registros de actividad de la ejecución.
Los elementos admitidos son mensajes de texto, llamadas a funciones y salidas de funciones de texto. Las llamadas realizadas por una respuesta del modelo se agrupan, seguidas de una única salida por llamada antes de cualquier otra cosa. Cualquier otro elemento, cualquier estado que pertenezca a una aplicación o sesión diferente, y un agente activo que la aplicación ya no tenga, provocan un fallo en el turno antes de que comience el trabajo del nuevo modelo o herramienta.
Sesiones y ramas
El estado confirmado del motor de orquestación es el único almacén de conversaciones. El adaptador realiza la llamada Runner por sí mismo con ese estado como entrada, por lo que no se utilizan los objetos Session del SDK, y la continuación alojada por el proveedor (previous_response_id, un ID de conversación o una solicitud alojada, ya sea que se pase al Runner, a Agent.as_tool o en extra_body o extra_args de un modelo registrado) falla antes de que cualquier trabajo del modelo.
Una rama es una nueva sesión que comienza desde el estado confirmado de otra sesión. Su primer turno continúa con la conversación y el agente activo de la sesión de origen, se confirma bajo su propia sesión y no comparte nada con la sesión de origen posteriormente; las aprobaciones funcionan en ella como en cualquier otro turno. Una conversación puede ramificarse al final de un turno completado, ya que el adaptador se confirma una vez por turno. La plataforma solo se ramifica a partir de un paso confirmado, por lo que un turno que espera una aprobación no ofrece ninguna opción para ramificarse hasta que finalice.
Crear una ramificación es una operación de la plataforma, no del SDK. El adaptador no agrega ninguna API de ramificación, y la ramificación de sesión propia del SDK (AdvancedSQLiteSession.create_branch_from_turn) no está disponible, por lo que una aplicación no puede ramificar su propia conversación; la API de ramificación de la plataforma sí lo hace.
Llamadas de modelos
Cada llamada al modelo que realiza el sistema nativo Runner se convierte en una actividad LLM del Motor de Orquestación. El modelo del agente responde a partir de la actividad grabada, y el modelo se ejecuta en el Tool Pod. Un intento de reemplazo reproduce la respuesta grabada y proporciona al sistema Runner elementos de salida idénticos.
Solo la plataforma maneja configuraciones por llamada que cruzan ese límite: max_tokens, top_p, frequency_penalty, presence_penalty, timeout, parallel_tool_calls, reasoning.effort, verbosity y un tool_choice con nombre. Las configuraciones del proveedor, como temperature, pertenecen al modelo que registra, y Tool Pod las aplica. Un modelo registrado no puede llevar un tool_choice o timeout (configúrelos en el agente), o campos extra_body / extra_args que hacen que el proveedor continúe una conversación almacenada (previous_response_id, conversation, conversation_id, prompt) o que anulan lo que el adaptador construye para cada llamada (como tools, input, instructions o el formato de salida), o que establecen reasoning, reasoning_effort o verbosity como campos sin procesar (use ModelSettings.reasoning y ModelSettings.verbosity). El rastreo nativo, la continuación alojada por el proveedor y las secuencias de detención fallan antes de que se envíe la llamada.
Razonamiento
Establezca reasoning=Reasoning(effort=...) y verbosity en el modelo registrado, en el agente o en ambos; los valores por llamada tienen prioridad sobre el registro. Prefiera el registro. El SDK no puede distinguir la configuración explícita de un agente de sus propios valores predeterminados: cuando Agent(model_settings=...) es igual a la configuración predeterminada del SDK (actualmente Reasoning(effort="none") con verbosity="low"), el SDK los borra en la construcción y se aplican los valores registrados. Para usar exactamente ese par por llamada, asígnelo después de la construcción (agent.model_settings = ...). Otras configuraciones de razonamiento, como reasoning.summary, fallan antes de que se envíe la llamada. Los recuentos de tokens de razonamiento se informan en el uso de la llamada del modelo. Los proveedores pueden limitar estas configuraciones por modelo y API: por ejemplo, un modelo de razonamiento de Chat Completions puede rechazar las herramientas de función a menos que el esfuerzo sea none. El rechazo del proveedor hace que falle la llamada del modelo.
Una ejecución persistente no conserva el razonamiento oculto del modelo entre llamadas. Los elementos de razonamiento cifrados del proveedor se guardan en el Tool Pod y nunca se almacenan, por lo que cada llamada, incluyendo cada paso del bucle de la herramienta, se basa únicamente en la conversación persistente. Si un agente necesita un plan que persista entre llamadas, debe registrarlo explícitamente, por ejemplo, en sus propios mensajes o en la memoria, en lugar de depender del razonamiento oculto del modelo.
El Tool Pod reintenta los fallos transitorios del proveedor antes de cualquier flujo de salida. El propio ModelSettings.retry del SDK se rechaza: en el agente, provocaría que el Runner emitiera actividades de modelo adicionales, y en un modelo registrado no tendría ningún efecto. Un flujo de modelo que finaliza sin una respuesta completa hace que la llamada falle en lugar de registrarse como una respuesta truncada.