第 17 章:设计你自己的 AI 网关
现在可以把前面的规则放回一个完整项目。设想你负责公司内部的知识助手入口,两个团队共享额度,各有不同的数据访问权限;一部分请求需要结构化输出,另一部分只需要文本。你希望在引入第二个模型服务时,仍能解释每一次执行和费用。
17.1 写下可验收的第一版范围
第一版可以只支持文本、有限的输出上限、两个固定上游和租户认证。把不支持的能力明确拒绝,再逐步增加。对每项能力写出入口语义、上游验证、错误表示、观测字段和回归样本。
以下交付表比一张只有模块名的架构图更接近实际验收:
| 交付对象 | 需要证明什么 |
|---|---|
| 能力清单 | 不合格上游不会收到请求 |
| 流式适配器 | 分片不损坏事件,完整与中断可区分 |
| 准入账本 | 并发竞争不会重复消费同一份可用额度 |
| 运行记录 | 请求、尝试、提交点和结算状态可关联 |
| 取消路径 | 本地资源最终释放,未知费用仍可追踪 |
| 发布流程 | 配置与代码版本可回退,回归结果可重现 |
17.2 选择一个语言,保留共同的不变量
在 Java 体系中,可以将这些约束映射到网关过滤链、异步流和事务存储;在 Go 体系中,可以映射到 context、流式 HTTP 处理和持久化组件。移植时应重新检查取消与缓冲语义:库是否提前聚合响应,写入是否真正刷新,取消如何传到上游读取。
不能因为接口名字相同,就把实验锁、队列和 socket 行为逐行照搬。保留的是不变量与证据设计。例如“未知不结算”应由新的持久状态机验证,而不是仅靠翻译 Python 的一个条件分支。
17.3 先做一次失败评审
让同事选一个故障:网关进程退出、账本暂不可用、上游半途断开、旧配置引用已吊销密钥,或者客户端不再读取。对每个故障回答四个问题:哪一步已发生,谁知道这个事实,哪些资源还占用,以及恢复时允许做什么。
如果答案依赖“通常会”“应该已经”,就需要增加证据或收紧范围。例如进程退出后不知道上游状态,可以把任务标为待核查;这虽然不能立刻恢复完整服务,却保留了诚实的状态。自动标记成功或者把预留全部清零,只会把故障搬到账单和用户反馈里。
17.4 一个实际的增量顺序
先接入一个真实上游,固定协议版本并跑契约测试;再引入持久任务与账本,验证重启和重复结算;随后加入第二上游,明确质量与能力边界;最后才考虑动态路由、灰度和自动调优。每一步都保留旧行为基线,避免同时改变协议、模型与策略后无法归因。
真实上游测试使用独立的测试账号和额度,把需要费用的用例与本地确定性测试分开。代码仓库只保存凭证读取方式和占位说明,实际密钥进入部署环境的受控配置。
17.5 交付给下一位维护者
项目应提供启动方式、已验证能力、故障矩阵、状态说明、日志示例、账本核查流程和未实现功能。在线阅读与 EPUB 同源,使读者离线时仍能核对规则;代码附录则保留该版实验的实现细节。
一个值得维护的 AI 网关,应该让新同事在没有参与最初设计的情况下,也能回答“为什么选了它”“为什么没有重试”“为什么还有一笔未知预留”。当这些问题都有证据,系统才具备继续扩展的基础。
综合练习。 用一页设计说明完成自己的第一版范围,再为上表每一行写一条正常测试和一条失败测试。选择一个目前不能提供的保证,把它写成后续改造任务,列出完成时需要补充的运行证据。