第 3 / 22 篇

第 2 章:把协议兼容写成可以测试的合同

两个接口都接受 JSON,不意味着它们表达同一件事。模型请求里的角色、内容块、工具定义和输出限制,都会影响任务语义。接入层如果默默忽略不认识的字段,得到的常常是“HTTP 成功,业务已经改变”。

2.1 保留原意比统一字段名更重要

假设入口允许调用者要求结构化输出。上游 A 能约束生成格式,上游 B 只能接受一段提示。把“必须符合 schema”改写成“请尽量输出 JSON”,是保证等级的变化。调用者应能选择拒绝、明确接受降级,或者把 B 从候选集合移除。

类似差异也发生在上下文限制、图片格式、工具参数、并行工具调用和流式用量事件上。能力合同需要区分支持、不支持、经验证的转换和未知。未知不能默认映射为支持。

项目 接入前要问的问题 失败时的合理出口
内容类型 文本、图片或其他内容块怎样编码? 请求校验错误
输出限制 限制指 token、字符、字节还是其他单位? 拒绝无法准确转换的限制
工具调用 参数什么时候完整,谁执行,怎样关联结果? 不执行未完成调用
用量 谁报告,何时报告,缺失如何标识? 保留未知用量

2.2 随书接口刻意保持独立

实验的入口是 POST /v1/generate,请求体包含 prompt、capabilities、max_output_units 和 request_id。身份放在 Bearer 凭证中。这里的路径和字段构成实验自己的协议;它不宣称完整兼容某家真实模型服务。

例如下面的对象表达输入意图,具体可运行调用由第 13 章的演示程序构造:

{
  "request_id": "example-001",
  "prompt": "解释一次请求的提交点",
  "capabilities": ["text"],
  "max_output_units": 128,
  "allow_failover": false
}

max_output_units 在实验中是字节 credit 上限,不能直接传给真实供应商的 token 限制字段。生产适配器应保存原始限制与转换策略,并把无法无损转换的情况暴露给调用者。

2.3 错误需要说明阶段

认证失败、能力不匹配、准入被拒绝、上游明确拒绝以及流中断,需要不同处理。流开始前,网关可以返回相应 HTTP 错误;流开始后,状态码已经无法重新写成另一个值。此时应按协议提供终止错误事件或断开,并在审计中记录不完整。

一次 HTTP 200 只能说明响应已按该状态开始。客户端还要检查业务终止条件。只检查状态码的监控,会把中途断开的流计为成功。

2.4 适配器的双向测试

请求转换测试验证入口语义是否被保存,响应转换测试验证事件边界是否被保存。可以给适配器一个固定输入,断言上游请求的必需字段;再给它一组分片字节,断言输出事件一致。把原始字节任意拆开之后,仍应得到同样的事件序列。

对于不能支持的能力,最重要的测试可能是“确实拒绝”。一个始终返回回答的适配器,反而可能掩盖契约丢失。

练习。 上游只报告最终总用量,不报告输入与输出拆分。入口希望按两种不同费率计费,应怎样处理?请区分展示一个总量、估算成本和确认结算三种行为。