AI 基建
0%
第十二部分 · 实践与运营 · 第 88 章

集成技术栈

作者Changkun Ou
阅读时长约 16 分钟

兼容的产品还不等于一个系统。只有每道边界都有明确契约,每个会影响行为的组件都有稳定身份,并且发布失败后能够沿着经过测试的路径恢复,它们才真正组成一个系统。因此,本章的交付物是一项集成发布:它用版本化记录说明哪些组件连接在一起、每条连接具有什么含义、由谁负责,以及哪些证据足以证明组装后的系统可以安全发布。

先确定发布决策,而不是先列采购清单。明确本次发布的能力、面向的用户和租户、允许处理的数据类别与操作、质量和运营门槛,以及回滚触发条件。随后固定一份系统指纹,其中包括模型修订版本、提示词修订版本、检索快照、工具 Schema、策略修订版本、路由配置、遥测 Schema 和可执行制品摘要。模型名称本身无法标识生成某个答案的系统。

这种契约优先的思路由来已久。Fielding 用受约束的接口,而不是具体产品,来描述网络化系统 (Fielding 2000)。Sculley 等人随后说明,胶水代码、配置和未声明的消费方会在机器学习系统周围积累技术债 (Sculley et al. 2015)。Breck 等人又把这项认识转化为生产就绪测试 (Breck et al. 2017)。本篇前面的章节已经给出了各个组件,本章要让它们的组合可以接受检验(第 1 章)。

固定集成发布

一次集成发布需要维护两类记录。系统清单标识组装后的完整系统,边界契约定义每一对生产方和消费方之间的连接。两者都不得使用 latest 之类的可变别名。

记录 必填内容 发布前要回答的问题
系统清单 应用、模型、提示词、检索、工具、策略、路由、遥测和制品的修订版本 将来还能否准确识别这套系统行为?
拓扑 每个生产方、消费方、协议版本、传输方式和负责人 每条线上连接是否都有明确负责人?
能力说明 原生支持、由适配器模拟、有损或不支持的行为 经过适配器时,哪些语义会发生变化?
信任契约 主体、工作负载、租户、权限、受众、作用域和数据类别 该调用方是否可以发送这些数据或执行这项操作?
故障契约 截止时间、取消、重试负责人、幂等范围、错误映射和背压 故障传播到哪里为止,谁可以重试?
证据契约 遥测、符合性测试结果、迁移状态、验收证据和回滚修订版本 什么能够证明本次发布有效,又如何恢复上一正常版本?
intent 发布意图 identity 系统指纹 intent->identity edges 边界契约 identity->edges evidence 验收证据 edges->evidence release 发布记录 evidence->release rollback 回滚版本 release->rollback
图 88.1. 集成发布把发布意图、不可变身份、边界语义、验收证据和回滚路径绑定在一起。产品清单只覆盖了这份契约中的很小一部分。

系统清单本身应经过签名或采用其他完整性保护。记录软件物料清单构建来源证明、容器和二进制文件的摘要值、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 展示了满足这些要求的最小架构。应用是调用方,策略层决定是否接纳请求,适配器把调用方契约转换为提供商契约。工具操作走另一条路径,先完成授权,再交给执行器。检索服务返回带标识的证据。遥测观察每项决策,但不授予任何权限。

caller 调用方 policy 策略 caller->policy adapter 适配器 policy->adapter retrieval 检索 policy->retrieval 证据 executor 工具执行器 policy->executor 已授权 telemetry 遥测 policy->telemetry provider 提供商 adapter->provider adapter->telemetry ledger 操作账本 executor->ledger executor->telemetry
图 88.2. 一套与厂商无关的集成架构。模型调用从调用方经过策略层和适配器进入提供商;工具提议沿着独立授权路径进入执行器和操作账本;检索与遥测仍是彼此独立的服务。

这张图刻意不设置一个包办一切的产品中心。模型边界、工具边界、检索边界和证据边界解决的是不同问题,分别由 第 82 章第 85 章第 86 章第 87 章 进一步展开。

定义模型操作状态机

