tinyhumansai/openhuman · error

Failed to parse Exa response: {e}

Error message

Failed to parse Exa response: {e}

What it means

Thrown in post_documents after post() has already delivered parsed JSON: converting the 'results' array of documents/contents endpoints into Vec<ExaResultItem> fails because individual items lack expected fields (id/title/url/text) or carry unexpected types. The transport succeeded; the faulty input is the shape of ExaResultItem entries in the response payload.

Source

Thrown at src/openhuman/search/tools/exa.rs:256

                anyhow::bail!(
                    "Exa rejected the configured API key (HTTP {status}). \
                     Check your Exa API key under Connections > Search engine."
                );
            }
            anyhow::bail!("Exa returned non-2xx status {status}");
        }

        resp.json().await.map_err(|e| {
            tracing::warn!("[exa] failed to read response JSON: {e}");
            anyhow::anyhow!("Failed to read Exa response JSON: {e}")
        })
    }

    async fn post_documents(&self, path: &str, body: Value) -> anyhow::Result<Vec<ExaResultItem>> {
        let value = self.post(path, body).await?;
        let parsed: ExaSearchResponse = serde_json::from_value(value).map_err(|e| {
            tracing::warn!("[exa] failed to parse {path} response: {e}");
            anyhow::anyhow!("Failed to parse Exa response: {e}")
        })?;
        tracing::debug!(path, result_count = parsed.results.len(), "[exa] call ok");
        Ok(parsed.results)
    }

    fn render_plain(&self, results: &[ExaResultItem], heading: &str, limit: usize) -> String {
        if results.is_empty() {
            return format!("No Exa results for: {heading}");
        }

        let mut lines = vec![format!("Search results for: {heading} (via Exa)")];
        for (i, item) in results.iter().take(limit).enumerate() {
            lines.push(format!("{}. {}", i + 1, item.display_title()));
            lines.push(format!("   {}", item.url.trim()));
            if let Some(date) = non_empty(item.published_date.as_deref()) {
                lines.push(format!("   Published: {date}"));
            }
            if let Some(author) = non_empty(item.author.as_deref()) {

View on GitHub (pinned to 7491200858)

Solutions

  1. Inspect the logged parse error and relax ExaResultItem fields to Option<> where Exa legitimately omits them.
  2. Add #[serde(default)] / alias attributes to tolerate field renames or additions.
  3. Log and skip individual unparseable items instead of failing the whole batch.
  4. Pin a response fixture test to catch Exa schema drift early.
Defensive patterns

Strategy: fallback

When it happens

Trigger: Thrown at src/openhuman/search/tools/exa.rs:256 when the library encounters an invalid state.

Common situations: See trigger scenarios.

Understand the failure class


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/d3f80bced388a04b. Report an issue: GitHub.