feat(xai): normalize xAI input reasoning items and enhance test cases
- Added `normalizeXAIInputReasoningItems` to clean up `input` reasoning items, removing null `content` and `encrypted_content` fields. - Updated `xai_executor` test cases to validate input normalization and reasoning item handling.
This commit is contained in:
@@ -500,6 +500,7 @@ func (e *XAIExecutor) prepareResponsesRequest(ctx context.Context, req cliproxye
|
|||||||
body, _ = sjson.DeleteBytes(body, "safety_identifier")
|
body, _ = sjson.DeleteBytes(body, "safety_identifier")
|
||||||
body, _ = sjson.DeleteBytes(body, "stream_options")
|
body, _ = sjson.DeleteBytes(body, "stream_options")
|
||||||
body = normalizeXAITools(body)
|
body = normalizeXAITools(body)
|
||||||
|
body = normalizeXAIInputReasoningItems(body)
|
||||||
body = normalizeCodexInstructions(body)
|
body = normalizeCodexInstructions(body)
|
||||||
body = sanitizeXAIResponsesBody(body, baseModel)
|
body = sanitizeXAIResponsesBody(body, baseModel)
|
||||||
|
|
||||||
@@ -704,6 +705,37 @@ func normalizeXAITools(body []byte) []byte {
|
|||||||
return updated
|
return updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeXAIInputReasoningItems(body []byte) []byte {
|
||||||
|
input := gjson.GetBytes(body, "input")
|
||||||
|
if !input.Exists() || !input.IsArray() {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := body
|
||||||
|
for i, item := range input.Array() {
|
||||||
|
if item.Get("type").String() != "reasoning" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
contentPath := fmt.Sprintf("input.%d.content", i)
|
||||||
|
if content := gjson.GetBytes(updated, contentPath); content.Exists() && content.Type == gjson.Null {
|
||||||
|
updatedBody, errDel := sjson.DeleteBytes(updated, contentPath)
|
||||||
|
if errDel != nil {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
updated = updatedBody
|
||||||
|
}
|
||||||
|
encryptedContentPath := fmt.Sprintf("input.%d.encrypted_content", i)
|
||||||
|
if encryptedContent := gjson.GetBytes(updated, encryptedContentPath); encryptedContent.Exists() && encryptedContent.Type == gjson.Null {
|
||||||
|
updatedBody, errDel := sjson.DeleteBytes(updated, encryptedContentPath)
|
||||||
|
if errDel != nil {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
updated = updatedBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
func removeXAIEncryptedReasoningInclude(body []byte) []byte {
|
func removeXAIEncryptedReasoningInclude(body []byte) []byte {
|
||||||
include := gjson.GetBytes(body, "include")
|
include := gjson.GetBytes(body, "include")
|
||||||
if !include.Exists() || !include.IsArray() {
|
if !include.Exists() || !include.IsArray() {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) {
|
|||||||
|
|
||||||
_, err := exec.Execute(context.Background(), auth, cliproxyexecutor.Request{
|
_, err := exec.Execute(context.Background(), auth, cliproxyexecutor.Request{
|
||||||
Model: "grok-4.3",
|
Model: "grok-4.3",
|
||||||
Payload: []byte(`{"model":"grok-4.3","input":"hello","include":["reasoning.encrypted_content"],"reasoning":{"effort":"high"},"tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`),
|
Payload: []byte(`{"model":"grok-4.3","input":[{"type":"reasoning","summary":[{"type":"summary_text","text":"test"}],"content":null,"encrypted_content":null},{"role":"user","content":"hello"}],"include":["reasoning.encrypted_content"],"reasoning":{"effort":"high"},"tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`),
|
||||||
}, cliproxyexecutor.Options{
|
}, cliproxyexecutor.Options{
|
||||||
SourceFormat: sdktranslator.FormatOpenAIResponse,
|
SourceFormat: sdktranslator.FormatOpenAIResponse,
|
||||||
Stream: false,
|
Stream: false,
|
||||||
@@ -91,6 +91,15 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) {
|
|||||||
if gjson.GetBytes(gotBody, "reasoning.effort").String() != "high" {
|
if gjson.GetBytes(gotBody, "reasoning.effort").String() != "high" {
|
||||||
t.Fatalf("reasoning.effort = %q, want high; body=%s", gjson.GetBytes(gotBody, "reasoning.effort").String(), string(gotBody))
|
t.Fatalf("reasoning.effort = %q, want high; body=%s", gjson.GetBytes(gotBody, "reasoning.effort").String(), string(gotBody))
|
||||||
}
|
}
|
||||||
|
if gjson.GetBytes(gotBody, "input.0.content").Exists() {
|
||||||
|
t.Fatalf("input.0.content exists, want removed; body=%s", string(gotBody))
|
||||||
|
}
|
||||||
|
if gjson.GetBytes(gotBody, "input.0.encrypted_content").Exists() {
|
||||||
|
t.Fatalf("input.0.encrypted_content exists, want removed; body=%s", string(gotBody))
|
||||||
|
}
|
||||||
|
if got := gjson.GetBytes(gotBody, "input.0.summary.0.text").String(); got != "test" {
|
||||||
|
t.Fatalf("input.0.summary.0.text = %q, want test; body=%s", got, string(gotBody))
|
||||||
|
}
|
||||||
tools := gjson.GetBytes(gotBody, "tools").Array()
|
tools := gjson.GetBytes(gotBody, "tools").Array()
|
||||||
if len(tools) != 3 {
|
if len(tools) != 3 {
|
||||||
t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody))
|
t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody))
|
||||||
@@ -183,7 +192,7 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) {
|
|||||||
|
|
||||||
result, err := exec.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{
|
result, err := exec.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{
|
||||||
Model: "grok-4.3",
|
Model: "grok-4.3",
|
||||||
Payload: []byte(`{"model":"grok-4.3","input":"hello","tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`),
|
Payload: []byte(`{"model":"grok-4.3","input":[{"type":"reasoning","summary":[{"type":"summary_text","text":"test"}],"content":null,"encrypted_content":null},{"role":"user","content":"hello"}],"tools":[{"type":"tool_search"},{"type":"image_generation"},{"type":"custom","name":"apply_patch"},{"type":"custom","name":"custom_lookup"},{"type":"function","name":"lookup"},{"type":"web_search","external_web_access":true,"search_content_types":["text","image"]}]}`),
|
||||||
}, cliproxyexecutor.Options{
|
}, cliproxyexecutor.Options{
|
||||||
SourceFormat: sdktranslator.FormatOpenAIResponse,
|
SourceFormat: sdktranslator.FormatOpenAIResponse,
|
||||||
Stream: true,
|
Stream: true,
|
||||||
@@ -201,6 +210,15 @@ func TestXAIExecutorExecuteStreamFiltersToolSearchTool(t *testing.T) {
|
|||||||
if len(tools) != 3 {
|
if len(tools) != 3 {
|
||||||
t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody))
|
t.Fatalf("tools length = %d, want 3; body=%s", len(tools), string(gotBody))
|
||||||
}
|
}
|
||||||
|
if gjson.GetBytes(gotBody, "input.0.content").Exists() {
|
||||||
|
t.Fatalf("input.0.content exists, want removed; body=%s", string(gotBody))
|
||||||
|
}
|
||||||
|
if gjson.GetBytes(gotBody, "input.0.encrypted_content").Exists() {
|
||||||
|
t.Fatalf("input.0.encrypted_content exists, want removed; body=%s", string(gotBody))
|
||||||
|
}
|
||||||
|
if got := gjson.GetBytes(gotBody, "input.0.summary.0.text").String(); got != "test" {
|
||||||
|
t.Fatalf("input.0.summary.0.text = %q, want test; body=%s", got, string(gotBody))
|
||||||
|
}
|
||||||
for i, tool := range tools {
|
for i, tool := range tools {
|
||||||
toolType := tool.Get("type").String()
|
toolType := tool.Get("type").String()
|
||||||
if toolType == "image_generation" {
|
if toolType == "image_generation" {
|
||||||
|
|||||||
Reference in New Issue
Block a user