mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
G-Star 合规要求下,把"模拟官方客户端、复刻私有协议、自动领取平台额度"这一类 能力整体移出仓库。这些实现依赖各平台未公开的端点、签名算法与官方 client id, 属于绕过客户端校验获取额度,风险高于普通协议适配。 移除(93 个文件): - 适配器:opencodezen(伪装 UA 取免费池)、trae、buddy、kimicode - 插件:channels.lua(12 个平台登录/领额度/签到)、quota.lua(厂商额度探针) - 随之删除的 78 个渠道/额度专用测试与 3 份渠道文档 同时修掉的既有问题: - **硬编码第三方 OAuth client_secret**(Google Cloud Code 的 GOCSPX-…,两处) 随 channels.lua 一起消失。 - `deploy.sh` 的 `sync_plugins` 整段删除:它仍按"内置插件是发行物"的旧前提 把插件复制/覆盖进 plugin_dir,与"插件属运维资产、网关不安装不覆盖"的契约 冲突。现在部署脚本只同步适配器与二进制/配置。 - 适配器清单去掉已移除项;e2e 配置改用显式 `auto:` 链(`models.priority` 早已 移除,该测试此前已失效)。 保留(公开协议):openai / anthropic / gemini / deepseek / github / groq / mistral / ollama / qwen38 / sensenova = 10 个适配器;插件示例 billing / usage。 验证:go build -tags luajit ./... OK;internal/lua 21.4s、provider、core 51s、 gateway 122.8s、e2e 9.0s 全绿。
106 lines
4.4 KiB
Go
106 lines
4.4 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 (an adapter without 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 里明明有可用文案。
|
||
//
|
||
// ⚠️ openai 系适配器**都定义了** transform_error,
|
||
// 内核却报 "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 错误",
|
||
`{"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"},
|
||
// 上游三层嵌套错误(实测 429 code=14018 quota exhausted):
|
||
// 此前落到"不识别",客户端只看到通用文案,用户分不清是额度还是故障。
|
||
{"三层嵌套额度用尽", `{"error":{"data":{"code":14018,"msg":"额度已用尽,请购买加量包","requestId":"r1"}}}`,
|
||
"14018: 额度已用尽,请购买加量包"},
|
||
// 同一形状但没有 code:只回 msg
|
||
{"嵌套无码", `{"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)
|
||
}
|
||
}
|