cacodex commited on
Commit
5217381
·
verified ·
1 Parent(s): 2c073e0

Upload 13 files

Browse files
Files changed (3) hide show
  1. README.md +99 -99
  2. static/models.js +5 -1
  3. 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
- card.appendChild(list);
 
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
- padding-right: 4px;
 
 
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 {