Skip to content

Commit fe43a82

Browse files
xiaolin593nutanix-Hrushikesh
authored andcommitted
fix: response_format schema type (envoyproxy#1299)
**Description** Fix response_format schema type with raw byte to avoid sorting behavior by `json/encode` Go library. Fixes envoyproxy#1298 --------- Signed-off-by: Xiaolin Lin <[email protected]> Signed-off-by: Hrushikesh Patil <[email protected]>
1 parent 2ae8746 commit fe43a82

File tree

5 files changed

+51
-78
lines changed

5 files changed

+51
-78
lines changed

internal/apischema/openai/openai.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ type ChatCompletionResponseFormatJSONSchemaJSONSchema struct {
618618
Description string `json:"description,omitempty"`
619619
// The schema for the response format, described as a JSON Schema object. Learn how
620620
// to build JSON schemas [here](https://json-schema.org/).
621-
Schema any `json:"schema"`
621+
Schema json.RawMessage `json:"schema"`
622622
// Whether to enable strict schema adherence when generating the output. If set to
623623
// true, the model will always follow the exact schema defined in the `schema`
624624
// field. Only a subset of JSON Schema is supported when `strict` is `true`. To

internal/apischema/openai/openai_test.go

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,7 @@ func TestOpenAIChatCompletionResponseFormatUnionUnmarshal(t *testing.T) {
121121
JSONSchema: ChatCompletionResponseFormatJSONSchemaJSONSchema{
122122
Name: "math_response",
123123
Strict: true,
124-
Schema: map[string]any{
125-
"additionalProperties": false,
126-
"type": "object",
127-
"properties": map[string]any{
128-
"step": map[string]any{
129-
"type": "string",
130-
},
131-
},
132-
"required": []any{"steps"},
133-
},
124+
Schema: json.RawMessage(`{ "type": "object", "properties": { "step": {"type": "string"} }, "required": [ "steps"], "additionalProperties": false }`),
134125
},
135126
},
136127
},
@@ -344,16 +335,7 @@ func TestOpenAIChatCompletionMessageUnmarshal(t *testing.T) {
344335
JSONSchema: ChatCompletionResponseFormatJSONSchemaJSONSchema{
345336
Name: "math_response",
346337
Strict: true,
347-
Schema: map[string]any{
348-
"additionalProperties": false,
349-
"type": "object",
350-
"properties": map[string]any{
351-
"step": map[string]any{
352-
"type": "string",
353-
},
354-
},
355-
"required": []any{"steps"},
356-
},
338+
Schema: json.RawMessage(`{ "type": "object", "properties": { "step": {"type": "string"} }, "required": [ "steps"], "additionalProperties": false }`),
357339
},
358340
},
359341
},
@@ -762,16 +744,7 @@ func TestChatCompletionResponseFormatUnionMarshal(t *testing.T) {
762744
JSONSchema: ChatCompletionResponseFormatJSONSchemaJSONSchema{
763745
Name: "math_response",
764746
Strict: true,
765-
Schema: map[string]any{
766-
"additionalProperties": false,
767-
"type": "object",
768-
"properties": map[string]any{
769-
"step": map[string]any{
770-
"type": "string",
771-
},
772-
},
773-
"required": []any{"steps"},
774-
},
747+
Schema: json.RawMessage(`{ "type": "object", "properties": { "step": {"type": "string"} }, "required": [ "steps"], "additionalProperties": false }`),
775748
},
776749
},
777750
},