把一次请求视为一项逻辑操作,其中包含已验证的租户、operation_id、载荷摘要、剩余截止时间、能力要求和策略修订版本。准入阶段要么拒绝它,要么记录不可变的路由决策。逻辑操作之下,每次上游调用都是一次实际尝试

状态机必须明确规定以下步骤:

  1. 接收操作并验证身份。
  2. 验证 Schema 和能力要求。
  3. 按租户、预算、区域和策略约束决定是否接纳。
  4. 解析并记录不可变的路由。
  5. 发起一次上游尝试,把收到的帧规范为有版本的带类型事件流,并为每个事件附上操作、尝试和序列标识符。
  6. 累积流式工具参数片段,但不执行工具。
  7. 收到工具块的终止标记后,解析完整参数,按照固定的 Schema 验证,授权这项具体操作,并且只执行一次。
  8. 把拒答、输出不完整、取消、部分输出、错误和正常完成保留为彼此不同的终止结果。

流式数据块不是一份完整文档。为了保持向前兼容,未知事件类型应原样保留或明确暴露,不能改当作普通文本。不完整的工具参数绝不能执行。只有组装完成后,结构化结果才能按照声明的 JSON Schema 能力说明接受最终验证。拒答、截断、传输失败和 Schema 违规都应产生不同的无效结果,不能伪装成一个看似合理的对象。

一旦调用方已经看到输出,自动重试就可能复制或自相矛盾地续写这条流。因此,向调用方输出内容后,不要再自动重放。把取消信号和剩余截止时间继续传递到下游;调用方断开时,应停止读取并取消上游请求。每个已接纳的操作都必须通过有限次尝试、有限的截止时间和有界资源到达终止状态。

规范错误而不掩盖原因

使用基于 RFC 9457 的稳定机器可读错误格式 (Nottingham et al. 2023),其媒体类型为 application/problem+json。定义稳定的问题类型,并记录可以安全公开的上游错误类别、HTTP 状态、提供商代码、上游请求 ID、操作 ID、尝试次数、是否已经输出部分内容、是否可重试,以及任何 Retry-After 值。原始原因则保留在受保护的诊断记录中。

面向人的详细说明只供人阅读。不得解析面向人的详细说明来决定是否重试。身份验证失败、授权失败、输入无效、能力不受支持、策略拒绝和配额耗尽通常都不可重试。过载和暂时性传输故障只有在操作预算与截止时间允许时,才可以重试。

限制重试与过载

客户端、网关、适配器和提供商各自重试,会形成重试放大。这里,\ell 表示一个会重试的层,rr_\ell 表示该层允许的最大重试次数。一项逻辑操作最多会产生以下数量的实际尝试:

Amax==1L(1+r),A_{\max}=\prod_{\ell=1}^{L}(1+r_\ell),

其中,LL 是彼此独立执行重试的层数。如果三层各允许重试两次,就可能产生 33=273^3=27 次尝试。整个系统只应指定一个重试负责人。它必须遵守总尝试次数上限和剩余截止时间,尊重 Retry-After,并使用带抖动的有界指数退避。即使逻辑操作最终成功,也要记录全部实际尝试。尾部延迟使这些限制尤为重要 (Dean and Barroso 2013)。

幂等键是一项应用契约,不是 HTTP 层提供的神奇“恰好执行一次”保证。它应绑定租户、路由类别、操作键、规范化载荷摘要和有效期。使用同一个键提交不同载荷必须报错。如果提供商或目标系统无法去重,就要明确说明不能保证恰好执行一次。会产生副作用的工具还需要自己的操作级幂等键和操作回执;模型请求的幂等键不能让工具自动获得幂等性。

过载控制也必须有界,包括每个租户的并发数、队列长度、流缓冲区、输入和输出词元数,以及工具扇出数。有界队列会显式产生背压,不会把延迟悄然转化为不断增长的内存占用。拒绝工作时,应返回合适的 429503 以及 Retry-After。断路器和重试预算则用于防止恢复流量演变成重试风暴。

回退会改变系统身份。候选回退系统必须在声明的兼容类别中预先验证,并满足同样的硬性策略与数据约束。新的路由决策也要记录。如果已经向调用方输出内容,或某项副作用结果仍不明确,绝不能悄然切换到另一个系统。

