目标岗位:Agent Harness 研发/工程方向(参考 ~/agent/jb/jd.md) 配套讲义:讲义 05 · LLM Gateway 用法:先自己口头答一遍,再对照"参考回答";重点看"考察点"和"坑"。 原则:所有回答结论先行 → 项目证据 → 原理 → 边界与改进,不要背概念。


0. 面试官视角:Gateway 题到底在考什么

Gateway 是"把分散的厂商调用收敛成平台能力"的典型系统设计题:

  1. 概念层:Gateway 解决什么问题(密钥、协议、可观测);
  2. 实践层:你的网关怎么分层、每个模块在哪、有没有测试;
  3. 判断层:为什么这么设计?并发和性能边界?新增厂商要改什么?——拉差距。

回答公式:一句话结论 → 你项目里的实现/例子 → 原理 → 边界与改进


1. 按讲义章节的题目与参考回答

Q1:LLM Gateway 解决什么问题?为什么不直接让业务调厂商 SDK?

参考回答:四个问题——密钥分散(每个组件持 key,泄露面大)、协议差异 (每家格式不同)、不可观测(没有统一日志)、无法统一治理(限流/回退/成本)。 网关收敛成一层:密钥集中在 config 层、协议统一成 OpenAI Compatible、 每次调用留痕、限流回退统一做。我的网关 create_app 组装了路由、限流、 模板、用量、provider 链,业务只发 HTTP。


Q2:密钥为什么集中?怎么防泄露?

参考回答:集中后业务组件不碰厂商密钥,泄露面从"每个组件"缩小到"网关 一个点"。密钥从环境变量读(config.py,支持 DASHSCOPE_API_KEY 等厂商 命名空间),不进代码库。诚实边界:网关本身是新的攻击面——生产上密钥 应放密钥管理服务(KMS/Vault),网关进程还要做最小权限和审计。


Q3:为什么对外统一成 OpenAI Compatible,而不是自定义协议?

参考回答:OpenAI Compatible 是事实标准,DeepSeek、DashScope、本地 Ollama/vLLM 都兼容,生态里现成 SDK 和工具直接可用。对内用 ChatProvider 接口 + adapter 屏蔽差异(讲义 01 §6),对外格式稳定、对内可演进。收益: 换厂商不改业务代码、OpenAI 官方 SDK 能直连网关。


Q4:流式代理为什么要"两段式"?坑在哪?

参考回答:StreamingResponse 只管"网关→客户端",上游"模型→网关"如果整体 缓冲,首字节照样等全部生成完——我之前就用 httpx.Client.request 踩过这个 坑,首页实测首字节≈总耗时。改成本仓的 _stream_sseclient.stream 逐行 转发)后首字节远小于总耗时。第二个坑是取消:客户端断开要一路关到上游连接, 否则 token 白付(讲义 02 §5)。第三个坑是代理缓冲:Cache-Control: no-cache

  • X-Accel-Buffering: no 是标配。

Q5:结构化输出屏蔽底层差异是怎么做的?

参考回答:上层只传 JSON Schema,网关做两件事——按厂商转换 response_format (OpenAI 透传 / Gemini 映射 / Anthropic 隐藏工具),返回后统一校验 + 纠错 重试 + 降级(structured.py)。业务层完全不感知模型协议,这是 Gateway “屏蔽差异"的代表性例子。


Q6:请求/响应校验"两层"各防什么?

参考回答:入口用 Pydantic(server/schemas.py,FastAPI 自动校验)拦住 “请求格式错”——缺字段、类型错直接 422;出口在结构化路径用 jsonschema 拦住"模型输出脏”。两层拦截的意义:错误发生在哪一层就归哪一层,排查不用 猜。诚实边界:普通 chat 的响应是自由文本,不强制出口校验;只有结构化 路径是严格校验。


Q7:Prompt 模板为什么网关统一管理?

参考回答:模板是"系统行为"的一部分,散在业务代码里没法审计、没法回滚。 网关统一管理后:上层只传变量(/v1/prompts/{name}/render)、变更走版本 历史可回滚、注入隔离声明集中维护(讲义 03)。这也是"平台化"的体现—— 业务不需要知道模板长什么样,只需要传变量。


Q8:限流、重试、fallback 三层怎么配合?

