feat(openai): 完善 Chat 与 Responses 参数转发
原生 Chat/Responses 改为透明转发,保留标准工具结构并保护调用方显式参数。补齐 Responses 到 Chat 的兼容转换、协议路由边界、完整响应和流式事件,并同步更新 Swagger、回归测试与真实验收脚本。 验证: - cd apps/api && env -u AI_GATEWAY_TEST_DATABASE_URL go test ./... -count=1 - pnpm openapi - pnpm lint - pnpm test - pnpm build - gofmt -l 无输出 - git diff --check 通过 风险: - Chat 回退无法等价表达的 Responses 原生能力现在会返回 unsupported_response_parameter - 真实供应商 E2E 因本地没有已启用的平台模型候选而未完成
This commit is contained in:
@@ -54,10 +54,14 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
return Response{}, normalizeErr
|
||||
}
|
||||
body = normalizedBody
|
||||
applyOpenAIChatReasoningParams(body, request.Candidate)
|
||||
if err := applyOpenAIChatReasoningParamsWithSource(body, request.Candidate, openAIAdaptationSource(request, endpointKind)); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
body = FilterOpenAIChatRequestBody(body)
|
||||
} else if request.Kind == "responses" {
|
||||
applyOpenAIResponsesReasoningParams(body, request.Candidate)
|
||||
if err := applyOpenAIResponsesReasoningParamsWithSource(body, request.Candidate, request.OriginalBody); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
body = FilterOpenAIResponsesRequestBody(body)
|
||||
if _, hasInput := body["input"]; !hasInput {
|
||||
if messages, hasMessages := request.Body["messages"]; hasMessages {
|
||||
@@ -79,8 +83,9 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
responseStartedAt := time.Now()
|
||||
correctionScope := newParameterCorrectionScope(request, endpointKind)
|
||||
correctionEnabled := endpointKind == "chat.completions" || endpointKind == "responses"
|
||||
protectedCorrections := callerProtectedCorrectionParameters(request, endpointKind)
|
||||
if correctionEnabled {
|
||||
c.Corrections.apply(correctionScope, body)
|
||||
c.Corrections.apply(correctionScope, body, protectedCorrections)
|
||||
}
|
||||
provisionalRules := make([]parameterCorrectionRule, 0, 2)
|
||||
seenCorrectionErrors := make(map[string]struct{})
|
||||
@@ -96,6 +101,7 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
applyUpstreamIdempotency(req, request)
|
||||
if err := notifySubmissionStarted(request); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
@@ -119,7 +125,7 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
return Response{}, annotateResponseError(upstreamErr, requestIDFromParameterError(upstreamErr), responseStartedAt, time.Now())
|
||||
}
|
||||
seenCorrectionErrors[fingerprint] = struct{}{}
|
||||
rule, safe := deriveParameterCorrection(upstreamErr, body, request.Candidate)
|
||||
rule, safe := deriveParameterCorrection(upstreamErr, body, request.Candidate, protectedCorrections)
|
||||
if !safe || !applyParameterCorrectionRule(body, rule) {
|
||||
return Response{}, annotateResponseError(upstreamErr, requestIDFromParameterError(upstreamErr), responseStartedAt, time.Now())
|
||||
}
|
||||
@@ -142,7 +148,7 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
var streamDelta StreamDelta = nativeStreamDelta
|
||||
var adapter *chatResponsesStreamAdapter
|
||||
if request.Kind == "responses" && protocol == ProtocolOpenAIChatCompletions && stream {
|
||||
adapter = newChatResponsesStreamAdapter(request.PublicResponseID, request.Model)
|
||||
adapter = newChatResponsesStreamAdapter(request.PublicResponseID, request.Model, request.Body)
|
||||
streamDelta = func(event StreamDeltaEvent) error { return adapter.delta(event, request.StreamDelta) }
|
||||
}
|
||||
if stream {
|
||||
@@ -212,6 +218,19 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
}, nil
|
||||
}
|
||||
|
||||
func openAIAdaptationSource(request Request, endpointKind string) map[string]any {
|
||||
if request.OriginalBody == nil || request.Kind != "responses" || endpointKind != "chat.completions" {
|
||||
return request.OriginalBody
|
||||
}
|
||||
out := cloneBody(request.OriginalBody)
|
||||
if reasoning, ok := request.OriginalBody["reasoning"].(map[string]any); ok {
|
||||
if effort, explicit := reasoning["effort"]; explicit {
|
||||
out["reasoning_effort"] = effort
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeOpenAIImageRequestBody(endpointKind string, body map[string]any, originalBody map[string]any) {
|
||||
if endpointKind != "images.generations" && endpointKind != "images.edits" {
|
||||
return
|
||||
@@ -541,12 +560,18 @@ func ensureOpenAIStreamUsage(body map[string]any, kind string, stream bool) {
|
||||
return
|
||||
}
|
||||
streamOptions := map[string]any{}
|
||||
if existing, ok := body["stream_options"].(map[string]any); ok {
|
||||
if raw, explicit := body["stream_options"]; explicit {
|
||||
existing, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for key, value := range existing {
|
||||
streamOptions[key] = value
|
||||
}
|
||||
}
|
||||
streamOptions["include_usage"] = true
|
||||
if _, explicit := streamOptions["include_usage"]; !explicit {
|
||||
streamOptions["include_usage"] = true
|
||||
}
|
||||
body["stream_options"] = streamOptions
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user