dsh-plugin-litellm-bridge 0.0.0-stage → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -2
- package/docs/README.md +716 -0
- package/dsh-plugin-litellm-bridge/README.md +32 -0
- package/dsh-plugin-litellm-bridge/client/index.js +1336 -0
- package/dsh-plugin-litellm-bridge/cordis.patch.yml +27 -0
- package/dsh-plugin-litellm-bridge/icon.svg +12 -0
- package/dsh-plugin-litellm-bridge/lib/assemble-chain.js +193 -0
- package/dsh-plugin-litellm-bridge/lib/config-store.js +290 -0
- package/dsh-plugin-litellm-bridge/lib/gateway-client.js +587 -0
- package/dsh-plugin-litellm-bridge/lib/gateway-supervisor.js +518 -0
- package/dsh-plugin-litellm-bridge/lib/index.js +760 -0
- package/dsh-plugin-litellm-bridge/lib/model-pool.js +248 -0
- package/dsh-plugin-litellm-bridge/lib/probe-log.js +102 -0
- package/dsh-plugin-litellm-bridge/lib/subagent-routing.js +280 -0
- package/dsh-plugin-litellm-bridge/locale/en.json +43 -0
- package/dsh-plugin-litellm-bridge/locale/zh.json +43 -0
- package/dsh-plugin-litellm-bridge/manifest.json +57 -0
- package/package.json +73 -4
package/docs/README.md
ADDED
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
# dsh-fallback-auto-pool-arch
|
|
2
|
+
|
|
3
|
+
DSH 插件 **litellm-bridge**:一个 LiteLLM 网关桥接面板。
|
|
4
|
+
|
|
5
|
+
> **核心定位**:本插件**不内置任何路由、重试、降级逻辑**。降级能力 100% 交给外部
|
|
6
|
+
> LiteLLM 网关,插件全部通过 LiteLLM 官方 REST API 与网关交互,只做四件事:
|
|
7
|
+
> 读配置、写配置、看日志、发探针。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 目录
|
|
12
|
+
|
|
13
|
+
- [1. 它解决什么](#1-它解决什么)
|
|
14
|
+
- [2. 架构与职责边界](#2-架构与职责边界)
|
|
15
|
+
- [3. 仓库结构](#3-仓库结构)
|
|
16
|
+
- [4. 密钥方案(重点)](#4-密钥方案重点)
|
|
17
|
+
- [5. 快速开始](#5-快速开始)
|
|
18
|
+
- [5.1 启动 LiteLLM 网关](#51-启动-litellm-网关)
|
|
19
|
+
- [5.2 跑链路探针](#52-跑链路探针)
|
|
20
|
+
- [5.3 导入 DSH 插件](#53-导入-dsh-插件)
|
|
21
|
+
- [6. 四个 Tab 使用说明](#6-四个-tab-使用说明)
|
|
22
|
+
- [7. 与 header 注入插件联动](#7-与-header-注入插件联动)
|
|
23
|
+
- [8. 测试流程](#8-测试流程)
|
|
24
|
+
- [9. 推送到 GitHub](#9-推送到-github)
|
|
25
|
+
- [10. 已知限制与设计决策](#10-已知限制与设计决策)
|
|
26
|
+
- [11. 排错](#11-排错)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. 它解决什么
|
|
31
|
+
|
|
32
|
+
在 DSH 里用多个模型供应商时,典型痛点是:
|
|
33
|
+
|
|
34
|
+
- 每家供应商的 key、base_url、超时散落在各处,改一处要翻好几个文件;
|
|
35
|
+
- 想要「主模型挂了自动换备选」,就得在插件里实现路由/重试/冷却 —— 逻辑重复、
|
|
36
|
+
容易和别的降级插件打架;
|
|
37
|
+
- 出了故障不知道到底是哪一层的问题。
|
|
38
|
+
|
|
39
|
+
本方案的答案是:**把这些全部推给 LiteLLM 网关**。LiteLLM 是成熟的开源 LLM 网关,
|
|
40
|
+
自带 fallback、重试、负载均衡、日志与花费统计。DSH 这边只留一个配置面板,
|
|
41
|
+
负责把「你想怎么降级」翻译成网关的 YAML,并调用网关的热重载接口。
|
|
42
|
+
|
|
43
|
+
好处:
|
|
44
|
+
|
|
45
|
+
- **DSH 侧零降级逻辑** —— 不会和 `dsh-llm-fallbacks` 之类的插件产生两套降级互相冲突;
|
|
46
|
+
- **密钥只在一个地方**(网关侧的 `.env`),DSH 插件永远不碰明文密钥;
|
|
47
|
+
- **可观测** —— 网关日志统一,插件只是把它拉过来展示。
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 2. 架构与职责边界
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ DSH │
|
|
56
|
+
│ │
|
|
57
|
+
│ ┌────────────────────────┐ ┌──────────────────────────┐ │
|
|
58
|
+
│ │ dsh-plugin- │ │ dsh-plugin-header-inject │ │
|
|
59
|
+
│ │ litellm-bridge │ │ │ │
|
|
60
|
+
│ │ │ │ 读会话 profile 变量 │ │
|
|
61
|
+
│ │ · 网关设置 Tab │ │ 注入 x-agent-type │ │
|
|
62
|
+
│ │ · 路由配置 Tab │ │ x-session-profile │ │
|
|
63
|
+
│ │ · 日志面板 Tab │ │ │ │
|
|
64
|
+
│ │ · 当前会话 Tab │ │ (不认识 LiteLLM) │ │
|
|
65
|
+
│ │ │ │ │ │
|
|
66
|
+
│ │ 写会话 profile 变量 ────┼──►│ │ │
|
|
67
|
+
│ └───────────┬────────────┘ └────────────┬─────────────┘ │
|
|
68
|
+
│ │ │ │
|
|
69
|
+
└──────────────┼──────────────────────────────┼─────────────────┘
|
|
70
|
+
│ LiteLLM REST API │ 出站 LLM 请求头
|
|
71
|
+
│ (/health, /v1/models, │
|
|
72
|
+
│ /spool/logs, /config/reload)│
|
|
73
|
+
▼ ▼
|
|
74
|
+
┌────────────────────────────────────────────────┐
|
|
75
|
+
│ LiteLLM 网关 (litellm --config config.yaml) │
|
|
76
|
+
│ │
|
|
77
|
+
│ ★ 全部路由 / 重试 / 降级 / 冷却 在这里 │
|
|
78
|
+
│ ★ 所有供应商密钥来自 .env(os.environ/XXX) │
|
|
79
|
+
└───────────────────┬─────────────────────────────┘
|
|
80
|
+
▼
|
|
81
|
+
Agnes / 智谱 / NVIDIA / 硅基流动 / AMD …
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**三条硬边界**(决定了两套降级逻辑不会冲突):
|
|
85
|
+
|
|
86
|
+
| 层 | 负责 | 不负责 |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `litellm-bridge` 插件 | 读写配置、拉日志、发探针、写会话变量 | 路由、重试、降级、选模型 |
|
|
89
|
+
| `header-inject` 插件 | 注入 HTTP 头(只读会话变量) | 认识 LiteLLM、写变量 |
|
|
90
|
+
| LiteLLM 网关 | **全部**路由/重试/降级/负载均衡 | UI、DSH 集成 |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 3. 仓库结构
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
dsh-fallback-auto-pool-arch/
|
|
98
|
+
├── dsh-plugin-litellm-bridge/ # ① DSH 桥接插件本体
|
|
99
|
+
│ ├── package.json # dsh.bundle.patch + dsh.client 声明
|
|
100
|
+
│ ├── cordis.patch.yml # 挂进 profile 组合树的补丁
|
|
101
|
+
│ ├── icon.svg
|
|
102
|
+
│ ├── manifest.json # 人类可读的插件描述(见下方说明)
|
|
103
|
+
│ ├── lib/
|
|
104
|
+
│ │ ├── index.js # 宿主半:apply() + RPC 路由
|
|
105
|
+
│ │ ├── gateway-client.js # LiteLLM REST 客户端(无降级逻辑)
|
|
106
|
+
│ │ ├── config-store.js # 配置持久化 + YAML 组装
|
|
107
|
+
│ │ ├── probe-log.js # 探针失败日志环形缓冲
|
|
108
|
+
│ │ └── subagent-routing.js # 子代理路由覆盖(internal/get seam)
|
|
109
|
+
│ ├── client/
|
|
110
|
+
│ │ └── index.js # 客户端半(4 个 Tab + 内联设计系统)
|
|
111
|
+
│ └── locale/{zh,en}.json
|
|
112
|
+
│
|
|
113
|
+
├── dsh-plugin-header-inject/ # ② 出站请求头注入插件(解耦)
|
|
114
|
+
│ ├── package.json
|
|
115
|
+
│ ├── cordis.patch.yml
|
|
116
|
+
│ └── lib/index.js
|
|
117
|
+
│
|
|
118
|
+
├── litellm-gateway/ # ③ 网关侧
|
|
119
|
+
│ ├── config.yaml.template # 用 ${VAR} / os.environ/ 引用密钥
|
|
120
|
+
│ ├── start-gateway.sh # 加载 .env → 生成 config.yaml → 启动
|
|
121
|
+
│ └── test-probe.py # 链路探针(6 项检查)
|
|
122
|
+
│
|
|
123
|
+
├── tests/
|
|
124
|
+
│ ├── run-all.mjs # 跑全部自测
|
|
125
|
+
│ ├── host-half.test.mjs # 宿主半逻辑自测(34 项断言)
|
|
126
|
+
│ ├── subagent-routing.test.mjs # 子代理路由自测(58 项断言)
|
|
127
|
+
│ └── client-slots.test.mjs # 客户端槽位契约自测(39 项断言)
|
|
128
|
+
│ route-contract.test.mjs # RPC 路由契约(26 项)
|
|
129
|
+
│ model-sync-batch.test.mjs # 需求9+10 自测(38 项)
|
|
130
|
+
│
|
|
131
|
+
├── docs/
|
|
132
|
+
│ └── README.md # 本文档
|
|
133
|
+
│
|
|
134
|
+
├── .env.example # 键名清单(无真实值)
|
|
135
|
+
└── .gitignore
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### 3.1 关于 `manifest.json`(与推荐结构的差异说明)
|
|
141
|
+
|
|
142
|
+
推荐目录结构里列了 `manifest.json`,但**DSH 的 Cordis 插件加载并不读它**。
|
|
143
|
+
|
|
144
|
+
实际验证(对照本机已安装的线上插件):
|
|
145
|
+
|
|
146
|
+
| 插件 | manifest.json | package.json 的 `dsh` 字段 |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| dsh-llm-fallbacks | ❌ 无 | ✅ 有 |
|
|
149
|
+
| dsh-context | ❌ 无 | ✅ 有 |
|
|
150
|
+
| dsh-browser | ❌ 无 | ✅ 有 |
|
|
151
|
+
| dsh-qq-plugin | ❌ 无 | ✅ 有 |
|
|
152
|
+
|
|
153
|
+
**真正的加载契约全在 `package.json`**:
|
|
154
|
+
|
|
155
|
+
| 字段 | 作用 |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `dsh.bundle.patch` | 指向 `cordis.patch.yml`,决定如何挂进 profile 组合树 |
|
|
158
|
+
| `dsh.client.platform` / `.inject` | 声明客户端半运行平台与依赖 |
|
|
159
|
+
| `exports['./client']` | 客户端模块入口(`__ModuleLoader__` 格式) |
|
|
160
|
+
| `exports['.']` | 宿主半入口(`export function apply` / `name` / `provide`) |
|
|
161
|
+
|
|
162
|
+
所以本仓库的 `manifest.json` 是**额外附赠的人类可读描述**(含用途、能力、
|
|
163
|
+
非目标、配套插件),供人查阅与清单工具使用;**删掉它不影响插件加载**。
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
## 4. 密钥方案(重点)
|
|
167
|
+
|
|
168
|
+
**要求**:所有模型密钥、npm 令牌统一从 `D:\AI agent cache\dsh\.env` 读取;
|
|
169
|
+
`.env` 只在网关服务侧加载;插件 UI **绝对不读取、不回显任何明文密钥**,
|
|
170
|
+
界面仅展示标记 **【来自 .env 环境变量】**。
|
|
171
|
+
|
|
172
|
+
**落地方式**:
|
|
173
|
+
|
|
174
|
+
| 环节 | 做法 |
|
|
175
|
+
|---|---|
|
|
176
|
+
| 密钥存放 | `D:\AI agent cache\dsh\.env`(**永不提交**,已在 `.gitignore`) |
|
|
177
|
+
| 谁加载 | `litellm-gateway/start-gateway.sh` —— `set -a` + 逐行解析后 `export` |
|
|
178
|
+
| yaml 里 | 全部写 `os.environ/XXX`,**零硬编码** |
|
|
179
|
+
| 插件读密钥? | **不读**。宿主半只从进程环境取 `LITELLM_MASTER_KEY`,用于访问网关 admin API |
|
|
180
|
+
| 插件存密钥? | **不存**。`state.json` 只有地址/超时/路由,无任何 key 字段 |
|
|
181
|
+
| 插件回显? | **不回显**。UI 只显示徽章「来自 .env 环境变量」 |
|
|
182
|
+
|
|
183
|
+
代码层的三道硬保证(`tests/host-half.test.mjs` 有对应断言):
|
|
184
|
+
|
|
185
|
+
1. **私有字段**:`GatewayClient` 的 key 存在 `#apiKey` 里,不是 `_apiKey` 约定。
|
|
186
|
+
`JSON.stringify(client)`、`{...client}`、`Object.keys(client)`、`console.log(client)`
|
|
187
|
+
都**拿不到**明文。
|
|
188
|
+
2. **强制脱敏**:任何抛给 UI 的错误文本都过 `redact()`,把 `sk-xxxx` 替换成
|
|
189
|
+
`sk-***REDACTED***`;即使某天误把 key 拼进错误消息,也不会漏出去。
|
|
190
|
+
3. **只报存在性**:`gateway/keyStatus` 只返回 `{ hasKey: true/false, source }`,
|
|
191
|
+
结构上就没有回传值的字段。
|
|
192
|
+
|
|
193
|
+
> ⚠️ 注意区分:**插件侧**不读 `.env`(那是网关侧的事)。插件从**环境变量**取
|
|
194
|
+
> master key —— 这个环境变量是你启动 DSH 前由网关脚本或系统注入的。
|
|
195
|
+
> 若 DSH 与网关不在同一台机器,请在启动 DSH 的环境里也设置 `LITELLM_MASTER_KEY`。
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 5. 快速开始
|
|
200
|
+
|
|
201
|
+
### 5.1 启动 LiteLLM 网关
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# 1) 安装(一次即可)
|
|
205
|
+
python -m pip install 'litellm[proxy]'
|
|
206
|
+
|
|
207
|
+
# 2) 准备 .env(可用仓库里的 .env.example 作模板)
|
|
208
|
+
# Windows 默认路径:D:\AI agent cache\dsh\.env
|
|
209
|
+
# 至少要有:LITELLM_MASTER_KEY=sk-<足够长的随机串>
|
|
210
|
+
|
|
211
|
+
# 3) 启动
|
|
212
|
+
cd litellm-gateway
|
|
213
|
+
chmod +x start-gateway.sh
|
|
214
|
+
./start-gateway.sh
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
脚本会依次:找到 `.env` → 注入环境变量 → 由模板生成 `config.yaml` →
|
|
218
|
+
校验关键变量 → 启动 `litellm`。启动后:
|
|
219
|
+
|
|
220
|
+
- 网关: <http://127.0.0.1:4000>
|
|
221
|
+
- Admin UI: <http://127.0.0.1:4000/ui>
|
|
222
|
+
|
|
223
|
+
自定义:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
./start-gateway.sh --port 4100 # 换端口
|
|
227
|
+
LITELLM_BRIDGE_ENV=/path/to/.env ./start-gateway.sh # 指定 .env
|
|
228
|
+
FORCE_REGEN=1 ./start-gateway.sh # 强制重生成 config.yaml
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 5.2 跑链路探针
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
export LITELLM_MASTER_KEY=sk-... # 与 .env 中一致
|
|
235
|
+
python litellm-gateway/test-probe.py
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
探针检查 6 件事并逐项打勾/打叉:
|
|
239
|
+
|
|
240
|
+
1. 网关存活 `/health/liveliness`
|
|
241
|
+
2. 网关就绪 + 数据库状态 `/health/readiness`
|
|
242
|
+
3. master admin key 有效性 `/v1/models`
|
|
243
|
+
4. 模型元信息 `/v1/model/info`
|
|
244
|
+
5. **热重载端点探测** —— 逐个试 `/config/reload`、`/reload/config`、`/config/update`,
|
|
245
|
+
报告**哪一个真正可用**
|
|
246
|
+
6. 真实补全 + header 透传(带 `x-agent-type` / `x-session-profile`)
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
python litellm-gateway/test-probe.py --model coder-primary --agent-type coder
|
|
250
|
+
python litellm-gateway/test-probe.py --json # 机器可读
|
|
251
|
+
python litellm-gateway/test-probe.py --skip-completion # 不花钱
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 5.3 导入 DSH 插件
|
|
255
|
+
|
|
256
|
+
> **前置条件(重要)**:先在插件管理器里**禁用 `dsh-llm-fallbacks`**。
|
|
257
|
+
> 两套降级逻辑同时存在会互相冲突 —— 这也是本项目的验收规则之一。
|
|
258
|
+
|
|
259
|
+
**方式 A:插件管理器(推荐)**
|
|
260
|
+
|
|
261
|
+
在 DSH → 设置 → 插件 → 添加,选择本地目录
|
|
262
|
+
`dsh-plugin-litellm-bridge` 的**绝对路径**,安装后启用。
|
|
263
|
+
|
|
264
|
+
**方式 B:手工安装(当作最后手段)**
|
|
265
|
+
|
|
266
|
+
```powershell
|
|
267
|
+
# 1) 完全退出 DSH(所有进程,不是关窗口)
|
|
268
|
+
Get-Process | Where-Object { $_.ProcessName -like '*DeepSeek*' } | Stop-Process -Force
|
|
269
|
+
|
|
270
|
+
# 2) 备份 profile 补丁
|
|
271
|
+
$p = "D:\AI agent cache\dsh\profiles\desktop"
|
|
272
|
+
Copy-Item "$p\cordis.patch.yml" "$p\cordis.patch.yml.bak" -Force
|
|
273
|
+
|
|
274
|
+
# 3) 以 file: 依赖安装(路径必须绝对)
|
|
275
|
+
Set-Location $p
|
|
276
|
+
pnpm add "file:D:/AI sandbox/dsh-sandbox/dsh-fallback-auto-pool-arch/dsh-plugin-litellm-bridge"
|
|
277
|
+
|
|
278
|
+
# 4) 确认包体完整(文件数 > 0,且 package.json 存在)
|
|
279
|
+
Get-ChildItem "$p\node_modules\dsh-plugin-litellm-bridge" -Recurse | Measure-Object
|
|
280
|
+
|
|
281
|
+
# 5) 把包名加进 package.json 的 dsh.profile.bundles 数组
|
|
282
|
+
# "dsh-plugin-litellm-bridge"
|
|
283
|
+
|
|
284
|
+
# 6) 检查 pnpm-workspace.yaml 的 allowBuilds 有没有
|
|
285
|
+
# "set this to true or false" 占位符,有就改成 true 或 false
|
|
286
|
+
# 7) 重启 DSH
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
同样的步骤安装 `dsh-plugin-header-inject`。
|
|
290
|
+
|
|
291
|
+
> ⚠️ **绝不**直接把目录 `Copy-Item` 进 `node_modules` —— 那会破坏 pnpm 的
|
|
292
|
+
> `.modules.yaml` 状态,曾导致整个 profile 的插件被清空。
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 6. 四个 Tab 使用说明
|
|
297
|
+
|
|
298
|
+
### Tab 1 · 网关设置
|
|
299
|
+
|
|
300
|
+
- **网关地址**:LiteLLM 的地址,默认 `http://127.0.0.1:4000`
|
|
301
|
+
- **master admin key**:**不可编辑**,只显示徽章【来自 .env 环境变量】+
|
|
302
|
+
「已注入 / 未检测到」
|
|
303
|
+
- **测试连通性**:三层依次探测(存活 → 就绪 → key 有效性),**分层报告**,
|
|
304
|
+
不会把「服务活着」和「key 有效」混为一谈
|
|
305
|
+
|
|
306
|
+
#### 模型清单与连通性(自动同步 + 一键批量测试)
|
|
307
|
+
|
|
308
|
+
**① 自动同步模型清单** —— 不用手填模型名。
|
|
309
|
+
|
|
310
|
+
点「🔄 从网关同步」后,插件并发拉取网关的两个接口并合并:
|
|
311
|
+
|
|
312
|
+
| 接口 | 提供什么 |
|
|
313
|
+
|---|---|
|
|
314
|
+
| `GET /v1/model/info` | `model_name`、底层 `litellm_params.model`、`api_base`、`timeout` |
|
|
315
|
+
| `GET /v1/models` | 兜底补齐只在对外清单里出现的模型 |
|
|
316
|
+
|
|
317
|
+
同步结果直接喂给「路由配置」Tab 的模型联想框,同时展示每条的
|
|
318
|
+
**provider 标签**(agnes / zhipuai / nvidia / siliconflow / amd / deepseek)。
|
|
319
|
+
|
|
320
|
+
> 还会主动**标红「配置异常」**:当 `litellm_params.model` 为空或形如 `openai/`
|
|
321
|
+
> 时(通常意味 `${XXX_MODEL}` 环境变量未设置),该模型会在请求时必然失败 ——
|
|
322
|
+
> 实测踩过,所以把它显式标出来,而不是等你调用时才发现。
|
|
323
|
+
|
|
324
|
+
**② 一键批量连通性测试**
|
|
325
|
+
|
|
326
|
+
先按**供应商筛选**(下拉,显示每个供应商的模型数),勾选模型(支持全选/全不选),→ 点「⚡ 一键连通性测试」,插件对每个模型依次发起
|
|
327
|
+
轻量探针请求。结果可视化展示:
|
|
328
|
+
|
|
329
|
+
- ✅/❌ 成功/失败
|
|
330
|
+
- **响应耗时**(按快慢着色:<5s 绿 / <20s 黄 / 更长红)
|
|
331
|
+
- 成功项的回复内容、失败项的**具体报错**
|
|
332
|
+
- 汇总:成功数 / 失败数 / 总数 / 并发度
|
|
333
|
+
|
|
334
|
+
失败记录会**自动写入日志面板**(来源标记 `local`),可在「日志面板」Tab 查看。
|
|
335
|
+
|
|
336
|
+
> **并发与超时是实测调出来的,不是拍脑袋**:
|
|
337
|
+
> 11 个模型全串行要 **398 秒**(上游 agnes/zhipu 单次就要 20–90s);
|
|
338
|
+
> 改成并发 3 + 30s 超时反而更糟 —— 慢模型互相争抢,11 个里 7 个因超时被误判失败,
|
|
339
|
+
> 还触发上游 **429 限流**。最终默认 **并发 2 + 单模型 60s**,
|
|
340
|
+
> 这两个值都可从 UI/调用方覆盖。
|
|
341
|
+
|
|
342
|
+
### Tab 2 · 路由配置
|
|
343
|
+
|
|
344
|
+
- 顶部切换 **profile-a / profile-b**
|
|
345
|
+
- 每个 profile 下对 **root / coder / research** 三类 agent 分别配置:
|
|
346
|
+
- **首选模型**(带模型名联想,来自 `/v1/models`)
|
|
347
|
+
- **超时时间**(毫秒)
|
|
348
|
+
- **fallback 备选模型列表** —— **可拖拽排序,顺序即降级优先级**
|
|
349
|
+
- 三个按钮:
|
|
350
|
+
- **仅保存** → 写入插件配置
|
|
351
|
+
- **预览 YAML** → 看将要下发的片段
|
|
352
|
+
- **保存并热重载** → 组装 YAML → 调网关热重载接口 → 回报**实际用了哪个端点**
|
|
353
|
+
|
|
354
|
+
### Tab 3 · 日志面板
|
|
355
|
+
|
|
356
|
+
- 从网关拉调用日志(`/spend/logs`,失败自动回退 `/global/spend/logs`)
|
|
357
|
+
- 按 **agent-type** 和 **profile** 下拉筛选
|
|
358
|
+
- **橙色** = 触发 fallback;**红色** = 全链路失败
|
|
359
|
+
- **默认只保留最近 50 条**(可在配置里调,上限 500),防止面板臃肿
|
|
360
|
+
- 判定不出来时标 `unknown` —— **不猜**,猜错会让高亮失去意义
|
|
361
|
+
|
|
362
|
+
### Tab 4 · 当前会话
|
|
363
|
+
|
|
364
|
+
- **会话绑定 profile 下拉选择器** → 保存进**会话变量**
|
|
365
|
+
(键名 `litellm-bridge/session-profile`)
|
|
366
|
+
- **本会话最近降级记录**:从日志里筛出 fallback / 失败行
|
|
367
|
+
- **一键探针测试**:自动发一条带 header 的测试请求,验证
|
|
368
|
+
「插件 → 网关 → 上游」整条链路 + header 透传
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 6.5 子代理路由覆盖
|
|
373
|
+
|
|
374
|
+
**需求**:当会话选中的供应商是 Trae / WorkBuddy 时,**root 主 Agent 保留原供应商**
|
|
375
|
+
(继续消耗 Trae/WorkBuddy 积分),但**所有 spawn 出来的子 Agent 强制覆盖为
|
|
376
|
+
LitellM-Gateway**,自动读会话 profile 并注入 header,走网关降级池;
|
|
377
|
+
当会话选中的供应商是 LitellM-Gateway 时,主 Agent、子 Agent 全部正常走网关链路。
|
|
378
|
+
|
|
379
|
+
### 行为矩阵
|
|
380
|
+
|
|
381
|
+
| 会话选中的供应商 | root 主 Agent | 子 Agent |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| `trae` / `workbuddy`(含 `-cn` 等后缀) | **保持原样**(消耗积分) | **强制改道 `litellm-gateway`** |
|
|
384
|
+
| `litellm-gateway` / `litellm*` | 走网关 | 走网关(不做覆盖) |
|
|
385
|
+
| 其它 / 未知 | 原样 | 原样(**fail-open,不猜**) |
|
|
386
|
+
|
|
387
|
+
子 Agent 改道后用的模型按 agent 类型映射(与网关 `config.yaml.template` 对齐):
|
|
388
|
+
|
|
389
|
+
| 类型 | 网关模型名 |
|
|
390
|
+
|---|---|
|
|
391
|
+
| `coder`(子代理默认) | `coder-primary` |
|
|
392
|
+
| `research` | `research-primary` |
|
|
393
|
+
| `root` | `root-primary` |
|
|
394
|
+
|
|
395
|
+
### 实现机制(这里有一处重要的技术更正)
|
|
396
|
+
|
|
397
|
+
> ⚠️ **DSH 里没有 `agent/spawn` 事件**(已用 `cordis_inspect` 核对宿主全部约 85 个事件)。
|
|
398
|
+
> 更关键的是:**「子代理用什么模型」是在子会话创建之前就定下来的**,
|
|
399
|
+
> 任何事后事件都改不了它 —— 所以「监听某事件再改路由」这条路在语义上就不成立。
|
|
400
|
+
|
|
401
|
+
真正的 hook 是 cordis 的**服务读取 waterfall**:
|
|
402
|
+
|
|
403
|
+
```js
|
|
404
|
+
ctx.on('internal/get', (readCtx, name, error, next) => {
|
|
405
|
+
const value = next();
|
|
406
|
+
if (name !== 'subagents') return value;
|
|
407
|
+
return wrapService(value); // 原型委托包装
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
包装对象只重写 `startActivation`,在委派给真服务**之前**改写
|
|
412
|
+
`spec.request.agentOptions` 的 `provider` / `model`:
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
value.startActivation({
|
|
416
|
+
...spec,
|
|
417
|
+
request: { ...spec.request, agentOptions: { ...ao, provider: 'litellm-gateway', model } },
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
三个关键设计点:
|
|
422
|
+
|
|
423
|
+
1. **绝不修改原服务对象** —— 用 `Object.create(value)` 做原型委托,
|
|
424
|
+
其他读取者透明拿到包装。这一点和 `dsh-llm-fallbacks` 的 subagent seam
|
|
425
|
+
完全同构(见其 `dist/index.js` 的 `installSubagentSeam` / `wrapService`)。
|
|
426
|
+
2. **供应商从事实推断,不靠猜** —— 主 Agent 每次模型请求都经过 `llm/stream`,
|
|
427
|
+
我们从那里记录该会话**实际使用**的 provider。读不到就**不覆盖**(fail-open):
|
|
428
|
+
猜错供应商比不覆盖危险得多。
|
|
429
|
+
3. **异常一律退回原生路径** —— 包装里的任何异常都不会让子代理起不来。
|
|
430
|
+
|
|
431
|
+
### 为什么不用 `llm/stream` 改路由
|
|
432
|
+
|
|
433
|
+
`llm/stream` 拿到的 `GenerateOptions` 是**深冻结**的(官方事件描述明确说
|
|
434
|
+
"mutation throws"),而且它在语义上是「读」不是「选路由」。
|
|
435
|
+
在 `internal/get` 这个源头改才干净。
|
|
436
|
+
|
|
437
|
+
### 配置项
|
|
438
|
+
|
|
439
|
+
在 profile 的 `cordis.patch.yml` 里可调(`litellm-bridge` 行):
|
|
440
|
+
|
|
441
|
+
```yaml
|
|
442
|
+
- id: litellm-bridge
|
|
443
|
+
config:
|
|
444
|
+
gatewayProvider: 'litellm-gateway' # 子代理被改道到的 provider 名
|
|
445
|
+
nativeProviders: ['trae', 'workbuddy'] # 触发改道的原生积分供应商
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
> **前提**:DSH 里必须有一个名为 `litellm-gateway` 的 provider 路由指向你的
|
|
449
|
+
> LiteLLM 网关。本插件负责*选路由*,不负责*建路由* —— 后者是 DSH 的 LLM 配置。
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## 7. 与 header 注入插件联动
|
|
454
|
+
|
|
455
|
+
**设计要求**:「本插件只读写会话 profile 变量;header 注入插件读取该变量自动注入
|
|
456
|
+
`x-agent-type`、`x-session-profile` 头,二者解耦。」
|
|
457
|
+
|
|
458
|
+
### 7.1 注入闸门(关键规则)
|
|
459
|
+
|
|
460
|
+
> **仅当会话选中的供应商是 LitellM-Gateway 时,才注入这两个头。**
|
|
461
|
+
> Trae / WorkBuddy / 其它供应商**一律跳过**,绝不干扰它们的原生链路。
|
|
462
|
+
|
|
463
|
+
| 会话选中的供应商 | 是否注入 header | 说明 |
|
|
464
|
+
|---|---|---|
|
|
465
|
+
| `litellm-gateway` / `litellm*` | ✅ 注入 | 主/子代理都带 header,走网关链路 |
|
|
466
|
+
| `trae` / `workbuddy` | ❌ 跳过 | 保护原生积分链路,一个头都不改 |
|
|
467
|
+
| 其它(openai / anthropic / agnes…) | ❌ 跳过 | 同样不干扰 |
|
|
468
|
+
| **无法确定** | ❌ 跳过(**fail-closed**) | 宁可少注入,也不误伤原生链路 |
|
|
469
|
+
|
|
470
|
+
供应商判定来源(按可靠性排序):
|
|
471
|
+
|
|
472
|
+
1. **本次请求的 `provider`** —— 最权威,反映这次请求实际走哪条路
|
|
473
|
+
2. **litellm-bridge 记录的会话供应商** —— 请求对象缺 provider 时兜底
|
|
474
|
+
3. 两者都没有 → **不注入**
|
|
475
|
+
|
|
476
|
+
> 这里有个容易混淆的场景:会话选的是 Trae,但子代理被改道到网关(需求7)。
|
|
477
|
+
> 此时**子代理这次请求的 provider 就是网关**,所以照样注入 header ——
|
|
478
|
+
> 这正是我们想要的:被改道的子代理要带 header 进网关降级池,
|
|
479
|
+
> 而主 Agent 继续走 Trae 原生链路、不带 header。
|
|
480
|
+
|
|
481
|
+
### 7.2 实现说明(含一处必要的技术更正)
|
|
482
|
+
|
|
483
|
+
> ⚠️ **`agent/spawn` 事件在本机 DSH(0.2.2-rc.1)中不存在。**
|
|
484
|
+
> 我们用 `cordis_inspect` 的 Event provider 逐个核对了宿主事件目录(约 85 个事件),
|
|
485
|
+
> 确认没有 `agent/spawn`。子代理生命周期相关的真实事件是
|
|
486
|
+
> `agent/created` / `agent/disposed` / `subagent/start` / `subagent/end`。
|
|
487
|
+
|
|
488
|
+
更重要的是:**`agent/spawn` 这类生命周期事件在语义上也无法修改出站 HTTP 头** ——
|
|
489
|
+
它发生在请求构造之前很久。修改出站请求头的唯一真实 hook 是 waterfall 事件:
|
|
490
|
+
|
|
491
|
+
```js
|
|
492
|
+
'llm-pi-ai/request-headers'(request, headers, next) => Headers
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
所以 `header-inject` 的实现是:
|
|
496
|
+
|
|
497
|
+
| 能力 | 用什么 |
|
|
498
|
+
|---|---|
|
|
499
|
+
| 修改出站 HTTP 头 | `llm-pi-ai/request-headers`(waterfall,主 hook) |
|
|
500
|
+
| 供应商闸门 | `request.provider` 优先,回落到 `litellmBridge.getSessionProvider()` |
|
|
501
|
+
| agent 类型推导 | 主 agent → `root`;按角色名归到 `coder` / `research` |
|
|
502
|
+
| 会话 profile | 问 `litellmBridge` 服务;并订阅 `litellm-bridge/session-profile-changed` 事件做本地镜像 |
|
|
503
|
+
|
|
504
|
+
**解耦程度**:`header-inject` 完全不 import `litellm-bridge` 的任何代码,
|
|
505
|
+
只通过**会话变量键**和**一个事件**通信。任一方不可用时,另一方降级运行而非崩溃。
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## 7.5 UI 设计范式(对齐 dsh-llm-fallbacks)
|
|
510
|
+
|
|
511
|
+
**要求**:UI 交互、面板布局、操作逻辑参考 `dsh-llm-fallbacks` 的设计范式,
|
|
512
|
+
保持操作习惯对齐;但**功能逻辑完全独立**,不复用其路由/fallback 代码。
|
|
513
|
+
|
|
514
|
+
我们逐项对齐了它的官方扁平设置表单语言:
|
|
515
|
+
|
|
516
|
+
| 维度 | 对齐做法 |
|
|
517
|
+
|---|---|
|
|
518
|
+
| **扁平化** | 没有可折叠卡片壳 —— 无 header 按钮、无旋转箭头、无「未保存」药丸 |
|
|
519
|
+
| **分区标题** | 用语义 `<h2>` 表达分组(网关连接 / Profile 分组 / 主代理 / 子代理) |
|
|
520
|
+
| **分区级 Save/Discard** | 每个分区标题右侧有**该区独立**的 Save 与 Discard,不是底部一个大保存 |
|
|
521
|
+
| **字段节奏** | 字段 12px 纵向内边距、0.5px 发丝分隔线、34px 输入框 |
|
|
522
|
+
| **通知平铺** | 错误 / 警告 / 成功提示**平铺在表单上方**,不用 toast 或弹层 |
|
|
523
|
+
| **导航** | 顶部低视觉重量标签栏,与 fallbacks 的分区导航一致 |
|
|
524
|
+
|
|
525
|
+
> **功能独立性**:设计系统只是 `client/index.js` 顶部的一组样式对象(`S`),
|
|
526
|
+
> 没有任何业务逻辑;我们**没有** import `dsh-llm-fallbacks` 的任何模块,
|
|
527
|
+
> 也**没有**复用它的路由、fallback、虚拟适配器代码。
|
|
528
|
+
> 降级能力 100% 仍在 LiteLLM 网关侧。
|
|
529
|
+
>
|
|
530
|
+
> **为什么设计系统内联而不单独成文件**:客户端半运行在浏览器的
|
|
531
|
+
> `__ModuleLoader__` 模块表里,只能 `require('react')` 等已注入的包,
|
|
532
|
+
> **无法 import 本地兄弟文件**。所以 `client/` 下若有独立的样式文件,
|
|
533
|
+
> 它既不生效又会被打进包体。`tests/client-slots.test.mjs` 里有专门断言
|
|
534
|
+
> 防止这类死文件混进来。
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## 8. 测试流程
|
|
540
|
+
|
|
541
|
+
按这个顺序走,每步都有明确的通过标准:
|
|
542
|
+
|
|
543
|
+
### 步骤 0 · 单元自测(不需要网关)
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
node tests/run-all.mjs
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
期望:**327 通过, 0 失败**(七个套件)。
|
|
550
|
+
|
|
551
|
+
- `host-half.test.mjs`(34 项):配置默认值、密钥脱敏(4 种泄漏向量)、
|
|
552
|
+
YAML 降级顺序、日志归一化、「不可达时绝不谎报成功」。
|
|
553
|
+
- `subagent-routing.test.mjs`(58 项):供应商归类、三种会话情形的改道决策、
|
|
554
|
+
服务包装的真实调用行为(原型委托、字段保留、包装复用)、
|
|
555
|
+
异常安全(原生错误不吞、查询失败退回原生路径)。
|
|
556
|
+
- `client-slots.test.mjs`(39 项):客户端模块格式、槽位注入与注册一一对应、
|
|
557
|
+
**`sidebar.panellist` 必须渲染图标而非整个面板**、四个 Tab 齐全、
|
|
558
|
+
以及需求里的硬性要求(profile 分组、三类 agent、拖拽、50 条日志、
|
|
559
|
+
橙/红高亮、「来自 .env 环境变量」且 UI 中无明文密钥输入框)。
|
|
560
|
+
- `route-contract.test.mjs`(26 项):`webServer.register` 的 WebRoute 契约
|
|
561
|
+
(`{kind, path, handler(req,res)}`,Node 原生对象,**不是** Fetch 风格),
|
|
562
|
+
并用**真实 `http.Server`** 端到端验证:200/JSON 形状、UTF-8 透传、
|
|
563
|
+
未知 action、handler 抛异常、405、坏 JSON、空 body。
|
|
564
|
+
- `model-sync-batch.test.mjs`(38 项):**需求9** 模型清单合并解析、provider 标签、
|
|
565
|
+
空 upstream / `openai/` 坏配置识别、网关全挂时不抛异常;
|
|
566
|
+
**需求10** 批量探测汇总、失败归集、**并发窗口真的受限**(实测 maxInFlight ≤ 2)、
|
|
567
|
+
abort 中断、ProbeLog 环形上限与「成功项不刷屏」;以及新功能的密钥不泄漏断言。
|
|
568
|
+
- `header-gate.test.mjs`(44 项):**注入闸门**的完整矩阵 —— 网关供应商必注入、Trae/WorkBuddy/其它一律不注入、拿不到供应商时 fail-closed、请求 provider 优先于会话记录、自定义头名、禁用开关、统计只计次数。
|
|
569
|
+
- `routing-header-combo.test.mjs`(19 项):**需求7 × header 闸门的联动** —— 会话选 Trae/WorkBuddy 时主 Agent 不带 header、子 Agent 改道后必带 header;会话选网关时主子都带 header 且不覆盖;以及**原生 authorization 头不被破坏**。
|
|
570
|
+
|
|
571
|
+
### 步骤 1 · 网关起来了吗
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
python litellm-gateway/test-probe.py --skip-completion
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
期望:存活、就绪、master key 三项全绿。
|
|
578
|
+
|
|
579
|
+
### 步骤 2 · 全链路 + header
|
|
580
|
+
|
|
581
|
+
```bash
|
|
582
|
+
python litellm-gateway/test-probe.py --model root-primary --agent-type root --profile profile-a
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
期望:6 项全绿,最后一步拿到模型回复。
|
|
586
|
+
|
|
587
|
+
### 步骤 3 · 插件 UI
|
|
588
|
+
|
|
589
|
+
1. 禁用 `dsh-llm-fallbacks` → 重启 DSH
|
|
590
|
+
2. 安装并启用 `dsh-plugin-litellm-bridge` + `dsh-plugin-header-inject`
|
|
591
|
+
3. 打开面板 → **网关设置** → 测试连通性 → 期望三层全绿
|
|
592
|
+
4. **路由配置** → 拖拽调整 fallback 顺序 → 保存并热重载 →
|
|
593
|
+
期望提示热重载成功(或明确告知需手动重启)
|
|
594
|
+
5. **当前会话** → 选 profile → 保存 → 一键探针 → 期望通过
|
|
595
|
+
6. **日志面板** → 刷新 → 期望看到刚才探针的调用记录
|
|
596
|
+
|
|
597
|
+
### 步骤 3.5 · 验证子代理改道
|
|
598
|
+
|
|
599
|
+
在会话里把供应商切成 `trae` 或 `workbuddy`,然后:
|
|
600
|
+
|
|
601
|
+
1. 打开面板 → **当前会话** → 看「子代理路由覆盖」卡片:
|
|
602
|
+
期望显示 **「子代理将改道网关」** + 当前供应商;
|
|
603
|
+
2. 派发一个子代理任务(例如 `spawn_teammate` 或任何 subagent 调用);
|
|
604
|
+
3. 观察子代理实际走的模型:应为 `coder-primary` / `research-primary`;
|
|
605
|
+
4. 卡片上的「已评估 / 改道」计数应各 +1。
|
|
606
|
+
|
|
607
|
+
再把会话供应商切成 `litellm-gateway`,重复第 1 步:
|
|
608
|
+
期望显示 **「全部走网关」**,且派发子代理后**改道计数不增长**。
|
|
609
|
+
|
|
610
|
+
### 步骤 4 · 验证降级真的生效
|
|
611
|
+
|
|
612
|
+
故意制造失败,确认网关接管降级:
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
# 把某个上游 key 改错,然后观察日志面板是否出现橙色 FALLBACK 行
|
|
616
|
+
python litellm-gateway/test-probe.py --model root-primary
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
期望:日志面板出现橙色高亮行;若整条链都挂,则出现红色失败行。
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## 9. 推送到 GitHub
|
|
624
|
+
|
|
625
|
+
仓库地址:<https://github.com/heixiua/dsh-fallback-auto-pool-arch>
|
|
626
|
+
|
|
627
|
+
```bash
|
|
628
|
+
cd dsh-fallback-auto-pool-arch
|
|
629
|
+
|
|
630
|
+
git init
|
|
631
|
+
git branch -M main
|
|
632
|
+
git remote add origin https://github.com/heixiua/dsh-fallback-auto-pool-arch.git
|
|
633
|
+
|
|
634
|
+
# 提交前务必确认没有密钥混进来
|
|
635
|
+
git status --short
|
|
636
|
+
git check-ignore -v .env litellm-gateway/config.yaml # 应看到被忽略
|
|
637
|
+
|
|
638
|
+
git add -A
|
|
639
|
+
git commit -m "feat: LiteLLM 网关桥接插件 + 网关配置模板 + 部署文档"
|
|
640
|
+
git push -u origin main
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
推送前自检清单:
|
|
644
|
+
|
|
645
|
+
- [ ] `.env` 未被提交(`git ls-files | findstr .env` 只应出现 `.env.example`)
|
|
646
|
+
- [ ] `litellm-gateway/config.yaml` 未被提交(只有 `.template`)
|
|
647
|
+
- [ ] 全仓库无 `sk-` 明文:`git grep -nE "sk-[A-Za-z0-9]{20,}"` 应无结果
|
|
648
|
+
- [ ] 无 `node_modules/`
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## 10. 已知限制与设计决策
|
|
653
|
+
|
|
654
|
+
这几处我们**主动偏离了字面要求**,因为照字面实现会得到一个不能工作的东西。
|
|
655
|
+
每一处都记在这里,便于你复核:
|
|
656
|
+
|
|
657
|
+
| # | 字面要求 | 实际问题 | 我们的做法 |
|
|
658
|
+
|---|---|---|---|
|
|
659
|
+
| 1 | 调 `/config/reload` 热重载 | 该端点在**部分 LiteLLM 构建中存在、部分返回 404**(官方源码注释提到它,但社区有 404 的 issue)。硬写会静默失败 | **多端点自动探测 + 回退**:依次试 `/config/reload` → `/reload/config` → `/config/update`,跑通哪个用哪个,UI 显示**实际生效的端点**;全都不可用时**明确报失败并提示手动重启**,绝不假装成功 |
|
|
660
|
+
| 2 | 监听 `agent/spawn` 注入 header | 该事件**不存在**;且生命周期事件在语义上无法改出站请求头 | 用真实存在的 waterfall `llm-pi-ai/request-headers`(已核实为唯一 header hook) |
|
|
661
|
+
| 3 | `@Remote` 做前后端通信 | 纯 JS 插件**用不了** `@Remote`(编译期标记,需 typert 代码生成) | 照 `dsh-llm-fallbacks` 的做法:宿主注册 `/api/litellm-bridge` 路由,客户端走 `connection.rpc` |
|
|
662
|
+
| 4 | 会话变量 | 服务名是 `sessionProjections`(复数);签名在版本间有差异 | 探测式注册 + **内存态兜底**,服务缺失时 UI 不崩,只是失去跨重启持久化 |
|
|
663
|
+
| 5 | 「spawn 出来的子 Agent 强制覆盖供应商」 | 子代理用什么模型在**子会话创建之前**就已确定,任何事后事件都改不了(`agent/spawn` 也不存在) | 用 cordis **服务读取 waterfall**(`internal/get`)包装 `subagents` 服务,在 `startActivation` 委派前改写 `agentOptions`;原服务对象零修改 |
|
|
664
|
+
| 6 | `config.yaml.template` 里写中文注释更易读 | LiteLLM 用**平台默认编码**打开 YAML。中文 Windows 默认 GBK,UTF-8 中文注释会抛 `UnicodeDecodeError: 'gbk' codec can't decode byte`,网关直接起不来(实测踩到) | 模板保持**纯 ASCII**(中文说明都放在本文档里),并在启动脚本 `export PYTHONUTF8=1` + `PYTHONIOENCODING=utf-8` 双保险 |
|
|
665
|
+
|
|
666
|
+
其他已知限制:
|
|
667
|
+
|
|
668
|
+
- **日志降级判定依赖网关字段**。LiteLLM 各版本对 fallback 痕迹的字段名不统一
|
|
669
|
+
(`attempted_fallbacks` / `metadata.*` / `model ≠ model_group`),
|
|
670
|
+
我们用多字段嗅探;**判不出来就标 `unknown`**,不猜。
|
|
671
|
+
- **`/spend/logs` 需要数据库**。纯 config.yaml 模式(无 `DATABASE_URL`)下网关不记
|
|
672
|
+
花费日志,日志 Tab 会提示无法获取 —— 这是网关侧的限制,不是插件 bug。
|
|
673
|
+
- **`header-inject` 的 `llm-pi-ai/request-headers` 只覆盖 pi-ai provider**,
|
|
674
|
+
且不含模型发现请求。用别的 adapter 时该 hook 不存在,插件会安静跳过。
|
|
675
|
+
- 两个插件都需要 **DSH ≥ 0.2.2-rc.1**(`sessionProjections`、`webServer.register` 等 API)。
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## 11. 排错
|
|
680
|
+
|
|
681
|
+
| 现象 | 原因 / 处理 |
|
|
682
|
+
|---|---|
|
|
683
|
+
| 测试连通性三层全红 | 网关没起来。`cd litellm-gateway && ./start-gateway.sh` |
|
|
684
|
+
| 存活绿、key 红 | `LITELLM_MASTER_KEY` 没注入到 DSH 进程的环境里。检查启动 DSH 的环境 |
|
|
685
|
+
| `未检测到(请在网关侧 .env 配置)` | 网关的 `.env` 里没有 `LITELLM_MASTER_KEY`,或没以 `sk-` 开头 |
|
|
686
|
+
| 保存并热重载提示「需手动重启」 | 该 LiteLLM 构建没有热重载端点。重启网关即可,这属于已知限制 #1 |
|
|
687
|
+
| 日志 Tab 提示无法获取 | 网关没有配数据库。加 `DATABASE_URL` 或改用 `store_model_in_db` |
|
|
688
|
+
| 插件加载失败、别处也报错 | 先看是不是 `dsh-llm-fallbacks` 还开着 —— 两套降级会冲突 |
|
|
689
|
+
| pnpm 装完 profile 坏了 | 见 5.3 的警告:绝不 `Copy-Item` 进 `node_modules`;用 `pnpm add` |
|
|
690
|
+
| UI 面板空白 | 打开浏览器控制台看 `__ModuleLoader__` 报错;确认 `client/index.js` 的 `id` 等于包名 |
|
|
691
|
+
| 网关启动报 `UnicodeDecodeError: 'gbk' codec can't decode byte` | 中文 Windows 用 GBK 读 YAML。模板已改为纯 ASCII,脚本已加 `PYTHONUTF8=1`;若你自己改过 config.yaml 又加了中文,请删掉中文或保持 UTF-8 环境变量 |
|
|
692
|
+
| 日志 Tab 提示「需要数据库支持」 | **实测确认**:LiteLLM 的 `/spend/logs` 依赖数据库,且**只支持 PostgreSQL**。SQLite 会在启动时报 `DATABASE_URL uses unsupported scheme 'sqlite'` 并拒绝启动。要启用日志:`.env` 里设 `DATABASE_URL=postgresql://...`,并在 `config.yaml` 打开 `store_model_in_db: true`。纯 config.yaml 模式下日志面板无法取到数据(不是插件缺陷) |
|
|
693
|
+
| 网关启动报 `DATABASE_URL uses unsupported scheme 'sqlite'` | 改用 PostgreSQL;LiteLLM 明确不支持 SQLite 及其它引擎 |
|
|
694
|
+
| `保存并热重载` 提示端点不存在 | 实测 LiteLLM 1.108.3 上 `/config/reload` 与 `/reload/config` 均返回 **404**,`/config/update` 存在但需 `store_model_in_db`+PostgreSQL。改完配置重启网关即可 |
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
## 附:诊断文档索引(从 `dsh-session-suite` 迁入)
|
|
699
|
+
|
|
700
|
+
以下文档是 0.2.2-rc.1 内核 / `dsh-llm-fallbacks` / 压缩机制的排查记录,统一归档在本仓库
|
|
701
|
+
(原先误提交到 `dsh-session-suite` 的 `docs/`):
|
|
702
|
+
|
|
703
|
+
| 文档 | 主题 |
|
|
704
|
+
| --- | --- |
|
|
705
|
+
| `fallbacks-analysis.md` | `dsh-llm-fallbacks@0.6.13` 源码分析(46KB,含偏移级证据) |
|
|
706
|
+
| `fallbacks-summary.md` | 故障降级结论摘要 |
|
|
707
|
+
| `fallbacks-replacement-plan.md` | 自研降级插件方案(只给方案,未落地) |
|
|
708
|
+
| `fallbacks-070-qa.md` | 0.7.0 升级后的五个问题 |
|
|
709
|
+
| `unexpected-role-diagnosis.md` | `TIMEOUT` + `Unexpected message role.` 诊断 |
|
|
710
|
+
| `context-overflow-deadlock.md` | `CONTEXT_WINDOW_EXCEEDED` + 压缩死锁诊断 |
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## 附:许可证
|
|
715
|
+
|
|
716
|
+
MIT
|