feat(search): 智谱 provider 按官方 schema 加固(请求补确证字段 + 错误码分档)
官方文档逐页确证后落地的四件(分册 11 的「官方 schema 确证(2026-10-07)」): - 请求补 search_intent:false(官方标 required,缺发可能触发 1213)与 content_size:medium;count 仅 1–50 透传,越界不发该字段; search_query >70 rune 直接报错且**不发请求**; - 响应按官方收窄:摘要只认 snippet/summary/abstract 候选(官方响应**没有** snippet),publish_date 入 Published; - 错误码分档:1000/1001/1003⇒unauthorized、1113⇒quota_exceeded、 1302/1305⇒rate_limited。🔴 1113 与 1302/1305 **同为 HTTP 429** ⇒ 只看状态码 分不开「该充值」与「该退避」,必须读 error.code(码同时进日志文案); - media/icon/refer 当前一律不投影(Result 没有「来源站点」位;要投影应给 Result 加字段 + 同步契约,不许塞进 Snippet —— 否则摘要位会被站点名恒填满)。 另修两处过期注释(providers_test.go 仍写「只发 search_engine/search_query」)。 验收(主会话复跑 + 亲自变异):go vet 绿;search 包与调用方 io 包全绿; 我用 -overlay 删掉 1113⇒quota_exceeded 映射 ⇒ TestZhipuErrorCodeMapping 变红 (欠费被误报成限流、探针码 rate_limited)。 ⚠️ 候选键容错暂未删除:官方已排除的键保留到**真机实测通过后**再收窄(现无 key)。 ⚠️ 真机未跑:仍只打 httptest,官方真实响应形态未实测。
This commit is contained in:
1 parent
c512609c63
commit
25aa50e532
4 files changed
+452
-16
No files matched your search
@@ -3,6 +3,7 @@ package search
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@@ -63,7 +64,24 @@ func Unavailable(err error) bool {
|
||||
|
||||
// classifyStatus 把上游的失败状态码归成本仓的哨兵错误。
|
||||
// 先看业务错误码(各家官方文档确证的那几个,见 upstreamCodeClass),再看 HTTP 状态。
|
||||
//
|
||||
// 业务错误码非空时,返回值会带上 `(code=…)`:**只看 HTTP 状态分不开同一状态下的不同含义**
|
||||
// —— 智谱把「欠费」与「限流」都放在 HTTP 429(1113 vs 1302/1305),码是唯一判据;这段文案
|
||||
// 会被工具层记进日志,运维据此知道该充值还是该退避(分册 11 错误码节)。`errors.Is` 仍能
|
||||
// 命中对应哨兵(用 %w 包装)。
|
||||
//
|
||||
// ⚠️ 只带 **code**、不带上游 message:message 是任意上游文本(可能回显请求信息),而 `do` 的
|
||||
// 契约是「错误体不入错误文案」(见 http.go),凭据更不许出现在错误/日志里。
|
||||
func classifyStatus(status int, code string) error {
|
||||
sentinel := classifyStatusSentinel(status, code)
|
||||
if c := strings.TrimSpace(code); c != "" {
|
||||
return fmt.Errorf("%w (code=%s)", sentinel, c)
|
||||
}
|
||||
return sentinel
|
||||
}
|
||||
|
||||
// classifyStatusSentinel 是「业务码优先、再看状态码」的原判定(不带码回显)。
|
||||
func classifyStatusSentinel(status int, code string) error {
|
||||
if e, ok := upstreamCodeClass(code); ok {
|
||||
return e
|
||||
}
|
||||
@@ -74,6 +92,7 @@ func classifyStatus(status int, code string) error {
|
||||
case status == http.StatusPaymentRequired:
|
||||
return ErrQuotaExceeded
|
||||
case status == http.StatusTooManyRequests:
|
||||
// 429 有两种含义(欠费 / 限流):表里没收录的码按**保守档**当限流处理。
|
||||
return ErrRateLimited
|
||||
default:
|
||||
// 5xx 与其余 4xx 一律归 ErrUpstream(工具侧只关心「不可用 ⇒ 前进一格」)。
|
||||
@@ -83,10 +102,14 @@ func classifyStatus(status int, code string) error {
|
||||
|
||||
// upstreamCodeClass 把各家**官方文档确证**的业务错误码归类到哨兵错误(大小写不敏感)。
|
||||
//
|
||||
// 来源:阿里云 IQS 错误码表(help.aliyun.com/document_detail/2857020.html;
|
||||
// DevDocs/reference/search-providers/15-阿里云-IQS.md 的「错误码」节)。
|
||||
// 来源:① 阿里云 IQS 错误码表(help.aliyun.com/document_detail/2857020.html;
|
||||
// DevDocs/reference/search-providers/15-阿里云-IQS.md 的「错误码」节);② 智谱 BigModel
|
||||
// 平台通用错误码表(见 zhipuCodeClass)。
|
||||
// 只收录确证过的条目 —— 未确证的错误码一律不猜。
|
||||
func upstreamCodeClass(code string) (error, bool) {
|
||||
if e, ok := zhipuCodeClass(code); ok {
|
||||
return e, true
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(code)) {
|
||||
case "retrieval.throttling.user", "retrieval.testuserqueryperdayexceeded":
|
||||
return ErrRateLimited, true
|
||||
@@ -99,6 +122,33 @@ func upstreamCodeClass(code string) (error, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// zhipuCodeClass 是智谱 BigModel 的**平台通用**错误码表(分册 11「官方 schema 确证」③
|
||||
// 逐码对应官方原文,适用 `open.bigmodel.cn/api/paas/v4/*`):
|
||||
//
|
||||
// 1000 / 1001 / 1003(HTTP 401)⇒ 身份验证失败 / 未收到凭据 / 凭据过期 ⇒ unauthorized
|
||||
// 1113(HTTP 429) ⇒ 账户欠费 ⇒ quota_exceeded
|
||||
// 1302 / 1305(HTTP 429) ⇒ 账户限流 / 平台过载 ⇒ rate_limited
|
||||
//
|
||||
// 🔴 1113 与 1302/1305 **同为 HTTP 429** ⇒ 不读 code 就分不开「该充值」与「该退避」。
|
||||
// 搜索专用码 1701(并发达上限)/1702/1703 只登记、不在此映射:官方未给它们的 HTTP 状态码,
|
||||
// 保守交给状态码兜底(429 未收录码 ⇒ rate_limited,其余 4xx/5xx ⇒ upstream)。
|
||||
//
|
||||
// ⚠️ 作用域:这些是**裸数字码**,而本函数被所有 provider 共用。当前六家里只有智谱用数字业务码
|
||||
// (阿里云是 `Retrieval.*` 点分码)⇒ 暂按码值归类。**若将来出现第二家用数字码的家,必须改成
|
||||
// 「按 provider 分表」**,不能靠码值碰运气。
|
||||
func zhipuCodeClass(code string) (error, bool) {
|
||||
switch strings.TrimSpace(code) {
|
||||
case "1000", "1001", "1003":
|
||||
return ErrUnauthorized, true
|
||||
case "1113":
|
||||
return ErrQuotaExceeded, true
|
||||
case "1302", "1305":
|
||||
return ErrRateLimited, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// codePaths 是上游错误体里「业务错误码」的候选位置(点分路径,按序取第一个非空字符串)。
|
||||
// 各家形状不一,这里给的是容错候选;取不到就当没有错误码(按状态码分类)。
|
||||
func codePaths() []string {
|
||||
|
||||
Reference in new issue
Block a user