Files
ModelRouter/HANDOFF_2026-10-19.md
JianFeeeee a708eb10c9 docs: 补入 2026-10-19 交接文档
记录当时的 Git 状态、渠道适配器进度、以及"provider 回归测试存在未完成编辑"
这一未决项。作为历史记录保留,不参与构建。
2026-10-27 00:07:37 +08:00

13 KiB
Raw Blame History

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 回归测试时出现了一个未完成且可能无法编译的测试编辑。最后一次工具报错:

internal/provider/provider_test.go:99: missing ',' in argument list

下一位 agent 接手后的第一步必须是:

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 源当前状态

生产源已经从旧的“一账号一源”收口成“一渠道一源”:

channels-trae
channels-minimax
channels-lobsterai
channels-buddy
channels-codearts
channels-zcode

旧形态源已经清理:

channels-<provider>-<account_id>

此前旧源和新源并存,造成同一渠道同一模型出现多份,只能靠账号 hex 区分。

根因已修复:内核 list_sources 返回裸数组,但旧插件代码按 {sources = [...]} 信封读取,导致迁移清理循环永远为空。当前 channels.lua 已兼容:

  • 裸数组
  • {sources = [...]}
  • {json = {sources = [...]}}

相关代码:

internal/lua/plugins/channels.lua: syncProviderSource
internal/core/channels_legacy_source_cleanup_test.go

当前生产源列表已实测只有 6 个新形态源。

4. CodeArts 当前状态

已确认可用的部分

CodeArts 使用:

adapter = codearts
base_url = https://snap-access.cn-north-4.myhuaweicloud.com

CodeArts 的 chat 请求需要完整 AK/SK/security token 凭据,由插件适配器签名,不是裸 Bearer token。

真实流式工具调用测试已经成功:

请求形状:

{
  "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 模型刷新

生产调用:

POST /api/plugins/http/channels/model/refresh
{"platform":"codearts"}

实测返回:

{
  "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

相关解析:

internal/lua/plugins/channels.lua: parseRemoteModels
internal/lua/plugins/channels.lua: refreshRemoteModels
internal/core/channels_codearts_catalog_test.go

已确认失效并禁用的 CodeArts 模型

下面 4 个模型曾收到明确的上游 404:

CodeArts-Pangu
deepseek-v4-flash-0731
deepseek-v4-pro-0813
glm-5.3-flash

生产上已通过 channels/model/set_disabled 禁用。它们仍可能在 model/list 中出现,但 disabled=true,不会作为可用模型路由。

新增的死模型判据:

model is not registered

对应提交:c9f3453。

注意:当前插件的 dead-model 机制只在收到真实请求失败后记录模型;刷新目录不会自动根据“远端目录没有该模型”删除模型,这是刻意设计,避免误删远端目录不完整的平台模型。

CodeArts 当前仍有一些模型返回 429/TPM 限流,例如:

deepseek-v4-flash
deepseek-v4.1-flash

429 不是模型失效,不能禁用。

5. 其他 Channel 实测结果

Trae

真实流式 + tools 请求:

channels-trae:DeepSeek-V4-Flash

结果:

  • HTTP 200
  • SSE 正常返回
  • 结构化 tool call 返回
  • 但上游最后的结束原因可能是 stop,即使前面已经出现 tool-call fragments

这会让 Agent 把工具调用误判成普通文本完成。

当前准备中的修复在统一 provider 流式层:

internal/provider/provider.go

逻辑是:只要本次流出现真实 tool-call 片段,后续 FinishReason == "stop" 就归一成 tool_calls。

另外,Trae 的非流式接口偶尔返回 SSE。当前 trae.lua 对非流式 SSE 只取最后一条 data,不能完整合并多条工具调用/推理片段。这是后续仍需处理的协议问题,但不能用非流式手工请求结果推断 Pi 的真实请求模式。

Buddy

真实流式 + tools 请求:

channels-buddy:glm-5.3-flash

结果:

  • HTTP 200
  • SSE 正常返回
  • tool-call 参数分片可见
  • 最终有 finish_reason = tool_calls
  • 但某些上游 chunk 的 tool_calls 可能是空对象 {}

空对象不是合法的 OpenAI tool-call 数组,部分 Agent 客户端会拒绝。

当前准备中的修复在:

internal/lua/adapters/buddy.lua

只在 type(delta.tool_calls) == "table" and #delta.tool_calls > 0 时输出 tool_calls,避免 Lua 空表被编码为 {}。

Buddy 的非流式错误:

11101: Non-stream chat request is currently not supported

之前出现的该错误来自手工 curl 测试中显式设置 stream:false,不能归因到 Pi。真实 stream:true + tools 已经证明 Buddy 流式工具调用可以工作。

MiniMax

生产日志反复出现:

adapter "anthropic": status=401 body={"error":"invalid access token"}
provider channels-minimax: api error 401

这是凭据失效问题,不是 tools 形状问题。

当前源:

channels-minimax
adapter = anthropic
endpoint = /mavis/api/v1/llm/v1/messages

需要用户重新登录/提供有效凭据。不要擅自刷新一次性 refresh token,之前已经发生过 refresh token 被消耗后无法再次使用的事故。

6. 当前未提交的代码改动

最后一轮已经改动但尚未完成提交:

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 测试最后一次编辑工具报错:

internal/provider/provider_test.go:99: missing ',' in argument list

因此必须先修测试文件,再提交。

7. 推荐接手顺序

第一步:修复工作树

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 写测试。

第二步:跑定向验证

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'

第三步:构建并部署临时验证版

修复确认后:

go build ./...
./deploy.sh

部署前确认:

sha256sum internal/lua/plugins/channels.lua
sha256sum /etc/llmsproxy/plugins/channels.lua
systemctl is-active llmsproxy

第四步:真实流式回归

不要用 stream:false 测试流式问题。使用:

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 替换为:

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 刷新验收

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

预期:

{"ok":true,"results":[{"platform":"codearts","ok":true,"count":11}]}

再检查:

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 方案:

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 包未完成。

版本脚本注意:

  • 核心包脚本使用位置参数:

    ./packaging/core-dist.sh 2.0.41
    

    不是 VERSION=2.0.41 ./packaging/core-dist.sh。

  • GUI 版本来自 cmd/gui/package.json,环境变量 VERSION 不会自动修改 Electron 产品版本。

  • 清理旧产物后再打包,并在全部包完成后统一计算 SHA256SUMS。

9. 参考插件状态

用户要求按:

https://github.com/Ebony-Vinyl/dsh-our-free-model

逐厂商对照参考插件。

当前本地路径:

/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 仓库源码,确认以下文件实际存在后再对照:

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 的半编辑,然后验证未提交的统一流式修复。
  • 未经测试、未重新构建、未重新部署,不要继续改生产账号或刷新一次性凭据。