feat(05-02): exa-search research integration

This commit is contained in:
2026-03-02 14:10:32 +01:00
parent 994cc9e984
commit 53f026aee2
6 changed files with 262 additions and 5 deletions
+75
View File
@@ -0,0 +1,75 @@
const DEFAULT_EXA_API_URL = 'https://api.exa.ai/search';
const buildSummary = (results) => {
if (!results || results.length === 0) {
return '';
}
const snippets = results
.map((result) => result.snippet || result.highlight)
.filter(Boolean);
if (snippets.length === 0) {
return results
.slice(0, 3)
.map((result) => result.title)
.filter(Boolean)
.join(' · ');
}
return snippets.slice(0, 3).join(' ');
};
const searchExerciseResearch = async ({ query, numResults = 5 }) => {
if (!query || typeof query !== 'string') {
throw new Error('Query must be a non-empty string');
}
const apiKey = process.env.EXA_API_KEY;
if (!apiKey) {
throw new Error('EXA_API_KEY is not configured');
}
const apiUrl = process.env.EXA_API_URL || DEFAULT_EXA_API_URL;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': apiKey
},
body: JSON.stringify({
query,
numResults,
type: 'neural',
useAutoprompt: true
})
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Exa search failed: ${response.status} ${text}`);
}
const data = await response.json();
const results = (data.results || []).map((result) => ({
id: result.id,
title: result.title,
url: result.url,
snippet: Array.isArray(result.highlights) && result.highlights.length > 0
? result.highlights[0]
: result.snippet,
highlight: result.highlight,
publishedDate: result.publishedDate,
score: result.score
}));
return {
summary: buildSummary(results),
results
};
};
module.exports = {
searchExerciseResearch
};