AI网关调用契约测试与Mock回放治理落地

日期:2026-08-12

一、项目背景

客户的 AI 网关后面挂了十几个模型服务,分属不同团队。有次一个团队升级了自己的服务,改了个返回字段类型,没通知上游,结果网关后面一串应用集体报错,谁也说不清到底改了哪个字段,回滚花了两小时,故障期间调用全挂。我们复盘的时候发现,他们根本没有模型接口的契约管理,升级靠自觉,出事靠人肉排查。

这个项目我们定的目标很朴素:把网关后面的接口当契约管起来,升级前能自动发现破坏性变更。

二、落地场景

我们在网关层引入了契约定义,每个模型服务的请求和响应结构都版本化登记。配套的 Mock 服务会录制线上真实流量,存成回放用例。每次后端准备升级,先跑一遍契约变更的破坏性检测,比如字段删除、类型变更、必填变可选这类,自动标记风险。灰度发布前用录制流量做回归,确认上游不受影响的才放行。平时还做故障注入演练,验证网关的降级和熔断是不是真管用。

我们把这套接进了他们的发布流程,模型服务上线前必须过契约门禁。

三、关键技术挑战与解决思路

契约表达和版本演进是基础。我们用 JSON Schema 描述接口,但允许字段级的向后兼容规则,比如新增可选字段不算破坏性,删除或改类型才算,规则写清楚才不会被误报烦死。

流量录制保真度决定回放有没有用。我们录的不只是请求响应体,还带上了当时的网关上下文,比如路由标记和调用方身份,回放时还原接近真实的调用环境,避免录制时好好的回放就挂。

破坏性变更识别不能只靠 schema diff。有些变更 schema 没变但语义变了,比如某个状态码含义调整,我们对这类做了人工标注加语义注释,机器查结构、人查语义,两层兜底。

回归覆盖盲点靠持续积累用例。新接口上线强制录一批流量进回归库,老接口定期补充边界用例,保证回放集随时间变厚。

四、效果数据

文中数据为项目复盘口径,已脱敏。契约门禁上线后,回归覆盖的接口数从约 20 个拓展到全部约 60 个模型服务。破坏性变更拦截率约 92%,也就是十次有九次能在上线前拦住。升级引发的故障平均时长从约 2 小时压到约 12 分钟,主要是定位快了。回滚耗时从约 30 分钟降到约 5 分钟,因为门禁提前拦下了大部分问题。

案例片段(已脱敏):一次模型服务把 usage.cost 从整数改成浮点,schema diff 标记为破坏性,门禁拦截:[contract] change=type(int->float)@usage.cost, breaking=true, action=block_releasereplay=312 cases, failed_before_fix=41, after_fix=0; release gated until schema v2

五、可复用经验总结

网关后面挂的模型越多,接口契约越要当正式接口管,这是被那次两小时故障买来的教训。没有录制回放,每次升级都是开盲盒,谁心里都没底。

我的看法是,契约工具别追求全自动,结构让机器查、语义留人查,两层配合最稳。还有,回放用例库要当成资产养,新接口强制录、老接口补边界,厚了才有用,薄薄一层等于没建。

附:规模化落地与工程细节

契约门禁推起来,最大的阻力是开发团队嫌麻烦,觉得每次升级都要跑回归拖慢节奏。我们用数据说话,把每次拦截的破坏性变更和对应的故障时长摆出来,他们才配合。语义级变更比如状态码含义调整,机器查不出,我们建了人工标注机制,谁改语义谁写注释,慢慢成了习惯。

回放用例库是越用越厚的资产。我们强制新接口上线录一批,老接口每季度补边界用例,半年下来从几十个用例涨到三百多个,覆盖越来越全。Mock 服务最初只录请求响应,后来补了网关上下文,回放才接近真实。

给后来者的提醒,契约治理别指望一步到位,先拦住结构破坏性变更这个大头,语义那层慢慢补,比一次性追求完美容易落地。升级前不跑回放,等于拿生产环境赌运气,这学费我们早年交过。

Mock 回放我们一开始只在升级前跑,后来改成每天定时跑一遍全量回归,当作日常健康巡检。有次提前发现一个服务悄悄改了响应结构,还没上线就拦下了。契约门禁从发布关卡变成日常巡检,价值翻了一倍。我们还把回放失败的用例自动建工单派给对应团队,谁的服务挂了谁修,责任清晰,再没人甩锅说这边没问题,协作效率提升比工具本身更明显。

还有个细节,契约定义我们放在网关侧统一维护,而不是每个模型服务自己存一份,避免多份不一致。谁要改接口,先提契约变更单,门禁自动校验,流程顺了大家才愿意守。我们见过把契约写在各自代码注释里的团队,结果注释和线上对不上,门禁形同虚设。治理类工具,单一可信源这条原则比功能多寡重要,这点在网关场景尤其明显。

回头看,契约门禁上线一年后,模型服务的线上故障数降了一半多,这笔投入早收回来了。