集成技术栈
兼容的产品还不等于一个系统。只有每道边界都有明确契约,每个会影响行为的组件都有稳定身份,并且发布失败后能够沿着经过测试的路径恢复,它们才真正组成一个系统。因此,本章的交付物是一项集成发布:它用版本化记录说明哪些组件连接在一起、每条连接具有什么含义、由谁负责,以及哪些证据足以证明组装后的系统可以安全发布。
先确定发布决策,而不是先列采购清单。明确本次发布的能力、面向的用户和租户、允许处理的数据类别与操作、质量和运营门槛,以及回滚触发条件。随后固定一份系统指纹,其中包括模型修订版本、提示词修订版本、检索快照、工具 Schema、策略修订版本、路由配置、遥测 Schema 和可执行制品摘要。模型名称本身无法标识生成某个答案的系统。
这种契约优先的思路由来已久。Fielding 用受约束的接口,而不是具体产品,来描述网络化系统 (Fielding 2000)。Sculley 等人随后说明,胶水代码、配置和未声明的消费方会在机器学习系统周围积累技术债 (Sculley et al. 2015)。Breck 等人又把这项认识转化为生产就绪测试 (Breck et al. 2017)。本篇前面的章节已经给出了各个组件,本章要让它们的组合可以接受检验(第 1 章)。
固定集成发布
一次集成发布需要维护两类记录。系统清单标识组装后的完整系统,边界契约定义每一对生产方和消费方之间的连接。两者都不得使用 latest 之类的可变别名。
| 记录 | 必填内容 | 发布前要回答的问题 |
|---|---|---|
| 系统清单 | 应用、模型、提示词、检索、工具、策略、路由、遥测和制品的修订版本 | 将来还能否准确识别这套系统行为? |
| 拓扑 | 每个生产方、消费方、协议版本、传输方式和负责人 | 每条线上连接是否都有明确负责人? |
| 能力说明 | 原生支持、由适配器模拟、有损或不支持的行为 | 经过适配器时,哪些语义会发生变化? |
| 信任契约 | 主体、工作负载、租户、权限、受众、作用域和数据类别 | 该调用方是否可以发送这些数据或执行这项操作? |
| 故障契约 | 截止时间、取消、重试负责人、幂等范围、错误映射和背压 | 故障传播到哪里为止,谁可以重试? |
| 证据契约 | 遥测、符合性测试结果、迁移状态、验收证据和回滚修订版本 | 什么能够证明本次发布有效,又如何恢复上一正常版本? |
系统清单本身应经过签名或采用其他完整性保护。记录软件物料清单、构建来源证明、容器和二进制文件的摘要值、Schema 修订版本、策略包以及完整路由表。in-toto 提供了可验证软件供应链步骤的模型 (Torres-Arias et al. 2019),SLSA 则定义了逐级增强的来源证明要求 (SLSA Community 2026)。这些控制不能证明发布一定正确,但能让经过测试的对象与之后被替换的对象清楚地区分开来。
分离三个平面
技术栈包含三个平面,它们的权限和故障方式各不相同。
- 数据平面承载模型请求、带类型的流事件、检索证据、工具提议和工具结果。
- 控制平面接纳请求,并执行路由、预算、能力限制、授权策略和过载规则。
- 管理平面修改路由表、凭据、策略包、Schema 和集成发布。
模型网关(gateway)可以执行一部分数据平面和控制平面策略,但它不是通用枢纽。检索授权属于检索服务,工具执行器也必须对自己的资源完成授权。管理平面的变更应走比普通推理请求更受限制的管理通道。一个代理进程可以同时承担多种角色,但各角色的契约和权限仍然必须彼此分离。
明确兼容性
相似的 JSON 结构不代表相同的语义。HTTP 定义传输语义 (Fielding et al. 2022),服务器发送事件定义流式帧的格式 (WHATWG n.d.)。两者都不会规定某个提供商的工具调用生命周期、拒答状态、结构化输出子集、用量计数、数据保留方式或安全策略。因此,适配器必须发布一份有版本的能力说明。
每项能力只能处于以下四种状态之一:
- 原生支持:提供商直接实现了声明的语义;
- 由适配器模拟:适配器能够保持声明的后置条件;
- 有损:适配器会改变语义,并且调用方已经明确接受这项降级;
- 不支持:系统在产生任何上游工作之前,就在准入阶段拒绝请求。
能力说明应列出模态、端点和状态模型、工具及并行调用语义、支持的 JSON Schema 方言与子集、带类型的流事件、上下文和输出限制、用量字段、区域、保留规则以及策略限制。JSON Schema 是一组可以明确标识方言的语言,并不保证每个提供商都会实现所有关键字 (Wright et al. 2022)。转换只有三种合理结果:保留、明确降级或拒绝。悄然丢弃工具、截短标识符或强行转换未知字段,都不能称为兼容。
路由也要遵守同样的纪律。先按硬性约束筛选,包括能力、租户策略、数据驻留、保留规则、区域和权限。然后才按照实测质量、延迟、可用性或成本,对符合条件的候选项排序。每个被接纳的操作都要解析出并记录不可变的提供商、模型修订版本、适配器修订版本、能力说明修订版本和路由策略修订版本。实验和金丝雀发布应使用粘性分配,避免同一项工作在不同系统之间来回漂移。
参考架构
图 88.2 展示了满足这些要求的最小架构。应用是调用方,策略层决定是否接纳请求,适配器把调用方契约转换为提供商契约。工具操作走另一条路径,先完成授权,再交给执行器。检索服务返回带标识的证据。遥测观察每项决策,但不授予任何权限。
这张图刻意不设置一个包办一切的产品中心。模型边界、工具边界、检索边界和证据边界解决的是不同问题,分别由 第 82 章、第 85 章、第 86 章 和 第 87 章 进一步展开。
定义模型操作状态机
把一次请求视为一项逻辑操作,其中包含已验证的租户、operation_id、载荷摘要、剩余截止时间、能力要求和策略修订版本。准入阶段要么拒绝它,要么记录不可变的路由决策。逻辑操作之下,每次上游调用都是一次实际尝试。
状态机必须明确规定以下步骤:
- 接收操作并验证身份。
- 验证 Schema 和能力要求。
- 按租户、预算、区域和策略约束决定是否接纳。
- 解析并记录不可变的路由。
- 发起一次上游尝试,把收到的帧规范为有版本的带类型事件流,并为每个事件附上操作、尝试和序列标识符。
- 累积流式工具参数片段,但不执行工具。
- 收到工具块的终止标记后,解析完整参数,按照固定的 Schema 验证,授权这项具体操作,并且只执行一次。
- 把拒答、输出不完整、取消、部分输出、错误和正常完成保留为彼此不同的终止结果。
流式数据块不是一份完整文档。为了保持向前兼容,未知事件类型应原样保留或明确暴露,不能改当作普通文本。不完整的工具参数绝不能执行。只有组装完成后,结构化结果才能按照声明的 JSON Schema 能力说明接受最终验证。拒答、截断、传输失败和 Schema 违规都应产生不同的无效结果,不能伪装成一个看似合理的对象。
一旦调用方已经看到输出,自动重试就可能复制或自相矛盾地续写这条流。因此,向调用方输出内容后,不要再自动重放。把取消信号和剩余截止时间继续传递到下游;调用方断开时,应停止读取并取消上游请求。每个已接纳的操作都必须通过有限次尝试、有限的截止时间和有界资源到达终止状态。
规范错误而不掩盖原因
使用基于 RFC 9457 的稳定机器可读错误格式 (Nottingham et al. 2023),其媒体类型为 application/problem+json。定义稳定的问题类型,并记录可以安全公开的上游错误类别、HTTP 状态、提供商代码、上游请求 ID、操作 ID、尝试次数、是否已经输出部分内容、是否可重试,以及任何 Retry-After 值。原始原因则保留在受保护的诊断记录中。
面向人的详细说明只供人阅读。不得解析面向人的详细说明来决定是否重试。身份验证失败、授权失败、输入无效、能力不受支持、策略拒绝和配额耗尽通常都不可重试。过载和暂时性传输故障只有在操作预算与截止时间允许时,才可以重试。
限制重试与过载
客户端、网关、适配器和提供商各自重试,会形成重试放大。这里, 表示一个会重试的层, 表示该层允许的最大重试次数。一项逻辑操作最多会产生以下数量的实际尝试:
其中, 是彼此独立执行重试的层数。如果三层各允许重试两次,就可能产生 次尝试。整个系统只应指定一个重试负责人。它必须遵守总尝试次数上限和剩余截止时间,尊重 Retry-After,并使用带抖动的有界指数退避。即使逻辑操作最终成功,也要记录全部实际尝试。尾部延迟使这些限制尤为重要 (Dean and Barroso 2013)。
幂等键是一项应用契约,不是 HTTP 层提供的神奇“恰好执行一次”保证。它应绑定租户、路由类别、操作键、规范化载荷摘要和有效期。使用同一个键提交不同载荷必须报错。如果提供商或目标系统无法去重,就要明确说明不能保证恰好执行一次。会产生副作用的工具还需要自己的操作级幂等键和操作回执;模型请求的幂等键不能让工具自动获得幂等性。
过载控制也必须有界,包括每个租户的并发数、队列长度、流缓冲区、输入和输出词元数,以及工具扇出数。有界队列会显式产生背压,不会把延迟悄然转化为不断增长的内存占用。拒绝工作时,应返回合适的 429 或 503 以及 Retry-After。断路器和重试预算则用于防止恢复流量演变成重试风暴。
回退会改变系统身份。候选回退系统必须在声明的兼容类别中预先验证,并满足同样的硬性策略与数据约束。新的路由决策也要记录。如果已经向调用方输出内容,或某项副作用结果仍不明确,绝不能悄然切换到另一个系统。
分离身份、凭据与操作
网关凭据、工作负载身份、委托用户令牌和下游提供商密钥回答的是不同问题。优先使用短期工作负载身份和 OAuth 令牌交换 (Jones et al. 2020)。令牌应绑定受众、作用域、已验证的租户、主体或行动者以及有效期。每个资源服务器都必须验证自己的受众,不能把签发给一个资源的令牌继续传给另一个资源。
虚拟密钥只是一种实现模型预算与路由策略的方式,并不是通用的工具授权。如果提供商只支持静态密钥,就把它保存在凭据代理中,只在允许的出站边界完成替换。应用和沙箱都不应接触原始提供商密钥。零信任架构同样认为,网络位置本身不足以构成权限 (Rose et al. 2020)。
工具与 MCP
模型上下文协议(MCP)通过 JSON-RPC 规范主机、客户端和服务器之间的消息,并支持能力协商、发现和工具 Schema (Model Context Protocol Contributors 2026)。发现不等于授权。工具注解是不受信任的元数据,不能证明某次调用安全。固定 MCP 协议修订版本、传输方式和协商结果,然后按照 第 56 章 中的控制措施完成逐次调用授权。
受信任的运行时应依次完成以下工作:
- 验证用户和工作负载身份,并从可信身份中取得租户。
- 根据白名单验证工具名称,再按照固定的输入和输出 Schema 规范化完整参数。
- 把影响类别划分为读取、可逆写入或不可逆外部操作。
- 针对主体、工作负载、租户、工具修订版本、资源、受众、规范化参数、影响类别和策略修订版本完成授权;必要时绑定会过期的同意或审批。
- 追加意图记录,获取最小权限凭据,再使用操作级幂等键执行。
- 在推进工作流检查点之前保存操作回执;返回结果前先验证并脱敏。
遇到模糊超时,应查询操作回执或目标状态,不能盲目重试非幂等操作。取消不等于回滚。并行调用需要各自独立的调用 ID 和回执。工具结果必须保留其来源调用 ID,避免运行时把结果接到错误的提议上。
沙箱(sandbox)可以缩小影响范围,但沙箱不是授权机制。它的威胁模型应限制文件系统访问、系统调用、进程、CPU、内存、磁盘、实际运行时间、工作区生命周期和网络访问。同时执行出站白名单和资源白名单。解析并验证 DNS 和每一次重定向,阻止访问私有地址、链路本地地址和云元数据端点。隔离能够约束不当操作造成的影响,却不能让一项不当操作变得合理。
检索与证据
先授权,再检索,不能等候选文档越过边界后才检查。每项结果都应携带证据 ID、来源和语料修订版本、索引修订版本、授权决定和租户边界。缓存、检查点、会话、任务和去重键都必须包含租户身份与系统策略身份。第 86 章 进一步说明了这份证据契约。
把遥测当作证据,而不是权限
W3C Trace Context 用于跨服务传递关联标识符 (Kanzhelev et al. 2021),但它不是权限。绝不能从 traceparent 或 Baggage 推断主体、租户或许可。Baggage 本身没有完整性保障,也不得包含秘密、个人数据或授权决定;跨越信任边界时,应按白名单保留并删除其余内容。
记录操作 ID、实际尝试 ID、路由和适配器修订版本、能力判断、错误类别、工具意图和回执、证据 ID 以及终止结果。同时报告缺失遥测数据、导出丢失、采样和脱敏失败。追踪记录只是部分证据,不能自动证明某项操作已经或尚未发生(第 87 章)。
核算整个合格任务
优化对象应是整个合格任务,而不是方便计算的模型调用小计。成本包括每次实际尝试、模型用量、检索、工具执行、裁判、基础设施和人工复核。延迟应从根操作开始计算实际经过时间,不能把并行子任务的耗时相加。记录确切的响应模型、区域、计费时间、币种和价格目录修订版本,不能依赖可变别名(第 76 章、第 31 章)。
对于任务 ,可以采用以下核算恒等式:
其中, 是任务 的全部实际尝试, 是尝试 中互不重叠的计费项, 是计费项 的用量。 根据响应模型修订版本 、区域 、计费时间 和价格目录修订版本 给出单价。 和 分别表示共享基础设施成本与人工复核成本。使用互不重叠的计费项,可以避免重复计算缓存或推理小计。
自建还是租用也应通过实测寻找交叉点,而不是套用一个通用百分比。把下图的两项价格改成当前报价。图中的默认值只是可以编辑的示例,不是建议。
def crossover_utilization(serverless_rate, reserved_monthly, hours=720):
"""Return the utilization fraction where pay-per-use equals the flat bill."""
if serverless_rate <= 0 or reserved_monthly < 0 or hours <= 0:
raise ValueError("rates and hours must define a non-negative comparison")
return reserved_monthly / (serverless_rate * hours)
example = crossover_utilization(4.0, 1440.0)
assert abs(example - 0.5) < 1e-12
print(f"illustrative crossover = {example:.0%}")
第 5 章 和 第 62 章 进一步说明,为什么工作负载形态、内存、批处理和硬件效率不能由这条简单曲线代替。
测试系统接缝,而不只是正常路径
每个适配器和边界加入发布之前,都应通过同一套符合性测试用例。
| 测试用例 | 必须验证的行为 |
|---|---|
| 单次请求 | 请求身份、终止状态、用量和错误映射准确无误 |
| 流中断 | 带类型的事件保持顺序,部分输出状态明确,已经输出内容后不再重放 |
| 工具参数 | 完整解析、Schema 验证、授权和调用 ID 绑定之前绝不执行 |
| 结构化输出 | 最终结果在本地验证;拒答、不完整和 Schema 错误保持彼此独立 |
| 限流与过载 | Retry-After、有界队列行为、取消以及正确的 429 或 503 |
| 重试与回退 | 只有一个重试负责人、尝试次数有限、回退兼容,而且系统身份变化得到记录 |
| 租户隔离 | 受众错误的令牌、跨租户缓存或句柄、过期审批和秘密泄漏都必须拒绝 |
| 遥测 | 操作与尝试能够关联,路由决策、遥测缺失和脱敏状态均可见 |
还要增加以下负向用例:未知流事件、重复交付、工作进程在提交后却在保存回执前崩溃、恶意工具输出、Schema 漂移、DNS 重绑定、重定向到元数据端点、文件系统逃逸和失控的进程创建。一次成功演示无法验证这些行为,符合性测试可以。
把切换视为协议
迁移本身也是一次集成。先运行适配器契约测试,再以影子模式执行新的只读行为,且不产生副作用。随后开启粘性金丝雀,让同一任务或租户始终停留在同一个系统指纹上。只有质量、安全性、延迟、错误和成本门槛持续满足时,才逐步扩大流量。路由和策略变更应以原子方式发布。上一已知正常版本的系统清单必须始终可以部署,而且要在第一批金丝雀流量进入前先测试回滚。
故障矩阵把笼统的谨慎要求转化为明确责任。
| 故障 | 检测方式 | 遏制与恢复 |
|---|---|---|
| 能力悄然丢失 | 能力说明与符合性测试结果不一致 | 拒绝准入,恢复之前的适配器 |
| 重试风暴 | 尝试次数放大和队列告警 | 禁用嵌套重试并丢弃过载请求 |
| 凭据泄漏 | 秘密诱饵或受众违规 | 撤销并轮换凭据,隔离相关证据 |
| 跨租户访问 | 授权与隔离测试失败 | 一律拒绝,并使相关缓存和句柄失效 |
| 工具只执行了一部分 | 意图记录没有对应的操作回执 | 查询目标状态并完成对账,不能直接重放 |
| 配置漂移 | 经过签名的系统清单或摘要不一致 | 停止扩大流量,以原子方式恢复上一已知正常版本 |
| 遥测缺失 | 未达到导出与覆盖率服务目标 | 暂停依赖这些证据的发布决策 |
发布生命周期
完整流程足够简短,应当定期演练:
- 明确能力、目标总体、约束和回滚触发条件。
- 清点生产方、消费方、负责人、信任边界和数据类别。
- 固定系统指纹和经过签名的系统清单。
- 发布能力、身份、故障、操作和证据契约。
- 实现只会保留语义、明确降级或拒绝请求的适配器。
- 指定一个重试负责人,并限制截止时间、队列和并发数。
- 把凭据和工具操作绑定到已验证身份及租户策略。
- 运行协议、策略、隔离、故障和供应链符合性测试。
- 测量合格任务的质量、延迟、成本和缺失证据。
- 先执行无副作用的影子流量,再运行粘性金丝雀并分阶段扩量。
- 以原子方式发布系统清单,并保留经过测试的回滚修订版本。
- 记录负责人、已知缺口、失效时间和重新验证触发条件。
最终产物是一份集成发布记录,其中包含确切的系统清单、边界契约、测试证据、已接受的降级、发布状态、已知缺口、负责人、失效时间和回滚流程。
上层无法恢复下层边界悄然删除的语义。提供商的限制会约束适配器,适配器会约束路由,路由和授权会约束应用,组装后的系统指纹则会约束评测结果能够提出的主张。最早识别出语义损失的下层边界就应明确报告它。
有些团队让整个系统统一使用某个提供商风格的 API,有些团队则把提供商原生客户端放在更窄的领域接口之后。有些团队由网关集中负责准入判断,另一些团队把这项职责分散到各个服务中。两种做法都可能成立。真正需要长期坚持的并不是一种特定的产品拓扑,而是明确呈现语义损失、坚持最小权限、保持有界故障、保留不可变身份和端到端证据,并且具备经过测试的回滚。
延伸阅读
- Fielding, “Architectural Styles and the Design of Network-based Software Architectures,” 2000. ics.uci.eduFielding 说明了架构约束和明确的接口语义如何塑造可演进的网络系统。
- Sculley et al., “Hidden Technical Debt in Machine Learning Systems” (评测债务也是系统债务), 2015. papers.nips.ccSculley 等人描述机器学习系统中的隐性技术债,包括边界侵蚀、纠缠、数据依赖和未声明消费者。
- Breck et al., “The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reduction,” 2017. research.google这套生产就绪度量表把数据、模型、基础设施和监控方面的假设转化为明确的测试。
- Fielding et al., “HTTP Semantics,” 2022. rfc-editor.orgHTTP 定义了传输语义,包括自动重试非幂等请求的限制;它不会让应用效果自动变成恰好一次。
- Nottingham et al., “Problem Details for HTTP APIs,” 2023. rfc-editor.orgRFC 9457 定义了可复用的机器可读错误格式,同时提醒客户端不要从面向人类的详情文本中解析程序逻辑。
- Jones et al., “OAuth 2.0 Token Exchange,” 2020. rfc-editor.orgOAuth 令牌交换通过明确的主体、行动者、受众和权限范围语义,支持委托式和模拟式安全令牌。
- Model Context Protocol Contributors, “Model Context Protocol Specification,” 2026. modelcontextprotocol.ioMCP 定义了基于 JSON-RPC 的宿主、客户端和服务器角色及能力协商,并明确把授权、同意和安全执行留给具体实现。
- Rose et al., “Zero Trust Architecture,” 2020. doi.org零信任以针对主体、资产和资源的显式身份认证与授权,取代基于网络位置的隐式信任。
- Kanzhelev et al., “Trace Context” (跨服务边界传播可互操作的请求标识), 2021. w3.orgTrace Context 标准化 traceparent 与 tracestate HTTP 字段,使同一分布式请求能跨服务和追踪厂商保持关联。
- Dean & Barroso, “The Tail at Scale” (尾延迟、对冲请求), 2013. research.googleDean 与 Barroso 解释了尾延迟为何主导大型分布式服务,并介绍了包括对冲请求在内的尾延迟容忍技术。
- Torres-Arias et al., “in-toto: Providing farm-to-table guarantees for bits and bytes,” 2019. usenix.orgin-toto 记录并验证软件供应链中获得授权的步骤、参与者和产物。
- SLSA Community, “SLSA Specification,” 2026. slsa.devSLSA 为软件构建和分发流水线定义了来源证明要求与保障等级。
评论
登录后评论