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:
@@ -108,7 +108,7 @@ func NewParameterCorrectionCache() *ParameterCorrectionCache {
|
||||
}
|
||||
}
|
||||
|
||||
func (cache *ParameterCorrectionCache) apply(scope parameterCorrectionScope, body map[string]any) []string {
|
||||
func (cache *ParameterCorrectionCache) apply(scope parameterCorrectionScope, body map[string]any, protected ...map[string]struct{}) []string {
|
||||
if cache == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -127,6 +127,9 @@ func (cache *ParameterCorrectionCache) apply(scope parameterCorrectionScope, bod
|
||||
applied := make([]string, 0, len(elements))
|
||||
for _, element := range elements {
|
||||
rule := element.Value.(parameterCorrectionCacheEntry).rule
|
||||
if correctionParamProtected(rule.Param, protected...) {
|
||||
continue
|
||||
}
|
||||
if applyParameterCorrectionRule(body, rule) {
|
||||
applied = append(applied, rule.Param)
|
||||
cache.lru.MoveToFront(element)
|
||||
@@ -183,7 +186,7 @@ func (cache *ParameterCorrectionCache) size() int {
|
||||
return cache.lru.Len()
|
||||
}
|
||||
|
||||
func deriveParameterCorrection(err error, body map[string]any, candidate store.RuntimeModelCandidate) (parameterCorrectionRule, bool) {
|
||||
func deriveParameterCorrection(err error, body map[string]any, candidate store.RuntimeModelCandidate, protected map[string]struct{}) (parameterCorrectionRule, bool) {
|
||||
var clientErr *ClientError
|
||||
if !errors.As(err, &clientErr) || (clientErr.StatusCode != 400 && clientErr.StatusCode != 422) {
|
||||
return parameterCorrectionRule{}, false
|
||||
@@ -196,9 +199,15 @@ func deriveParameterCorrection(err error, body map[string]any, candidate store.R
|
||||
if !isSafeCorrectionParam(param) {
|
||||
return parameterCorrectionRule{}, false
|
||||
}
|
||||
if correctionParamProtected(param, protected) {
|
||||
return parameterCorrectionRule{}, false
|
||||
}
|
||||
lowerMessage := strings.ToLower(message)
|
||||
lowerCode := strings.ToLower(code)
|
||||
if conflictRule, ok := deriveConflictCorrection(param, lowerMessage); ok {
|
||||
if correctionParamProtected(conflictRule.Param, protected) {
|
||||
return parameterCorrectionRule{}, false
|
||||
}
|
||||
return conflictRule, true
|
||||
}
|
||||
if strings.Contains(lowerMessage, "unknown parameter") ||
|
||||
@@ -228,6 +237,76 @@ func deriveParameterCorrection(err error, body map[string]any, candidate store.R
|
||||
return parameterCorrectionRule{}, false
|
||||
}
|
||||
|
||||
func correctionParamProtected(param string, protected ...map[string]struct{}) bool {
|
||||
for _, values := range protected {
|
||||
if _, ok := values[normalizeCorrectionParam(param)]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func callerProtectedCorrectionParameters(request Request, endpointKind string) map[string]struct{} {
|
||||
if request.OriginalBody == nil {
|
||||
return nil
|
||||
}
|
||||
protected := make(map[string]struct{})
|
||||
protect := func(param string) {
|
||||
param = normalizeCorrectionParam(param)
|
||||
if isSafeCorrectionParam(param) {
|
||||
protected[param] = struct{}{}
|
||||
}
|
||||
}
|
||||
for key := range request.OriginalBody {
|
||||
protect(key)
|
||||
}
|
||||
if reasoning, ok := request.OriginalBody["reasoning"].(map[string]any); ok {
|
||||
if _, explicit := reasoning["effort"]; explicit {
|
||||
if endpointKind == "chat.completions" {
|
||||
protect("reasoning_effort")
|
||||
} else {
|
||||
protect("reasoning.effort")
|
||||
}
|
||||
}
|
||||
}
|
||||
if request.Kind == "responses" && endpointKind == "chat.completions" {
|
||||
if _, explicit := request.OriginalBody["max_output_tokens"]; explicit {
|
||||
protect("max_completion_tokens")
|
||||
}
|
||||
if _, explicit := request.OriginalBody["top_logprobs"]; explicit {
|
||||
protect("top_logprobs")
|
||||
protect("logprobs")
|
||||
}
|
||||
if responseIncludeContains(request.OriginalBody["include"], "message.output_text.logprobs") {
|
||||
protect("logprobs")
|
||||
}
|
||||
if text, ok := request.OriginalBody["text"].(map[string]any); ok {
|
||||
if _, explicit := text["verbosity"]; explicit {
|
||||
protect("verbosity")
|
||||
}
|
||||
}
|
||||
}
|
||||
return protected
|
||||
}
|
||||
|
||||
func responseIncludeContains(value any, target string) bool {
|
||||
switch values := value.(type) {
|
||||
case []any:
|
||||
for _, value := range values {
|
||||
if stringFromAny(value) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []string:
|
||||
for _, value := range values {
|
||||
if value == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func requestIDFromParameterError(err error) string {
|
||||
var clientErr *ClientError
|
||||
if errors.As(err, &clientErr) {
|
||||
|
||||
Reference in New Issue
Block a user