原生 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 因本地没有已启用的平台模型候选而未完成
88 lines
3.8 KiB
Go
88 lines
3.8 KiB
Go
package clients
|
|
|
|
// Keep these lists aligned with openai-node 6.47.0 and the public OpenAI API
|
|
// reference. They document and test the currently known surface, but are not an
|
|
// upstream allowlist: unknown fields may be introduced by OpenAI after a
|
|
// Gateway release and must remain transparent.
|
|
var openAIChatRequestParameters = stringSet(
|
|
"messages", "model", "audio", "frequency_penalty", "function_call", "functions",
|
|
"logit_bias", "logprobs", "max_completion_tokens", "max_tokens", "metadata",
|
|
"modalities", "moderation", "n", "parallel_tool_calls", "prediction",
|
|
"presence_penalty", "prompt_cache_key", "prompt_cache_options", "prompt_cache_retention",
|
|
"reasoning_effort", "response_format", "safety_identifier", "seed", "service_tier",
|
|
"stop", "store", "stream", "stream_options", "temperature", "tool_choice", "tools",
|
|
"top_logprobs", "top_p", "user", "verbosity", "web_search_options",
|
|
)
|
|
|
|
var openAIResponsesRequestParameters = stringSet(
|
|
"background", "context_management", "conversation", "include", "input", "instructions",
|
|
"max_output_tokens", "max_tool_calls", "metadata", "model", "moderation",
|
|
"parallel_tool_calls", "previous_response_id", "prompt", "prompt_cache_key",
|
|
"prompt_cache_options", "prompt_cache_retention", "reasoning", "safety_identifier",
|
|
"service_tier", "store", "stream", "stream_options", "temperature", "text",
|
|
"tool_choice", "tools", "top_logprobs", "top_p", "truncation", "user",
|
|
)
|
|
|
|
var gatewayOpenAIRequestExtensions = stringSet(
|
|
"runMode", "run_mode", "conversationId", "conversation_id", "sessionId", "session_id",
|
|
"requestId", "request_id", "signal", "userMessage", "user_message", "platformId",
|
|
"platform_id", "options", "enable_thinking", "thinking_budget", "thinking_budget_tokens", "enable_web_search",
|
|
"modelType", "model_type", "capability", "capabilityType", "mode", "simulation", "testMode",
|
|
"cacheAffinityKey", "cache_affinity_key", "simulationDurationMs", "simulationDurationSeconds",
|
|
"simulationMinDurationMs", "simulationMaxDurationMs", "simulationMinDurationSeconds",
|
|
"simulationMaxDurationSeconds", "simulationDurationMinMs", "simulationDurationMaxMs",
|
|
"simulationDurationMinSeconds", "simulationDurationMaxSeconds", "simulationFailure",
|
|
"simulationProfile", "simulationUsage",
|
|
)
|
|
|
|
var gatewayResponsesRequestExtensions = stringSet("messages", "presence_penalty", "frequency_penalty")
|
|
|
|
var controlledOpenAIChatProviderParameters = stringSet(
|
|
"enable_thinking", "thinking_budget", "thinking", "enable_web_search",
|
|
)
|
|
|
|
var controlledOpenAIResponsesProviderParameters = stringSet("presence_penalty", "frequency_penalty")
|
|
|
|
func ValidateOpenAIRequestParameters(kind string, body map[string]any) error {
|
|
// OpenAI-compatible public endpoints intentionally accept future official
|
|
// fields. Provider validation remains authoritative for fields the selected
|
|
// upstream does not support.
|
|
_ = kind
|
|
_ = body
|
|
return nil
|
|
}
|
|
|
|
func FilterOpenAIChatRequestBody(body map[string]any) map[string]any {
|
|
return filterOpenAIRequestBody(body, controlledOpenAIChatProviderParameters, nil)
|
|
}
|
|
|
|
func FilterOpenAIResponsesRequestBody(body map[string]any) map[string]any {
|
|
return filterOpenAIRequestBody(body, controlledOpenAIResponsesProviderParameters, gatewayResponsesRequestExtensions)
|
|
}
|
|
|
|
func filterOpenAIRequestBody(body map[string]any, controlled map[string]struct{}, protocolInternal map[string]struct{}) map[string]any {
|
|
out := make(map[string]any, len(body))
|
|
for key, value := range body {
|
|
if _, ok := controlled[key]; ok {
|
|
out[key] = value
|
|
continue
|
|
}
|
|
if _, internal := gatewayOpenAIRequestExtensions[key]; internal {
|
|
continue
|
|
}
|
|
if _, internal := protocolInternal[key]; internal {
|
|
continue
|
|
}
|
|
out[key] = value
|
|
}
|
|
return out
|
|
}
|
|
|
|
func stringSet(values ...string) map[string]struct{} {
|
|
out := make(map[string]struct{}, len(values))
|
|
for _, value := range values {
|
|
out[value] = struct{}{}
|
|
}
|
|
return out
|
|
}
|