Upload 13 files
Browse files- README.md +99 -99
- static/models.js +5 -1
- static/style.css +18 -1
README.md
CHANGED
|
@@ -1,100 +1,100 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: NVIDIA NIM 响应网关
|
| 3 |
-
sdk: docker
|
| 4 |
-
app_port: 7860
|
| 5 |
-
pinned: false
|
| 6 |
-
---
|
| 7 |
-
|
| 8 |
-
# NVIDIA NIM 响应网关
|
| 9 |
-
|
| 10 |
-
这是一个面向公开使用的 NVIDIA NIM 到 OpenAI `/v1/responses` 兼容网关。
|
| 11 |
-
|
| 12 |
-
它不在本地保存任何用户的 NIM API Key。用户调用本项目时,需要自己通过请求头携带 NIM Key,网关只负责协议转换、性能优化、聚合统计和官方模型目录展示。
|
| 13 |
-
|
| 14 |
-
## 主要能力
|
| 15 |
-
|
| 16 |
-
- 将 NVIDIA 官方 `POST /v1/chat/completions` 转换为 OpenAI 风格的 `POST /v1/responses`
|
| 17 |
-
- 支持 tool calling / function calling
|
| 18 |
-
- 支持 `function_call_output` 回灌
|
| 19 |
-
- 支持 `previous_response_id` 对话续写
|
| 20 |
-
- 对 `/v1/responses` 和 `/v1/responses/{response_id}` 使用用户自带的 NIM Key 做鉴权与上游转发
|
| 21 |
-
- `/v1/models` 直接返回来自 NVIDIA 官方 `/v1/models` 的同步结果,保持 OpenAI 风格结构
|
| 22 |
-
- `/` 为白色主题的模型健康度页面,按 10 分钟成功率矩阵展示 MODEL_LIST 中的模型
|
| 23 |
-
- `/models` 为独立的白色主题官方模型列表页面,支持按提供商筛选模型
|
| 24 |
-
- 模型提供商卡片为固定高度,避免模型较多时卡片过长
|
| 25 |
-
- 使用共享 HTTP 连接池、SQLite WAL 和异步线程化落库来增强高并发场景下的转发性能
|
| 26 |
-
|
| 27 |
-
## 用户如何调用
|
| 28 |
-
|
| 29 |
-
对于 `POST /v1/responses`,请通过下面任意一种方式传入你自己的 NVIDIA NIM Key:
|
| 30 |
-
|
| 31 |
-
- `Authorization: Bearer <你的 NIM Key>`
|
| 32 |
-
- `X-API-Key: <你的 NIM Key>`
|
| 33 |
-
|
| 34 |
-
网关不会把原始 Key 持久化到数据库中,只会在内存中用于当前请求,并对响应链路使用 Key 哈希做隔离。
|
| 35 |
-
|
| 36 |
-
## 官方模型目录同步
|
| 37 |
-
|
| 38 |
-
项目会定时从官方接口拉取模型列表:
|
| 39 |
-
|
| 40 |
-
`https://integrate.api.nvidia.com/v1/models`
|
| 41 |
-
|
| 42 |
-
同步后的模型目录同时用于:
|
| 43 |
-
|
| 44 |
-
- `GET /v1/models`
|
| 45 |
-
- `GET /models`
|
| 46 |
-
- `GET /api/catalog`
|
| 47 |
-
|
| 48 |
-
## 页面与接口
|
| 49 |
-
|
| 50 |
-
页面:
|
| 51 |
-
|
| 52 |
-
- `GET /`:模型健康度页面
|
| 53 |
-
- `GET /models`:官方模型列表页面
|
| 54 |
-
|
| 55 |
-
前端数据接口:
|
| 56 |
-
|
| 57 |
-
- `GET /api/dashboard`
|
| 58 |
-
- `GET /api/catalog`
|
| 59 |
-
|
| 60 |
-
兼容接口:
|
| 61 |
-
|
| 62 |
-
- `POST /v1/responses`
|
| 63 |
-
- `GET /v1/responses/{response_id}`
|
| 64 |
-
- `GET /v1/models`
|
| 65 |
-
|
| 66 |
-
## 环境变量
|
| 67 |
-
|
| 68 |
-
- `NVIDIA_API_BASE`:默认 `https://integrate.api.nvidia.com/v1`
|
| 69 |
-
- `MODEL_LIST`:健康度页面监控模型列表,逗号分隔
|
| 70 |
-
- `MODEL_SYNC_INTERVAL_MINUTES`:官方模型目录同步周期,默认 `30`
|
| 71 |
-
- `PUBLIC_HISTORY_BUCKETS`:健康页展示最近多少个 10 分钟时间片,默认 `6`
|
| 72 |
-
- `REQUEST_TIMEOUT_SECONDS`:上游请求超时,默认 `90`
|
| 73 |
-
- `MAX_UPSTREAM_CONNECTIONS`:共享连接池最大连接数,默认 `512`
|
| 74 |
-
- `MAX_KEEPALIVE_CONNECTIONS`:共享连接池最大 keep-alive 连接数,默认 `128`
|
| 75 |
-
- `DATABASE_PATH`:默认 `./data.sqlite3`
|
| 76 |
-
|
| 77 |
-
## 本地验证
|
| 78 |
-
|
| 79 |
-
我已经完成两层本地联调:
|
| 80 |
-
|
| 81 |
-
1. Mock 联调:
|
| 82 |
-
- 通过 `scripts/local_smoke_test.py` 验证了协议转换、官方模型同步、用户 Key 鉴权、`previous_response_id`、tool call、健康页数据接口、模型页数据接口和两个独立页面路由。
|
| 83 |
-
|
| 84 |
-
2. 真实上游联调:
|
| 85 |
-
- 通过 `scripts/live_e2e_validation.py` 使用提供的测试 NIM Key,真实调用了 NVIDIA 官方模型目录和实际模型响应。
|
| 86 |
-
- 实测结果:`live_gateway_ok`,并成功通过 `z-ai/glm5` 得到 `OK`。
|
| 87 |
-
|
| 88 |
-
## 部署到 Hugging Face Space
|
| 89 |
-
|
| 90 |
-
1. 新建 Hugging Face Space,SDK 选择 `Docker`
|
| 91 |
-
2. 将 `hf_space` 目录内的内容作为 Space 根目录上传
|
| 92 |
-
3. 按需配置 `MODEL_LIST` 等环境变量
|
| 93 |
-
4. 启动后即可直接公开使用
|
| 94 |
-
|
| 95 |
-
## 参考资料
|
| 96 |
-
|
| 97 |
-
- OpenAI Responses API: https://platform.openai.com/docs/guides/responses-vs-chat-completions
|
| 98 |
-
- OpenAI Function Calling: https://platform.openai.com/docs/guides/function-calling
|
| 99 |
-
- NVIDIA Build: https://build.nvidia.com/
|
| 100 |
- NVIDIA NIM API 文档: https://docs.api.nvidia.com/
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: NVIDIA NIM 响应网关
|
| 3 |
+
sdk: docker
|
| 4 |
+
app_port: 7860
|
| 5 |
+
pinned: false
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# NVIDIA NIM 响应网关
|
| 9 |
+
|
| 10 |
+
这是一个面向公开使用的 NVIDIA NIM 到 OpenAI `/v1/responses` 兼容网关。
|
| 11 |
+
|
| 12 |
+
它不在本地保存任何用户的 NIM API Key。用户调用本项目时,需要自己通过请求头携带 NIM Key,网关只负责协议转换、性能优化、聚合统计和官方模型目录展示。
|
| 13 |
+
|
| 14 |
+
## 主要能力
|
| 15 |
+
|
| 16 |
+
- 将 NVIDIA 官方 `POST /v1/chat/completions` 转换为 OpenAI 风格的 `POST /v1/responses`
|
| 17 |
+
- 支持 tool calling / function calling
|
| 18 |
+
- 支持 `function_call_output` 回灌
|
| 19 |
+
- 支持 `previous_response_id` 对话续写
|
| 20 |
+
- 对 `/v1/responses` 和 `/v1/responses/{response_id}` 使用用户自带的 NIM Key 做鉴权与上游转发
|
| 21 |
+
- `/v1/models` 直接返回来自 NVIDIA 官方 `/v1/models` 的同步结果,保持 OpenAI 风格结构
|
| 22 |
+
- `/` 为白色主题的模型健康度页面,按 10 分钟成功率矩阵展示 MODEL_LIST 中的模型
|
| 23 |
+
- `/models` 为独立的白色主题官方模型列表页面,支持按提供商筛选模型
|
| 24 |
+
- 模型提供商卡片为固定高度,避免模型较多时卡片过长
|
| 25 |
+
- 使用共享 HTTP 连接池、SQLite WAL 和异步线程化落库来增强高并发场景下的转发性能
|
| 26 |
+
|
| 27 |
+
## 用户如何调用
|
| 28 |
+
|
| 29 |
+
对于 `POST /v1/responses`,请通过下面任意一种方式传入你自己的 NVIDIA NIM Key:
|
| 30 |
+
|
| 31 |
+
- `Authorization: Bearer <你的 NIM Key>`
|
| 32 |
+
- `X-API-Key: <你的 NIM Key>`
|
| 33 |
+
|
| 34 |
+
网关不会把原始 Key 持久化到数据库中,只会在内存中用于当前请求,并对响应链路使用 Key 哈希做隔离。
|
| 35 |
+
|
| 36 |
+
## 官方模型目录同步
|
| 37 |
+
|
| 38 |
+
项目会定时从官方接口拉取模型列表:
|
| 39 |
+
|
| 40 |
+
`https://integrate.api.nvidia.com/v1/models`
|
| 41 |
+
|
| 42 |
+
同步后的模型目录同时用于:
|
| 43 |
+
|
| 44 |
+
- `GET /v1/models`
|
| 45 |
+
- `GET /models`
|
| 46 |
+
- `GET /api/catalog`
|
| 47 |
+
|
| 48 |
+
## 页面与接口
|
| 49 |
+
|
| 50 |
+
页面:
|
| 51 |
+
|
| 52 |
+
- `GET /`:模型健康度页面
|
| 53 |
+
- `GET /models`:官方模型列表页面
|
| 54 |
+
|
| 55 |
+
前端数据接口:
|
| 56 |
+
|
| 57 |
+
- `GET /api/dashboard`
|
| 58 |
+
- `GET /api/catalog`
|
| 59 |
+
|
| 60 |
+
兼容接口:
|
| 61 |
+
|
| 62 |
+
- `POST /v1/responses`
|
| 63 |
+
- `GET /v1/responses/{response_id}`
|
| 64 |
+
- `GET /v1/models`
|
| 65 |
+
|
| 66 |
+
## 环境变量
|
| 67 |
+
|
| 68 |
+
- `NVIDIA_API_BASE`:默认 `https://integrate.api.nvidia.com/v1`
|
| 69 |
+
- `MODEL_LIST`:健康度页面监控模型列表,逗号分隔
|
| 70 |
+
- `MODEL_SYNC_INTERVAL_MINUTES`:官方模型目录同步周期,默认 `30`
|
| 71 |
+
- `PUBLIC_HISTORY_BUCKETS`:健康页展示最近多少个 10 分钟时间片,默认 `6`
|
| 72 |
+
- `REQUEST_TIMEOUT_SECONDS`:上游请求超时,默认 `90`
|
| 73 |
+
- `MAX_UPSTREAM_CONNECTIONS`:共享连接池最大连接数,默认 `512`
|
| 74 |
+
- `MAX_KEEPALIVE_CONNECTIONS`:共享连接池最大 keep-alive 连接数,默认 `128`
|
| 75 |
+
- `DATABASE_PATH`:默认 `./data.sqlite3`
|
| 76 |
+
|
| 77 |
+
## 本地验证
|
| 78 |
+
|
| 79 |
+
我已经完成两层本地联调:
|
| 80 |
+
|
| 81 |
+
1. Mock 联调:
|
| 82 |
+
- 通过 `scripts/local_smoke_test.py` 验证了协议转换、官方模型同步、用户 Key 鉴权、`previous_response_id`、tool call、健康页数据接口、模型页数据接口和两个独立页面路由。
|
| 83 |
+
|
| 84 |
+
2. 真实上游联调:
|
| 85 |
+
- 通过 `scripts/live_e2e_validation.py` 使用提供的测试 NIM Key,真实调用了 NVIDIA 官方模型目录和实际模型响应。
|
| 86 |
+
- 实测结果:`live_gateway_ok`,并成功通过 `z-ai/glm5` 得到 `OK`。
|
| 87 |
+
|
| 88 |
+
## 部署到 Hugging Face Space
|
| 89 |
+
|
| 90 |
+
1. 新建 Hugging Face Space,SDK 选择 `Docker`
|
| 91 |
+
2. 将 `hf_space` 目录内的内容作为 Space 根目录上传
|
| 92 |
+
3. 按需配置 `MODEL_LIST` 等环境变量
|
| 93 |
+
4. 启动后即可直接公开使用
|
| 94 |
+
|
| 95 |
+
## 参考资料
|
| 96 |
+
|
| 97 |
+
- OpenAI Responses API: https://platform.openai.com/docs/guides/responses-vs-chat-completions
|
| 98 |
+
- OpenAI Function Calling: https://platform.openai.com/docs/guides/function-calling
|
| 99 |
+
- NVIDIA Build: https://build.nvidia.com/
|
| 100 |
- NVIDIA NIM API 文档: https://docs.api.nvidia.com/
|
static/models.js
CHANGED
|
@@ -84,6 +84,9 @@ function renderProviders() {
|
|
| 84 |
</div>
|
| 85 |
`;
|
| 86 |
|
|
|
|
|
|
|
|
|
|
| 87 |
const list = document.createElement("div");
|
| 88 |
list.className = "provider-model-list";
|
| 89 |
(group.models || []).forEach((model) => {
|
|
@@ -94,7 +97,8 @@ function renderProviders() {
|
|
| 94 |
list.appendChild(chip);
|
| 95 |
});
|
| 96 |
|
| 97 |
-
|
|
|
|
| 98 |
providerGrid.appendChild(card);
|
| 99 |
});
|
| 100 |
}
|
|
|
|
| 84 |
</div>
|
| 85 |
`;
|
| 86 |
|
| 87 |
+
const body = document.createElement("div");
|
| 88 |
+
body.className = "provider-card-body";
|
| 89 |
+
|
| 90 |
const list = document.createElement("div");
|
| 91 |
list.className = "provider-model-list";
|
| 92 |
(group.models || []).forEach((model) => {
|
|
|
|
| 97 |
list.appendChild(chip);
|
| 98 |
});
|
| 99 |
|
| 100 |
+
body.appendChild(list);
|
| 101 |
+
card.appendChild(body);
|
| 102 |
providerGrid.appendChild(card);
|
| 103 |
});
|
| 104 |
}
|
static/style.css
CHANGED
|
@@ -416,10 +416,15 @@ body {
|
|
| 416 |
padding: 20px;
|
| 417 |
display: flex;
|
| 418 |
flex-direction: column;
|
|
|
|
|
|
|
| 419 |
}
|
| 420 |
|
| 421 |
.provider-card-fixed {
|
| 422 |
height: 320px;
|
|
|
|
|
|
|
|
|
|
| 423 |
}
|
| 424 |
|
| 425 |
.provider-card-head {
|
|
@@ -427,6 +432,7 @@ body {
|
|
| 427 |
justify-content: space-between;
|
| 428 |
align-items: flex-start;
|
| 429 |
gap: 12px;
|
|
|
|
| 430 |
}
|
| 431 |
|
| 432 |
.provider-card h3 {
|
|
@@ -438,13 +444,24 @@ body {
|
|
| 438 |
margin: 10px 0 0;
|
| 439 |
}
|
| 440 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
.provider-model-list {
|
| 442 |
margin-top: 18px;
|
| 443 |
display: flex;
|
|
|
|
| 444 |
flex-direction: column;
|
| 445 |
gap: 10px;
|
|
|
|
| 446 |
overflow-y: auto;
|
| 447 |
-
|
|
|
|
|
|
|
| 448 |
}
|
| 449 |
|
| 450 |
.provider-model-chip {
|
|
|
|
| 416 |
padding: 20px;
|
| 417 |
display: flex;
|
| 418 |
flex-direction: column;
|
| 419 |
+
align-self: stretch;
|
| 420 |
+
min-height: 0;
|
| 421 |
}
|
| 422 |
|
| 423 |
.provider-card-fixed {
|
| 424 |
height: 320px;
|
| 425 |
+
min-height: 320px;
|
| 426 |
+
max-height: 320px;
|
| 427 |
+
overflow: hidden;
|
| 428 |
}
|
| 429 |
|
| 430 |
.provider-card-head {
|
|
|
|
| 432 |
justify-content: space-between;
|
| 433 |
align-items: flex-start;
|
| 434 |
gap: 12px;
|
| 435 |
+
flex: 0 0 auto;
|
| 436 |
}
|
| 437 |
|
| 438 |
.provider-card h3 {
|
|
|
|
| 444 |
margin: 10px 0 0;
|
| 445 |
}
|
| 446 |
|
| 447 |
+
.provider-card-body {
|
| 448 |
+
display: flex;
|
| 449 |
+
flex: 1 1 auto;
|
| 450 |
+
min-height: 0;
|
| 451 |
+
flex-direction: column;
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
.provider-model-list {
|
| 455 |
margin-top: 18px;
|
| 456 |
display: flex;
|
| 457 |
+
flex: 1 1 auto;
|
| 458 |
flex-direction: column;
|
| 459 |
gap: 10px;
|
| 460 |
+
min-height: 0;
|
| 461 |
overflow-y: auto;
|
| 462 |
+
overflow-x: hidden;
|
| 463 |
+
padding-right: 6px;
|
| 464 |
+
scrollbar-gutter: stable;
|
| 465 |
}
|
| 466 |
|
| 467 |
.provider-model-chip {
|