SPB Git

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%
13.6 KB · 383 lines typescript
Raw Blame History
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