Tencent/WeKnora · error

failed to read Exa response: %w

Error message

failed to read Exa response: %w

What it means

Wraps errors from io.ReadAll when reading the Exa response body (bounded by maxExaResponseBytes via io.LimitReader). Fires on mid-stream connection resets, premature EOF, or body read failures after headers were received.

Source

Thrown at internal/infrastructure/web_search/exa.go:102

	if err != nil {
		return nil, fmt.Errorf("failed to marshal Exa request: %w", err)
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.baseURL, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to create Exa request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-api-key", p.apiKey)

	logger.Infof(ctx, "[WebSearch][Exa] query=%q maxResults=%d url=%s", query, maxResults, p.baseURL)
	resp, err := p.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to execute Exa request: %w", err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxExaResponseBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to read Exa response: %w", err)
	}
	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		logger.Warnf(ctx, "[WebSearch][Exa] API returned status %d: %s", resp.StatusCode, string(body))
		return nil, fmt.Errorf("exa API returned status %d: %s", resp.StatusCode, string(body))
	}

	var data exaSearchResponse
	if err := json.Unmarshal(body, &data); err != nil {
		return nil, fmt.Errorf("failed to unmarshal Exa response: %w", err)
	}
	if data.Error != "" {
		return nil, fmt.Errorf("exa API error: %s", data.Error)
	}

	results := make([]*types.WebSearchResult, 0, len(data.Results))
	for _, item := range data.Results {
		if len(results) >= maxResults {
			break

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Retry the request — this is typically transient
  2. Check proxy/LB idle and response timeouts versus Exa latency
  3. Reduce maxResults to shrink response size
  4. Verify maxExaResponseBytes is not causing interaction issues with compressed responses
  5. Log the unwrapped error (unexpected EOF vs connection reset) to target the fix
Defensive patterns

Strategy: retry

Validate before calling

// cannot be pre-validated; mitigate by bounding request size
if maxResults > 20 {
    maxResults = 20 // smaller responses reduce mid-body disconnections
}

Try / catch

results, err := provider.Search(ctx, query, 10, false)
if err != nil {
    if strings.Contains(err.Error(), "failed to read Exa response") ||
       errors.Is(err, io.ErrUnexpectedEOF) {
        return retryWithBackoff(ctx, query)
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling Search when the connection to Exa drops while the body is being read, a proxy terminates the stream, or the server closes the connection prematurely.

Common situations: Flaky networks or load balancers cutting long responses, idle-timeout at a proxy smaller than Exa's response time, very large result sets hitting connection limits, HTTP/2 stream resets.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/ec54a3256a4695d4. Report an issue: GitHub.