mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
① 商汤(Raccoon)扫码「链接无效」——两个真根因
- 登录页 /login/mp 已被站点删除(Next 路由 404,实测只有 404 页);
- 旧实现本地随机造 qrcode_code,而上游早已不认自造码:旧端点
/api/web/auth/v1/login_with_qrcode_code 对任意 code 恒回 pending
(服务端没有该会话)⇒ 扫码永远完不成,账号一直是空凭据。
修法:改为上游现行 IAM 流程(逐行对齐其网页客户端 auth chunk)——
POST /api/iam/v1/auth/mp/qrcode 建会话 → 链接用
/login?code=…&appname=…&source=platform(实测 200 + 微信扫码面板)
→ 轮询 /api/iam/v1/auth/mp/qrcode/login(IAM code:"OK" 与旧 code:0 都认)。
服务端建码失败才退回旧本地码并打 warn。UI 对扫码流程改文案为
「用微信扫码」(原本文案会让人以为点开就自动登录)。
生产实测:链接 HTTP 200、轮询回 pending(会话真实存在)。
② 双区登录(P7)
- 凭据增加 region(intl|cn,缺省=历史行为);start_login 接受 region,
会话按区域选端点,完成登录写进凭据;
- CodeBuddy:cn=copilot.tencent.com(platform=ide)/
intl=www.workbuddy.ai(platform=workbuddy-ai,X-Domain、
X-Product-Code、UA 按区域;实测 auth/state 回 200 信封);
- Qoder:intl=openapi.qoder.sh+qoder.com(client_id e883ade2-…)/
cn=openapi.qoder.com.cn+qoder.cn(client_id 不同 732aef47-…,照参考
QODER_CN)。CN 无公开 chat 端点 ⇒ CN 只做登录/领取;
- UI:buddy/qoder 详情页增加「+国际区」按钮。
③ 凭据过期时刻的时区口径(P6 真根因)
credentialExpiresAtMs 把 ISO 过期时刻按本地时区解析,而 codearts 给的是
UTC ISO(2026-10-19T16:42:43.06Z)⇒ 本机 +08 下过期时刻虚早 8 小时 ⇒
refresh_creds 每 30 分钟都判"已过期"并对华为云真打一次 STS(token 本还有
23 小时寿命)。改为按声明时区解析(Z / ±hh:mm / 无后缀=UTC 三种形态同一
epoch),补 TestCredentialExpiresAtMsParsesISOAsUTC。
④ 四条老化/假红判据
- TestCreditsPoolReason*:account_add 触发的选号会用构造期默认余额 (0,0)
先查一次并缓存 60s ⇒ 之后改假上游读不到("余额 100 却报 exhausted")。
harness 改余额后清 TTL 缓存(__ttl_reset_probe,与生产同一实现)。
- TestAccountReorderActually... / TestPoolListOrderIsDeterministic:随
「一通道一源」失效(按 channels-buddy-<acct> 过滤恒得空)。改写为——
重排必须切换服务账号(新增 __chosen_account_probe),顺序确定性改为断言
选号顺序(__rank_accounts_probe)由 id 稳定决定。
- channels.lua 顶层 local 余量:区域表与 5 份适配器源码常量收进 plugin.K
(TestChannelsLuaHasLocalHeadroom 需要 ≥10 槽位);ZCode 适配器源码里的
CLI_PREFIX 必须留在源码内(适配器沙箱没有 plugin 全局)。
⑤ 错误文案
openai_error 增补腾讯 codebuddy 三层嵌套 {error:{data:{code,msg}}}:实测
429 code=14018「额度已用尽」此前落到"不识别",客户端只看到通用文案。
验证:core 434s 全绿、lua 62s 全绿、provider 全绿;生产已部署并实测
raccoon 链接 200 / trae 非流式 200 / zcode 适配器加载。
106 lines
4.5 KiB
Go
106 lines
4.5 KiB
Go
package lua
|
||
|
||
import (
|
||
"testing"
|
||
)
|
||
|
||
// ★ 上游错误体抽取必须覆盖**网关与腾讯系**形状 —— 这两类实测都漏过。
|
||
//
|
||
// 发现路径(2026-10-16 读生产日志):
|
||
//
|
||
// unhandled upstream error body (adapter "openai" lacks transform_error):
|
||
// status=404 body={"error_msg":"404 Route Not Found"}
|
||
// unhandled upstream error body (adapter "buddy" lacks transform_error):
|
||
// status=400 body={"code":11101,"msg":"Non-stream chat request is not supported"}
|
||
//
|
||
// 两者都是 `{"error_msg":...}` / `{"msg":...}` —— 旧实现只认
|
||
// error/message/detail,于是返回 nil ⇒ 客户端只看到
|
||
// `api error 404: unknown error`,而 body 里明明有可用文案。
|
||
//
|
||
// ⚠️ 那两个适配器**都定义了** transform_error(openai.lua:139、buddy.lua:166),
|
||
// 内核却报 "lacks" ⇒ 一句会把排查带偏的诊断(已另行修正内核文案)。
|
||
func TestPreludeErrorExtractionCoversGatewayShapes(t *testing.T) {
|
||
vm := NewVM(t.TempDir())
|
||
defer vm.Stop()
|
||
// 用**仓库里的真实** openai 适配器(含共享前导),不是自造桩:
|
||
// 判据必须走真实实现,否则"补了形状"这件事根本没被验。
|
||
src, err := bundledAdapters.ReadFile("adapters/openai.lua")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := vm.LoadAdapterSource("openai", string(src)); err != nil {
|
||
t.Fatalf("装载 openai 适配器: %v", err)
|
||
}
|
||
|
||
cases := []struct {
|
||
name, body, want string
|
||
}{
|
||
{"APISIX error_msg",
|
||
`{"error_msg":"404 Route Not Found"}`, "404 Route Not Found"},
|
||
{"腾讯 msg(buddy 11101)",
|
||
`{"code":11101,"msg":"Non-stream chat request is currently not supported"}`,
|
||
"11101: Non-stream chat request is currently not supported"},
|
||
{"error_code + error_msg",
|
||
`{"error_code":"APIG.0301","error_msg":"verify ak sk signature fail"}`,
|
||
"APIG.0301: verify ak sk signature fail"},
|
||
{"腾讯 displayMsg 本地化(最可读,排最前)",
|
||
`{"code":11101,"msg":"Non-stream chat request is currently not supported",` +
|
||
`"displayMsg":{"en":"Invalid request. Please retry","zh":"请求内容不合法,请重试"}}`,
|
||
"请求内容不合法,请重试 11101: Non-stream chat request is currently not supported"},
|
||
{"仅 displayMsg",
|
||
`{"displayMsg":{"zh":"只有本地化文案"}}`, "只有本地化文案"},
|
||
{"仅 error_code",
|
||
`{"error_code":"InferHub.4291.200"}`, "InferHub.4291.200"},
|
||
// 回归:原有的 6 种形状不得丢
|
||
{"OpenAI 信封", `{"error":{"message":"boom"}}`, "boom"},
|
||
{"裸字符串 error", `{"error":"boom"}`, "boom"},
|
||
{"扁平 code/message", `{"message":"boom"}`, "boom"},
|
||
{"FastAPI detail", `{"detail":"boom"}`, "boom"},
|
||
{"HTML title", `<html><title>413 Too Large</title></html>`, "413 Too Large"},
|
||
{"裸文本", "plain failure line\nmore", "plain failure line"},
|
||
// 腾讯 codebuddy 三层嵌套(实测 429 code=14018 额度用尽):
|
||
// 此前落到"不识别",客户端只看到通用文案,用户分不清是额度还是故障。
|
||
{"buddy 额度用尽嵌套", `{"error":{"data":{"code":14018,"msg":"额度已用尽,请购买加量包","requestId":"r1"}}}`,
|
||
"14018: 额度已用尽,请购买加量包"},
|
||
// 同一形状但没有 code:只回 msg
|
||
{"buddy 嵌套无码", `{"error":{"data":{"msg":"请求被拒绝"}}}`, "请求被拒绝"},
|
||
}
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
got, ok, err := vm.TransformError("openai", 400, c.body)
|
||
if err != nil {
|
||
t.Fatalf("TransformError: %v", err)
|
||
}
|
||
if !ok {
|
||
t.Fatalf("★ 抽不出消息(返回 ok=false)⇒ 客户端只会看到 "+
|
||
"'unknown error',而 body 里明明有:%s", c.body)
|
||
}
|
||
if got != c.want {
|
||
t.Fatalf("抽出的文案不对\n 实得: %q\n 期望: %q", got, c.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 配对判据:真正**没有** transform_error 的适配器仍应返回 ok=false
|
||
// (不能为了让上面那条通过就把"抽不出"和"没这个钩子"混为一谈)。
|
||
func TestPreludeErrorExtractionStillReportsUnknownShapes(t *testing.T) {
|
||
vm := NewVM(t.TempDir())
|
||
defer vm.Stop()
|
||
src, err := bundledAdapters.ReadFile("adapters/openai.lua")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := vm.LoadAdapterSource("openai", string(src)); err != nil {
|
||
t.Fatalf("装载 openai 适配器: %v", err)
|
||
}
|
||
// 一个确实认不出的 JSON(没有 message/error/detail/msg/error_msg)
|
||
got, ok, err := vm.TransformError("openai", 400, `{"weird":true}`)
|
||
if err != nil {
|
||
t.Fatalf("TransformError: %v", err)
|
||
}
|
||
if ok {
|
||
t.Fatalf("认不出的形状不该硬凑文案,实得 %q", got)
|
||
}
|
||
}
|