分离身份、凭据与操作

网关凭据、工作负载身份、委托用户令牌和下游提供商密钥回答的是不同问题。优先使用短期工作负载身份和 OAuth 令牌交换 (Jones et al. 2020)。令牌应绑定受众、作用域、已验证的租户、主体或行动者以及有效期。每个资源服务器都必须验证自己的受众,不能把签发给一个资源的令牌继续传给另一个资源。

虚拟密钥只是一种实现模型预算与路由策略的方式,并不是通用的工具授权。如果提供商只支持静态密钥,就把它保存在凭据代理中,只在允许的出站边界完成替换。应用和沙箱都不应接触原始提供商密钥。零信任架构同样认为,网络位置本身不足以构成权限 (Rose et al. 2020)。

工具与 MCP

模型上下文协议(MCP)通过 JSON-RPC 规范主机、客户端和服务器之间的消息,并支持能力协商、发现和工具 Schema (Model Context Protocol Contributors 2026)。发现不等于授权。工具注解是不受信任的元数据,不能证明某次调用安全。固定 MCP 协议修订版本、传输方式和协商结果,然后按照 第 56 章 中的控制措施完成逐次调用授权。

受信任的运行时应依次完成以下工作:

  1. 验证用户和工作负载身份,并从可信身份中取得租户。
  2. 根据白名单验证工具名称,再按照固定的输入和输出 Schema 规范化完整参数。
  3. 把影响类别划分为读取、可逆写入或不可逆外部操作。
  4. 针对主体、工作负载、租户、工具修订版本、资源、受众、规范化参数、影响类别和策略修订版本完成授权;必要时绑定会过期的同意或审批。
  5. 追加意图记录,获取最小权限凭据,再使用操作级幂等键执行。
  6. 在推进工作流检查点之前保存操作回执;返回结果前先验证并脱敏。

遇到模糊超时,应查询操作回执或目标状态,不能盲目重试非幂等操作。取消不等于回滚。并行调用需要各自独立的调用 ID 和回执。工具结果必须保留其来源调用 ID,避免运行时把结果接到错误的提议上。

沙箱(sandbox)可以缩小影响范围,但沙箱不是授权机制。它的威胁模型应限制文件系统访问、系统调用、进程、CPU、内存、磁盘、实际运行时间、工作区生命周期和网络访问。同时执行出站白名单和资源白名单。解析并验证 DNS 和每一次重定向,阻止访问私有地址、链路本地地址和云元数据端点。隔离能够约束不当操作造成的影响,却不能让一项不当操作变得合理。

检索与证据

先授权,再检索,不能等候选文档越过边界后才检查。每项结果都应携带证据 ID、来源和语料修订版本、索引修订版本、授权决定和租户边界。缓存、检查点、会话、任务和去重键都必须包含租户身份与系统策略身份。第 86 章 进一步说明了这份证据契约。

把遥测当作证据,而不是权限

W3C Trace Context 用于跨服务传递关联标识符 (Kanzhelev et al. 2021),但它不是权限。绝不能从 traceparent 或 Baggage 推断主体、租户或许可。Baggage 本身没有完整性保障,也不得包含秘密、个人数据或授权决定;跨越信任边界时,应按白名单保留并删除其余内容。

记录操作 ID、实际尝试 ID、路由和适配器修订版本、能力判断、错误类别、工具意图和回执、证据 ID 以及终止结果。同时报告缺失遥测数据、导出丢失、采样和脱敏失败。追踪记录只是部分证据,不能自动证明某项操作已经或尚未发生(第 87 章)。

核算整个合格任务

优化对象应是整个合格任务,而不是方便计算的模型调用小计。成本包括每次实际尝试、模型用量、检索、工具执行、裁判、基础设施和人工复核。延迟应从根操作开始计算实际经过时间,不能把并行子任务的耗时相加。记录确切的响应模型、区域、计费时间、币种和价格目录修订版本,不能依赖可变别名(第 76 章第 31 章)。

对于任务 qq,可以采用以下核算恒等式:

