spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: server/services/exaService.ts6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import axios from 'axios';1819const EXA_API_KEY = process.env.EXA_API_KEY;20const EXA_BASE_URL = 'https://api.exa.ai';2122export interface ExaSearchResult {23 url: string;24 title: string;25 publishedDate?: string;26 author?: string;27 score?: number;28 text?: string;29 highlights?: string[];30 summary?: string;31}3233export interface ExaSearchResponse {34 results: ExaSearchResult[];35 autopromptString?: string;36}3738export interface ExaContentsResult {39 url: string;40 title: string;41 text?: string;42 highlights?: string[];43 summary?: string;44}4546/**47 * Search the web using Exa's semantic search48 */49export async function searchExa(50 query: string,51 options: {52 numResults?: number;53 type?: 'auto' | 'neural' | 'keyword';54 category?: string;55 includeDomains?: string[];56 excludeDomains?: string[];57 startPublishedDate?: string;58 endPublishedDate?: string;59 includeText?: boolean;60 maxCharacters?: number;61 } = {}62): Promise<ExaSearchResponse> {63 if (!EXA_API_KEY) {64 throw new Error('EXA_API_KEY not configured');65 }6667 try {68 const requestBody: any = {69 query,70 type: options.type || 'auto',71 numResults: Math.min(options.numResults || 10, 20), // Limit to 20 to avoid context overflow72 };7374 if (options.category) requestBody.category = options.category;75 if (options.includeDomains) requestBody.includeDomains = options.includeDomains;76 if (options.excludeDomains) requestBody.excludeDomains = options.excludeDomains;77 if (options.startPublishedDate) requestBody.startPublishedDate = options.startPublishedDate;78 if (options.endPublishedDate) requestBody.endPublishedDate = options.endPublishedDate;7980 // Include text content if requested81 if (options.includeText) {82 requestBody.text = {83 maxCharacters: options.maxCharacters || 2000,84 includeHtmlTags: false85 };86 }8788 const response = await axios.post(`${EXA_BASE_URL}/search`, requestBody, {89 headers: {90 'x-api-key': EXA_API_KEY,91 'Content-Type': 'application/json',92 },93 timeout: 15000,94 });9596 return response.data;97 } catch (error) {98 console.error('Exa search error:', error);99 throw new Error(`Failed to search with Exa: ${(error as Error).message}`);100 }101}102103/**104 * Get full content from specific URLs using Exa's crawling105 */106export async function getContentsExa(107 urls: string[],108 options: {109 includeText?: boolean;110 maxCharacters?: number;111 includeHighlights?: boolean;112 highlightsQuery?: string;113 numSentences?: number;114 includeSummary?: boolean;115 summaryQuery?: string;116 } = {}117): Promise<ExaContentsResult[]> {118 if (!EXA_API_KEY) {119 throw new Error('EXA_API_KEY not configured');120 }121122 try {123 const requestBody: any = {124 urls: urls.slice(0, 5), // Limit to 5 URLs to avoid context overflow125 };126127 // Include text content128 if (options.includeText !== false) {129 requestBody.text = {130 maxCharacters: options.maxCharacters || 3000,131 includeHtmlTags: false132 };133 }134135 // Include highlights136 if (options.includeHighlights) {137 requestBody.highlights = {138 query: options.highlightsQuery,139 numSentences: options.numSentences || 3,140 highlightsPerUrl: 3141 };142 }143144 // Include summary145 if (options.includeSummary && options.summaryQuery) {146 requestBody.summary = {147 query: options.summaryQuery148 };149 }150151 const response = await axios.post(`${EXA_BASE_URL}/contents`, requestBody, {152 headers: {153 'x-api-key': EXA_API_KEY,154 'Content-Type': 'application/json',155 },156 timeout: 20000,157 });158159 return response.data.results || [];160 } catch (error) {161 console.error('Exa get contents error:', error);162 throw new Error(`Failed to get contents from Exa: ${(error as Error).message}`);163 }164}165