mirror of
https://gh-proxy.org/https://github.com/klinsmaya/CLIProxyAPI.git
synced 2026-11-10 18:36:47 +08:00
feat(translator): sanitize parametersJsonSchema in OpenAI to Gemini request handling
- Applied `CleanJSONSchemaForGemini` to ensure compatibility by removing unsupported fields and cleaning schema requirements. - Added test cases to validate schema transformation, ensuring only necessary `required` fields are retained. - Enhanced both request and response flows to consistently clean and sanitize tool parameter schemas. Closes: #3863
This commit is contained in:
@@ -363,6 +363,9 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
||||
fnRawBytes := []byte(fnRaw)
|
||||
fnRawBytes, _ = sjson.SetBytes(fnRawBytes, "name", util.SanitizeFunctionName(fn.Get("name").String()))
|
||||
fnRaw = string(fnRawBytes)
|
||||
if parameters := gjson.Get(fnRaw, "parametersJsonSchema"); parameters.Exists() {
|
||||
fnRaw, _ = sjson.SetRaw(fnRaw, "parametersJsonSchema", util.CleanJSONSchemaForGemini(parameters.Raw))
|
||||
}
|
||||
fnRaw, _ = sjson.Delete(fnRaw, "strict")
|
||||
if !hasFunction {
|
||||
functionToolNode, _ = sjson.SetRawBytes(functionToolNode, "functionDeclarations", []byte("[]"))
|
||||
|
||||
@@ -58,3 +58,46 @@ func TestConvertOpenAIRequestToGeminiPreservesInputAudio(t *testing.T) {
|
||||
t.Fatalf("audio data = %q, want %q", got, "SUQzBA==")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIRequestToGeminiCleansToolSchemaRequiredFields(t *testing.T) {
|
||||
inputJSON := `{
|
||||
"model": "gemini-2.0-flash",
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"tools": [{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_company",
|
||||
"description": "Search",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"title": "SearchCompany",
|
||||
"properties": {
|
||||
"country": {"type": "string"},
|
||||
"industry": {"type": "string"}
|
||||
},
|
||||
"required": ["country", "industry", "stale_field", "another_stale"]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}`
|
||||
|
||||
output := ConvertOpenAIRequestToGemini("gemini-2.0-flash", []byte(inputJSON), false)
|
||||
schema := gjson.GetBytes(output, "tools.0.functionDeclarations.0.parametersJsonSchema")
|
||||
|
||||
if !schema.Exists() {
|
||||
t.Fatalf("parametersJsonSchema missing. Output: %s", output)
|
||||
}
|
||||
if schema.Get("title").Exists() {
|
||||
t.Fatalf("schema title should be removed. Output: %s", output)
|
||||
}
|
||||
required := schema.Get("required").Array()
|
||||
if len(required) != 2 {
|
||||
t.Fatalf("required length = %d, want 2. Schema: %s", len(required), schema.Raw)
|
||||
}
|
||||
if got := required[0].String(); got != "country" {
|
||||
t.Fatalf("required[0] = %q, want country. Schema: %s", got, schema.Raw)
|
||||
}
|
||||
if got := required[1].String(); got != "industry" {
|
||||
t.Fatalf("required[1] = %q, want industry. Schema: %s", got, schema.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte
|
||||
funcDecl, _ = sjson.SetBytes(funcDecl, "description", desc.String())
|
||||
}
|
||||
if params := tool.Get("parameters"); params.Exists() {
|
||||
funcDecl, _ = sjson.SetRawBytes(funcDecl, "parametersJsonSchema", []byte(params.Raw))
|
||||
funcDecl, _ = sjson.SetRawBytes(funcDecl, "parametersJsonSchema", []byte(util.CleanJSONSchemaForGemini(params.Raw)))
|
||||
}
|
||||
|
||||
geminiTools, _ = sjson.SetRawBytes(geminiTools, "0.functionDeclarations.-1", funcDecl)
|
||||
|
||||
@@ -245,6 +245,47 @@ func TestConvertOpenAIResponsesRequestToGemini_SystemAndDeveloperRoles(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIResponsesRequestToGeminiCleansToolSchemaRequiredFields(t *testing.T) {
|
||||
inputJSON := `{
|
||||
"model": "gemini-2.0-flash",
|
||||
"input": "hi",
|
||||
"tools": [{
|
||||
"type": "function",
|
||||
"name": "search_company",
|
||||
"description": "Search",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"title": "SearchCompany",
|
||||
"properties": {
|
||||
"country": {"type": "string"},
|
||||
"industry": {"type": "string"}
|
||||
},
|
||||
"required": ["country", "industry", "stale_field", "another_stale"]
|
||||
}
|
||||
}]
|
||||
}`
|
||||
|
||||
output := ConvertOpenAIResponsesRequestToGemini("gemini-2.0-flash", []byte(inputJSON), false)
|
||||
schema := gjson.GetBytes(output, "tools.0.functionDeclarations.0.parametersJsonSchema")
|
||||
|
||||
if !schema.Exists() {
|
||||
t.Fatalf("parametersJsonSchema missing. Output: %s", output)
|
||||
}
|
||||
if schema.Get("title").Exists() {
|
||||
t.Fatalf("schema title should be removed. Output: %s", output)
|
||||
}
|
||||
required := schema.Get("required").Array()
|
||||
if len(required) != 2 {
|
||||
t.Fatalf("required length = %d, want 2. Schema: %s", len(required), schema.Raw)
|
||||
}
|
||||
if got := required[0].String(); got != "country" {
|
||||
t.Fatalf("required[0] = %q, want country. Schema: %s", got, schema.Raw)
|
||||
}
|
||||
if got := required[1].String(); got != "industry" {
|
||||
t.Fatalf("required[1] = %q, want industry. Schema: %s", got, schema.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
func validResponsesGPTReasoningSignature() string {
|
||||
raw := make([]byte, 1+8+16+16+32)
|
||||
raw[0] = 0x80
|
||||
|
||||
Reference in New Issue
Block a user