第 2 周 · 原生模型 API 与 Model Gateway
不用任何框架,手写统一 Model Gateway,掌握多 Provider、流式取消与成本追踪
本周产出:packages/model-gateway,支持至少两个 Provider · 预计投入:15 到 20 小时 · 对应原 24 周编号:第 2 周
学前自测
1. 流式响应和普通响应,在错误处理上有什么本质区别?
普通响应的错误在一次 await 里抛出,捕获点唯一。流式响应已经吐出了一半内容才失败,这时你面对的是部分结果加一个错误,必须决定是丢弃、保留还是标记不完整。绝大多数人第一版实现都会漏掉流中断的处理。
2. 什么是 Circuit Breaker?和 Retry 的区别是什么?
Retry 是单次请求失败后重试,解决瞬时抖动。Circuit Breaker 是在连续失败达到阈值后直接短路,不再发请求,解决下游已经挂了还在疯狂重试打爆自己的问题。两者是互补的,只有 Retry 没有 Breaker 的系统在 Provider 故障时会自我雪崩。
3. 为什么要在请求前做 capability 预检,而不是让它运行时失败?
因为运行时的静默降级最难排查。你请求一个不支持 tool calling 的模型去调工具,如果 Gateway 悄悄降级成纯文本生成,Agent 会拿到一段看起来正常的文字然后走进错误分支,几十步之后才暴露问题,且没有任何日志指向真实原因。预检的价值是把失败提前到唯一能定位的地方。
4. Token 和字符是什么关系?为什么成本要按 Token 算而不是按请求算?
Token 是模型处理文本的最小单位,中文大致一个字一到两个 Token,英文大致一个词零点七五个 Token。按 Token 计费是因为模型的计算量正比于 Token 数量。这直接影响架构决策:同样一次 Agent Run,上下文管理做得差可能贵十倍。
学习目标
不使用 LangChain 或 Mastra,实现统一 Model Gateway。
这一周的核心不是学会调 API,而是建立一层能挡住 Provider 差异的抽象。等到第 7 周引入 Mastra 时,你会发现自己已经理解了框架在这一层帮你做了什么,以及它没做什么。这是判断框架该不该用的唯一可靠方式。
课程内容
- Messages 与 Instructions
- Text Generation
- Streaming
- Structured Output
- Tool Calling
- 多模态输入
- Usage 与 Token
- 模型成本计算
- Timeout、Retry、Backoff
- Rate Limit
- Circuit Breaker
- Provider Adapter
- Model Fallback
- Request Trace
- Capability Discovery(tools / vision / structured output / reasoning)
- Finish Reason 与错误语义归一化
- 首 Token 延迟、生成吞吐与端到端 SLO
- Provider Contract Test 与录制回放
实施任务
实现:
interface ModelGateway {
capabilities(model: ModelRef): Promise<ModelCapabilities>
generate(request: GenerateRequest): Promise<GenerateResult>
stream(request: GenerateRequest): AsyncIterable<ModelEvent>
generateObject<T>(request: StructuredRequest<T>): Promise<T>
generateWithTools(request: ToolRequest): Promise<ToolResult>
}支持至少两个 Provider。
用同一输入集对两个 Provider 做配对测试,记录成功率、Schema 合规率、首 Token 延迟、总延迟、Token 与成本。禁止把 Provider 原始对象泄漏到业务层。
动手挑战
做一次真实的取消实验,测量你到底浪费了多少钱。
步骤:发起一个会生成 2000 Token 的流式请求,在收到第 50 个 Token 时调用 abort。然后回答三个问题:
- 你的 Gateway 有没有真的中断上游 HTTP 连接,还是只是停止了消费?
- Provider 的账单里这次请求算了多少 Token?
- 你的成本追踪记录的是 50 还是 2000?
大多数人第一版实现三个答案都是错的。修到三个都对,你就真正理解了取消传播。
Agent OS 里程碑
完成 packages/model-gateway。
验收标准
核心档(程序员基础版毕业线)
- 可配置切换两个模型 Provider
- 输出通过 Zod 校验
- 支持取消流式请求
- 记录 Token、延迟、成本、错误
- 模型失败可重试或降级
- 不支持的 capability 在请求前失败,而不是运行中静默降级
进阶档验收见程序员进阶版附录 B。
学后自测
1. 你的配对测试里,两个 Provider 的 Schema 合规率差多少?差距来自哪里?
这个数字要能报出来。差距通常来自结构化输出的实现方式不同:有的 Provider 用约束解码保证 JSON 合法,有的只是在 Prompt 里要求。知道这个差异,才能在选型时判断哪些场景必须锁定某个 Provider。
2. 模型返回 finish_reason 为 length 时,你的系统怎么处理?
这代表输出被 max_tokens 截断了,内容是不完整的。绝对不能当作正常结果返回给业务层,否则会得到半截 JSON 或者半句话。正确做法是归一化成一个明确的截断错误,让调用方决定是重试加大预算还是走降级路径。
3. 业务代码里有没有出现过 Provider SDK 的类型?怎么验证?
用 ESLint 的 no-restricted-imports 规则在 CI 里强制,比人工 review 可靠。这条规则从第 2 周加上,一直守到第 7 周引入 Mastra,那时候它会救你。
本周作业
packages/model-gateway/:完整实现,含单元测试与集成测试docs/model-gateway.md:设计文档,说明 Provider 抽象边界、错误分类表、降级策略docs/provider-comparison.md:两个 Provider 的配对测试报告,六个指标的实测数据加你的选型结论
配对测试报告是这周最值钱的作品集素材,因为它证明你做过量化选型,而不是跟风。
面试考点
这一周对应面试里的工程稳定性考察,是后端出身候选人最容易拿分的地方。
- 「模型 API 超时了你怎么办?」:不要只答重试。完整答法是分层:超时归一化 → 判断是否可重试(幂等性)→ 指数退避 → 熔断 → 降级到备用模型 → 最终失败时的用户可见错误。能把这条链路说完整,直接拉开和其他候选人的差距。
- 「你怎么控制 AI 功能的成本?」:这一周的答案是可观测(Token、延迟、成本全记录),后面第 5 周会补上下文预算,第 9 周补 Policy 层预算。面试时按这三层答。
- 「为什么不直接用 LangChain 的 LLM 抽象?」:答你需要 capability 预检和错误语义归一化,而通用抽象为了兼容所有 Provider 会把这两件事削平。注意态度要中立,不要显得为了造轮子而造轮子,重点讲你的具体需求。
- 手写题:现场实现一个带超时和取消的流式包装函数。这道题高频,建议提前手写熟练。
复习与延伸
官方文档
- OpenAI Responses API
- OpenAI Structured Outputs
- Anthropic Messages API
- Anthropic Tool Use
- Google Gemini API
本仓库对应源码
packages/contracts/src/model.ts是框架无关的模型请求类型定义,你的 Gateway 应该实现这套契约而不是自己另起一套