mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-02 15:14:22 +00:00
fix(opencode): 采纳客户端真实会话 id + 超窗消息不再被限流措辞封杀
两处都源于同一次排查:pi 到底有没有带会话标识、超窗为什么触发不了压缩。 ## 1) 客户端会话 id:pi 一直在发,只是被配置关掉了 之前结论是「通用客户端不发会话 id」——只对了一半。pi 有会话 id,且能发: pi-ai 的 createClient 在 compat.sendSessionAffinityHeaders 为真时,会把 平台会话 id(uuidv7,整个会话恒定)放到 x-session-affinity / x-client-request-id / session_id 上。该开关默认 false,而 llmsproxy 的 provider 配置里没开,所以此前一直收不到。 现在网关按优先级采纳:x-session-affinity → x-session-id → session_id → body 的 prompt_cache_key,并把值经 types.ChatRequest.ClientSession 传到 适配器 meta.client_session。适配器的会号种子优先级变为: 客户端会话 id > 首条 user 消息指纹 > 按源固定。 刻意不采纳 x-client-request-id:名字含 request,部分客户端每请求都换, 拿它当会话会让上游前缀缓存永不命中(pi 总会同时发 x-session-affinity,够用)。 实测:抓 127.0.0.1:8081 的真实 pi 请求,配置打开后收到 x-session-affinity = session_id = x-client-request-id = <子会话 uuid>。 上游缓存确为会话级隔离(同前缀、不同会号:A 冷→命中,B 首次仍为 0), 两个不同 header 值互不命中,反证网关确实采纳了客户端会话 id。 ## 2) 超窗消息必须「干净」,否则被同链的限流措辞反向封杀 pi 的 isContextOverflow 先查 NON_OVERFLOW_PATTERNS(/rate limit/、 /too many requests/、Bedrock 前缀),命中就直接判为「非超窗」——**即使 消息里已经有 context_length_exceeded**,pi 也不会压缩重试。 而 AUTO 链的失败消息天生是多 tier 原因的拼接,超窗 tier(gozen 400 maximum context length)常与配额/限流 tier(429 token plan exhausted、 cooling、no free slot)同时出现。此前把 tier 明细原样拼在归一化标记后面, 等于让一条限流 tier 的措辞反过来封杀超窗识别。 现在超窗走独立的干净消息: context_length_exceeded: context window is full; reduce the length of the messages (gozen/deepseek-v4.1-flash) 只留超窗措辞 + 超窗源名,不带任何其它 tier 的文本。 测试:TestOverflowMessageSurvivesRateLimitedSiblingTier 用 pi 的完整判定 顺序(先 NON_OVERFLOW 后 OVERFLOW)断言同链限流 tier 不再封杀超窗识别; TestClientSessionFromRequestHeaders / TestClientRequestIDIsNotUsedAsSession / TestOpenCodePrefersClientSessionID 覆盖会话采纳与优先级。
This commit is contained in:
@ -707,7 +707,7 @@ func (p *Provider) probeModels(ctx context.Context) (bool, string) {
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
if hdrs, herr := p.buildHeaders("{}", u); herr == nil {
|
||||
if hdrs, herr := p.buildHeaders("{}", u, ""); herr == nil {
|
||||
req.Header = hdrs
|
||||
}
|
||||
resp, err := p.client.Do(req)
|
||||
@ -750,7 +750,7 @@ func (p *Provider) probeChat(ctx context.Context) (bool, string) {
|
||||
body, err := json.Marshal(probe)
|
||||
if err == nil {
|
||||
var hdr http.Header
|
||||
if hdrs, herr := p.buildHeaders(string(body), p.URL()); herr == nil {
|
||||
if hdrs, herr := p.buildHeaders(string(body), p.URL(), ""); herr == nil {
|
||||
hdr = hdrs
|
||||
}
|
||||
raw, status, derr := p.do(ctx, p.URL(), string(body), hdr)
|
||||
@ -1031,13 +1031,15 @@ func (p *Provider) Throttle(ctx context.Context) error {
|
||||
|
||||
// ---- request construction ----
|
||||
|
||||
func (p *Provider) buildHeaders(body, url string) (http.Header, error) {
|
||||
func (p *Provider) buildHeaders(body, url, clientSession string) (http.Header, error) {
|
||||
meta := map[string]interface{}{
|
||||
"url": url,
|
||||
"method": http.MethodPost,
|
||||
"body": body,
|
||||
"api_key": p.cfg.APIKey,
|
||||
"timestamp": types.Now(),
|
||||
// client_session 是客户端自带的会话标识(请求头),为空时适配器自行推导。
|
||||
"client_session": clientSession,
|
||||
"source": map[string]interface{}{
|
||||
"name": p.cfg.Name,
|
||||
"meta": p.cfg.Meta,
|
||||
@ -1082,7 +1084,7 @@ func (p *Provider) Chat(ctx context.Context, req *types.ChatRequest) (*types.Uni
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdrs, err := p.buildHeaders(body, p.URL())
|
||||
hdrs, err := p.buildHeaders(body, p.URL(), req.ClientSession)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1142,7 +1144,7 @@ func (p *Provider) ChatStream(ctx context.Context, req *types.ChatRequest) (<-ch
|
||||
p.Release()
|
||||
return nil, err
|
||||
}
|
||||
hdrs, err := p.buildHeaders(body, p.URL())
|
||||
hdrs, err := p.buildHeaders(body, p.URL(), req.ClientSession)
|
||||
if err != nil {
|
||||
p.Release()
|
||||
return nil, err
|
||||
@ -1411,7 +1413,7 @@ func (p *Provider) Image(ctx context.Context, req *types.ImageGenRequest) (*type
|
||||
// fall back to passthrough adapter (openai-style)
|
||||
transformed = string(b)
|
||||
}
|
||||
hdrs, err := p.buildHeaders(transformed, p.ImageURL())
|
||||
hdrs, err := p.buildHeaders(transformed, p.ImageURL(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user