{"record":{"id":"dfb1de9e25468aa7","repo":"Tencent/WeKnora","slug":"decode-suggestion-json-w","errorCode":null,"errorMessage":"decode suggestion JSON: %w","messagePattern":"decode suggestion JSON: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/application/service/message_suggestion.go","lineNumber":815,"sourceCode":"}\n\ntype generatedSuggestionEnvelope struct {\n\tQuestions []struct {\n\t\tText     string `json:\"text\"`\n\t\tCategory string `json:\"category\"`\n\t} `json:\"questions\"`\n}\n\nfunc parseGeneratedSuggestions(content string, allowedCategories []string, limit int) (types.SuggestionItems, error) {\n\tcontent = strings.TrimSpace(suggestionThinkBlock.ReplaceAllString(content, \"\"))\n\tstart := strings.Index(content, \"{\")\n\tend := strings.LastIndex(content, \"}\")\n\tif start < 0 || end < start {\n\t\treturn nil, errors.New(\"model returned invalid suggestion JSON\")\n\t}\n\tvar envelope generatedSuggestionEnvelope\n\tif err := json.Unmarshal([]byte(content[start:end+1]), &envelope); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode suggestion JSON: %w\", err)\n\t}\n\tallowed := make(map[string]struct{}, len(allowedCategories))\n\tfor _, category := range allowedCategories {\n\t\tallowed[category] = struct{}{}\n\t}\n\tseen := make(map[string]struct{})\n\titems := make(types.SuggestionItems, 0, limit)\n\tfor _, question := range envelope.Questions {\n\t\ttext := strings.TrimSpace(question.Text)\n\t\tif text == \"\" || len([]rune(text)) > 200 {\n\t\t\tcontinue\n\t\t}\n\t\tkey := normalizeSuggestionText(text)\n\t\tif _, exists := seen[key]; exists {\n\t\t\tcontinue\n\t\t}\n\t\tseen[key] = struct{}{}\n\t\tcategory := question.Category","sourceCodeStart":797,"sourceCodeEnd":833,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/message_suggestion.go#L797-L833","documentation":"parseGeneratedSuggestions extracts a JSON envelope from raw LLM output and unmarshals it. If the extracted substring is not valid JSON (malformed model output), json.Unmarshal fails and the error is wrapped as 'decode suggestion JSON'. This is a model-output quality problem, not a caller bug.","triggerScenarios":"generateWithModel receiving model content where the text between the first '{' and last '}' is not valid JSON — truncated response, prose mixed with JSON, markdown fences inside the braces, or non-JSON hallucination.","commonSituations":"Model hitting max_tokens and truncating the JSON; model wrapping JSON in ```json fences; small/weak models producing comments or trailing commas; temperature too high for structured output.","solutions":["Strengthen the prompt: require strict JSON, no fences, no commentary, and set a sufficient max_tokens.","Use structured output / JSON mode if the model provider supports it, or lower temperature.","Log the raw content on failure to see what the model actually returned, then widen the extraction or repair (e.g. strip fences) before unmarshaling.","Retry the generation once on decode failure; treat repeated failures as model regression."],"exampleFix":"// before\nif err := json.Unmarshal([]byte(content[start:end+1]), &envelope); err != nil {\n    return nil, fmt.Errorf(\"decode suggestion JSON: %w\", err)\n}\n// after\ncandidate := strings.ReplaceAll(content[start:end+1], \"```\", \"\")\nif err := json.Unmarshal([]byte(candidate), &envelope); err != nil {\n    return nil, fmt.Errorf(\"decode suggestion JSON: %w (content: %.200s)\", err, content)\n}","handlingStrategy":"validation","validationCode":"start := strings.Index(content, \"{\")\nend := strings.LastIndex(content, \"}\")\nif start < 0 || end <= start { return fmt.Errorf(\"no JSON in model output\") }\njson.Valid([]byte(content[start : end+1])) // cheap pre-check","typeGuard":"func looksLikeSuggestionJSON(content string) bool {\n    s := strings.Index(content, \"{\"); e := strings.LastIndex(content, \"}\")\n    return s >= 0 && e > s && json.Valid([]byte(content[s:e+1]))\n}","tryCatchPattern":"suggestions, err := parseGeneratedSuggestions(content, allowed)\nif err != nil && strings.Contains(err.Error(), \"decode suggestion JSON\") {\n    log.Warnf(\"bad model output: %.200s\", content)\n    suggestions, err = retryGeneration(ctx) // one retry\n}","preventionTips":["Prompt for strict JSON with no fences or commentary","Set max_tokens high enough to avoid truncation","Prefer provider JSON/structured-output mode; log raw outputs on failure"],"tags":["llm","json","parsing"],"backgroundTag":"llm-output-not-valid-json","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}