# llmproxy **Repository Path**: bingersoft/llmproxy ## Basic Information - **Project Name**: llmproxy - **Description**: LLM大模型服务代理系统 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: dev-0.1.0 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-22 - **Last Updated**: 2026-10-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # LLM Proxy(llmproxy) 多厂商大模型统一网关 + 组织多租户 + Token 计费售卖 + 指标监控 + Web 管理台,单进程单端口(默认 **3002**)。 客户端面向 **OpenAI / Anthropic / OpenAI Responses 三种客户端协议**(对应 SDK 改 `base_url` + `api_key` 即可直连), 网关内部把请求翻译成上游真实协议(OpenAI 兼容 / Anthropic Messages / Gemini generateContent / openai_responses), 并统一完成鉴权授权(用户组 → 用户 → API Key 继承链)、能力与预算/余额预检、重试、跨服务降级、 Token 计费售卖(定价表 / 余额台账 / 计量扣减 / 账单)、成本与性能指标、请求审计。 ## 架构 ``` 客户端(OpenAI / Anthropic / OpenAI Responses SDK,仅改 base_url + api_key) │ /v1/chat/completions · /v1/messages · /v1/responses │ /v1/embeddings · /v1/images/generations · /v1/models │ Bearer sk-proxy-… ▼ ┌────────────────────────── FastAPI 单进程(uvicorn, :3002)──────────────────────────┐ │ 网关流水线:鉴权授权 → 预检(模态/能力/预算/余额)→ 路由(模型组/降级链)→ 上游调用 → 结算(计费/指标/审计) │ │ protocols/ 客户端协议适配器:OpenAI / Anthropic / Responses(双向翻译,错误与流式按线格式) │ services/ 上游协议适配器:OpenAICompat(24 家兼容系)/ Anthropic / Gemini / openai_responses │ 厂商目录 CATALOG(27 条:在线厂商/聚合商 + 自部署后端 + openai_compatible 兜底) │ gateway/ 执行引擎:模型解析、预检、降级链、指数退避重试、并发、计费结算 │ storage/ SQLite(WAL,单连接+线程锁)+ Registry 快照原子热重载 + 计费台账(定价/余额/计量) │ metrics/ 队列批量落库 → 小时/日 rollup → 内存实时窗口 + Prometheus │ auth/ 客户端 key(SHA-256 哈希)/ admin·user 双角色 JWT / 登录限流 │ │ 管理 API(/api/v1/admin/*,JWT)+ Vue3 管理台(web/dist,SPA 兜底) │ Prometheus /metrics └──────────────────────────────────────────────────────────────────────────────────────┘ │ 各厂商 API(Qwen/DeepSeek/豆包/Kimi/GLM/MiniMax/混元/百度/OpenAI/ Anthropic/Gemini/Azure/OpenRouter/Groq/… + vLLM/Ollama/llama.cpp 等) ``` 技术栈:Python 3.10+(FastAPI / httpx / SQLite 标准库 WAL / prometheus-client / PyJWT / pydantic)、 Vue3 + TypeScript + Vite + Element Plus + ECharts + Pinia。 ## 快速开始 ```bash # 1. 依赖(Python 3.10+) pip install -r requirements.txt # 2.(可选)前端构建:产物落 web/dist,由 FastAPI 直接托管 cd web && npm install && npm run build && cd .. # 3. 配置(.env,均可选,默认值见 config/config.py) # ADMIN_PASSWORD / ADMIN_JWT_SECRET 生产环境务必覆盖 # 4. 首次启动引导:config/bootstrap.yaml 定义的服务/模型自动导入(仅当库为空) uvicorn main:app --port 3002 ``` 启动后: - 管理台:`http://localhost:3002/`(登录用 `.env` 中 `ADMIN_PASSWORD`) - OpenAPI 文档:`http://localhost:3002/docs` - Prometheus:`http://localhost:3002/metrics` ## 客户端接入(三种 SDK 均仅改 base_url) ```python # OpenAI SDK from openai import OpenAI client = OpenAI( base_url="http://localhost:3002/v1", api_key="sk-proxy-xxxxxxxx", # 管理台「客户端」页创建,明文仅展示一次 ) # 非流式 resp = client.chat.completions.create(model="deepseek/deepseek-chat", messages=[...]) # 流式 for chunk in client.chat.completions.create(..., stream=True): print(chunk.choices[0].delta.content, end="") # 工具调用:tools / tool_choice / role=tool 回传,全链路支持 # 向量 / 文生图:同一 key 直接可用 client.embeddings.create(model="...", input="...") client.images.generate(model="...", prompt="...", n=1) ``` ```python # Anthropic SDK import anthropic client = anthropic.Anthropic(base_url="http://localhost:3002", api_key="sk-proxy-xxxxxxxx") resp = client.messages.create(model="claude-...", max_tokens=1024, messages=[...]) # 流式 / tool_use / tool_choice 双向翻译 ``` ```python # OpenAI Responses API(openai SDK) resp = client.responses.create(model="...", input="...") # text + tool calls,流式/非流式 ``` 模型名使用公开名(`厂商/模型` 或自定义 `public_name`),支持别名;`GET /v1/models` 列出全部(含能力/模态旗标)。 三种客户端协议下,网关本地错误(400/402/403/404/429)也按对应协议的线格式返回。 ## 端点清单 ### 网关面(客户端 key `sk-proxy-*`) | 端点 | 说明 | | --- | --- | | `POST /v1/chat/completions` | OpenAI 协议:非流式 / SSE 流式 / 伪流式,tool-call、图片输入 | | `POST /v1/messages` | Anthropic 协议:text / tool_use / 流式事件 / usage 双向翻译 | | `POST /v1/responses` | OpenAI Responses 协议:CC↔Responses 互转(text + tool calls) | | `POST /v1/embeddings` | 向量接口(计费仅按输入 token) | | `POST /v1/images/generations` | 文生图:同步透传;异步上游由网关内部提交-轮询-返回(超时 503、失败不计费) | | `GET /v1/models` | 聚合全部注册模型(OpenAI 格式 + 能力/模态旗标、上下文窗口) | ### 登录与用户自助 | 端点 | 鉴权 | 说明 | | --- | --- | --- | | `POST /api/v1/admin/login` | 密码 | 双通道:username 空 = 管理员(.env 密码);非空 = users 表用户(限流、不泄露用户存在性) | | `GET /api/v1/admin/users/me` | user/admin | 当前用户信息 | | `GET /api/v1/admin/users/me/keys` | user/admin | 名下 API Key | | `GET /api/v1/admin/users/me/billing` | user/admin | 本组余额 / 可用模型 / 最近扣减 / 本月账单摘要(只读) | | `GET /api/v1/admin/metrics/usage` | user/admin | 本组用量统计 | | `GET /api/v1/admin/clients` | user/admin | 名下客户端(user 角色仅见本人) | ### 管理面(admin JWT) | 端点 | 说明 | | --- | --- | | `GET /api/v1/admin/catalog` | 厂商目录(27 条:默认 base_url/鉴权样式/协议/模型) | | `GET /api/v1/admin/dashboard/*` | overview/traffic/tokens/cost/models/latency/errors/user-client-usage | | `GET/POST /api/v1/admin/services`、`GET/PUT/DELETE /services/{id}` | 服务 CRUD(key 打码;有模型占用时删除 409) | | `POST /api/v1/admin/services/{id}/test` | 连接测试(凭证 + 模型列表,附失败原因) | | `GET /api/v1/admin/services/{id}/models` | 拉取上游模型列表 | | `GET/POST /api/v1/admin/models`、`PUT/DELETE /models/{name}` | 模型 CRUD(能力/模态旗标、单价、别名、上下文窗口) | | `GET/POST /api/v1/admin/model-groups`、`GET/PUT/DELETE /model-groups/{id}` | 模型组 CRUD(成员模型 + 每模型有序上游候选) | | `GET/POST /api/v1/admin/users`、`GET/PUT/DELETE /users/{id}` | 用户 CRUD(用户名/密码哈希/角色/挂组) | | `GET /api/v1/admin/users/{id}/keys`、`PUT /users/{id}/group` | 用户名下 Key / 挂用户组 | | `GET/POST /api/v1/admin/groups`、`GET/PUT/DELETE /groups/{id}` | 用户组 CRUD(模型访问/并发上限/Token 配额/折扣率/计费开关/授信) | | `GET /api/v1/admin/groups/{id}/statement` | 组账单(模型 × 客户端协议聚合,JSON/CSV) | | `GET /api/v1/admin/pricing`、`PUT /pricing/{model}` | 定价表(每百万 Token 输入/输出/缓存读 + 图像每张 + 币种,版本化) | | `GET /api/v1/admin/billing/balances`、`GET /billing/ledger` | 组余额 / 台账流水(append-only,逐笔查询) | | `POST /api/v1/admin/groups/{id}/charges`、`POST /groups/{id}/reversals` | 台账充值 / 冲正(admin + 审计) | | `POST /api/v1/admin/billing/reconcile` | 对账校验(余额 = 初始 + Σ充值 − Σ扣减) | | `GET /api/v1/admin/billing/org-usage` | 组织用量:组级成本/收入/毛利(JSON/CSV) | | `GET/POST /api/v1/admin/clients`、`PUT/DELETE /clients/{id}` | 客户端 CRUD(创建返回一次性明文 key) | | `POST /api/v1/admin/clients/{id}/disable` | 停用客户端(下一请求即 401) | | `GET/PUT /api/v1/admin/settings` | 系统参数(保存即热生效) | | `GET/POST /api/v1/admin/config/export|import` | 注册表导出/导入(含 key,敏感) | | `GET /api/v1/admin/metrics/requests` | 请求明细查询 | | `GET /api/v1/admin/request-audit` | 请求审计归档(脱敏摘要) | | `GET /api/v1/admin/full-audit`(`/{request_id}`、`/files/{filename}`) | 全量审计(单一目标客户端)列表/详情/文件下载 | | `GET /api/v1/admin/audit-logs` | 管理操作审计日志 | ### 指标与静态 | 端点 | 鉴权 | 说明 | | --- | --- | --- | | `GET /api/v1/metrics/live` | JWT | 实时窗口(QPS/活跃流/token 速率) | | `GET /metrics` | 无 | Prometheus 抓取 | | `GET /`(其余路径) | 无 | Vue 管理台(SPA 兜底;`/api/*`、`/v1/*`、`/metrics` 优先) | 错误统一为 `{"error": {"code", "message"}}`,message 已脱敏(无上游地址/key/堆栈); `/v1/messages` 与 `/v1/responses` 入口的错误按对应客户端协议线格式发出(如 Anthropic 的 type/code/message 结构)。 错误码:`unauthorized`、`invalid_request`、`model_not_found`、`model_not_authorized`(模型未授权)、 `model_modality_mismatch`(跨模态调用)、`protocol_combination_unsupported`、`vision_not_supported`、 `param_not_supported`、`stream_not_supported`、`rate_limit`、`context_length_exceeded`、 `content_filter`、`budget_exceeded`(预算超限 429)、`insufficient_balance`(余额不足 402)、 `upstream_error`、`timeout` 等。 ## 多租户与计费售卖 **三级权限体系**:用户组(Group)→ 用户(User)→ API Key(Key),属性解析顺序固定为 Key → User → Group → 网关默认;存量无组 Key 行为向后兼容。 - 客户端 Key `sk-proxy-*`:库中仅存 SHA-256 哈希,明文仅创建时展示一次;隶属用户,继承用户组属性 - **模型组**:成员模型集合 + 每模型有序上游服务候选(泛化原"模型→服务+降级链");模型恰好属于一个模型组 - **请求时授权**:按继承链解析模型访问白名单,越权 → 403 `model_not_authorized`(不产生上游调用、不扣费) - 角色 admin / user:user 仅能访问本组数据(服务端强制数据范围,越权 403) **计费售卖闭环**(按百万 Token 计价): ``` 定价表(版本化)→ 余额台账(充值/冲正/对账)→ 计量扣减(幂等结算)→ 账单 / 组织用量 ``` - **定价表**:按模型对外价(输入/输出/缓存读每百万 Token 单价 + 图像每张单价 + 币种); 按生效时间版本化,历史计量按当时价格版本计价(改价不改历史) - **余额台账**:组余额 = 初始 + Σ充值 − Σ扣减;流水 append-only、逐笔可查; 充值/冲正仅 admin 且全量审计;管理台"对账"按钮触发重算校验 - **计量扣减**:请求完成(含流式最终 usage)生成 request_id 幂等的计量事件,单事务结算; 调用前预检余额,不足且未开授信 → 402 `insufficient_balance`;全模态全协议路径均计费 - **授信**:组级可选透支额度,余额可负但不超 -额度,授信缩小即刻收紧 - **折扣率**:组级 0–100%,折后金额 = 对外价 × 折扣率(0% = 免费) - **账单**:组级区间账单按模型 × 客户端协议聚合 token 与金额,CSV 导出; user 角色「我的」页自助查看余额、可用模型、最近扣减与账单摘要 - **组织用量**:组级成本(`llm_requests.cost`,上游单价口径)/ 收入(`billing_event.amount`,折后)/ 毛利, 日/月 rollup,CSV 导出与页面数值一致 ## 指标与预算管控 - 每次请求记录:token(prompt/completion/total/**缓存读/写**)、TTFT、延迟、吞吐、重试次数、 归一化错误码、按模型单价即时计算的输入/输出/总成本(币种随模型条目,默认 CNY) - 三级存储:`llm_requests` 明细(保留期自动清理)→ `stats_hourly`/`stats_daily` rollup (客户端×服务×模型,upsert 幂等;指针式补算,重启不丢桶)→ 内存实时窗口(QPS/token 速率/活跃流) - Prometheus:请求计数、token 累计、延迟/TTFT 直方图、成本累计、活跃流、服务可用性、重试/降级计数 - 预算管控:客户端日成本 / 月 token 上限,请求前预检,超限 `429 budget_exceeded` ## 配置与部署 ### 启动级参数(`.env`) | 变量 | 默认 | 说明 | | --- | --- | --- | | `PORT` / `HOST` | `3002` / `0.0.0.0` | 监听地址 | | `DB_PATH` | `llmproxy.db` | SQLite 文件(WAL,单连接) | | `BOOTSTRAP_FILE` | `config/bootstrap.yaml` | 首次启动引导配置 | | `WEB_DIST_DIR` | `web/dist` | 前端产物目录(缺失时根路径返回提示 JSON) | | `FULL_AUDIT_DIR` | `data/full_audit` | 全量审计归档根目录(单一客户端的完整请求/应答文件) | | `ADMIN_PASSWORD` | `admin123` | 管理员密码(生产务必覆盖) | | `ADMIN_JWT_SECRET` | dev 默认 | JWT 签名密钥(生产务必覆盖) | ### 运行期系统参数(管理台「设置」页 / `PUT /api/v1/admin/settings`,热生效) | 参数 | 默认 | 说明 | | --- | --- | --- | | `upstream_timeout_seconds` | 120 | 上游请求超时 | | `retry_max` | 2 | 同服务重试次数(429/5xx/超时) | | `retry_backoff_base_seconds` | 1.0 | 指数退避基数(支持 Retry-After) | | `max_concurrent` | 32 | 网关并发上限 | | `metrics_retention_days` | 30 | 明细保留期(到期自动清理) | | `health_probe_interval_seconds` | 30 | 健康探测周期 | | `param_strategy` | `drop` | 不支持参数策略:`drop` 丢弃 / `strict` 400 | | `fake_stream` | `allow` | 无 SSE 上游:`allow` 包装伪流式 / `reject` 400 | | `audit_requests` | `false` | 请求审计归档开关(脱敏摘要) | | `full_audit_client_id` | `""` | 全量审计目标客户端(空 = 关闭;仅该客户端完整 prompt + 图片 + 应答落盘,其他客户端零审计) | | `full_audit_retention_days` | 7 | 全量审计保留期(超期归档目录 + 表行自动清理) | | `image_poll_interval_seconds` | 2.0 | 文生图异步任务轮询间隔 | | `image_poll_timeout_seconds` | 120 | 文生图异步任务总超时(超时 503,该请求不计费) | | `default_currency` | `CNY` | 站点默认币种(v1 单币种,价表币种强制一致) | > 预留参数:`admission_max_wait_seconds`、`affinity_ttl_seconds`、`affinity_queue_depth_threshold`、 > `affinity_lru_limit`、`classifier_timeout_seconds`(多层并发 / 会话亲和 / 智能路由,见 Roadmap) > 已预注册于 system_settings,功能落地即热生效。 > 全量审计磁盘规划:归档逐字保存完整内容(不截断、不脱敏),单条视觉请求含图片可达数十 MB; > 请按「保留期 × 目标客户端日均流量」预估磁盘占用,敏感内容保留期宜短。 ### 新增厂商 / 协议(四种途径) 1. **目录内厂商**(`services/catalog.py` 已内置 27 条:OpenAI/Anthropic/Gemini/Azure/DeepSeek/千问/Kimi/ GLM/MiniMax/豆包/混元/百度/OpenRouter/Groq/Mistral/Together/Perplexity/xAI 在线厂商与聚合商, vLLM/Ollama/llama.cpp/SGLang/TGI/LMDeploy/LM Studio 自部署后端,`openai_responses` 协议条目, `openai_compatible` 通用兜底):管理台「服务」页选 provider,base_url/鉴权样式自动填充, 填 key 后点「连接测试」即可,再把上游模型注册为网关模型。 2. **OpenAI 兼容的自部署/新厂商**:provider 选 `openai_compatible`,直接填 base_url 与鉴权样式。 3. **新上游协议**:在 `services/` 继承 `BaseLLMAdapter` 实现 `transform_request / transform_response / aiter_chunks / get_error_class / get_supported_openai_params`,并在 `create_adapter` 工厂与 catalog 登记协议名, 补双向翻译 fixture 测试。 4. **新客户端协议**:在 `protocols/` 顶级包实现客户端适配器(请求 decode / 响应·流·错误 encode), 在协议矩阵 `protocols/matrix.py` 登记合法组合并补 fixture。 ### 部署形态 - **生产**:`uvicorn main:app --host 0.0.0.0 --port 3002`(建议置于反代之后,反代需支持 SSE 长连接) - **开发**:后端 `uvicorn main:app --reload --port 3002`;前端 `cd web && npm run dev`(代理到 3002) - 单进程即可承载网关 + 管理台 + 指标;横向扩展时 SQLite 为单写设计,请保持单实例或外接存储 ## 测试 ```bash python -m pytest tests/ -q ``` 全量 474 用例,覆盖:协议双向翻译 fixture(3 客户端协议 × 4 上游协议组合矩阵,含错误向)、 网关集成(真实 uvicorn + OpenAI SDK 直连)、多协议端到端(`test_multivendor_e2e.py`)、 rollup 幂等、审计归档、管理 API、降级链、模型组迁移对照、继承链穷举、请求时授权(403 零上游调用)、 计费结算(幂等/台账对账/授信边界/账单/组织用量)等。 - **性能基线**(`tests/test_perf_billing_baseline.py`,`perf` mark,默认套件排除,CI/发布门显式触发): 50 RPS 持续吞吐、新增流水线 p99 增量 ≤5ms、1000 并发同 key 幂等零重复扣减 ### Web 界面 E2E(Playwright) 管理台 UI 与网关面的端到端测试(`tests/e2e/`),共 **66 用例 / 12 文件**:按 `openspec/specs/` 规格 (8 个 capability)1:1 映射组织用例,另含管理台页面/API 用例与真实上游全链路用例。 ```bash # 依赖(仅首次) npm install && npm run test:e2e:install npm run test:e2e # 无头全量(CI) npm run test:e2e:headed # 有头窗口:可见浏览器自动点击/输入/跳转 npm run test:e2e:ui # Playwright UI 模式:单步/断点/逐用例重跑/查看请求与截图 npm run test:e2e:report # HTML 报告(失败截图/trace) npx playwright codegen http://127.0.0.1:3002 # 手动操作录制生成用例 ``` - **服务自动拉起**:`playwright.config.ts` 的 `webServer` 检测 3002 端口,未运行则自动 启动 uvicorn,跑完自动回收;`workers: 1` 串行,保证真实 DB/限流桶/热设置的确定性。 - **登录双通道**:`login.spec.ts` 走真实 UI 点击(填密码 → 「登录」→ `/dashboard`, 含错误密码/限流 429);其余用例经 fixture API 登录取 JWT 注入 localStorage。 密码从 `.env` 的 `ADMIN_PASSWORD` 读取,测试代码不硬编码。 - **用例 ↔ 规格映射**:`specs/user-auth`、`user-management`、`traffic-stats`、 `dashboard-user-usage`、`request-audit`、`full-client-audit`、`dark-mode`、`menu-collapse` 各对应同名 capability;`gateway-e2e.spec.ts` 覆盖全链路 (建用户+key → `/v1/models` → 真实上游 chat → 热设置全量归档目标 → 请求逐字/信封打码/文件下载校验 → 清空目标停止归档),上游不健康时整文件自动 skip。 - **边界**:DB 明细对账、流式逐块事件流等深度场景由 pytest 集成测试覆盖,E2E 不重复; 可抛测试数据一律 `e2e_` 前缀并在 `afterAll` 清理,不污染真实注册表。 ## Roadmap - **多层并发控制 + 等待队列**:全局/组/Key/上游服务四级并发上限,超限请求 FIFO 排队, 超时 429 + `Retry-After`,排队时长与上游时长指标分离 - **会话亲和**:同一 Key 的连续请求在 TTL 内钉住同一上游服务,提升 prefix cache 命中率, 钉住不可用时软回退常规路由 - **智能路由**:`model=auto` 入口,分类器把简单/复杂请求分流到不同模型组(简单请求走便宜模型降本), 分类失败 fail-open,每次路由决策全量可审计 - **调用前内容合规**:组级敏感词库 + 可选语义检查,命中即 400 `content_policy_violation` 不出网, 拦截事件全量审计(明细仅 admin 可见) ## 文档 - 架构与核心机制:[docs/technical/architecture.md](docs/technical/architecture.md) - 技术栈:[docs/technical/tech-stack.md](docs/technical/tech-stack.md) - 后端规格(API/数据库/鉴权/错误码):[docs/technical/backend-spec.md](docs/technical/backend-spec.md) - 逆向需求分析与产品设计:[docs/reverse-engineering-prd.md](docs/reverse-engineering-prd.md) - PRD 与 Epic/Story 拆分(BMAD):`_bmad-output/planning-artifacts/` - 能力规格(OpenSpec):`openspec/specs/`(14 个 capability)与进行中的变更 `openspec/changes/` - 开发博客:[blog/](blog/)(系列第一篇:系统开发目的和基本功能) - 开发约定与 OpenSpec/Comet 工作流:[AGENTS.md](AGENTS.md)、[CLAUDE.md](CLAUDE.md) - BMAD Method(bmad-code-org 原版插件 `bmad-method` + `bmad-toolbox`):见 [CLAUDE.md](CLAUDE.md)「BMAD 工作流」段