Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

llm-gateway

CI

统一代理 DeepSeek / Claude 的 LLM 网关:SSE 流式 · Function Calling 回环 · token 计费 · 熔断降级 · 指数退避重试

Built in public:规格驱动开发的示范项目——先写验收标准,AI 实现,人工逐条验收。10/10 测试 + Docker 验收。

三个值得看的实现: ① SSE 断连的取消传播链——客户端断开 → 生成器被取消 → async with 退出 → 上游连接关闭。 没有这条链,上游会继续吐 token 而无人接收,是流式场景最隐蔽的烧钱方式。 ② 三态熔断器(CLOSED/OPEN/HALF_OPEN)按 provider 隔离——一家挂了不拖死另一家,有测试锁死。 ③ 4xx 不重试也不计入熔断——4xx 是请求方的问题,计入会误伤健康的上游;只有 429/5xx/超时才退避重试。

快速开始

uv sync                      # 安装依赖(含 dev)
cp .env.example .env         # 填入 GW_DEEPSEEK_API_KEY / GW_ANTHROPIC_API_KEY
uv run uvicorn app.main:app --reload

跑测试(无需任何 key,上游全部 mock):

uv run pytest -q
uv run ruff check .

API 示例

# 非流式(默认 deepseek;换模型加 -H "X-Provider: claude")
curl -s localhost:8000/v1/chat -H 'content-type: application/json' -d '{
  "messages": [{"role": "user", "content": "算一下 2+3*4"}],
  "tools": ["calculator"]
}' | jq

# SSE 流式
curl -sN localhost:8000/v1/chat/stream -H 'content-type: application/json' -d '{
  "messages": [{"role": "user", "content": "用一句话介绍北京"}]
}'

# 用量与费用
curl -s localhost:8000/v1/usage | jq

架构

Route(main.py) ── 协议/校验/错误映射
   └─ ChatService(chat_service.py) ── 工具回环 + guarded_call(熔断×重试) + 计费
        ├─ Provider 适配层(providers/) ── DeepSeek(OpenAI 协议) / Claude(Messages API)
        ├─ CircuitBreaker(resilience.py) ── CLOSED → OPEN → HALF_OPEN 状态机,按 provider 隔离
        ├─ tools.py ── 本地工具注册表(ast 白名单计算器 / mock 天气)
        └─ UsageStore(usage.py) ── 内存计数 + 价格表估费

设计决策(面试可讲)

  1. 为什么计费放网关层:业务方无感、跨模型统一口径、限额与告警有单一落点。
  2. 熔断为什么按 provider 隔离:DeepSeek 挂了不该拖死 Claude 通道(test_breaker_isolated_per_provider 验证)。
  3. 4xx 不重试不计熔断:4xx 是请求方问题,重试只会浪费配额;只有 429/5xx/超时值得退避重试。
  4. SSE 断连取消链:客户端断开 → Starlette 取消响应生成器(CancelledError)→ provider 内 async with client.stream 退出 → 上游连接关闭。没有这条链,上游会一直吐 token 白烧钱。
  5. calculator 用 ast 白名单不用 eval:工具参数来自模型输出,等于不可信输入。

Java 程序员对照表

本项目 Java 世界
pydantic BaseModel DTO + Bean Validation
Depends / Annotated Spring 构造器注入
lifespan @PostConstruct / @PreDestroy
exception_handler @ControllerAdvice
asyncio + httpx.AsyncClient CompletableFuture / WebClient
CircuitBreaker (手写) Resilience4j
ASGITransport 测试 MockMvc

已知边界(刻意不做)

无鉴权、无限流、用量不持久化(进程内存,重启清零——demo 定位,是规格声明不是遗漏)。

流式的三个设计边界(每条都是一道「为什么不做」的面试题):

  1. 流式不重试:SSE 一旦开始发帧,HTTP 200 已定、部分内容已到达客户端——此时上游失败只能发 error 事件交给客户端决策,网关若静默重试会吐出重复内容。重试与熔断退避只保护非流式路径(guarded_call)。
  2. 断流不计费:usage 事件在流末尾才到,中途失败拿不到真实 token 数——宁可少记也不估算,猜出来的计费数字会污染对账。
  3. 流式不带工具调用:工具回环要求「完整收到 tool_call → 本地执行 → 回填再问」,与逐 token 透传天然冲突;要支持就得在网关缓冲整轮再转发,v1 明确不做。

关于这一组项目

这是三套评估驱动的 AI 应用系统,同期开源,可以单独用也可以对照看:

做什么 关键实测
repo-rag 中文知识库 RAG:结构分块 + 两层拒答 + 引用溯源 hit@1 95.8% · faithfulness 0.981
orchestrator-lg 自研 DAG 引擎迁到 LangGraph:checkpoint + 可持久化审批中断 8/8 测试 · YAML 零改动兼容 · 跨进程恢复
llm-gateway 多模型网关:SSE 取消链 + 三态熔断 + token 计费 10/10 测试 · Docker

共同的方法论:先建评估集,再写优化——每个技术决策都由实测数据推导,包括那些「该做但做了反而更差」的决策。

关于作者

@jnMetaCode · 11 年 IT、8 年技术团队管理 · 公众号 AI不止语 其他开源:agency-agents-zh(19.8k★,267 个 AI 专家角色 × 18 类工具链)· superpowers-zh(7.8k★)· agency-orchestrator(2.1k★,本项目的上游)

About

多模型 LLM 网关:熔断/SSE流式/Function Calling回环/token计费(DeepSeek+Claude,10 tests+Docker)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages