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 {
breakView on GitHub (pinned to 988cbb0330)
Solutions
- Retry the request — this is typically transient
- Check proxy/LB idle and response timeouts versus Exa latency
- Reduce maxResults to shrink response size
- Verify maxExaResponseBytes is not causing interaction issues with compressed responses
- 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
- Bound maxResults to keep responses small
- Align proxy/LB read timeouts with the HTTP client timeout
- Prefer uncompressed or properly negotiated compression with LimitReader
- Treat body-read failures as transient and retry idempotent searches
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
- read response body: %w
- read poll response body: %w
- read body: %w
- rerank call failed: %w
- failed to do bulk: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/ec54a3256a4695d4.
Report an issue: GitHub.