国产大模型不同厂商返回字段不一致,网关层统一格式化输出
当业务同时接入多家国产大模型时,最头疼的往往不是模型能力,而是各家接口返回的字段命名、嵌套结构和错误码完全不一样。
有的返回 content,有的返回 text;
有的把内容放在 message.content,有的放在 choices[0].text。
如果在业务代码里逐家适配,会非常痛苦。
本文介绍一种通用做法:在网关层完成字段归一化,让下游只认一套标准格式。
为什么要放在网关层做格式化
网关层通常是请求统一入口,在这里做字段转换有三个明显好处:
- 业务解耦:业务代码只需对接网关定义的标准响应格式,无需感知具体是哪个厂商的模型。
- 集中维护:新增模型或厂商时,只在网关增加一套映射规则,不用改多个业务服务。
- 便于监控和审计:统一后的响应可带上模型来源、耗时、token 用量等公共字段,方便日志分析。
如果你只是临时接一个模型,可以在服务内写转换函数;
但只要有两个以上厂商,建议先把网关层做起来,后面扩展会省力得多。
前置准备:确认入口和标准格式
开始前先明确三件事:
- 网关选型:可以用现成的 API 网关(如 Apache APISIX、Kong),也可以用 Nginx + Lua 或自研 Spring Cloud Gateway。如果不方便引入重型网关,单独写一个轻量转发服务也可以。
- 标准响应格式:先定义自己的统一返回结构。建议参考 OpenAI 风格,因为不少国产模型本身兼容这种格式,示例如下:
{
"code": 0,
"message": "success",
"data": {
"model": "qwen-plus",
"content": "统一后的文本内容",
"finish_reason": "stop",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
}
}
}
- 厂商映射关系清单:把要接入的厂商字段整理成表格,比如通义千问返回
output.text,智谱返回choices[0].message.content,文心一言返回result等。这一步是后续写转换规则的依据。
核心操作:用配置化映射做字段归一
以轻量网关服务为例,推荐采用 JSON Schema 映射方式。
下面用伪代码演示核心逻辑:
{
"provider": "qwen",
"endpoint": "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
"map": {
"content": "output.text",
"finish_reason": "output.finish_reason",
"usage.prompt_tokens": "usage.input_tokens"
}
}
网关收到响应后,按照 map 配置深拷贝取值并组装成统一结构。
如果某个字段不存在,则按默认值处理。
伪代码示意:
def convert_response(raw: dict, mapping: dict) -> dict:
result = {}
for standard_key, provider_path in mapping.items():
result[standard_key] = fetch_by_dot_path(raw, provider_path)
# 补充公共字段
result["model"] = raw.get("model", "")
return result
实际操作时不需要自己发明轮子,网上很多现成的 JSON 映射库可以省不少事。
重点是配置要能在线更新,否则每加一个模型都要重新发布网关。
避坑:错误码和流式输出也要统一
很多人在字段转换时只关注正常响应,忽略了错误处理和流式输出,导致生产环境经常踩坑。
错误码务必转换。
不同厂商错误码含义差异很大,网关层要统一映射成自己的业务码。
例如:
- 厂商返回
Throttling.RateLimit,网关统一为429; - 厂商返回
InvalidParameter,网关统一为400。
不然下游拿到原始错误码无法差异化处理。
流式输出要单独处理。
如果通义千问用 SSE 流返回 data: {...},智谱也差不多,但字段结构仍可能不同。
网关需要按行解析每个 chunk,再把每个 chunk 内部字段统一映射后重新拼成标准的 SSE 事件。
特别留意结束标记,有些结束事件没有 content 字段,映射逻辑必须容错。
不建议在响应体里直接拼凑多个模型的结果。
如果做模型路由或 fallback,网关应只返回最终选中的那份结果,不要把中间过程的原始响应暴露出去。
验证效果:用 mock 数据和真实请求分别测
配置完成后,至少验证以下三类情况:
- 正常响应:调用各厂商真实接口,检查网关输出是否符合统一 JSON 结构。
- 错误响应:故意传错参数或触发限流,确认网关返回的统一错误码是否符合约定。
- 流式响应:逐一检查每个 SSE chunk 的 content 字段是否为拼接后的标准格式,最后确认事件能正确结束。
可以写一个简单的断言脚本,自动比对字段是否齐全:
curl 'http://gateway.example.com/v1/chat/completions' \
-H 'Content-Type: application/json' \
-d '{"provider":"qwen","messages":[{"role":"user","content":"ping"}]}'
用 jq 检查返回:
curl ... | jq '.data.content, .data.model, .code'
如果输出里能看到统一后的 content 字段且 code 为 0,说明转换链路基本正常。
最后提醒一句:不同厂商的接口版本和字段变更频率并不一样,网关层建议保留原始响应日志便于回溯对比;
变更前建议先在测试环境跑一遍映射用例,确认正常再切线上流量。