Handle reasoning-only vision responses #345

Merged
m merged 1 commits from codex/household-scenario-audit into main 2026-08-25 09:37:23 +00:00
2 changed files with 24 additions and 3 deletions
+11 -3
View File
@@ -134,17 +134,25 @@ func (c VisionClient) Describe(ctx context.Context, model, contentType string, d
var result struct {
Choices []struct {
Message struct {
Content string `json:"content"`
Content string `json:"content"`
ReasoningContent string `json:"reasoning_content"`
} `json:"message"`
} `json:"choices"`
}
if err := json.NewDecoder(io.LimitReader(response.Body, 2<<20)).Decode(&result); err != nil {
return "", err
}
if len(result.Choices) == 0 || strings.TrimSpace(result.Choices[0].Message.Content) == "" {
if len(result.Choices) == 0 {
return "", ErrEmptyResult
}
return strings.TrimSpace(result.Choices[0].Message.Content), nil
text := strings.TrimSpace(result.Choices[0].Message.Content)
if text == "" {
text = strings.TrimSpace(result.Choices[0].Message.ReasoningContent)
}
if text == "" {
return "", ErrEmptyResult
}
return text, nil
}
func (c SpeechClient) client() *http.Client {
+13
View File
@@ -63,3 +63,16 @@ func TestVisionClientSendsImageDataAndInstruction(t *testing.T) {
t.Fatalf("description=%q err=%v", text, err)
}
}
func TestVisionClientUsesReasoningContentWhenProviderOmitsContent(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{
"message": map[string]string{"content": "", "reasoning_content": "чёрный квадрат"},
}}})
}))
defer server.Close()
text, err := (VisionClient{BaseURL: server.URL, Client: server.Client()}).Describe(context.Background(), "vision-model", "image/png", []byte("image"), "Опиши изображение")
if err != nil || text != "чёрный квадрат" {
t.Fatalf("description=%q err=%v", text, err)
}
}