# ModelRouter / llmsproxy 交接文档 更新时间:2026-10-19 工作目录:`/home/program/llmsproxy` 当前分支:`release/v2.0.x` ## 1. 当前 Git 状态 最近提交: - `c9f3453 fix(codearts): 识别 InferHub 未注册模型` - `12bfd53 fix: 收口渠道源迁移与测试页合并` - `25970a2 refactor(scheduler)!: 移除 priority 废弃字段 + 一通道一源 + discover 复用凭据` `c9f3453` 已推送到 `origin/release/v2.0.x`。 注意:当前工作树在最后一轮添加 provider 回归测试时出现了一个**未完成且可能无法编译的测试编辑**。最后一次工具报错: ```text internal/provider/provider_test.go:99: missing ',' in argument list ``` 下一位 agent 接手后的第一步必须是: ```bash cd /home/program/llmsproxy git status --short gofmt -w internal/provider/provider_test.go 2>&1 || true go test -tags luajit ./internal/provider/ 2>&1 ``` 如果测试文件处于半编辑状态,先检查 `TestProviderChatStreamNormalizesStopAfterToolCall`,修正 Go 字符串转义或删除该未完成测试后再继续。不要直接部署当前工作树。 ## 2. 生产服务状态 生产服务:`llmsproxy.service` - 当前服务 active - 监听:`0.0.0.0:8081` - 二进制已通过 `./deploy.sh` 原子替换并重启 - 最近一次部署健康检查通过 - 当前生产插件 SHA256 前缀曾核对为:`1bf26cbb3abcb107` - 当前生产二进制在最近部署后已更新 不要把密钥写入交接记录。生产 admin key 保存在本机运维环境中,不应复制到聊天、文档或提交。 ## 3. Channel 源当前状态 生产源已经从旧的“一账号一源”收口成“一渠道一源”: ```text channels-trae channels-minimax channels-lobsterai channels-buddy channels-codearts channels-zcode ``` 旧形态源已经清理: ```text channels-- ``` 此前旧源和新源并存,造成同一渠道同一模型出现多份,只能靠账号 hex 区分。 根因已修复:内核 `list_sources` 返回裸数组,但旧插件代码按 `{sources = [...]}` 信封读取,导致迁移清理循环永远为空。当前 `channels.lua` 已兼容: - 裸数组 - `{sources = [...]}` - `{json = {sources = [...]}}` 相关代码: ```text internal/lua/plugins/channels.lua: syncProviderSource internal/core/channels_legacy_source_cleanup_test.go ``` 当前生产源列表已实测只有 6 个新形态源。 ## 4. CodeArts 当前状态 ### 已确认可用的部分 CodeArts 使用: ```text adapter = codearts base_url = https://snap-access.cn-north-4.myhuaweicloud.com ``` CodeArts 的 chat 请求需要完整 AK/SK/security token 凭据,由插件适配器签名,不是裸 Bearer token。 真实流式工具调用测试已经成功: 请求形状: ```json { "model": "channels-codearts:openpangu-2.0-flash", "messages": [{"role": "user", "content": "Use the weather tool for Beijing."}], "tools": [{ "type": "function", "function": { "name": "get_weather", "description": "Get weather", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"] } } }], "tool_choice": "auto", "stream": true, "max_tokens": 64 } ``` 结果: - HTTP 200 - `Content-Type: text/event-stream` - 返回结构化 `tool_calls` - 函数名 `get_weather` - 参数最终为 `{"city":"Beijing"}` - `finish_reason = tool_calls` 因此 CodeArts 的流式工具调用链路当前是通的。 ### CodeArts 模型刷新 生产调用: ```http POST /api/plugins/http/channels/model/refresh {"platform":"codearts"} ``` 实测返回: ```json { "ok": true, "results": [ {"platform": "codearts", "ok": true, "count": 11} ] } ``` CodeArts 模型目录来自两个签名端点并合并: 1. `opengw.developer.huaweicloud.com/api/v1/gateway/config` - `result.models` 2. `snap-access.cn-north-4.myhuaweicloud.com/v1/model/builtin` - `builtinModels` 相关解析: ```text internal/lua/plugins/channels.lua: parseRemoteModels internal/lua/plugins/channels.lua: refreshRemoteModels internal/core/channels_codearts_catalog_test.go ``` ### 已确认失效并禁用的 CodeArts 模型 下面 4 个模型曾收到明确的上游 404: ```text CodeArts-Pangu deepseek-v4-flash-0731 deepseek-v4-pro-0813 glm-5.3-flash ``` 生产上已通过 `channels/model/set_disabled` 禁用。它们仍可能在 `model/list` 中出现,但 `disabled=true`,不会作为可用模型路由。 新增的死模型判据: ```lua model is not registered ``` 对应提交:`c9f3453`。 注意:当前插件的 dead-model 机制只在收到真实请求失败后记录模型;刷新目录不会自动根据“远端目录没有该模型”删除模型,这是刻意设计,避免误删远端目录不完整的平台模型。 CodeArts 当前仍有一些模型返回 429/TPM 限流,例如: ```text deepseek-v4-flash deepseek-v4.1-flash ``` 429 不是模型失效,不能禁用。 ## 5. 其他 Channel 实测结果 ### Trae 真实流式 + tools 请求: ```text channels-trae:DeepSeek-V4-Flash ``` 结果: - HTTP 200 - SSE 正常返回 - 结构化 tool call 返回 - 但上游最后的结束原因可能是 `stop`,即使前面已经出现 tool-call fragments 这会让 Agent 把工具调用误判成普通文本完成。 当前准备中的修复在统一 provider 流式层: ```text internal/provider/provider.go ``` 逻辑是:只要本次流出现真实 tool-call 片段,后续 `FinishReason == "stop"` 就归一成 `tool_calls`。 另外,Trae 的非流式接口偶尔返回 SSE。当前 `trae.lua` 对非流式 SSE 只取最后一条 data,不能完整合并多条工具调用/推理片段。这是后续仍需处理的协议问题,但不能用非流式手工请求结果推断 Pi 的真实请求模式。 ### Buddy 真实流式 + tools 请求: ```text channels-buddy:glm-5.3-flash ``` 结果: - HTTP 200 - SSE 正常返回 - tool-call 参数分片可见 - 最终有 `finish_reason = tool_calls` - 但某些上游 chunk 的 `tool_calls` 可能是空对象 `{}` 空对象不是合法的 OpenAI tool-call 数组,部分 Agent 客户端会拒绝。 当前准备中的修复在: ```text internal/lua/adapters/buddy.lua ``` 只在 `type(delta.tool_calls) == "table" and #delta.tool_calls > 0` 时输出 `tool_calls`,避免 Lua 空表被编码为 `{}`。 Buddy 的非流式错误: ```text 11101: Non-stream chat request is currently not supported ``` 之前出现的该错误来自手工 `curl` 测试中显式设置 `stream:false`,不能归因到 Pi。真实 `stream:true + tools` 已经证明 Buddy 流式工具调用可以工作。 ### MiniMax 生产日志反复出现: ```text adapter "anthropic": status=401 body={"error":"invalid access token"} provider channels-minimax: api error 401 ``` 这是凭据失效问题,不是 tools 形状问题。 当前源: ```text channels-minimax adapter = anthropic endpoint = /mavis/api/v1/llm/v1/messages ``` 需要用户重新登录/提供有效凭据。不要擅自刷新一次性 refresh token,之前已经发生过 refresh token 被消耗后无法再次使用的事故。 ## 6. 当前未提交的代码改动 最后一轮已经改动但尚未完成提交: ```text internal/provider/provider.go internal/lua/adapters/trae.lua internal/lua/adapters/buddy.lua internal/provider/provider_test.go # 可能有半编辑、需先修复 ``` 已完成的意图: 1. provider 流式层记录 `sawToolCalls` 2. 看到真实 tool-call 后,把后续 `stop` 归一为 `tool_calls` 3. Trae 不输出空工具调用表 4. Buddy 不输出空工具调用表 5. provider 回归测试模拟“tool fragment + stop”场景 但 provider 测试最后一次编辑工具报错: ```text internal/provider/provider_test.go:99: missing ',' in argument list ``` 因此必须先修测试文件,再提交。 ## 7. 推荐接手顺序 ### 第一步:修复工作树 ```bash cd /home/program/llmsproxy git status --short sed -n '1,150p' internal/provider/provider_test.go gofmt -w internal/provider/provider_test.go 2>&1 || true go test -tags luajit ./internal/provider/ 2>&1 ``` 若测试函数字符串转义难以修复,先删除半成品测试,再重新用 raw string 写测试。 ### 第二步:跑定向验证 ```bash luac -p internal/lua/adapters/trae.lua luac -p internal/lua/adapters/buddy.lua go test -tags luajit ./internal/provider/ go test -tags luajit ./internal/lua/ -run 'Adapter|Buddy|Trae|Stream' ``` ### 第三步:构建并部署临时验证版 修复确认后: ```bash go build ./... ./deploy.sh ``` 部署前确认: ```bash sha256sum internal/lua/plugins/channels.lua sha256sum /etc/llmsproxy/plugins/channels.lua systemctl is-active llmsproxy ``` ### 第四步:真实流式回归 不要用 `stream:false` 测试流式问题。使用: ```bash KEY='<从本机安全环境读取,不写入文档>' TOOL='{"type":"function","function":{"name":"get_weather","description":"Get weather","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}' curl --no-buffer --max-time 90 \ -H "Authorization: Bearer $KEY" \ -H 'Content-Type: application/json' \ -d "{\"model\":\"channels-codearts:openpangu-2.0-flash\",\"messages\":[{\"role\":\"user\",\"content\":\"Use the weather tool for Beijing.\"}],\"tools\":[$TOOL],\"tool_choice\":\"auto\",\"stream\":true,\"max_tokens\":64}" \ http://127.0.0.1:8081/v1/chat/completions ``` 分别将 model 替换为: ```text channels-codearts:openpangu-2.0-flash channels-trae:DeepSeek-V4-Flash channels-buddy:glm-5.3-flash ``` 验收: - HTTP 200 - `Content-Type: text/event-stream` - 不能出现 `tool_calls: {}` - 有 tool-call fragments 时最终必须是 `finish_reason: tool_calls` - CodeArts 当前应继续返回结构化工具调用 ### 第五步:CodeArts 刷新验收 ```bash curl -sS -X POST \ -H "Authorization: Bearer $KEY" \ -H 'Content-Type: application/json' \ -d '{"platform":"codearts"}' \ http://127.0.0.1:8081/api/plugins/http/channels/model/refresh ``` 预期: ```json {"ok":true,"results":[{"platform":"codearts","ok":true,"count":11}]} ``` 再检查: ```bash curl -sS -X POST \ -H "Authorization: Bearer $KEY" \ -H 'Content-Type: application/json' \ -d '{"platform":"codearts"}' \ http://127.0.0.1:8081/api/plugins/http/channels/model/list ``` 预期:远端目录 11 个 + 兜底补位,已确认 404 的 4 个仍保持 `disabled=true`。 ## 8. 发布/安装包状态 目前没有完成“全部安装包”发布闭环。 已经发生的事情: - 核心包构建成功,但脚本实际使用了 `git describe`,产物版本类似: `2.0.40~4-gc9f3453` - Linux GUI 包构建成功,版本为 `1.9.7` - Windows 主机 Wine 构建失败,生成了一个 264KB 的坏 `ModelRouter Setup 1.9.7.exe`,绝对不能发布 - Docker Windows 构建也失败在拉取 Docker Hub 基础镜像 `golang:1.25` 网络超时 - apt 无法找到 `wine32:i386` - 旧的 1.9.5/1.9.6 Windows 包是历史产物,不能作为当前版本 不要把旧 Windows exe 当作新包发布。 仓库已有 Docker 方案: ```text cmd/gui/scripts/dist-win-docker.sh cmd/gui/docker/win-builder/Dockerfile ``` 但当前 Docker Hub 网络不可用。若要继续做 Windows 包: 1. 在可访问 Docker Hub 的构建机/CI 执行 Docker 方案;或 2. 提供包含 Wine32/Wine64 的完整构建环境;或 3. 暂时只发布 Linux/core 包,并明确说明 Windows 包未完成。 版本脚本注意: - 核心包脚本使用位置参数: ```bash ./packaging/core-dist.sh 2.0.41 ``` 不是 `VERSION=2.0.41 ./packaging/core-dist.sh`。 - GUI 版本来自 `cmd/gui/package.json`,环境变量 `VERSION` 不会自动修改 Electron 产品版本。 - 清理旧产物后再打包,并在全部包完成后统一计算 `SHA256SUMS`。 ## 9. 参考插件状态 用户要求按: ```text https://github.com/Ebony-Vinyl/dsh-our-free-model ``` 逐厂商对照参考插件。 当前本地路径: ```text /tmp/pi-github-repos/Ebony-Vinyl/dsh-our-free-model ``` 但该目录当前不是有效可读源码副本: - `vendor/channel-pack/src` 下面只有空目录; - `.git` 没有有效 `HEAD`/refs; - 无法从本地 Git 对象恢复源码; - 本地也没有 `buddy.ts`、`trae.ts`、`codearts.ts` 等文件。 因此不能声称当前适配器是逐行照参考插件移植的。若要继续按用户要求完成,必须先重新 clone/恢复该 GitHub 仓库源码,确认以下文件实际存在后再对照: ```text vendor/channel-pack/src/trae.ts vendor/channel-pack/src/buddy.ts vendor/channel-pack/src/codearts.ts vendor/channel-pack/src/*adapter* vendor/channel-pack/src/*model* ``` 当前仓库代码只能作为临时协议修复基础,不能代替参考插件源码。 ## 10. 关键结论 - Pi 是否发送流式请求,历史日志没有保存入站 `stream/tools` 字段,不能靠旧日志判断。 - 本次真实 `stream:true + tools` 测试证明 ModelRouter 流式路径能工作: - CodeArts:通过; - Trae:通过但结束原因需归一; - Buddy:通过但需过滤空 `{}`; - MiniMax:凭据 401,独立问题。 - 不要再用 `stream:false` 的手工请求推断 Pi 的问题。 - CodeArts 刷新接口当前真实返回 `ok=true,count=11`,刷新链路不是当前阻塞点。 - 当前最紧急的代码状态是修复 `provider_test.go` 的半编辑,然后验证未提交的统一流式修复。 - 未经测试、未重新构建、未重新部署,不要继续改生产账号或刷新一次性凭据。