日期:2026-08-20
我们网关后面挂了四五个模型,有自研的,有开源社区拉的,还有某头部云厂商的托管模型。问题来了,有的返回标准 json,有的返回纯文本,有的把思考过程也带出来,字段名各叫各的,有的叫 content,有的叫 answer,有的叫 text。业务方接一个模型写一套解析,换模型前端就崩,接两个模型维护两套适配,痛苦指数随模型数平方涨。我们进场时,前端同学已经不肯走网关了,宁可直连模型,网关形同虚设。
根子是网关把模型输出原样往外扔,没做归一。说到底,网关的价值之一是屏蔽后端差异,输出不归一,这个价值就没了。业务方要的是稳定 schema,不是模型个性。
方案加了个输出适配层。统一响应 schema:定义一个标准响应结构,所有模型输出先归一到这里。模型输出适配:每个模型写一个适配器,把自家格式翻译成标准 schema。字段校验与补全:归一后做 schema 强校验,缺的字段补默认值,类型不对的转换。异常格式兜底:模型返回格式彻底坏了,返回标准错误结构而非乱码,前端永远能解析。
适配器的维护是长期活。每接一个新模型就要写一个适配器,我们做成了可插拔,新模型注册时挂适配器,不碰核心代码。早期那版适配器写死在网关主流程,加一个模型改一次发版,运维怨言很大,改成插件化后,接模型从发版变成配置。
校验严格度要拿捏。我们一开始强校验,模型偶发多返回个实验字段就整条拒掉,业务误伤,前端白等。后来改成宽松校验加字段白名单,白名单内的必须齐,白名单外的一律剥掉,既不误伤也不让脏字段进业务。这平衡点调了几次才稳。
兜底结构别忽视。模型偶尔抽风返回空或者非 json,前端如果没兜底就崩。我们统一了错误响应结构,code、message、fallback 字段齐全,前端按这套处理,再没出现过因为模型输出异常导致的白屏。说实话,这层兜底救过我们好几次,上游模型升级改了格式,我们适配没跟上,靠兜底兜住了用户侧。
schema 本身也要版本演进。业务需求会变,今天要加个 reasoning 字段,明天要标置信度,schema 改了老模型适配器得跟着动。我们给 schema 打了版本号,网关按版本协商,老模型走老适配、新模型走新适配,前端按自己支持的版本解析,向后兼容不卡脖子。早期那版 schema 写死无版本,一加字段老模型全拒,业务骂声一片,加版本后平稳多了。还有个细节,不同模型对 finish_reason 的语义不一样,有的叫 stop 有的叫 length,我们归一成统一枚举,前端少一堆 if else。这种看着小的地方,积起来就是接入体验的天壤之别。
兜底错误结构还得配上监控埋点。每次触发 fallback 我们都记一条,按模型维度聚合,哪个模型兜底率高说明它输出不稳定,优先排查适配或联系上游。之前兜底静默生效,前端不崩但业务失败率悄悄涨,没人知道。加了埋点后,某开源模型一次升级后兜底率从近乎零跳到百分之三,我们当天就发现并挂了临时适配,没让用户侧持续受影响。说实话,归一层的价值不只是格式统一,更是把后端模型的参差暴露成一个可观测的数字,运维第一次能拿这个数去和模型提供方对话。
业务接入改造量从每接一个模型重写一套,降到零,换模型前端不动。格式异常导致的解析错误从每周十几起降到零。模型切换成本从平均两天,改适配加发版,降到两小时,挂适配器配置。解析错误率归零,前端不再绕开网关。文中数据为项目复盘口径,已做脱敏。
网关得把模型输出归一了再给业务,统一 schema 加适配层,换模型前端零改动,接入量一下子上来。适配器一定要插件化,别写死主流程,我们早期写死那版接模型等于发版,运维苦不堪言。校验宽松加白名单比强校验稳,既不误伤也不放脏字段。说实话,兜底错误结构比适配本身更值得常驻,上游哪天改格式你未必跟得上,但用户侧永远不能崩。网关屏蔽差异这件事,输出归一比输入归一更被业务感知,因为业务只关心拿到手的东西长什么样。
案例片段(已脱敏): 统一响应 schema 与适配节选(配置节选): 接入某头部云厂商模型时,其返回字段为 output.answer,旧版前端按 content 解析全崩。挂上适配器把 answer 映射到 data 后,前端零改动恢复。同期该模型偶发多返回 debug 字段,被白名单剥除,未进入业务,未触发误拒。
schema:
fields: [code, message, data, model, finish_reason]
required: [code, data]
adapter:
plugin: true
whitelist_fields: [content, answer, text]
normalize:
map: {content: data, answer: data, text: data}
missing: default_empty
fallback:
on_bad_format: {code: 500, message: "model output invalid"}案例片段(已脱敏): 一次网关升级漏配某开源模型的适配器,其返回带思考链前缀,归一失败。兜底结构生效,前端收到标准错误而非乱码,用户侧无白屏,但那批请求实际失败。我们事后把适配器缺失也纳入告警,新模型注册未挂适配器直接拦截注册,从源头防住。这让我们把归一层当成网关的必过点,而非可选装饰。