Harden Gemini JSON parsing
build-push / docker (push) Successful in 4m10s

This commit is contained in:
AI Assistant
2026-03-13 11:02:50 +09:00
parent 4d8d317c50
commit 129507357e
+67 -4
View File
@@ -94,9 +94,14 @@ User query: ` + query,
return []string{query}, fmt.Errorf("gemini query expansion returned no candidates")
}
jsonText, err := extractJSONObject(payload.Candidates[0].Content.Parts[0].Text)
if err != nil {
return []string{query}, fmt.Errorf("gemini query expansion JSON extraction failed: %w", err)
}
var parsed QueryExpansion
if err := json.Unmarshal([]byte(payload.Candidates[0].Content.Parts[0].Text), &parsed); err != nil {
return []string{query}, fmt.Errorf("gemini query expansion JSON parse failed: %w", err)
if err := json.Unmarshal([]byte(jsonText), &parsed); err != nil {
return []string{query}, fmt.Errorf("gemini query expansion JSON parse failed: %w; raw=%q", err, truncateForError(payload.Candidates[0].Content.Parts[0].Text, 200))
}
queries := []string{query}
@@ -187,6 +192,11 @@ User query: ` + query,
return nil, fmt.Errorf("gemini vision returned no candidates")
}
jsonText, err := extractJSONObject(payload.Candidates[0].Content.Parts[0].Text)
if err != nil {
return nil, fmt.Errorf("gemini vision JSON extraction failed: %w", err)
}
var parsed struct {
Recommendations []struct {
Index int `json:"index"`
@@ -194,8 +204,8 @@ User query: ` + query,
Recommended bool `json:"recommended"`
} `json:"recommendations"`
}
if err := json.Unmarshal([]byte(payload.Candidates[0].Content.Parts[0].Text), &parsed); err != nil {
return nil, fmt.Errorf("gemini vision JSON parse failed: %w", err)
if err := json.Unmarshal([]byte(jsonText), &parsed); err != nil {
return nil, fmt.Errorf("gemini vision JSON parse failed: %w; raw=%q", err, truncateForError(payload.Candidates[0].Content.Parts[0].Text, 200))
}
recommendations := make([]AIRecommendation, 0, len(parsed.Recommendations))
@@ -260,3 +270,56 @@ func min(a, b int) int {
}
return b
}
func extractJSONObject(text string) (string, error) {
cleaned := strings.TrimSpace(text)
cleaned = strings.TrimPrefix(cleaned, "```json")
cleaned = strings.TrimPrefix(cleaned, "```")
cleaned = strings.TrimSuffix(cleaned, "```")
cleaned = strings.TrimSpace(cleaned)
start := strings.Index(cleaned, "{")
if start == -1 {
return "", fmt.Errorf("no JSON object start found in %q", truncateForError(cleaned, 200))
}
depth := 0
inString := false
escaped := false
for i := start; i < len(cleaned); i++ {
ch := cleaned[i]
if escaped {
escaped = false
continue
}
if ch == '\\' && inString {
escaped = true
continue
}
if ch == '"' {
inString = !inString
continue
}
if inString {
continue
}
switch ch {
case '{':
depth++
case '}':
depth--
if depth == 0 {
return cleaned[start : i+1], nil
}
}
}
return "", fmt.Errorf("no complete JSON object found in %q", truncateForError(cleaned, 200))
}
func truncateForError(text string, limit int) string {
trimmed := strings.TrimSpace(text)
if len(trimmed) <= limit {
return trimmed
}
return trimmed[:limit] + "..."
}