fix(gateway): 修复 Qwen3.8 推理参数并增加上游自愈
统一 Qwen3.8 固定思考、推理档位、预算互斥与温度下限规则,并覆盖 Chat Completions 和原生 Responses 请求。 新增安全参数纠正、有限重试和并发安全的进程内 LRU 学习;补充共享行为向量、httptest 回归与真实模型验收用例。 验证:go test ./... -count=1 通过,gofmt 检查通过;真实 Qwen3.8 流式、非流式及缓存重学习验收通过。
This commit is contained in:
@@ -18,7 +18,8 @@ import (
|
||||
)
|
||||
|
||||
type OpenAIClient struct {
|
||||
HTTPClient *http.Client
|
||||
HTTPClient *http.Client
|
||||
Corrections *ParameterCorrectionCache
|
||||
}
|
||||
|
||||
func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error) {
|
||||
@@ -56,6 +57,7 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
applyOpenAIChatReasoningParams(body, request.Candidate)
|
||||
body = FilterOpenAIChatRequestBody(body)
|
||||
} else if request.Kind == "responses" {
|
||||
applyOpenAIResponsesReasoningParams(body, request.Candidate)
|
||||
body = FilterOpenAIResponsesRequestBody(body)
|
||||
if _, hasInput := body["input"]; !hasInput {
|
||||
if messages, hasMessages := request.Body["messages"]; hasMessages {
|
||||
@@ -73,31 +75,64 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
normalizeOpenAIImageRequestBody(endpointKind, body, request.OriginalBody)
|
||||
stream := openAIEndpointSupportsStream(endpointKind) && (request.Stream || boolValue(body, "stream"))
|
||||
ensureOpenAIStreamUsage(body, endpointKind, stream)
|
||||
raw, contentType, err := openAIRequestPayload(endpointKind, body, request.Candidate)
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
upstreamEndpoint := joinURL(openAIBaseURL(endpointKind, request.Candidate), endpoint)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, upstreamEndpoint, bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
responseStartedAt := time.Now()
|
||||
if err := notifySubmissionStarted(request); err != nil {
|
||||
return Response{}, err
|
||||
correctionScope := newParameterCorrectionScope(request, endpointKind)
|
||||
correctionEnabled := endpointKind == "chat.completions" || endpointKind == "responses"
|
||||
if correctionEnabled {
|
||||
c.Corrections.apply(correctionScope, body)
|
||||
}
|
||||
resp, err := httpClient(request.HTTPClient, c.HTTPClient).Do(req)
|
||||
if err != nil {
|
||||
return Response{}, &ClientError{Code: "network", Message: err.Error(), Retryable: true}
|
||||
provisionalRules := make([]parameterCorrectionRule, 0, 2)
|
||||
seenCorrectionErrors := make(map[string]struct{})
|
||||
var resp *http.Response
|
||||
for correctionAttempt := 0; ; correctionAttempt++ {
|
||||
raw, contentType, payloadErr := openAIRequestPayload(endpointKind, body, request.Candidate)
|
||||
if payloadErr != nil {
|
||||
return Response{}, payloadErr
|
||||
}
|
||||
req, requestErr := http.NewRequestWithContext(ctx, http.MethodPost, upstreamEndpoint, bytes.NewReader(raw))
|
||||
if requestErr != nil {
|
||||
return Response{}, requestErr
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
if err := notifySubmissionStarted(request); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
resp, requestErr = httpClient(request.HTTPClient, c.HTTPClient).Do(req)
|
||||
if requestErr != nil {
|
||||
return Response{}, &ClientError{Code: "network", Message: requestErr.Error(), Retryable: true}
|
||||
}
|
||||
if err := notifyResponseReceived(request); err != nil {
|
||||
_ = resp.Body.Close()
|
||||
return Response{}, err
|
||||
}
|
||||
if !correctionEnabled || (resp.StatusCode != http.StatusBadRequest && resp.StatusCode != http.StatusUnprocessableEntity) {
|
||||
break
|
||||
}
|
||||
_, _, upstreamErr := decodeHTTPResponseForProtocol(resp, openAIWireProtocol(endpointKind))
|
||||
if upstreamErr == nil {
|
||||
break
|
||||
}
|
||||
fingerprint := fmt.Sprintf("%d:%s", resp.StatusCode, strings.ToLower(upstreamErr.Error()))
|
||||
if _, repeated := seenCorrectionErrors[fingerprint]; repeated || correctionAttempt >= 2 {
|
||||
return Response{}, annotateResponseError(upstreamErr, requestIDFromParameterError(upstreamErr), responseStartedAt, time.Now())
|
||||
}
|
||||
seenCorrectionErrors[fingerprint] = struct{}{}
|
||||
rule, safe := deriveParameterCorrection(upstreamErr, body, request.Candidate)
|
||||
if !safe || !applyParameterCorrectionRule(body, rule) {
|
||||
return Response{}, annotateResponseError(upstreamErr, requestIDFromParameterError(upstreamErr), responseStartedAt, time.Now())
|
||||
}
|
||||
c.Corrections.invalidate(correctionScope, rule.Param)
|
||||
provisionalRules = append(provisionalRules, rule)
|
||||
}
|
||||
if err := notifyResponseReceived(request); err != nil {
|
||||
return Response{}, err
|
||||
if len(provisionalRules) > 0 && resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
c.Corrections.commit(correctionScope, provisionalRules)
|
||||
}
|
||||
requestID := requestIDFromHTTPResponse(resp)
|
||||
var result map[string]any
|
||||
var wire *WireResponse
|
||||
var err error
|
||||
upstreamResponseID := ""
|
||||
nativeStreamDelta := openAIWireStreamDelta(request.StreamDelta, openAIWireProtocol(endpointKind), resp)
|
||||
if request.Kind == "responses" && protocol == ProtocolOpenAIResponses && stream {
|
||||
|
||||
Reference in New Issue
Block a user