internal/extproc/translator/gemini_helper.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -415,14 +415,8 @@ func openAIReqToGeminiGenerationConfig(openAIReq *openai.ChatCompletionRequest)
415415
gc.ResponseMIMEType = mimeTypeApplicationJSON
416416
case openAIReq.ResponseFormat.OfJSONSchema != nil:
417417
var schemaMap map[string]any
418-
419-
switch sch := openAIReq.ResponseFormat.OfJSONSchema.JSONSchema.Schema.(type) {
420-
case string:
421-
if err := json.Unmarshal([]byte(sch), &schemaMap); err != nil {
422-
return nil, fmt.Errorf("invalid JSON schema string: %w", err)
423-
}
424-
case map[string]any:
425-
schemaMap = sch
418+
if err := json.Unmarshal([]byte(openAIReq.ResponseFormat.OfJSONSchema.JSONSchema.Schema), &schemaMap); err != nil {
419+
return nil, fmt.Errorf("invalid JSON schema: %w", err)
426420
}
427421

428422
gc.ResponseMIMEType = mimeTypeApplicationJSON

internal/extproc/translator/gemini_helper_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package translator
77

88
import (
9+
"encoding/json"
910
"fmt"
1011
"testing"
1112

@@ -800,9 +801,7 @@ func TestOpenAIReqToGeminiGenerationConfig(t *testing.T) {
800801
OfJSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{
801802
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
802803
JSONSchema: openai.ChatCompletionResponseFormatJSONSchemaJSONSchema{
803-
Schema: map[string]any{
804-
"type": "string",
805-
},
804+
Schema: json.RawMessage(`{"type": "string"}`),
806805
},
807806
},
808807
},
@@ -819,7 +818,7 @@ func TestOpenAIReqToGeminiGenerationConfig(t *testing.T) {
819818
OfJSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{
820819
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
821820
JSONSchema: openai.ChatCompletionResponseFormatJSONSchemaJSONSchema{
822-
Schema: `{"type":"string"}`,
821+
Schema: json.RawMessage(`{"type":"string"}`),
823822
},
824823
},
825824
},
@@ -836,12 +835,12 @@ func TestOpenAIReqToGeminiGenerationConfig(t *testing.T) {
836835
OfJSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{
837836
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
838837
JSONSchema: openai.ChatCompletionResponseFormatJSONSchemaJSONSchema{
839-
Schema: `{"type":`, // invalid JSON.
838+
Schema: json.RawMessage(`{"type":`), // invalid JSON.
840839
},
841840
},
842841
},
843842
},
844-
expectedErrMsg: "invalid JSON schema string",
843+
expectedErrMsg: "invalid JSON schema",
845844
},
846845
}
847846

tests/internal/testopenai/chat_requests.go

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package testopenai
77

88
import (
9+
"encoding/json"
10+
911
"k8s.io/utils/ptr"
1012

1113
"github.com/envoyproxy/ai-gateway/internal/apischema/openai"
@@ -387,39 +389,44 @@ var chatRequests = map[Cassette]*openai.ChatCompletionRequest{
387389
JSONSchema: openai.ChatCompletionResponseFormatJSONSchemaJSONSchema{
388390
Name: "final_output",
389391
Strict: true,
390-
Schema: map[string]interface{}{
391-
"$defs": map[string]interface{}{
392-
"FinancialSearchItem": map[string]interface{}{
393-
"properties": map[string]interface{}{
394-
"reason": map[string]interface{}{
395-
"title": "Reason",
396-
"type": "string",
397-
},
398-
"query": map[string]interface{}{
399-
"title": "Query",
400-
"type": "string",
401-
},
402-
},
403-
"required": []string{"reason", "query"},
404-
"title": "FinancialSearchItem",
405-
"type": "object",
406-
"additionalProperties": false,
407-
},
408-
},
409-
"properties": map[string]interface{}{
410-
"searches": map[string]interface{}{
411-
"items": map[string]interface{}{
412-
"$ref": "#/$defs/FinancialSearchItem",
413-
},
414-
"title": "Searches",
415-
"type": "array",
416-
},
417-
},
418-
"required": []string{"searches"},
419-
"title": "FinancialSearchPlan",
420-
"type": "object",
421-
"additionalProperties": false,
422-
},
392+
Schema: json.RawMessage(`{
393+
"$defs": {
394+
"FinancialSearchItem": {
395+
"additionalProperties": false,
396+
"properties": {
397+
"query": {
398+
"title": "Query",
399+
"type": "string"
400+
},
401+
"reason": {
402+
"title": "Reason",
403+
"type": "string"
404+
}
405+
},
406+
"required": [
407+
"reason",
408+
"query"
409+
],
410+
"title": "FinancialSearchItem",
411+
"type": "object"
412+
}
413+
},
414+
"additionalProperties": false,
415+
"properties": {
416+
"searches": {
417+
"items": {
418+
"$ref": "#/$defs/FinancialSearchItem"
419+
},
420+
"title": "Searches",
421+
"type": "array"
422+
}
423+
},
424+
"required": [
425+
"searches"
426+
],
427+
"title": "FinancialSearchPlan",
428+
"type": "object"
429+
}`),
423430
},
424431
},
425432
},

0 commit comments

Comments
 (0)