1c7b86e2c0264066201f85e2611436391b125190
Add structured tool event propagation from Lingma stream/finish metadata and map it to OpenAI tool_calls and Anthropic tool_use/tool_result in both streaming and non-streaming responses. Add focused bridge tests and update docs/design notes to match current behavior. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lingma OpenAI Gateway
把本地 Lingma 插件封装成 OpenAI 兼容接口。任何能调 OpenAI 的客户端(Cursor、Dify、LangChain、curl…)都能直接接入。
支持:
- OpenAI 兼容:
GET /v1/models/POST /v1/chat/completions(含 SSE 流式) / Bearer 鉴权 - Anthropic 兼容:
POST /v1/messages(含 Anthropic SSE 事件流) /x-api-key鉴权 - Prometheus / 多账号实例池 / 会话复用(跨两种协议共享) / 免浏览器登录态注入
想看架构、模块划分、设计决策、二开路线图 → 直接读
DESIGN.md。
架构速览
┌─────────────┐ OpenAI 协议 ┌─────────────────────────────────────────┐
│ 任意客户端 │ ───────────▶ │ FastAPI (app/main.py) │
│ (curl/ │ │ ├─ auth_guard / admin_guard │
│ Cursor/ │ │ ├─ chat_guard (InFlightGuard 背压) │
│ Dify…) │ │ ├─ SessionCache (LRU+TTL, KV 复用) │
└─────────────┘ │ └─ StatsCollector + Prometheus │
└────────────────┬────────────────────────┘
│ 选实例 (least-in-flight + affinity)
┌────────────────▼────────────────────────┐
│ LingmaPool (app/lingma_pool.py) │
│ ├─ inst-0 inst-1 inst-N … │
│ └─ 启动前自动 restore session bundle │
└────────────────┬────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ LingmaGatewayClient│ │ … │ │ … │
│ (LSP over WS) │ │ │ │ │
│ ├─ Popen (PID管理) │ │ │ │ │
│ ├─ reconnect loop │ │ │ │ │
│ └─ ws://:PORT │ │ │ │ │
└──────────┬─────────┘ └────────────────────┘ └────────────────────┘
│ spawn + ws
┌──────────▼─────────┐
│ Lingma 二进制 │
│ --workDir /… │
└────────────────────┘
一、快速开始
git clone <repo>
cd lingma-openai-gateway
cp .env.example .env
# 至少填 API_KEYS + LINGMA_USERNAME + LINGMA_PASSWORD(或 session bundle)
mkdir -p data secrets
docker compose up -d --build
docker compose logs -f # 看到 "Uvicorn running on..." 就 OK
冒烟测试:
API_KEY=$(grep '^API_KEYS=' .env | cut -d= -f2 | cut -d, -f1)
curl -s http://127.0.0.1:8317/healthz
curl -s http://127.0.0.1:8317/v1/models -H "Authorization: Bearer $API_KEY"
curl -s http://127.0.0.1:8317/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"org_auto","messages":[{"role":"user","content":"hi"}]}'
二、配置参考
.env.example 是权威说明,这里按主题分组。
2.1 核心
| 变量 | 默认 | 说明 |
|---|---|---|
HOST / PORT |
0.0.0.0 / 8317 |
网关监听地址与端口 |
API_KEYS |
— | Bearer key,多个逗号分隔;留空则 /v1/* 无鉴权,启动会 warn |
LOG_LEVEL |
INFO |
DEBUG/INFO/WARNING/ERROR,日志为结构化 JSON,含 request_id |
DEFAULT_MODEL |
org_auto |
模型无法映射时兜底 |
DEFAULT_ASK_MODE |
chat |
chat 或 agent(传 model: "agent" 时自动切) |
DEDICATED_DOMAIN_URL |
— | 企业专属域(可空) |
2.2 权限分层(生产建议全配)
| 变量 | 默认 | 说明 |
|---|---|---|
ADMIN_TOKEN |
— | /internal/* 专属 token;未配置时 fallback 到 API_KEYS(兼容);都为空 → 503 |
METRICS_TOKEN |
— | /metrics 专属 token;未配置时 fallback 到 API_KEYS |
METRICS_PUBLIC |
false |
显式公开 /metrics(仅用于私网采集器) |
ADMIN_TOKEN/METRICS_TOKEN/API_KEYS三者都为空时,/metrics和/internal/*会返回 503(拒绝裸奔)。
2.3 并发与背压
| 变量 | 默认 | 说明 |
|---|---|---|
GATEWAY_MAX_IN_FLIGHT |
4 |
并发上限;<=0 表示不限 |
GATEWAY_QUEUE_TIMEOUT_SEC |
30 |
排队超时;超时直接返回 429 + Retry-After |
2.4 Lingma 进程
| 变量 | 默认 | 说明 |
|---|---|---|
LINGMA_BIN |
/app/data/bin/Lingma |
容器内二进制路径 |
LINGMA_SOURCE_TYPE |
marketplace |
marketplace 或 vsix |
LINGMA_MARKETPLACE_PUBLISHER |
Alibaba-Cloud |
Marketplace 发布者 |
LINGMA_MARKETPLACE_EXTENSION |
tongyi-lingma |
Marketplace 扩展名 |
LINGMA_VSIX_URL |
官方地址 | 兜底 VSIX 下载地址 |
LINGMA_BOOTSTRAP_ALWAYS |
true |
启动时总是尝试刷新二进制 |
LINGMA_FORCE_REFRESH |
false |
强制忽略本地缓存重新下载 |
LINGMA_WORK_DIR |
/app/data/.lingma/vscode/sharedClientCache |
登录态/缓存所在目录 |
LINGMA_SOCKET_PORT |
36510 |
单实例模式下的 Lingma WS 端口 |
LINGMA_STARTUP_TIMEOUT |
40 |
启动超时秒 |
LINGMA_RPC_TIMEOUT |
30 |
单次 RPC 超时秒 |
2.5 多账号 / 多实例池
| 变量 | 默认 | 说明 |
|---|---|---|
LINGMA_ACCOUNTS |
— | u1:p1,u2:p2 或 JSON 数组;配置后每个账号 = 一个独立 Lingma 子进程 |
LINGMA_INSTANCE_COUNT |
账号数 | 显式指定实例数;不足账号循环复用并打 warn |
LINGMA_USERNAME / LINGMA_PASSWORD |
— | 单实例兼容模式(仅 LINGMA_ACCOUNTS 为空时生效) |
2.6 会话复用(KV cache 优化)
| 变量 | 默认 | 说明 |
|---|---|---|
SESSION_REUSE_ENABLED |
true |
多轮对话命中时只发增量 user 消息 + 复用上游 sessionId |
SESSION_CACHE_MAX_ENTRIES |
256 |
LRU 容量 |
SESSION_CACHE_TTL_SEC |
1800 |
TTL(秒),避免命中已回收的 session |
2.7 登录态注入(跳过 Playwright)
| 变量 | 默认 | 说明 |
|---|---|---|
LINGMA_SESSION_BUNDLE |
— | base64 格式的 bundle(inline,适合短字符串) |
LINGMA_SESSION_BUNDLE_FILE |
— | bundle 文件路径(推荐,避免 env 过长) |
2.8 自动登录
| 变量 | 默认 | 说明 |
|---|---|---|
AUTO_LOGIN_ENABLED |
true |
未登录时自动启 Playwright |
AUTO_LOGIN_HEADLESS |
true |
无头浏览器 |
AUTO_LOGIN_TIMEOUT |
180 |
登录超时秒 |
AUTO_LOGIN_MAX_RETRY |
2 |
登录失败重试次数 |
三、API 参考
3.1 公共(API_KEYS)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /healthz |
免鉴权;返回 ok / pool_size / pool_ready / 每实例状态 |
| GET | /v1/models |
OpenAI 兼容;id 是 Lingma 原 key,name 是可读名 |
| POST | /v1/chat/completions |
OpenAI 兼容;stream=true 走 SSE;model: "agent" 切 agent 模式 |
| POST | /v1/messages |
Anthropic Messages 兼容;x-api-key 或 Authorization: Bearer;stream=true 走 Anthropic 命名事件 SSE |
chat 请求示例(非流式)
curl -s http://127.0.0.1:8317/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"model":"dashscope_qmodel","messages":[{"role":"user","content":"你好"}]}'
chat 请求示例(流式 + usage)
curl -N http://127.0.0.1:8317/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{
"model":"dashscope_qmodel",
"stream":true,
"stream_options":{"include_usage":true},
"messages":[{"role":"user","content":"介绍一下你自己"}]
}'
Anthropic Messages 示例(非流式)
curl -s http://127.0.0.1:8317/v1/messages \
-H "x-api-key: $API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model":"claude-3-5-sonnet-20241022",
"max_tokens":256,
"system":"你是一个简洁的助手",
"messages":[{"role":"user","content":"你好"}]
}'
Anthropic Messages 示例(流式)
curl -N http://127.0.0.1:8317/v1/messages \
-H "x-api-key: $API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model":"claude-3-5-sonnet-20241022",
"max_tokens":256,
"stream":true,
"messages":[{"role":"user","content":"写一首四行诗"}]
}'
# 返回 message_start / content_block_start / content_block_delta* /
# content_block_stop / message_delta / message_stop
说明:
- 模型名兼容:客户端可以继续传
claude-3-*等名字;未识别的 model 会回退到DEFAULT_MODEL对应的 Lingma key,后端实际仍由 Lingma 提供(Qwen 系列)。如需显式选模型,直接传 Lingma key(dashscope_qmodel等)。 - 会话复用共享:Anthropic 与 OpenAI 两个端点共用同一
SessionCache,只要 API key 相同、对话前缀相同,就会命中同一上游sessionId。 - 多模态:
image块会被降级为[image]占位符(Lingma 不支持 vision)。 - 工具事件桥接:当 Lingma 上游返回
tool事件时,网关会输出为 OpenAItool_calls(含 stream/non-stream)和 Anthropictool_use/tool_resultblocks(含 stream/non-stream);但请求侧tools/tool_choice仍不会透传到 Lingma。 - 鉴权:优先
x-api-key头(Anthropic 官方 SDK 默认),回退Authorization: Bearer(方便 curl / OpenAI 风格客户端)。
3.2 观测(METRICS_TOKEN 或 API_KEYS)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /metrics |
Prometheus 文本;含池每实例 gauge、并发、session cache 命中率、token 计数 |
3.3 管理(ADMIN_TOKEN 或 fallback 到 API_KEYS)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /internal/stats |
JSON:stats + concurrency + pool + session_cache |
| GET | /internal/auto-login/status |
每实例登录态与 auto_login 状态 |
| POST | /internal/auto-login/start?instance=inst-0 |
主动触发某实例登录(可不传,由 pool.pick 选) |
| POST | /internal/session/export?instance=inst-0 |
把已登录实例的 cache 打包成 base64 bundle |
| GET | /internal/models/raw?instance=inst-0 |
Lingma 原始 config/queryModels 响应(displayName / isReasoning / isVl 等) |
四、常用场景
4.1 多账号池
LINGMA_ACCOUNTS=user1:pass1,user2:pass2,user3:pass3
# LINGMA_INSTANCE_COUNT=3 # 不写默认=账号数
- 每个账号一个独立 Lingma 子进程 + 独立
workDir(data/.lingma/pool/inst-<i>/)。 - 路由:同
user字段或同 system prompt 的请求粘性分到同一实例;其他按最小在途分配。 - 一个实例挂掉不影响整体,
/healthz.pool_ready下降,自动重连。
4.2 跳过 Playwright(session bundle)
从已登录实例导出:
curl -sS -X POST \
-H "Authorization: Bearer $ADMIN_TOKEN" \
"http://host:port/internal/session/export" \
| jq -r '.bundle_b64' > secrets/lingma-session.b64
chmod 600 secrets/lingma-session.b64
在新部署注入(选一种):
# 文件注入(推荐)—— 需要在 docker-compose.yml 挂载 secrets 目录
LINGMA_SESSION_BUNDLE_FILE=/secrets/lingma-session.b64
# 或 inline(适合小 bundle)
LINGMA_SESSION_BUNDLE=H4sIAAAA...
# 多账号 JSON 模式,每账号独立 bundle
LINGMA_ACCOUNTS=[
{"username":"u1","password":"p1","session_bundle_file":"/secrets/u1.b64"},
{"username":"u2","password":"p2","session_bundle":"H4sIAAAA..."}
]
行为保证:
- 只在目标
workDir空(cache/user不存在或 empty)时才注入;不会覆盖活跃登录态。 - 注入失败(损坏/权限)自动 fallback 到 Playwright。
- bundle 只含
cache/{id,user,quota,config.json}4 个文件;大小上限 4 MiB,实际通常 < 10 KB。 - bundle 等同于密钥,落盘需
chmod 600,不要进 git。
4.3 Prometheus 接入
# prometheus scrape_configs 片段
- job_name: lingma-gateway
bearer_token: <METRICS_TOKEN>
static_configs: [{targets: ['host:8317']}]
metrics_path: /metrics
关键指标:
| 指标 | 类型 | 意义 |
|---|---|---|
gateway_in_flight / gateway_queued |
gauge | 并发 / 排队 |
gateway_rejected_total |
counter | 背压拒绝(429)累计 |
gateway_pool_instance_ready{name} |
gauge | 每实例是否就绪(0/1) |
gateway_pool_instance_in_flight{name} |
gauge | 每实例在途 |
gateway_session_cache_hit_total / _miss_total |
counter | 会话复用命中率原料 |
gateway_chat_requests_success / _error |
counter | chat 成功率 |
五、升级注意事项
从旧版本升级时注意破坏性变更(每一项都有 fallback,默认不会炸,但建议显式配置):
| 版本 | 变更 | 应对 |
|---|---|---|
| v0.3 | /metrics 裸奔时(无 token / 无 key)由公开改为 503 |
显式配 METRICS_PUBLIC=true 或 METRICS_TOKEN |
| v0.3 | /internal/* 引入 ADMIN_TOKEN |
未配置自动 fallback 到 API_KEYS,生产建议单独配 |
| v0.2 | 默认会话复用(多轮对话只发增量) | 如果你的客户端裁剪了历史导致语义不连续,设 SESSION_REUSE_ENABLED=false |
| v0.2 | Chat 请求走 JSON-RPC notify 而非 request(修复 30s TTFB bug) |
无需行动 |
| v0.2 | 多实例池(LINGMA_ACCOUNTS 存在时启用) |
不配则保持单实例行为 |
六、故障排查(FAQ)
| 症状 | 排查方向 |
|---|---|
/healthz 返回 ok=false / pool_ready=0 |
查 docker logs,关键字 lingma spawned / state ... -> ready;若卡在 starting → Lingma 二进制或 workDir 权限问题 |
返回 401 且带 Invalid admin token |
你用了 API_KEYS 去打 /internal/*,但服务端已设了 ADMIN_TOKEN;用 ADMIN_TOKEN 或清空 ADMIN_TOKEN |
返回 503 metrics scraping disabled |
三个 env 全空,按 "权限分层" 章节配任一 |
返回 429 Too many in-flight |
并发超过 GATEWAY_MAX_IN_FLIGHT;增大或客户端加重试 |
| 首 token 延迟 2-3 秒 | Lingma 侧常态;多轮对话第二轮起,会话复用命中后 TTFB 明显降低(看 gateway_session_cache_hit_total) |
| Playwright 登录失败 | 导出一个已登录 bundle 注入(见 4.2),彻底跳过浏览器 |
| 容器重启后 Lingma 要重新登录 | data/ 没挂在卷上或被清过;确认 ./data:/app/data 挂载 + bundle fallback |
升级后 /metrics 返回 503 |
v0.3 默认严格;按表格 5.1 配置 |
开 LOG_LEVEL=DEBUG 可以看到 Lingma 子进程的 stderr 输出,便于定位 native 崩溃。
七、开发与二开
项目本身是单仓 FastAPI,3400 行 Python。推荐阅读路径:
- 先读
DESIGN.md—— 架构、模块职责、关键设计决策、二开指引。 - 再按需读对应模块:
- 想改请求入口 / 路由 →
app/main.py - 想加实例调度策略 →
app/lingma_pool.py::pick() - 想改 Lingma 通信协议 →
app/lingma_client.py - 想扩展会话复用 →
app/session_cache.py+main.py的 reuse 块 - 想做认证改造 →
app/auth.py+main.py::*_guard
- 想改请求入口 / 路由 →
- 本地跑:
pip install -r requirements.txt && uvicorn app.main:app --reload。
八、目录结构
lingma-openai-gateway/
├── app/ # 主代码(见 DESIGN.md 模块一览)
│ ├── main.py # FastAPI 入口 + 路由
│ ├── lingma_pool.py # N 实例池
│ ├── lingma_client.py # LSP over WS + 子进程管理
│ ├── session_cache.py # 多轮对话 sessionId 复用
│ ├── session_bundle.py # 登录态 export/import
│ ├── concurrency.py # InFlightGuard 背压
│ ├── auto_login.py # Playwright 登录
│ ├── auth.py # Bearer / admin / metrics 三档鉴权
│ ├── config.py # 环境变量 → dataclass
│ ├── model_map.py # 模型 key ↔ displayName
│ ├── openai_schema.py # OpenAI 请求/响应 Pydantic
│ ├── stats.py # StatsCollector + Prometheus
│ ├── logging_config.py # 结构化 JSON log + request_id 上下文
│ └── bootstrap_lingma.py # 启动时下载/提取 Lingma 二进制
├── data/ # 持久化(Lingma 二进制 + workDir),不进 git
├── secrets/ # 注入的 bundle 等敏感文件,不进 git
├── Dockerfile # Playwright base + HEALTHCHECK
├── docker-compose.yml
├── .env.example # 配置权威文档
├── requirements.txt
├── README.md # 本文件
└── DESIGN.md # 架构与二开手册
License
内部使用,按需调整。
Description
Languages
Python
98.8%
Shell
0.9%
Dockerfile
0.3%