Cq=aAqmMaua,m×pm(va,ρa,ta,ka)+Cinfra+Chuman.\begin{aligned} C_q={}&\sum_{a\in\mathcal{A}_q}\sum_{m\in\mathcal{M}_a} u_{a,m}\\ &\quad {}\times p_m(v_a,\rho_a,t_a,k_a)\\ &+C_{\mathrm{infra}}+C_{\mathrm{human}}. \end{aligned}

其中,Aq\mathcal{A}_q 是任务 qq 的全部实际尝试,Ma\mathcal{M}_a 是尝试 aa 中互不重叠的计费项,ua,mu_{a,m} 是计费项 mm 的用量。pmp_m 根据响应模型修订版本 vav_a、区域 ρa\rho_a、计费时间 tat_a 和价格目录修订版本 kak_a 给出单价。CinfraC_{\mathrm{infra}}ChumanC_{\mathrm{human}} 分别表示共享基础设施成本与人工复核成本。使用互不重叠的计费项,可以避免重复计算缓存或推理小计。

自建还是租用也应通过实测寻找交叉点,而不是套用一个通用百分比。把下图的两项价格改成当前报价。图中的默认值只是可以编辑的示例,不是建议。

图 88.3. 这张图展示 720 个可用 GPU 小时内的成本交叉点。按量成本随实际使用时数增长,固定月费则保持不变。决策前,应使用当前报价替换两项示例价格,并把闲置容量、运维和可靠性成本一并纳入。
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、有界队列行为、取消以及正确的 429503
重试与回退 只有一个重试负责人、尝试次数有限、回退兼容,而且系统身份变化得到记录
租户隔离 受众错误的令牌、跨租户缓存或句柄、过期审批和秘密泄漏都必须拒绝
遥测 操作与尝试能够关联,路由决策、遥测缺失和脱敏状态均可见

还要增加以下负向用例:未知流事件、重复交付、工作进程在提交后却在保存回执前崩溃、恶意工具输出、Schema 漂移、DNS 重绑定、重定向到元数据端点、文件系统逃逸和失控的进程创建。一次成功演示无法验证这些行为,符合性测试可以。

把切换视为协议

迁移本身也是一次集成。先运行适配器契约测试,再以影子模式执行新的只读行为,且不产生副作用。随后开启粘性金丝雀,让同一任务或租户始终停留在同一个系统指纹上。只有质量、安全性、延迟、错误和成本门槛持续满足时,才逐步扩大流量。路由和策略变更应以原子方式发布。上一已知正常版本的系统清单必须始终可以部署,而且要在第一批金丝雀流量进入前先测试回滚。

tests 契约测试 shadow 影子模式 tests->shadow canary 粘性金丝雀 shadow->canary lkg 上一正常版本 shadow->lkg 失败 ramp 分阶段扩量 canary->ramp canary->lkg 失败 release 正式发布 ramp->release ramp->lkg 失败
图 88.4. 一条可逆的集成切换路径。先运行契约测试,再进入无副作用的影子模式、粘性金丝雀、分阶段扩量和正式发布。任何门槛失败都会恢复上一已知正常版本。

故障矩阵把笼统的谨慎要求转化为明确责任。

故障 检测方式 遏制与恢复
能力悄然丢失 能力说明与符合性测试结果不一致 拒绝准入,恢复之前的适配器
重试风暴 尝试次数放大和队列告警 禁用嵌套重试并丢弃过载请求
凭据泄漏 秘密诱饵或受众违规 撤销并轮换凭据,隔离相关证据
跨租户访问 授权与隔离测试失败 一律拒绝,并使相关缓存和句柄失效
工具只执行了一部分 意图记录没有对应的操作回执 查询目标状态并完成对账,不能直接重放
配置漂移 经过签名的系统清单或摘要不一致 停止扩大流量,以原子方式恢复上一已知正常版本
遥测缺失 未达到导出与覆盖率服务目标 暂停依赖这些证据的发布决策

发布生命周期

