云计算百科
云计算领域专业知识百科平台

openai-agents-python-sdk 源码解析 | 第十二篇:模型适配层:Responses、Chat Completions 与第三方 Provider

本篇导读

前十一篇已经把 Agents SDK 的运行主线拆开了:

  • Agent 定义角色、工具、handoffs 和输出类型。
  • Runner 推进一次 run。
  • Function Tool 把 Python 函数暴露给模型。
  • Handoff 和 Agent-as-tool 组织多 Agent 工作流。
  • Streaming 把运行过程变成事件流。
  • Sessions 保存多轮历史。
  • Guardrails 控制输入、输出和工具边界。
  • Tracing 记录一次 run 的可观测链路。
  • 这些机制最终都要落到一个核心动作上:

    调用模型,拿到模型响应,再把模型响应归一化成 SDK 能处理的 items。

    这就是模型适配层的职责。

    本篇重点回答五个问题:

  • SDK 如何抽象 Model 和 ModelProvider。
  • Runner 如何决定一次 run 用哪个模型。
  • OpenAI Responses 和 Chat Completions 两条路径有什么差异。
  • ModelSettings 如何合并、转换和下发。
  • LiteLLM、Any-LLM、OpenAI-compatible endpoint 如何接入。
  • 先给结论:Runner 不关心具体 Provider

    Agents SDK 的核心 run loop 不直接依赖 OpenAI Responses、Chat Completions、LiteLLM 或 Any-LLM 的请求结构。

    它依赖的是统一接口:

    class Model(abc.ABC):
    async def get_response(...) –> ModelResponse:
    ...

    def stream_response(...) –> AsyncIterator[TResponseStreamEvent]:
    ...

    也就是说,run loop 只需要:

  • 把本轮输入、工具、handoffs、输出 schema、model settings 交给 Model。
  • 拿回统一的 ModelResponse 或统一的 Responses 风格 stream events。
  • 继续执行工具、handoff、输出校验和下一轮。
  • 真正知道“怎么调用某个后端”的,是 model adapter:

    Adapter负责什么
    OpenAIResponsesModel 调用 OpenAI Responses API
    OpenAIResponsesWSModel 通过 WebSocket 调用 OpenAI Responses API
    OpenAIChatCompletionsModel 调用 Chat Completions API,并做格式转换
    LitellmModel 通过 LiteLLM 调用第三方 provider
    AnyLLMModel 通过 any-llm 调用 Responses 或 Chat Completions 风格 provider

    因此,模型适配层的核心设计是:

    Runner 只看 Model 抽象;
    Provider 负责把名字解析成 Model;
    Adapter 负责 provider-specific 请求、能力校验、错误和 usage 归一化。

    第十二篇关注的源码入口

    本篇主要涉及这些文件:

    文件作用
    src/agents/models/interface.py Model、ModelProvider、ModelTracing 抽象
    src/agents/models/openai_provider.py OpenAI 默认 provider
    src/agents/models/multi_provider.py 前缀路由 provider
    src/agents/models/openai_responses.py OpenAI Responses adapter
    src/agents/models/openai_chatcompletions.py Chat Completions adapter
    src/agents/models/chatcmpl_converter.py Responses items 与 Chat messages 转换
    src/agents/model_settings.py ModelSettings 定义和合并逻辑
    src/agents/models/default_models.py 默认模型和默认 settings
    src/agents/run_internal/turn_preparation.py run loop 解析模型和 settings
    src/agents/run_internal/run_loop.py 调用 model.get_response() / stream_response()
    src/agents/run_internal/model_retry.py runner-managed retry
    src/agents/extensions/models/litellm_model.py LiteLLM adapter
    src/agents/extensions/models/any_llm_model.py Any-LLM adapter
    docs/models/index.md 模型配置文档
    examples/model_providers 第三方 provider 示例

    建议阅读顺序:

  • 先读 interface.py,理解 SDK 对模型的最低要求。
  • 再读 turn_preparation.py,看 Runner 如何解析模型。
  • 然后读 openai_provider.py,看默认 provider 如何选择 Responses 或 Chat Completions。
  • 最后对比 openai_responses.py 与 openai_chatcompletions.py。
  • ModelTracing:模型调用的 trace 开关

    ModelTracing 是模型 adapter 接收的 tracing 配置:

    class ModelTracing(enum.Enum):
    DISABLED = 0
    ENABLED = 1
    ENABLED_WITHOUT_DATA = 2

    def is_disabled(self) –> bool:
    return self == ModelTracing.DISABLED

    def include_data(self) –> bool:
    return self == ModelTracing.ENABLED

    它来自 RunConfig:

    def get_model_tracing_impl(
    tracing_disabled: bool,
    trace_include_sensitive_data: bool,
    ) –> ModelTracing:
    if tracing_disabled:
    return ModelTracing.DISABLED
    if trace_include_sensitive_data:
    return ModelTracing.ENABLED
    return ModelTracing.ENABLED_WITHOUT_DATA

    含义很明确:

    配置行为
    tracing_disabled=True 模型 span 禁用
    trace_include_sensitive_data=True span 记录输入输出
    trace_include_sensitive_data=False span 存在,但不记录输入输出

    这就是第十一篇提到的敏感数据边界在模型层的落点。

    Model 接口

    Model 是所有模型 adapter 的公共接口:

    class Model(abc.ABC):
    async def get_response(
    self,
    system_instructions,
    input,
    model_settings,
    tools,
    output_schema,
    handoffs,
    tracing,
    *,
    previous_response_id,
    conversation_id,
    prompt,
    ) –> ModelResponse:
    ...

    stream_response(…) 参数几乎相同,只是返回异步事件流:

    def stream_response(...) –> AsyncIterator[TResponseStreamEvent]:
    ...

    注意几个设计点:

  • input 使用 Responses 风格的 SDK canonical items。
  • tools 是 SDK 统一的 Tool 对象。
  • handoffs 也交给 model adapter 转换成模型可见工具。
  • output_schema 由 adapter 转换为 provider 支持的结构化输出配置。
  • previous_response_id、conversation_id、prompt 是 Responses 特性,但接口统一暴露。
  • 这意味着 Chat Completions adapter 也会收到这些参数,但它不一定支持。

    它的处理策略是:

  • 默认兼容模式下记录 warning 并忽略不支持的特性。
  • strict_feature_validation=True 时直接抛 UserError。
  • ModelProvider 接口

    ModelProvider 只做一件事:把模型名解析成 Model 实例。

    class ModelProvider(abc.ABC):
    @abc.abstractmethod
    def get_model(self, model_name: str | None) –> Model:
    ...

    async def aclose(self) –> None:
    return None

    这个接口的边界很重要:

  • Model 负责一次具体模型调用。
  • ModelProvider 负责模型名解析、缓存、连接复用和资源关闭。
  • 例如 OpenAI WebSocket Responses 模型可能持有持久连接,所以 OpenAIProvider.aclose() 要关闭缓存的 websocket model。

    普通 HTTP Responses model 不需要在 provider 里缓存实例。

    Runner 如何选择模型

    turn_preparation.py 中的 get_model(…) 是模型解析入口:

    def get_model(agent: Agent[Any], run_config: RunConfig) –> Model:
    if isinstance(run_config.model, Model):
    return run_config.model
    elif isinstance(run_config.model, str):
    return run_config.model_provider.get_model(run_config.model)
    elif isinstance(agent.model, Model):
    return agent.model

    return run_config.model_provider.get_model(agent.model)

    优先级是:

    优先级来源
    1 RunConfig.model 是具体 Model 实例
    2 RunConfig.model 是字符串,通过 RunConfig.model_provider 解析
    3 Agent.model 是具体 Model 实例
    4 Agent.model 是字符串或 None,通过 RunConfig.model_provider 解析

    这说明 RunConfig.model 可以覆盖 Agent 自己的模型。

    例如:

    agent = Agent(name="Assistant", model="gpt-5.4-mini")

    result = await Runner.run(
    agent,
    "你好",
    run_config=RunConfig(model="gpt-5.6-sol"),
    )

    本次 run 实际使用 gpt-5.6-sol。

    ModelSettings 如何解析

    模型 settings 不是简单拿 Agent 上的配置。

    源码中有一层默认 settings 对齐逻辑:

    def get_model_settings(agent: Agent[Any], run_config: RunConfig) –> ModelSettings:
    model_settings = agent.model_settings
    if model_settings == _implicit_model_settings_for_agent(agent):
    model_settings = _model_settings_for_resolved_name(agent, run_config)
    return model_settings.resolve(run_config.model_settings)

    含义是:

  • 如果 Agent 使用的是隐式默认 settings,且 run-level model 覆盖了模型名,默认 settings 要跟着新的模型名重新计算。
  • 如果 Agent 明确设置过 ModelSettings,则保持用户显式配置。
  • 最后用 run_config.model_settings 覆盖 Agent settings。
  • ModelSettings.resolve(…) 的覆盖规则是:

    changes = {
    field.name: getattr(override, field.name)
    for field in fields(self)
    if getattr(override, field.name) is not None
    }

    即非 None 字段覆盖。

    两个特殊合并:

  • extra_args 会合并 dict,而不是整体替换。
  • retry 会深度合并 backoff 等字段。
  • 默认模型和默认 Settings

    当前源码中默认模型来自:

    OPENAI_DEFAULT_MODEL_ENV_VARIABLE_NAME = "OPENAI_DEFAULT_MODEL"

    def get_default_model() –> str:
    return os.getenv(OPENAI_DEFAULT_MODEL_ENV_VARIABLE_NAME, "gpt-5.4-mini").lower()

    默认是:

    gpt-5.4-mini

    GPT-5 系列会有默认 ModelSettings:

    _GPT_5_NONE_DEFAULT_MODEL_SETTINGS = ModelSettings(
    reasoning=Reasoning(effort="none"),
    verbosity="low",
    )

    例如 gpt-5.4-mini 默认是:

    ModelSettings(
    reasoning=Reasoning(effort="none"),
    verbosity="low",
    )

    非 GPT-5 模型默认是空 ModelSettings()。

    这解释了一个常见现象:

    Agent(name="A")

    不传模型时,并不是完全没有模型配置。默认模型如果是 GPT-5 系列,SDK 会给它补一组低延迟默认 settings。

    OpenAIProvider:默认模型 Provider

    OpenAIProvider 是默认 provider。

    它的构造参数包括:

    OpenAIProvider(
    api_key=None,
    base_url=None,
    websocket_base_url=None,
    openai_client=None,
    organization=None,
    project=None,
    use_responses=None,
    use_responses_websocket=None,
    strict_feature_validation=False,
    responses_websocket_options=None,
    buffer_streamed_tool_calls=False,
    )

    几个关键点:

  • 如果传入 openai_client,不能再传 api_key、base_url、websocket_base_url。
  • 如果没有传 client,会懒加载 AsyncOpenAI。
  • HTTP client 是共享的,避免每次请求新建连接池。
  • 默认使用 Responses API。
  • 可以切换到 Chat Completions。
  • Responses 还可以切换 HTTP 或 websocket transport。
  • 默认 client 解析:

    self._client = _openai_shared.get_default_openai_client() or AsyncOpenAI(
    api_key=self._stored_api_key or _openai_shared.get_default_openai_key(),
    base_url=self._stored_base_url or os.getenv("OPENAI_BASE_URL"),
    websocket_base_url=(
    self._stored_websocket_base_url or os.getenv("OPENAI_WEBSOCKET_BASE_URL")
    ),
    http_client=shared_http_client(),
    )

    这说明你可以用环境变量或代码全局配置 OpenAI client。

    切换默认 OpenAI API

    默认是 Responses:

    set_default_openai_api("responses")

    也可以切换为 Chat Completions:

    from agents import set_default_openai_api

    set_default_openai_api("chat_completions")

    切换后,OpenAIProvider().get_model("…") 会返回不同 adapter:

    配置返回
    Responses HTTP OpenAIResponsesModel
    Responses WebSocket OpenAIResponsesWSModel
    Chat Completions OpenAIChatCompletionsModel

    如果只想本次 run 使用 Chat Completions,更推荐直接传 provider:

    provider = OpenAIProvider(use_responses=False)

    result = await Runner.run(
    agent,
    "你好",
    run_config=RunConfig(model_provider=provider),
    )

    这样不会影响进程内其他 run。

    OpenAI Responses Model

    OpenAIResponsesModel 的非流式调用是:

    with response_span(disabled=tracing.is_disabled()) as span_response:
    response = await self._fetch_response(...)

    它最终调用:

    response = await client.responses.create(**create_kwargs)

    _build_response_create_kwargs(…) 负责构造 Responses 请求。

    核心字段包括:

    create_kwargs = {
    "previous_response_id": ...,
    "conversation": ...,
    "instructions": ...,
    "model": ...,
    "input": list_input,
    "include": include,
    "tools": tools_param,
    "prompt": ...,
    "temperature": ...,
    "top_p": ...,
    "truncation": ...,
    "max_output_tokens": ...,
    "tool_choice": tool_choice_param,
    "parallel_tool_calls": parallel_tool_calls,
    "text": response_format,
    "store": ...,
    "prompt_cache_options": ...,
    "reasoning": ...,
    "metadata": ...,
    "context_management": ...,
    }

    这条路径天然支持 Responses 的能力:

  • previous_response_id
  • conversation
  • prompt
  • Responses tool surfaces
  • hosted tools
  • response include
  • context management
  • Responses structured output
  • Responses streaming terminal events
  • 所以 OpenAI-only 应用默认推荐走 Responses。

    Responses 工具转换

    Responses adapter 会把 SDK tools 和 handoffs 转成 Responses 工具参数。

    converted_tools = Converter.convert_tools(
    tools,
    handoffs,
    model=effective_computer_tool_model,
    tool_choice=model_settings.tool_choice,
    )

    Converter.convert_tool_choice(…) 支持的选择包括:

  • auto
  • required
  • none
  • function tool name
  • file_search
  • web_search
  • computer
  • image_generation
  • code_interpreter
  • mcp
  • MCPToolChoice
  • 同时它会校验不合法组合。例如 deferred-loading function tools 没有 ToolSearchTool() 时,tool_choice="required" 可能会被拒绝。

    这类校验放在 adapter 层是合理的:只有 adapter 知道目标 API 支持什么。

    Responses 输出格式

    结构化输出通过 text 字段传给 Responses API:

    {
    "format": {
    "type": "json_schema",
    "name": "final_output",
    "schema": output_schema.json_schema(),
    "strict": output_schema.is_strict_json_schema(),
    }
    }

    如果没有结构化输出,返回 omit。

    如果设置了 ModelSettings.verbosity,adapter 会把它放到 response format 相关结构中:

    if model_settings.verbosity is not None:
    if response_format is not omit:
    response_format["verbosity"] = model_settings.verbosity
    else:
    response_format = {"verbosity": model_settings.verbosity}

    这也是为什么 ModelSettings 不是简单照搬到 API 参数,而是需要 adapter 逐项解释。

    OpenAI Chat Completions Model

    Chat Completions adapter 的核心工作是转换。

    SDK 内部输入是 Responses 风格 items,但 Chat Completions API 要的是 messages:

    converted_messages = Converter.items_to_messages(
    input,
    model=self.model,
    base_url=str(self._client.base_url),
    strict_feature_validation=self._strict_feature_validation,
    )

    如果有 system instructions,会插到 messages 开头:

    if system_instructions:
    converted_messages.insert(
    0,
    {"content": system_instructions, "role": "system"},
    )

    最终调用:

    ret = await self._get_client().chat.completions.create(**create_kwargs)

    核心字段包括:

    create_kwargs = {
    "model": self.model,
    "messages": converted_messages,
    "tools": tools_param,
    "temperature": ...,
    "top_p": ...,
    "frequency_penalty": ...,
    "presence_penalty": ...,
    "max_tokens": ...,
    "tool_choice": tool_choice,
    "response_format": response_format,
    "parallel_tool_calls": parallel_tool_calls,
    "stream": ...,
    "stream_options": ...,
    "store": ...,
    "reasoning_effort": ...,
    "verbosity": ...,
    "top_logprobs": ...,
    }

    Chat Completions 路径会把返回的 ChatCompletionMessage 再转换回 Responses 风格 output items:

    items = Converter.message_to_output_items(message)

    这保证 run loop 后续仍然处理统一的 SDK item。

    Chat Completions 不支持的能力

    Chat Completions adapter 会收到接口中的 Responses-only 参数,例如:

  • previous_response_id
  • conversation_id
  • prompt
  • reasoning.mode
  • reasoning.context
  • 一些 Responses-only tool surface
  • 默认兼容模式下,它会 warning 并忽略。

    如果启用 strict validation:

    provider = OpenAIProvider(
    use_responses=False,
    strict_feature_validation=True,
    )

    这些不匹配会变成 UserError。

    开发期建议打开 strict validation,避免某些字段被静默降级。

    生产期是否打开,要看你是否依赖兼容老 provider 的容忍行为。

    Responses 与 Chat Completions 对比

    维度ResponsesChat Completions
    SDK 默认推荐 是 否
    OpenAI hosted tools 支持更多 支持有限
    previous_response_id 支持 不支持
    conversation_id 支持 不支持
    prompt-managed request 支持 不支持
    Responses tool search 支持 不支持
    输入格式 Responses items chat messages
    SDK 转换成本 低 高
    第三方 provider 兼容性 取决于 provider 通常更好
    stream terminal 语义 原生 Responses events 需要转换成 Responses 风格 events

    实用建议:

  • OpenAI-only 新项目优先 Responses。
  • OpenAI-compatible 第三方 provider 通常先试 Chat Completions。
  • 如果依赖 server-managed conversation、Responses hosted tools、tool search,用 Responses。
  • 如果必须混用两种 adapter,逐项验证工具、结构化输出、streaming 和 retry 行为。
  • Streaming 终止语义

    Responses stream 的终止事件不是简单的“流结束就成功”。

    Responses adapter 会检查:

  • response.completed
  • response.failed
  • response.incomplete
  • error
  • response.error
  • 如果收到 failed 或 incomplete terminal,会转换成 ModelBehaviorError。

    Chat Completions stream 则需要 ChatCmplStreamHandler 把 chat chunks 转换成 Responses 风格 events。

    这也是 adapter 层的职责:不同 provider 的 stream 协议不同,但 run loop 只消费统一的 TResponseStreamEvent。

    ModelResponse:统一返回值

    无论底层是 Responses 还是 Chat Completions,最终都要返回:

    ModelResponse(
    output=response.output,
    usage=usage,
    response_id=response.id,
    request_id=getattr(response, "_request_id", None),
    )

    或由 Chat Completions 转换后的:

    ModelResponse(
    output=items,
    usage=usage,
    response_id=None,
    )

    output 是 SDK 后续处理的关键:

  • message output 进入最终输出判断。
  • function call 进入工具执行。
  • handoff tool call 进入 Agent 切换。
  • reasoning item 进入历史和追踪。
  • 只要 adapter 归一化正确,run loop 不需要知道底层 provider。

    ModelSettings 到请求参数的映射

    ModelSettings 是 SDK 统一模型配置:

    字段常见用途
    temperature 采样温度
    top_p nucleus sampling
    frequency_penalty 频率惩罚
    presence_penalty 存在惩罚
    tool_choice 工具选择
    parallel_tool_calls 是否允许并行工具调用
    truncation Responses 截断策略
    max_tokens 最大输出 token
    reasoning 推理模型配置
    verbosity 输出详略
    metadata provider metadata
    store 是否服务端存储响应
    response_include Responses include
    top_logprobs top token logprobs
    retry runner-managed retry
    context_management Responses context management
    prompt_cache_options prompt cache 配置
    extra_args 直接传给 provider 的额外参数

    但不是每个 adapter 都支持所有字段。

    例如:

  • Responses 使用 max_output_tokens。
  • Chat Completions 使用 max_tokens。
  • Responses 使用 text 表示结构化输出。
  • Chat Completions 使用 response_format。
  • Chat Completions 只使用 reasoning.effort,不支持 reasoning.mode 和 reasoning.context。
  • 所以 ModelSettings 是统一入口,不是统一 wire format。

    extra_args 的重复键保护

    两个 OpenAI adapter 都会检查 extra_args 与显式参数是否重复。

    例如 Responses:

    duplicate_extra_arg_keys = sorted(
    k
    for k in extra_args
    if k in create_kwargs and not _is_openai_omitted_value(create_kwargs[k])
    )

    如果重复,会抛:

    responses.create() got multiple values for keyword argument '…'

    这可以避免同一个字段既通过 ModelSettings.temperature 传,又通过 extra_args={"temperature": …} 传,造成行为不确定。

    建议:

  • SDK 已有字段,优先用 ModelSettings 直接字段。
  • SDK 暂未暴露的新 provider 参数,再用 extra_args。
  • 不要同时使用两条路径传同一个参数。
  • Runner-managed Retry

    模型重试不是默认开启的。

    需要显式设置:

    from agents import ModelRetrySettings, ModelSettings, retry_policies

    model_settings = ModelSettings(
    retry=ModelRetrySettings(
    max_retries=2,
    policy=retry_policies.network_error(),
    )
    )

    run loop 调用模型时会包一层:

    new_response = await get_response_with_retry(
    get_response=lambda: model.get_response(...),
    rewind=rewind_model_request,
    retry_settings=model_settings.retry,
    get_retry_advice=model.get_retry_advice,
    previous_response_id=previous_response_id,
    conversation_id=conversation_id,
    )

    重试需要考虑 replay safety。

    特别是:

  • 使用 previous_response_id。
  • 使用 conversation_id。
  • 流式响应已经开始输出。
  • 可能已经发生工具副作用。
  • 这些场景不能简单按 HTTP 500 或网络错误重放。

    Adapter 可以通过 get_retry_advice(…) 提供 provider-specific 建议。OpenAI、LiteLLM、Any-LLM adapter 都会复用 OpenAI 风格 retry advice 提取逻辑。

    Provider-managed retry 与 Runner retry

    OpenAI SDK、LiteLLM、any-llm 自身也可能有重试机制。

    Runner-managed retry 在重放时会通过 contextvar 禁用 provider-managed retries:

    provider_managed_retries_disabled(disabled=True)

    adapter 里会检查:

    if should_disable_provider_managed_retries():
    return client.with_options(max_retries=0)

    这样避免两层 retry 叠加,导致请求次数难以控制。

    文章里的实践建议是:如果你需要可解释、可审计的重试策略,优先使用 runner-managed retry,并明确 policy。

    MultiProvider:基于前缀的路由

    MultiProvider 是默认 RunConfig.model_provider。

    它支持按模型名前缀路由:

    模型名默认路由
    gpt-5.4-mini OpenAI provider
    openai/gpt-4.1 OpenAI provider,默认去掉 openai/
    litellm/openrouter/openai/gpt-5.4-mini LitellmProvider
    any-llm/openrouter/openai/gpt-5.4-mini AnyLLMProvider

    源码中默认说明:

    class MultiProvider(ModelProvider):
    """Maps model based on prefix."""

    如果遇到未知前缀,默认抛 UserError。

    如果你使用 OpenAI-compatible endpoint,并且后端期望完整 namespaced model id,可以配置:

    provider = MultiProvider(
    openai_base_url="https://openrouter.ai/api/v1",
    openai_api_key="…",
    openai_prefix_mode="model_id",
    unknown_prefix_mode="model_id",
    )

    这样 openai/gpt-4.1 不会被当成 OpenAI alias 去掉前缀。

    OpenAI-compatible endpoint

    最小接入方式是自定义 AsyncOpenAI:

    from openai import AsyncOpenAI
    from agents import OpenAIChatCompletionsModel

    client = AsyncOpenAI(
    api_key="provider_api_key",
    base_url="https://provider.example.com/v1",
    )

    model = OpenAIChatCompletionsModel(
    model="provider-model-name",
    openai_client=client,
    )

    然后放到 Agent:

    agent = Agent(
    name="Assistant",
    instructions="你是一个简洁助手。",
    model=model,
    )

    如果要按 run 生效,可以写 ModelProvider:

    class CustomModelProvider(ModelProvider):
    def get_model(self, model_name: str | None) –> Model:
    return OpenAIChatCompletionsModel(
    model=model_name or "provider-model-name",
    openai_client=client,
    )

    再传入:

    result = await Runner.run(
    agent,
    "你好",
    run_config=RunConfig(model_provider=CustomModelProvider()),
    )

    这适合一个 run 内所有 Agent 都走同一个自定义 provider 的场景。

    LiteLLM adapter

    LitellmModel 通过 LiteLLM 调用第三方 provider。

    直接使用:

    from agents.extensions.models.litellm_model import LitellmModel

    agent = Agent(
    name="Assistant",
    model=LitellmModel(
    model="openrouter/openai/gpt-5.4-mini",
    api_key="…",
    ),
    )

    或使用 MultiProvider 前缀:

    agent = Agent(
    name="Assistant",
    model="litellm/openrouter/openai/gpt-5.4-mini",
    )

    LiteLLM adapter 基本走 Chat Completions 风格:

  • 把 SDK items 转成 chat messages。
  • 把 SDK tools 转成 chat completion tools。
  • 调用 litellm.acompletion(…)。
  • 把返回 message 转成 SDK output items。
  • 使用 generation_span 做 tracing。
  • 源码中调用形态:

    ret = await litellm.acompletion(
    model=self.model,
    messages=converted_messages,
    tools=converted_tools or None,
    temperature=model_settings.temperature,
    tool_choice=self._remove_not_given(tool_choice),
    response_format=self._remove_not_given(response_format),
    parallel_tool_calls=parallel_tool_calls,
    api_key=self.api_key,
    base_url=self.base_url,
    **extra_kwargs,
    )

    适合:

  • 需要接入多个第三方 provider。
  • provider 已被 LiteLLM 支持。
  • 能接受 Chat Completions 风格的能力边界。
  • Any-LLM adapter

    AnyLLMModel 的特点是可以按 provider 能力选择 Responses 或 Chat Completions:

    def _selected_api(self) –> Literal["responses", "chat_completions"]:
    if self.api is not None:
    ...
    return "responses" if self._supports_responses() else "chat_completions"

    直接使用:

    from agents.extensions.models.any_llm_model import AnyLLMModel

    agent = Agent(
    name="Assistant",
    model=AnyLLMModel(
    model="openrouter/openai/gpt-5.4-mini",
    api_key="…",
    ),
    )

    或使用 MultiProvider 前缀:

    agent = Agent(
    name="Assistant",
    model="any-llm/openrouter/openai/gpt-5.4-mini",
    )

    如果 provider 支持 Responses,它会走 _get_response_via_responses(…);否则走 _get_response_via_chat(…)。

    这比 LiteLLM 多了一个选择层:

    AdapterAPI 风格
    LiteLLM Chat Completions 风格
    Any-LLM Responses 或 Chat Completions,取决于 provider 能力

    但选择更多也意味着更要测试 provider 能力差异。

    非 OpenAI Provider 与 Tracing

    第三方 provider 示例里通常会调用:

    set_tracing_disabled(disabled=True)

    原因是默认 tracing 会上传到 OpenAI traces backend,需要 OpenAI API key。

    如果你的模型 provider 不是 OpenAI,但仍想使用 OpenAI tracing,可以单独设置 tracing export key:

    from agents import set_tracing_export_api_key

    set_tracing_export_api_key("sk-…")

    或者接入自定义 tracing processor。

    生产建议:

  • 非 OpenAI provider 默认先关闭 OpenAI tracing。
  • 如需 trace,接入企业内部 tracing processor。
  • 不要把第三方 provider 的 API key 写进 trace metadata。
  • 混用模型时的边界

    Agents SDK 允许一个 workflow 中不同 Agent 使用不同模型。

    例如:

    triage_agent = Agent(
    name="Triage agent",
    model="gpt-5.4-mini",
    )

    expert_agent = Agent(
    name="Expert agent",
    model=OpenAIChatCompletionsModel(
    model="provider-model",
    openai_client=custom_client,
    ),
    )

    但混用模型要注意:

  • 工具能力是否一致。
  • handoff 是否能被目标 adapter 转成工具。
  • output schema 是否被目标 provider 支持。
  • reasoning items 是否能正确 replay。
  • stream event 是否能归一化。
  • usage 是否完整。
  • retry 是否 replay-safe。
  • 文档也建议:同一个 workflow 尽量使用同一种 model shape。只有确实需要时再混用。

    实践一:显式使用 OpenAI Responses

    默认情况下 OpenAIProvider 已经走 Responses。

    你仍然可以显式写清楚:

    from agents import Agent, OpenAIProvider, RunConfig, Runner

    agent = Agent(
    name="Assistant",
    instructions="你是一个简洁助手。",
    model="gpt-5.4-mini",
    )

    provider = OpenAIProvider(use_responses=True)

    result = await Runner.run(
    agent,
    "用一句话解释 Responses API。",
    run_config=RunConfig(model_provider=provider),
    )

    如果要启用 websocket transport:

    provider = OpenAIProvider(
    use_responses=True,
    use_responses_websocket=True,
    )

    WebSocket transport 适合需要复用连接或特定低延迟场景,但要注意连接生命周期和 keepalive 配置。

    实践二:切换到 Chat Completions

    如果 provider 不支持 Responses,可以用 Chat Completions:

    from openai import AsyncOpenAI
    from agents import Agent, OpenAIChatCompletionsModel

    client = AsyncOpenAI(
    api_key="provider_api_key",
    base_url="https://provider.example.com/v1",
    )

    agent = Agent(
    name="Assistant",
    instructions="你是一个简洁助手。",
    model=OpenAIChatCompletionsModel(
    model="provider-model",
    openai_client=client,
    strict_feature_validation=True,
    ),
    )

    开发期建议打开 strict_feature_validation=True。

    如果发现代码依赖 conversation_id、prompt、Responses tool search 等能力,就应该切回 Responses 或调整功能设计。

    实践三:使用 LiteLLM

    from agents import Agent, Runner, function_tool, set_tracing_disabled

    set_tracing_disabled(True)

    @function_tool
    def get_weather(city: str) –> str:
    return f"{city} 天气晴。"

    agent = Agent(
    name="Assistant",
    instructions="你只用俳句回答。",
    model="litellm/openrouter/openai/gpt-5.4-mini",
    tools=[get_weather],
    )

    result = await Runner.run(agent, "东京天气怎么样?")

    这里 litellm/ 前缀会被 MultiProvider 路由到 LitellmProvider。

    如果要传显式 key,使用 LitellmModel(…) 直接构造更清楚。

    实践四:使用 Any-LLM

    from agents import Agent, Runner, set_tracing_disabled

    set_tracing_disabled(True)

    agent = Agent(
    name="Assistant",
    instructions="你是一个简洁助手。",
    model="any-llm/openrouter/openai/gpt-5.4-mini",
    )

    result = await Runner.run(agent, "你好")

    Any-LLM 会解析 provider:

    openrouter/openai/gpt-5.4-mini

    provider 是 openrouter,provider model 是 openai/gpt-5.4-mini。

    如果你要强制 API 类型:

    AnyLLMModel(
    model="openrouter/openai/gpt-5.4-mini",
    api="chat_completions",
    )

    这适合 provider 同时支持多种 API,但你希望固定行为的场景。

    测试建议

    模型适配层测试不能只测 final output。

    至少覆盖:

  • RunConfig.model 覆盖 Agent.model。
  • ModelSettings run-level 覆盖 agent-level。
  • extra_args 与显式字段重复时报错。
  • Responses 路径能传 previous_response_id 和 conversation_id。
  • Chat Completions strict validation 能拒绝 Responses-only 字段。
  • Function tool call 能从 provider response 转成 SDK item。
  • streaming 终止事件能正确处理 failed/incomplete。
  • usage 能归一化。
  • retry policy 只在 replay-safe 场景重试。
  • provider aclose() 能释放缓存资源。
  • 本仓库已有相关测试:

  • tests/models/test_openai_responses.py
  • tests/models/test_openai_chatcompletions.py
  • tests/models/test_openai_chatcompletions_converter.py
  • tests/models/test_model_retry.py
  • tests/models/test_default_models.py
  • tests/models/test_litellm_chatcompletions_stream.py
  • tests/models/test_any_llm_model.py
  • tests/test_config.py
  • 新增 provider 或 adapter 时,应优先参考这些测试风格。

    常见错误

    第一,把 ModelSettings 当成所有 provider 都支持的参数集合。

    它是 SDK 统一配置入口,不代表所有 adapter 都能原样支持每个字段。

    第二,用 Chat Completions 但依赖 Responses-only 功能。

    例如 conversation_id、prompt、tool search、deferred-loading Responses tools。

    第三,混用 Responses 与 Chat Completions 后没有测 streaming。

    非流式能跑不代表 streaming event 归一化正确。

    第四,忽略 strict validation。

    开发期不开 strict validation,容易把字段静默降级成 warning。

    第五,把 extra_args 当成万能出口。

    重复传参会报错;未知 provider 参数也可能只在运行时失败。

    第六,第三方 provider 不关闭 tracing。

    没有 OpenAI tracing key 时,默认 exporter 可能出现 401 或跳过上报。

    第七,重试策略过于粗暴。

    带 conversation_id 或已开始 streaming 的请求不能简单重放。

    第八,没有关闭持久 provider 资源。

    WebSocket Responses model 和某些第三方 client 可能持有连接,应在生命周期结束时调用 provider/model 的 close。

    本篇小结

    本篇拆解了 Agents SDK 的模型适配层。

    核心结论:

  • Runner 只依赖 Model 抽象,不依赖具体 provider 请求结构。
  • ModelProvider 负责把模型名解析成具体 Model。
  • RunConfig.model 优先级高于 Agent.model。
  • ModelSettings 使用非 None 覆盖,并特殊合并 extra_args 和 retry。
  • 当前源码默认模型是 gpt-5.4-mini,并带 GPT-5 默认 settings。
  • OpenAIProvider 默认走 Responses,可切换 Chat Completions 或 Responses WebSocket。
  • Responses adapter 原生支持 server-managed state、prompt、Responses tools、context management 等能力。
  • Chat Completions adapter 需要在 SDK Responses items 与 chat messages 之间转换。
  • LiteLLM 更偏 Chat Completions 风格,Any-LLM 可按 provider 能力选择 Responses 或 Chat Completions。
  • 新增或切换 provider 时,必须验证工具、handoff、结构化输出、streaming、usage、retry 和 tracing。
  • 下一篇会进入 MCP 集成:让 Agent 使用外部工具生态,理解 MCP server 如何映射为 SDK tools,以及连接、缓存、过滤和审批如何工作。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » openai-agents-python-sdk 源码解析 | 第十二篇:模型适配层:Responses、Chat Completions 与第三方 Provider
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!