micro/go-micro · error

failed to parse response

Error message

failed to parse response: %w

What it means

callAPI wraps the error from json.Unmarshal when the HTTP 200 body cannot be decoded into the expected chat-completion response struct. It means the API returned a 200 whose JSON shape does not match the expected {choices:[{message:{...}}]} schema. The library throws it to surface the decode failure with the underlying *json.UnmarshalTypeError or *json.SyntaxError attached.

Solutions

  1. Log string(respBody) on this error to see what the API actually returned and compare against the expected chat response schema.
  2. Confirm the API version/model endpoint matches what this provider version expects; pin or update the provider code if AtlasCloud changed its schema.
  3. Check for intermediaries returning HTML (proxy auth pages) by inspecting Content-Type of the response.
  4. Capture the error from io.ReadAll instead of discarding it so truncated bodies are detected before unmarshal.

Example fix

if err := json.Unmarshal(respBody, &chatResp); err != nil {
    return nil, nil, fmt.Errorf("failed to parse response: %w (body: %.200s)", err, string(respBody))
}
Defensive patterns

Strategy: try-catch

Type guard

func isValidChatResponse(b []byte) bool {
    var probe struct {
        Choices []json.RawMessage `json:"choices"`
    }
    return json.Unmarshal(b, &probe) == nil
}

Try / catch

resp, err := provider.Generate(ctx, req)
if err != nil {
    if strings.Contains(err.Error(), "failed to parse response") {
        var uerr *json.UnmarshalTypeError
        if errors.As(err, &uerr) {
            log.Printf("schema mismatch at %s: %v", uerr.Field, uerr)
        }
    }
    return err
}

Prevention

When it happens

Trigger: json.Unmarshal(respBody, &chatResp) returns an error: the body is empty, truncated, HTML (e.g. a proxy/login page), or field types diverge from the struct (e.g. content returned as an array or null where a string is expected).

Common situations: Provider changed its response schema after an API version update; an intermediary (gateway, WAF, captive portal) returned HTML with status 200; response body was read incompletely because io.ReadAll's error was ignored; model returned tool_calls in an unexpected format.

Understand the failure class

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/62769d7ef73410a9. Report an issue: GitHub.

Appendix: source

Thrown at ai/atlascloud/atlascloud.go:466

		retryAfter := time.Duration(0)
		var retryErr interface{ RetryAfter() time.Duration }
		if errors.As(ai.NewHTTPError(httpResp, respBody), &retryErr) {
			retryAfter = retryErr.RetryAfter()
		}
		return nil, nil, &atlascloudAPIError{Status: httpResp.Status, Code: httpResp.StatusCode, Retry: retryAfter, Phase: phase, Summary: atlascloudRequestSummary(req), Body: string(respBody)}
	}

	var chatResp struct {
		Choices []struct {
			Message struct {
				Content   string          `json:"content"`
				ToolCalls []atlasToolCall `json:"tool_calls"`
			} `json:"message"`
		} `json:"choices"`
	}

	if err := json.Unmarshal(respBody, &chatResp); err != nil {
		return nil, nil, fmt.Errorf("failed to parse response: %w", err)
	}

	if len(chatResp.Choices) == 0 {
		return nil, nil, fmt.Errorf("no response from API")
	}

	choice := chatResp.Choices[0]
	response := &ai.Response{
		Reply: choice.Message.Content,
	}

	for _, tc := range choice.Message.ToolCalls {
		var input map[string]any
		if err := json.Unmarshal([]byte(tc.Function.Arguments), &input); err != nil {
			input = map[string]any{}
		}
		response.ToolCalls = append(response.ToolCalls, ai.ToolCall{
			ID:    tc.ID,

View on GitHub (pinned to 24529f1404)