参考回答:网关入口令牌桶限流(429 + Retry-After)保护网关自己;单请求对 上游 429/5xx 指数退避重试(流式只在首字节前);重试耗尽由 FallbackProvider 换备用模型,连续失败还触发熔断。关键判断是错误分类——鉴权/内容过滤这类 错误重试没用,直接抛(classify_provider_error)。三层顺序不能乱:先挡 外部洪峰,再消化瞬时抖动,最后处理端点故障。

加分点:能说出"每层解决不同故障类型,且错误分类决定是否值得重试"。


Q9:用量日志记录什么?怎么支撑成本治理?

参考回答:每次调用一条 UsageRecord:provider/model、输入输出 token、耗时、 估算成本、状态与错误,可选落 JSONL。/v1/usage/summary 聚合(请求数/ 错误率/成本/平均延迟),/v1/usage/recent 给明细。成本治理 = 先有数据: 哪家贵、哪个接口慢、错误率高都能查。诚实边界:成本是估算(按模型名前缀 匹配价格表),正式计费以厂商账单为准。


Q10:网关的性能和并发边界?(判断层)

参考回答:诚实说当前是"演示级"性能:provider 是同步 httpx,流式用 asyncio.to_thread 逐块转发(事件循环可取消但每块有线程切换开销); 长文本任务用后台线程。生产演进:provider 改 async httpx、流式用 worker 线程 + asyncio.Queue 单连接转发、限流换分布式令牌桶(Redis)、多实例下 用量/模板/任务存储换数据库。面试时我会主动说"当前优先正确性和可演示, 性能边界清楚,知道怎么演进"。

:别吹"高并发生产级"——项目测试都是单机、内存态。


Q11:新增一个厂商或新协议,要改多少代码?

参考回答:协议兼容的厂商(又一个 OpenAI 兼容服务)只需在 specs.py 加 一行 ProviderSpec(默认端点、key 环境变量、模型关键词);协议不同的才写 新 adapter(实现 ChatProvider.complete/stream 两个方法 + 注册)。因为 厂商差异做成了"声明式数据 + 少量钩子",而不是散落的 if 分支。


Q12:网关的测试策略是什么?

参考回答:三层——provider 层用 httpx.MockTransport 模拟各家 HTTP 响应 (不碰真实网络);网关层用 FastAPI TestClient + stub provider 跑完整 HTTP 链路(流式 SSE、限流、错误块、断点任务、结构化输出);存储层测 JSONL 持久化与恢复。当前 95 个测试,conda run -n llmops pytest 一条命令全跑。


2. 通用回答技巧

2.1 用 PREP 结构

  • Point:“网关把分散的厂商调用收敛成平台能力”;
  • Reason:密钥、协议、可观测、治理四个问题都需要统一层;
  • Example:OpenAI 兼容入口 + ChatProvider + 限流/回退/用量 + 首页演示;
  • Point:边界——单机内存态、同步 httpx,性能演进路径清楚。

2.2 弹药库

  • 首页五个面板(对话/模型/模板/结构化/长文本/用量)一条龙演示;
  • /docs Swagger;
  • tests/ 95 个用例;
  • 限流 429、demo:boom 错误链路、结构化降级等"可触发演示"。

2.3 诚实边界清单

  • 单机 + 内存态(模板/用量/任务),多实例需换存储;
  • provider 同步 httpx,性能是演示级(有 async 演进路径);
  • 用量成本是估算;
  • 无鉴权(网关本身没有 API key 保护,属后续阶段)。

2.4 反问环节

  • “贵司网关多实例部署后,限流和用量存储怎么做一致性?”
  • “线上怎么监控各厂商的 P99 延迟和结构化输出失败率?”

3. 一分钟项目介绍(开场自述模板 · Gateway 版)

“我做的 Anna 项目第一阶段就是一个 LLM 统一模型调用服务:FastAPI 网关 + ChatProvider 抽象 + adapter 屏蔽 OpenAI/Anthropic/Gemini/Ollama 的协议差异, 对外 OpenAI Compatible,业务不碰密钥。网关统一做了五件事:流式两段代理、 结构化输出(Schema 校验 + 纠错重试 + 降级)、模板库(版本化 + 注入隔离)、 限流/重试/跨模型回退熔断、Token/成本/延迟日志。每个能力首页都能现场演示, 95 个测试覆盖。设计上我坚持"声明式数据 + 少量钩子"而不是散落的 if 分支, 所以新增一个 OpenAI 兼容厂商只加一行 spec。诚实说,当前是单机内存态、 provider 还是同步 httpx,性能是演示级;我的演进路径是 async 化 + 分布式 存储,这些边界我都清楚。”