openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由
【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python
本文围绕 openai-agents-python 中位于 src/agents/extensions/models/any_llm_model.py 的AnyLLMModel类展开,讲解如何通过第三方适配层 any-llm 在一个 Agent 应用中接入 OpenAI 之外的数十种模型提供商(OpenRouter、Anthropic、Gemini、Vertex AI 等),并自动选择 Responses API 或 Chat Completions 两种接口面。读完本文,你将掌握 any-llm 的安装与模型命名规范、AnyLLMModel的三种接入方式、API 自动选择与手动固定、工具调用与结构化输出的底层转换逻辑,以及流式、推理内容、用量统计、追踪等生产级细节。
本文对应的 API 参考页为 docs/ref/extensions/models/any_llm_model.md,该页通过 mkdocstrings 指令
::: agents.extensions.models.any_llm_model自动渲染模块 API。
一、AnyLLMModel 是什么:SDK 与上游模型之间的适配层
openai-agents-python 自身内置了针对 OpenAI 的OpenAIResponsesModel等模型实现。当你需要同时使用非 OpenAI 提供商、或需要跨提供商统一路由时,官方建议优先考虑内置集成点,只有在不够用时才引入第三方适配层。项目文档在 docs/models/index.md 的 "Third-party adapters" 一节中明确说明:仅使用 OpenAI 模型时应优先走内置的OpenAIResponsesModel路径,而不是 Any-LLM 或 LiteLLM;第三方适配层用于需要组合 OpenAI 与非 OpenAI 提供商、或需要适配器提供的提供商覆盖与路由能力的场景。
AnyLLMModel正是 SDK 内置的两个 beta 级第三方适配器(Any-LLM 与 LiteLLM)之一。从源码看,它实现了Model接口(见 src/agents/extensions/models/interface.py 中的抽象定义),因此可以直接作为Agent(model=...)的模型参数,也可通过前缀路由与MultiProvider协作。
二、安装与运行环境
any-llm 属于可选依赖组,在 pyproject.toml 中声明为:
any-llm = ["any-llm-sdk>=1.11.0, <2; python_version >= '3.11'"]对应的安装命令有两种等价写法:
pip install 'openai-agents[any-llm]' # 或使用项目默认的包管理器 uv sync --extra any-llm需要注意两个硬性前提:
- Python 版本要求 3.11+。源码在模块导入时通过
importlib.import_module("any_llm")加载 SDK,若失败会直接抛出ImportError,提示信息即为 "any-llm-sdkis required to use the AnyLLMModel...any-llm-sdkcurrently requires Python 3.11+"(见 any_llm_model.py)。 - any-llm-sdk 版本范围
>=1.11.0, <2。源码中多处行为与 any-llm 1.11.0 的接口强相关,例如流式清理说明、ResponsesParams校验行为等(见 any_llm_model.py 与 any_llm_model.py)。
三、模型命名规范与三种接入方式
3.1 模型名格式:provider/model
AnyLLMModel的构造函数签名(见 any_llm_model.py)为:
AnyLLMModel( model: str, base_url: str | None = None, api_key: str | None = None, api: Literal["responses", "chat_completions"] | None = None, )其中model必须采用provider/model两段式命名。_split_model_name的实现(见 any_llm_model.py)揭示了命名规则:
- 模型名为空 → 抛出
UserError("AnyLLMModel requires a non-empty model name.") - 模型名不含
/→ 默认提供商为openai,/后部分作为模型名 - 模型名含
/→ 以第一个/分割为provider与model两部分,任一部分为空都会报错,官方示例格式为openrouter/openai/gpt-5.4-mini(即提供商是openrouter,模型是openai/gpt-5.4-mini)
也就是说,AnyLLMModel构造时接收的是去掉any-llm/前缀之后的字符串。base_url与api_key分别对应上游提供商的接口地址与密钥;两者均可在构造时省略,交由 any-llm 从环境变量读取。
3.2 三种接入方式
根据 docs/models/index.md 的 Any-LLM 章节,SDK 提供三条接入路径:
方式一:直接实例化AnyLLMModel。参考 examples/model_providers/any_llm_provider.py:
from agents import Agent, Runner, set_tracing_disabled from agents.decorators import tool from agents.extensions.models.any_llm_model import AnyLLMModel set_tracing_disabled(disabled=True) @tool def get_weather(city: str): print(f"[debug] getting weather for {city}") return f"The weather in {city} is sunny." async def main(model: str, api_key: str): agent = Agent( name="Assistant", instructions="You only respond in haikus.", model=AnyLLMModel(model=model, api_key=api_key), tools=[get_weather], ) result = await Runner.run(agent, "What's the weather in Tokyo?") print(result.final_output)运行方式(示例默认走 OpenRouter,只需一个 API Key):
export OPENROUTER_API_KEY="..." uv run examples/model_providers/any_llm_provider.py --model openrouter/openai/gpt-5.4-mini uv run examples/model_providers/any_llm_provider.py --model openrouter/anthropic/claude-4.5-sonnet方式二:使用any-llm/...前缀模型名。SDK 会识别该前缀并路由到 any-llm 适配层。参考 examples/model_providers/any_llm_auto.py:
agent = Agent( name="Assistant", instructions="You only respond in haikus.", model="any-llm/openrouter/openai/gpt-5.4-mini", tools=[get_weather], model_settings=ModelSettings(tool_choice="required"), output_type=Result, )该示例同时展示了ModelSettings(tool_choice="required")强制工具调用与output_type=Result结构化输出两个能力。
方式三:运行作用域使用AnyLlmProvider。当需要在一次运行中混用openai/...与any-llm/...两类模型名时,用 [MultiProvider][agents.MultiProvider] 并设置openai_use_responses_websocket=True,再注册AnyLlmProvider(实现在 src/agents/extensions/models/any_llm_provider.py),实现基于前缀的模型路由。
如果希望显式固定接口面,可以在构造AnyLLMModel时传入api="responses"或api="chat_completions"(详见下一节)。
四、API 自动选择机制:Responses 与 Chat Completions
any-llm 会根据上游提供商能力,自动选择 OpenAI Responses API 或 Chat Completions 兼容接口。源码中的选择逻辑(见 any_llm_model.py)如下:
def _selected_api(self) -> Literal["responses", "chat_completions"]: if self.api is not None: if self.api == "responses" and not self._supports_responses(): raise UserError( f"Provider '{self._provider_name}' does not support the Responses API." ) return self.api return "responses" if self._supports_responses() else "chat_completions"规则归纳:
- 显式指定优先:构造时传入
api="responses"或api="chat_completions"则强制使用该接口;若指定responses而提供商不支持,抛出UserError。_validate_api(见 any_llm_model.py)会校验取值只能是None、"responses"、"chat_completions"三者之一。 - 自动探测:未指定时,查看提供商对象的
SUPPORTS_RESPONSES属性(见_supports_responses,any_llm_model.py),支持则走 Responses,否则走 Chat Completions。
两条路径最终都收敛到统一的ModelResponse:
- Responses 路径(
_get_response_via_responses/_stream_response_via_responses):直接复用 OpenAI Responses 的请求参数模型(model、input、instructions、tools、tool_choice、temperature、top_p、max_output_tokens、truncation、store、prompt_cache_retention、previous_response_id、conversation、include、parallel_tool_calls、reasoning、text等字段在_ANY_LLM_RESPONSES_PARAM_FIELDS中列出),非流式响应直接返回Response对象(见 any_llm_model.py)。 - Chat Completions 路径(
_get_response_via_chat/_stream_response_via_chat):把 SDK 的输入项(items)转换成 Chat Completions 消息,调用provider.acompletion(...)(见 any_llm_model.py),再通过ChatCmplStreamHandler把流式 chunk 转成 SDK 的流事件(见 any_llm_model.py)。
值得注意的是,两条路径目前都不支持 prompt-managed 请求:当prompt参数非None时,会直接抛出UserError("AnyLLMModel does not currently support prompt-managed requests.")(见 any_llm_model.py 与 any_llm_model.py)。
五、消息转换、工具调用与提供商特化处理
AnyLLMModel内部把 SDK 的 Agent 输入(TResponseInputItem)转换为上游 API 可识别的消息,这依赖chatcmpl_converter.Converter(见 src/agents/models/chatcmpl_converter.py),核心逻辑集中在_fetch_chat_response:
- 系统指令:
system_instructions以role: "system"消息插入到消息列表头部(any_llm_model.py)。 - 推理块保留:当
model_settings.reasoning.effort被设置时,preserve_thinking_blocks=True保留思考块(any_llm_model.py)。 - 工具定义:
tools经Converter.tool_to_openai转换,handoffs经Converter.convert_handoff_tool转换后追加到tools列表(any_llm_model.py),说明handoff(交接)在底层就是工具调用的一种。 - 工具选择:
model_settings.tool_choice经Converter.convert_tool_choice转换;仅当存在工具时才允许parallel_tool_calls(any_llm_model.py)。 - 结构化输出:
output_schema经Converter.convert_response_format转换为response_format(any_llm_model.py)。
5.1 工具消息顺序修复:面向 Anthropic / Gemini
Anthropic、Claude、Gemini 系列模型对工具调用(assistant+tool_calls)与工具结果(tool)消息的配对顺序有严格要求。源码检测模型名中是否包含anthropic、claude、gemini,命中则调用_fix_tool_message_ordering重排消息(any_llm_model.py)。该函数(any_llm_model.py)的核心思路:
- 把每条 assistant 工具调用拆成独立的 assistant 消息(每条只含一个
tool_call),并按tool_call_id与 tool 结果配对; - 只有第一条拆分消息保留
content、thinking_blocks、reasoning_content、reasoning字段,后续拆分消息仅携带tool_calls,避免重复携带被 Anthropic 拒绝的签名思考块; - 最终按"工具调用紧跟其工具结果"的顺序重组消息列表。
5.2 Google 工具结果角色归一化
针对gemini/vertexai提供商,_normalize_google_tool_result_roles会包装其_convert_completion_params,把 contents 中role == "function"的内容改写为role == "user"(any_llm_model.py),使其符合 Google 接口的user角色约束。
5.3 推理内容(reasoning)的标准化
不同提供商返回推理文本的字段五花八门(reasoning_content、reasoning、thinking,可能是字符串、字典或嵌套对象)。SDK 通过_extract_any_llm_reasoning_text与_flatten_any_llm_reasoning_value(any_llm_model.py)统一提取,并在非流式路径构造InternalChatCompletionMessage时填充标准化的reasoning_content/reasoning字段(any_llm_model.py);流式路径则在_normalize_chat_chunk中把推理文本注入delta["reasoning"](any_llm_model.py)。
六、流式响应的资源管理细节
stream_response使用contextlib.aclosing(...)包裹内部生成器(any_llm_model.py),保证消费者提前aclose()时委托生成器的清理逻辑能确定性执行,而不是等待垃圾回收。源码注释还特别指出:截至 any-llm 1.11.0,其异常与提供商包装层使用裸async for ... yield委托,不会把aclose()转发到底层传输层,因此底层传输的关闭属于上游 any-llm 的职责。
流式过程中的错误与取消处理同样细致:
- 收到
response.completed时先填充 span 的 usage 与响应数据,再向下游产出事件,确保消费者在终止事件处停止也能留下完整的追踪记录(any_llm_model.py); - 遇到
response.failed/response.incomplete事件时构造ModelBehaviorError并在 yield 后抛出(any_llm_model.py); - 捕获
asyncio.CancelledError时把流关闭调度到后台任务,避免取消中途放弃半完成的关闭操作(_close_stream_allowing_background_completion使用asyncio.shield,见 any_llm_model.py)。
七、错误处理、用量统计与追踪
7.1 终止状态与内容过滤
- Responses 终止状态:非流式调用返回
status为failed或incomplete时,抛出ModelBehaviorError,异常标识终止状态并携带响应中的错误或未完成详情(any_llm_model.py)。这与 docs/running_agents.md 中描述的OpenAIResponsesModel行为一致。 - 内容过滤:部分提供商只通过
finish_reason="content_filter"且空消息来标记过滤结果,SDK 会将其保留为refusal信号,而不是返回空输出(any_llm_model.py)。 - 重试建议:
get_retry_advice复用 OpenAI 的重试建议逻辑(any_llm_model.py)。
7.2 用量统计
Usage对象在两个路径中分别从response.usage(Responses)或response.usage.prompt_tokens/completion_tokens(Chat Completions)构造;即使提供商未返回 usage,请求也会按Usage(requests=1)计数(any_llm_model.py 与 any_llm_model.py)。当model_settings.preserve_raw_usage=True时,还会通过_attach_raw_usage_snapshot保留原始 usage 快照。
关于流式 usage,docs/usage.md 特别提示:从 Chat Completions 后端流式获取响应时,可能需要设置ModelSettings(include_usage=True)才会产出 usage chunk。源码对应逻辑在 any_llm_model.py:仅当stream=True且include_usage非空时才设置stream_options={"include_usage": ...}。
7.3 追踪(Tracing)
- Chat Completions 路径使用
generation_span,span 的model_config中带有provider与model_impl: "any-llm"信息(any_llm_model.py),可在追踪面板中区分适配层来源; - Responses 路径使用
response_span(any_llm_model.py); - 错误会被
model_span_errors记录到 span 上;调试日志中DONT_LOG_MODEL_DATA为真时只输出 "LLM responded" 级别信息,否则打印完整请求/响应 JSON。
使用追踪的参考写法(docs/tracing.md):
from agents.extensions.models.any_llm_model import AnyLLMModel model = AnyLLMModel(model="openrouter/openai/gpt-5.4-mini", api_key="...")7.4 高级参数透传
ModelSettings中无法一一映射的参数通过以下方法透传(any_llm_model.py):
- Chat Completions 路径(
_build_chat_extra_kwargs):extra_query→extra_query,metadata→metadata,extra_body→extra_body,extra_args直接并入顶层 kwargs; - Responses 路径(
_build_responses_extra_kwargs):额外支持top_logprobs; - 请求头合并(
_merge_headers):默认头(HEADERS)+model_settings.extra_headers+ 全局覆盖头(HEADERS_OVERRIDE),按顺序合并(any_llm_model.py)。对gemini/vertexai提供商,请求头会注入http_options;其他提供商走extra_headers(any_llm_model.py); - logprobs:设置
top_logprobs时自动补logprobs=True(Chat Completions 要求),避免与调用方显式传入的logprobs键冲突(any_llm_model.py),并将 logprobs 附加到输出文本(_attach_logprobs_to_output,any_llm_model.py); - repetition penalty 等推理参数:
reasoning_effort优先取自model_settings.reasoning.effort,其次回退到extra_args["reasoning_effort"](any_llm_model.py)。
八、Provider 实例缓存与重试策略
AnyLLMModel通过_provider_cache缓存提供商实例,键为"是否禁用提供商侧重试"(any_llm_model.py):
- 首次使用时以
AnyLLM.create(provider_name, api_key=..., api_base=...)创建基础实例; - 当全局配置要求禁用提供商托管重试时(
should_disable_provider_managed_retries),会基于client.with_options(max_retries=0)克隆一份无重试实例,避免与 SDK 自身的重试机制叠加; close()会遍历缓存去重关闭底层 client(支持aclose或close两种协议,见 any_llm_model.py 与 any_llm_model.py)。
九、测试与验证
仓库为AnyLLMModel提供了完整的单元测试 tests/models/test_any_llm_model.py,覆盖工具调用转换、消息重排、usage 传播、API 选择、错误路径等行为;此外 tests/models/test_map.py 覆盖前缀路由到 any-llm 的映射逻辑。若你计划在结构化输出、工具调用、用量上报或 Responses 特定行为上依赖某个具体提供商,建议参考这些测试,并像 docs/models/index.md 建议的那样先验证目标提供商后端——适配层的能力边界由上游 any-llm 定义,不同提供商的特性支持与请求语义存在差异。
十、小结与适用建议
- 适用场景:需要在单一 Agent 应用中接入非 OpenAI 提供商、或通过 any-llm 获得统一路由与提供商覆盖能力。
- 不适用场景:仅使用 OpenAI 模型——此时请使用内置的
OpenAIResponsesModel路径,避免多余兼容层。 - 关键实践清单:使用
provider/model格式命名模型;需要固定接口时显式传api;流式 Chat Completions 场景记得配ModelSettings(include_usage=True);多提供商混用且需要前缀路由时,用MultiProvider+openai_use_responses_websocket=True+AnyLlmProvider;部署前针对目标提供商验证结构化输出与工具调用行为。
从 examples/model_providers/README.md 提供的统一运行方式出发(export OPENROUTER_API_KEY="..."后uv run examples/model_providers/any_llm_provider.py),你可以在几分钟内完成从 OpenAI 模型到 OpenRouter 上任意模型的切换验证。
【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考