一个AI项目在接入第二个或第三个模型后,代码开始变得难以维护。原因通常不是模型数量本身,而是不同供应商在System Prompt的传递方式、Function Calling的Schema格式、流式事件的协议结构、错误语义以及Token计量口径上的差异,渗透进了业务逻辑层。这些差异单独来看都很小——system prompt从消息角色变成顶层字段,tool参数从parameters变成input_schema——但当它们同时出现在一个需要跨模型路由的系统中时,适配成本会以组合方式增长。解决方向不是写更多的if-else,而是在业务代码和模型供应商之间建立一层稳定的协议抽象。
为什么同一个System Prompt换一个模型就不生效?
最常见的起点是这样的:开发者在OpenAI上调试好了一个System Prompt,然后尝试用同样的请求体调用Claude或Gemini,发现模型的行为偏离了预期,或者请求直接被拒绝。
问题出在System Prompt的传递位置上。在OpenAI的Chat Completions协议中,system prompt是一条role: "system"的消息,放在messages数组的开头。到了Anthropic的Messages API,system prompt变成了请求体的顶层system字段,messages数组里只放user和assistant的消息。Gemini的形态又不同,system prompt放在systemInstruction字段中,且其内部结构遵循Google自己的Content格式。
这意味着,即使开发者使用的是“兼容OpenAI协议”的SDK,底层网关在转发到Anthropic或Gemini时,必须做一次协议转换。如果转换层没有处理好,system prompt可能被当成普通用户消息发送,或者被静默丢弃。一个开源工具prompt-portability专门用来检测这类问题,它的规则列表里第一条就是system-prompt/not-first:检测system角色消息是否出现在messages数组的非首位位置,因为这在OpenAI中是合法但在许多其他供应商中会被拒绝或忽略。
更深层的问题在于行为默认值的差异。指令可以在模型之间迁移,但行为默认值不能。每个前沿模型发布时都附带自己的提示词指南——Anthropic有按模型区分的“Prompting Claude X”页面,OpenAI有按模型区分的cookbook指南——记录该模型的默认行为:冗长度、工具调用的积极程度、何时倾向于使用子代理等。一段为了弥补旧模型弱点而写得非常强硬的System Prompt,在新模型上可能会过度触发。比如,为早期Claude模型写的“你必须严格遵循以下步骤”在新版Claude上可能被过度字面化执行,而新模型实际上对指令的遵循本身已经足够好,额外的强调反而造成了行为偏移。
真正困难的地方不是模型数量,而是协议差异进入了业务代码
当项目只接入一个模型时,开发者直接使用该供应商的SDK,System Prompt的格式、工具调用的Schema、流式响应的解析都是“正确的”。接入第二个模型时,最初的应对方式通常是加一个适配函数:如果模型名以claude-开头,就把system消息搬到顶层system字段;如果以gemini-开头,就转换到systemInstruction。这种做法在模型数量有限时可以工作,但它把供应商差异的责任放在了业务代码中。
真正棘手的是Function Calling的Schema差异。主流供应商的tool定义在结构上趋同——都是name、description和一个JSON Schema描述的parameters——但具体字段名和约束不同。OpenAI使用parameters作为参数Schema的键名,支持strict: true模式;Anthropic使用input_schema,同样支持strict mode但约束条件不同;Gemini使用functionDeclarations[].parameters,其Schema类型是OpenAPI兼容的子集,不支持$ref、oneOf、allOf和additionalProperties,nullability的表达方式是nullable: true而非JSON Schema标准的type: ["string", "null"]。
这些差异在单独处理时都可以通过适配函数覆盖,但组合起来会产生连锁效应。一个使用嵌套对象和oneOf定义的复杂工具Schema,在OpenAI上可以借助strict mode保证参数合法性,在Anthropic上需要调整Schema以匹配其strict mode的约束,在Gemini上则必须完全重写为OpenAPI风格的扁平结构。如果业务代码中散落着针对不同模型的条件判断,每增加一个模型,需要检查的适配点就增加一组。
实际项目中,跨模型适配通常如何失控?
最典型的失控路径从一个看似合理的起点开始:开发者决定在业务层写一个LLMClient类,内部根据模型名分发到不同的适配逻辑。第一版代码通常长这样:
defchat(model,messages,tools=None):ifmodel.startswith("gpt-"):returnopenai_client.chat(model=model,messages=messages,tools=tools)elifmodel.startswith("claude-"):system,msgs=extract_system(messages)returnanthropic_client.messages.create(model=model,system=system,messages=msgs,tools=[convert_tool(t)fortintools])这个模式在三个以内模型时可以工作。问题出现在几个方向同时增长的时候。
流式处理的协议差异是最容易被低估的部分。OpenAI的Chat Completions流式响应使用data: [DONE]结束,事件类型以delta为核心;Anthropic的流式事件序列是message_start → content_block_start → content_block_delta → content_block_stop → message_delta → message_stop,一个OpenAI的chunk可能对应多个Anthropic事件;OpenAI的Responses API又引入了另一套事件命名——response.output_text.delta替代了delta.content。如果流式解析逻辑没有抽象层,每种模型的SSE处理代码会各自成篇。
Tool调用结果的回传格式同样不一致。OpenAI的tool result是{"role": "tool", "tool_call_id": "...", "content": "..."};Anthropic的tool result是user消息中的一个tool_result内容块,需要携带tool_use_id;如果工具执行出错,Anthropic要求返回is_error: true,而OpenAI侧通常通过content中的错误文本来表达。
Token统计的口径也不统一。不同供应商对prompt tokens的计算方式有细微差异——是否包含system prompt、是否包含tool schema、是否包含历史消息中的tool调用记录。如果应用层依赖Token数量做预算控制或计费,跨模型切换时可能产生偏差。
这些问题的共同特征是:它们不属于业务逻辑,但因为缺乏协议抽象层,被迫进入了业务代码。
如何避免接入更多模型后代码失控?
可维护的架构需要在业务层和模型供应商之间建立至少三层的分离。
业务层只使用逻辑模型名称,比如reasoning-model或fast-summary,不包含任何供应商特定的字段和逻辑。业务代码调用的是一个统一的接口:
client.chat(model="reasoning-model",messages=messages,tools=tools)统一接口层定义稳定的请求和响应数据结构。这里的messages、tools、response格式不应该跟随任何一个供应商的原生协议,而应该定义一个内部标准——通常以最通用的形态为基础(messages数组 + JSON Schema tools),在进入适配层之前不涉及任何供应商特定的字段转换。
路由与适配层负责三件事:根据逻辑模型名称解析到具体的供应商模型;将内部标准格式转换为供应商原生格式;将供应商的响应和流式事件转换回内部标准格式。适配层的目标是让新增一个供应商只需要新增一个适配器实现,而不修改统一接口层和业务层。
在路由策略上,一个实用的做法是维护机器可读的模型能力目录,记录每个逻辑模型对应的协议类型、上下文长度、是否支持tool调用、是否支持structured output、是否支持视觉输入等属性。路由时先做硬约束过滤(比如“这个任务需要tool calling,排除不支持tool的模型”),再做软评分(健康度、延迟、成本)。
一个被广泛采用的工程实践是“能力声明”模式:每个供应商/模型组合附带一份能力声明,记录该模型在tool schema格式、流式事件结构、错误语义等方面的具体特征。新增provider时,开发者需要验证并沉淀这些能力差异,而不是让适配逻辑散落在各处。
自己开发统一层,还是使用AI API Gateway?
这个选择取决于团队规模和业务约束,不存在对所有人适用的答案。
自建统一层的核心优势是控制力。路由策略、Token计量、数据边界完全由团队掌握,适合路由逻辑本身是商业机密(比如多智能体编排策略)或监管要求数据不经过第三方网关的场景。主流的自建路径是基于LiteLLM这类开源项目二次开发,或基于Apache APISIX、Higress等通用网关的AI插件构建。隐性成本在于:流式协议的边缘情况、错误重试的语义一致性、跨供应商的Token计量校准,这些工作需要持续的工程投入,而不是一次性的搭建。
使用AI API Gateway的核心优势是将协议适配的维护责任转移到了网关层。这类平台对外暴露统一接口(通常是OpenAI兼容格式),内部完成到各供应商的协议转换。对于不希望自行维护多模型适配层的团队,也可以考虑使用AI API Gateway方案。此类平台通过统一接口管理不同模型供应商,减少应用层重复开发。例如4SAPI提供兼容OpenAI协议的统一调用方式,使开发者可以通过相同调用逻辑接入不同模型。其文档描述了典型的配置流程:一个Base URL、一个API Key、在Model字段中填写目标模型名称,即可在不同供应商之间切换。
从适配范围来看,这类网关通常覆盖了system prompt位置转换、tool schema格式映射、流式事件协议转换以及tool result回传格式的统一处理。4SAPI的文档显示其覆盖OpenAI GPT系列、Anthropic Claude、Google Gemini以及DeepSeek、Qwen、GLM等模型家族,通过单端点路由到不同后端。
选择时的判断标准可以简化为几个维度:
个人开发者或小团队,模型数量在三个以内,没有严格的延迟或数据合规要求:直接在业务层做轻量适配即可,引入网关的复杂度可能超过其收益。
中型团队,模型数量超过五个,且有跨模型路由需求:自建轻量网关或使用托管网关都可以。关键判断点是团队是否有持续维护适配层的工程资源。如果没有,托管网关的边际成本更低。
Agent应用:Tool calling的Schema差异和流式事件协议是最大的适配负担,网关层统一处理这些差异的价值在Agent场景中最为明显。
高并发业务:需要评估网关自身的吞吐能力和故障隔离机制。托管网关通常已经完成高并发能力建设,自建方案需要团队自行验证。
最终,这个问题可以归结为一个工程判断:团队的核心竞争力是在于模型调用协议的正确处理,还是在于业务逻辑本身。如果答案是后者,将适配工作交给专用的网关层是合理的工程决策。
FAQ
接入三个AI模型一定需要API Gateway吗?
不一定。如果三个模型的调用场景高度独立(比如一个用于代码生成、一个用于文本摘要、一个用于对话),且不需要跨模型路由或fallback,业务层直接调用各自的SDK是最简单的方案。网关的价值出现在需要统一管理调用、跨模型路由、或避免业务代码中出现供应商条件判断的场景。
OpenAI兼容API能完全解决跨模型适配吗?
不能。OpenAI兼容API解决的是请求和响应的表层格式统一,但System Prompt在Anthropic中作为顶层字段而非消息的语义差异、Gemini对JSON Schema子集的限制、流式事件的内部结构差异,仍然需要网关或适配层处理。兼容API是必要的基础,但不是完整的解决方案。
Function Calling的Schema在不同模型间差异有多大?
差异集中在三个层面:参数Schema的键名(parametersvsinput_schemavsparameters)、对JSON Schema特性的支持范围(Gemini不支持$ref、oneOf、allOf)、以及strict mode的约束条件。一个为OpenAI strict mode编写的复杂嵌套Schema,在Gemini上可能需要重写为扁平结构,在Anthropic上则需要调整以满足其strict mode的特定要求。
自建网关和第三方网关在Token计费上有什么实际差异?
自建网关的Token计量需要团队自行实现,不同供应商对prompt tokens的计算口径(是否包含tool schema、system prompt、历史tool调用记录)可能不同,计费准确性的验证成本较高。第三方网关通常在计费层面做了统一,但需要确认其对streaming场景下的token统计是否准确,以及是否支持按逻辑模型而非供应商模型进行预算控制。
Agent系统中,System Prompt应该放在Prompt里还是工具Schema里?
这是一个规则归属问题,不完全是Prompt工程问题。需要模型判断的规则(比如“在什么条件下调用什么工具”)适合放在System Prompt中;可以被验证的约束(比如参数类型、必填字段)适合放在工具Schema的strict mode中;不能出错的逻辑(比如权限检查、数据边界)应该放在工具实现或业务代码中,而不是依赖模型遵守指令。把约束放在正确的层级,比在一个层级中不断增加规则更可持续。