完整流程足够简短,应当定期演练:

  1. 明确能力、目标总体、约束和回滚触发条件。
  2. 清点生产方、消费方、负责人、信任边界和数据类别。
  3. 固定系统指纹和经过签名的系统清单。
  4. 发布能力、身份、故障、操作和证据契约。
  5. 实现只会保留语义、明确降级或拒绝请求的适配器。
  6. 指定一个重试负责人,并限制截止时间、队列和并发数。
  7. 把凭据和工具操作绑定到已验证身份及租户策略。
  8. 运行协议、策略、隔离、故障和供应链符合性测试。
  9. 测量合格任务的质量、延迟、成本和缺失证据。
  10. 先执行无副作用的影子流量,再运行粘性金丝雀并分阶段扩量。
  11. 以原子方式发布系统清单,并保留经过测试的回滚修订版本。
  12. 记录负责人、已知缺口、失效时间和重新验证触发条件。

最终产物是一份集成发布记录,其中包含确切的系统清单、边界契约、测试证据、已接受的降级、发布状态、已知缺口、负责人、失效时间和回滚流程。

约束如何传导

上层无法恢复下层边界悄然删除的语义。提供商的限制会约束适配器,适配器会约束路由,路由和授权会约束应用,组装后的系统指纹则会约束评测结果能够提出的主张。最早识别出语义损失的下层边界就应明确报告它。

争议所在

有些团队让整个系统统一使用某个提供商风格的 API,有些团队则把提供商原生客户端放在更窄的领域接口之后。有些团队由网关集中负责准入判断,另一些团队把这项职责分散到各个服务中。两种做法都可能成立。真正需要长期坚持的并不是一种特定的产品拓扑,而是明确呈现语义损失、坚持最小权限、保持有界故障、保留不可变身份和端到端证据,并且具备经过测试的回滚。

延伸阅读

  • Fielding, “Architectural Styles and the Design of Network-based Software Architectures,” 2000. ics.uci.edu
    Fielding 说明了架构约束和明确的接口语义如何塑造可演进的网络系统。
  • Sculley et al., “Hidden Technical Debt in Machine Learning Systems” (评测债务也是系统债务), 2015. papers.nips.cc
    Sculley 等人描述机器学习系统中的隐性技术债,包括边界侵蚀、纠缠、数据依赖和未声明消费者。
  • 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.org
    HTTP 定义了传输语义,包括自动重试非幂等请求的限制;它不会让应用效果自动变成恰好一次。
  • Nottingham et al., “Problem Details for HTTP APIs,” 2023. rfc-editor.org
    RFC 9457 定义了可复用的机器可读错误格式,同时提醒客户端不要从面向人类的详情文本中解析程序逻辑。
  • Jones et al., “OAuth 2.0 Token Exchange,” 2020. rfc-editor.org
    OAuth 令牌交换通过明确的主体、行动者、受众和权限范围语义,支持委托式和模拟式安全令牌。
  • Model Context Protocol Contributors, “Model Context Protocol Specification,” 2026. modelcontextprotocol.io
    MCP 定义了基于 JSON-RPC 的宿主、客户端和服务器角色及能力协商,并明确把授权、同意和安全执行留给具体实现。
  • Rose et al., “Zero Trust Architecture,” 2020. doi.org
    零信任以针对主体、资产和资源的显式身份认证与授权,取代基于网络位置的隐式信任。
  • Kanzhelev et al., “Trace Context” (跨服务边界传播可互操作的请求标识), 2021. w3.org
    Trace Context 标准化 traceparent 与 tracestate HTTP 字段,使同一分布式请求能跨服务和追踪厂商保持关联。
  • Dean & Barroso, “The Tail at Scale” (尾延迟、对冲请求), 2013. research.google
    Dean 与 Barroso 解释了尾延迟为何主导大型分布式服务,并介绍了包括对冲请求在内的尾延迟容忍技术。
  • Torres-Arias et al., “in-toto: Providing farm-to-table guarantees for bits and bytes,” 2019. usenix.org
    in-toto 记录并验证软件供应链中获得授权的步骤、参与者和产物。
  • SLSA Community, “SLSA Specification,” 2026. slsa.dev
    SLSA 为软件构建和分发流水线定义了来源证明要求与保障等级。

评论

登录后评论