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/tavilyService.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';18import { summarizeContent, shouldSummarize } from './contentSummarizerService';1920const TAVILY_API_KEY = process.env.TAVILY_API_KEY;21const TAVILY_BASE_URL = 'https://api.tavily.com';2223// ==================== COMPLETE INTERFACES FROM DOCUMENTATION ====================2425export interface TavilySearchOptions {26 query: string;27 search_depth?: 'advanced' | 'basic' | 'fast' | 'ultra-fast';28 chunks_per_source?: number;29 max_results?: number;30 topic?: 'general' | 'news' | 'finance';31 time_range?: 'day' | 'week' | 'month' | 'year' | 'd' | 'w' | 'm' | 'y';32 start_date?: string; // YYYY-MM-DD33 end_date?: string; // YYYY-MM-DD34 include_answer?: boolean | 'basic' | 'advanced';35 include_raw_content?: boolean | 'markdown' | 'text';36 include_images?: boolean;37 include_image_descriptions?: boolean;38 include_favicon?: boolean;39 include_domains?: string[];40 exclude_domains?: string[];41 country?: string;42 auto_parameters?: boolean;43 include_usage?: boolean;44}4546export interface TavilyExtractOptions {47 urls: string | string[];48 query?: string;49 chunks_per_source?: number;50 extract_depth?: 'basic' | 'advanced';51 include_images?: boolean;52 include_favicon?: boolean;53 format?: 'markdown' | 'text';54 timeout?: number;55 include_usage?: boolean;56}5758export interface TavilyCrawlOptions {59 url: string;60 instructions?: string;61 chunks_per_source?: number;62 max_depth?: number;63 max_breadth?: number;64 limit?: number;65 select_paths?: string[];66 select_domains?: string[];67 exclude_paths?: string[];68 exclude_domains?: string[];69 allow_external?: boolean;70 include_images?: boolean;71 extract_depth?: 'basic' | 'advanced';72 format?: 'markdown' | 'text';73 include_favicon?: boolean;74 timeout?: number;75 include_usage?: boolean;76}7778export interface TavilyMapOptions {79 url: string;80 instructions?: string;81 max_depth?: number;82 max_breadth?: number;83 limit?: number;84 select_paths?: string[];85 select_domains?: string[];86 exclude_paths?: string[];87 exclude_domains?: string[];88 allow_external?: boolean;89 timeout?: number;90 include_usage?: boolean;91}9293export interface TavilyResearchOptions {94 input: string;95 model?: 'mini' | 'pro' | 'auto';96 stream?: boolean;97 output_schema?: any;98 citation_format?: 'numbered' | 'mla' | 'apa' | 'chicago';99}100101export interface TavilySearchResult {102 title: string;103 url: string;104 content: string;105 score: number;106 raw_content?: string;107 favicon?: string;108 published_date?: string;109}110111export interface TavilySearchResponse {112 query: string;113 answer?: string;114 results: TavilySearchResult[];115 images?: Array<{116 url: string;117 description?: string;118 }>;119 response_time?: number;120 usage?: {121 credits_used: number;122 credits_remaining: number;123 };124}125126export interface TavilyExtractResult {127 url: string;128 content: string;129 chunks: Array<{130 text: string;131 score: number;132 }>;133 images?: string[];134 favicon?: string;135}136137export interface TavilyCrawlResult {138 url: string;139 results: Array<{140 url: string;141 content: string;142 chunks: Array<{143 text: string;144 score: number;145 }>;146 images?: string[];147 favicon?: string;148 }>;149 usage?: {150 credits_used: number;151 };152}153154export interface TavilyMapResult {155 url: string;156 links: string[];157 usage?: {158 credits_used: number;159 };160}161162export interface TavilyResearchResult {163 report: string;164 sources: Array<{165 url: string;166 title: string;167 }>;168 usage?: {169 credits_used: number;170 };171}172173// ==================== HELPER FUNCTION ====================174175async function tavilyRequest<T>(endpoint: string, data: any): Promise<T> {176 if (!TAVILY_API_KEY) {177 throw new Error('TAVILY_API_KEY not configured in environment variables');178 }179180 try {181 const response = await axios.post(`${TAVILY_BASE_URL}${endpoint}`, data, {182 headers: {183 'Authorization': `Bearer ${TAVILY_API_KEY}`,184 'Content-Type': 'application/json',185 },186 });187 return response.data;188 } catch (error) {189 console.error(`Tavily ${endpoint} error:`, error);190 if (axios.isAxiosError(error) && error.response) {191 throw new Error(`Tavily API error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);192 }193 throw new Error(`Tavily request failed: ${(error as Error).message}`);194 }195}196197// ==================== API FUNCTIONS ====================198199// Search - Advanced web search with AI200export async function searchTavily(options: TavilySearchOptions): Promise<TavilySearchResponse> {201 const requestData: any = {202 query: options.query,203 api_key: TAVILY_API_KEY,204 };205206 // Add optional parameters207 if (options.search_depth) requestData.search_depth = options.search_depth;208 if (options.chunks_per_source !== undefined) requestData.chunks_per_source = options.chunks_per_source;209 if (options.max_results !== undefined) requestData.max_results = options.max_results;210 if (options.topic) requestData.topic = options.topic;211 if (options.time_range) requestData.time_range = options.time_range;212 if (options.start_date) requestData.start_date = options.start_date;213 if (options.end_date) requestData.end_date = options.end_date;214 if (options.include_answer !== undefined) requestData.include_answer = options.include_answer;215 if (options.include_raw_content !== undefined) requestData.include_raw_content = options.include_raw_content;216 if (options.include_images !== undefined) requestData.include_images = options.include_images;217 if (options.include_image_descriptions !== undefined) requestData.include_image_descriptions = options.include_image_descriptions;218 if (options.include_favicon !== undefined) requestData.include_favicon = options.include_favicon;219 if (options.include_domains) requestData.include_domains = options.include_domains;220 if (options.exclude_domains) requestData.exclude_domains = options.exclude_domains;221 if (options.country) requestData.country = options.country;222 if (options.auto_parameters !== undefined) requestData.auto_parameters = options.auto_parameters;223 if (options.include_usage !== undefined) requestData.include_usage = options.include_usage;224225 const response = await tavilyRequest<TavilySearchResponse>('/search', requestData);226 console.log(`[Tavily Search] Found ${response.results?.length || 0} results for: "${options.query}"`);227 return response;228}229230// Extract - Extract content from specific URLs231export async function extractTavily(options: TavilyExtractOptions): Promise<TavilyExtractResult[]> {232 const requestData: any = {233 urls: options.urls,234 api_key: TAVILY_API_KEY,235 };236237 // Add optional parameters238 if (options.query) requestData.query = options.query;239 if (options.chunks_per_source !== undefined) requestData.chunks_per_source = options.chunks_per_source;240 if (options.extract_depth) requestData.extract_depth = options.extract_depth;241 if (options.include_images !== undefined) requestData.include_images = options.include_images;242 if (options.include_favicon !== undefined) requestData.include_favicon = options.include_favicon;243 if (options.format) requestData.format = options.format;244 if (options.timeout !== undefined) requestData.timeout = options.timeout;245 if (options.include_usage !== undefined) requestData.include_usage = options.include_usage;246247 const response = await tavilyRequest<{ results: TavilyExtractResult[] }>('/extract', requestData);248 const urlCount = Array.isArray(options.urls) ? options.urls.length : 1;249 console.log(`[Tavily Extract] Extracted content from ${urlCount} URL(s)`);250251 // Smart summarization for long extracts252 const results = response.results || [];253 const processedResults = await Promise.all(254 results.map(async (result) => {255 const content = result.content || '';256257 if (shouldSummarize(content, 8000)) {258 console.log(`[Tavily Extract] Large content detected (${content.length} chars), summarizing...`);259 try {260 const summaryResult = await summarizeContent(content, 'document');261 return {262 ...result,263 content: summaryResult.summary,264 metadata: {265 summarized: true,266 originalLength: summaryResult.metadata.originalLength,267 compressionRatio: summaryResult.metadata.compressionRatio,268 }269 };270 } catch (error) {271 console.error(`[Tavily Extract] Summarization failed for ${result.url}:`, error);272 return result;273 }274 }275 return result;276 })277 );278279 return processedResults;280}281282// Crawl - Graph-based website traversal with extraction283export async function crawlTavily(options: TavilyCrawlOptions): Promise<TavilyCrawlResult> {284 const requestData: any = {285 url: options.url,286 api_key: TAVILY_API_KEY,287 };288289 // Add optional parameters290 if (options.instructions) requestData.instructions = options.instructions;291 if (options.chunks_per_source !== undefined) requestData.chunks_per_source = options.chunks_per_source;292 if (options.max_depth !== undefined) requestData.max_depth = options.max_depth;293 if (options.max_breadth !== undefined) requestData.max_breadth = options.max_breadth;294 if (options.limit !== undefined) requestData.limit = options.limit;295 if (options.select_paths) requestData.select_paths = options.select_paths;296 if (options.select_domains) requestData.select_domains = options.select_domains;297 if (options.exclude_paths) requestData.exclude_paths = options.exclude_paths;298 if (options.exclude_domains) requestData.exclude_domains = options.exclude_domains;299 if (options.allow_external !== undefined) requestData.allow_external = options.allow_external;300 if (options.include_images !== undefined) requestData.include_images = options.include_images;301 if (options.extract_depth) requestData.extract_depth = options.extract_depth;302 if (options.format) requestData.format = options.format;303 if (options.include_favicon !== undefined) requestData.include_favicon = options.include_favicon;304 if (options.timeout !== undefined) requestData.timeout = options.timeout;305 if (options.include_usage !== undefined) requestData.include_usage = options.include_usage;306307 const response = await tavilyRequest<TavilyCrawlResult>('/crawl', requestData);308 console.log(`[Tavily Crawl] Crawled ${response.results?.length || 0} pages from ${options.url}`);309 return response;310}311312// Map - Generate comprehensive site maps313export async function mapTavily(options: TavilyMapOptions): Promise<TavilyMapResult> {314 const requestData: any = {315 url: options.url,316 api_key: TAVILY_API_KEY,317 };318319 // Add optional parameters320 if (options.instructions) requestData.instructions = options.instructions;321 if (options.max_depth !== undefined) requestData.max_depth = options.max_depth;322 if (options.max_breadth !== undefined) requestData.max_breadth = options.max_breadth;323 if (options.limit !== undefined) requestData.limit = options.limit;324 if (options.select_paths) requestData.select_paths = options.select_paths;325 if (options.select_domains) requestData.select_domains = options.select_domains;326 if (options.exclude_paths) requestData.exclude_paths = options.exclude_paths;327 if (options.exclude_domains) requestData.exclude_domains = options.exclude_domains;328 if (options.allow_external !== undefined) requestData.allow_external = options.allow_external;329 if (options.timeout !== undefined) requestData.timeout = options.timeout;330 if (options.include_usage !== undefined) requestData.include_usage = options.include_usage;331332 const response = await tavilyRequest<TavilyMapResult>('/map', requestData);333 console.log(`[Tavily Map] Mapped ${response.links?.length || 0} links from ${options.url}`);334 return response;335}336337// Research - Comprehensive research agent338export async function researchTavily(options: TavilyResearchOptions): Promise<TavilyResearchResult> {339 const requestData: any = {340 input: options.input,341 api_key: TAVILY_API_KEY,342 };343344 // Add optional parameters345 if (options.model) requestData.model = options.model;346 if (options.stream !== undefined) requestData.stream = options.stream;347 if (options.output_schema) requestData.output_schema = options.output_schema;348 if (options.citation_format) requestData.citation_format = options.citation_format;349350 const response = await tavilyRequest<TavilyResearchResult>('/research', requestData);351 console.log(`[Tavily Research] Completed research on: "${options.input.substring(0, 50)}..."`);352 return response;353}354355// Usage - Get API usage information356export async function getUsageTavily(): Promise<{357 key: { usage: number; limit: number };358 account: {359 current_plan: string;360 plan_usage: number;361 plan_limit: number;362 paygo_usage: number;363 paygo_limit: number;364 };365}> {366 if (!TAVILY_API_KEY) {367 throw new Error('TAVILY_API_KEY not configured in environment variables');368 }369370 try {371 const response = await axios.get(`${TAVILY_BASE_URL}/usage`, {372 headers: {373 'Authorization': `Bearer ${TAVILY_API_KEY}`,374 },375 });376 console.log(`[Tavily Usage] Credits used: ${response.data.key?.usage || 0}/${response.data.key?.limit || 'unlimited'}`);377 return response.data;378 } catch (error) {379 console.error('Tavily usage error:', error);380 throw new Error(`Tavily usage check failed: ${(error as Error).message}`);381 }382}383