@librechat/agents 3.1.75-dev.1 → 3.1.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cjs/llm/openai/index.cjs +43 -0
  2. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  3. package/dist/cjs/llm/openai/utils/index.cjs +19 -10
  4. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  5. package/dist/cjs/messages/format.cjs +67 -10
  6. package/dist/cjs/messages/format.cjs.map +1 -1
  7. package/dist/cjs/tools/search/search.cjs +55 -66
  8. package/dist/cjs/tools/search/search.cjs.map +1 -1
  9. package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
  10. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
  11. package/dist/cjs/tools/search/tavily-search.cjs +372 -0
  12. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
  13. package/dist/cjs/tools/search/tool.cjs +26 -4
  14. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  15. package/dist/cjs/tools/search/utils.cjs +10 -3
  16. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  17. package/dist/esm/llm/openai/index.mjs +43 -0
  18. package/dist/esm/llm/openai/index.mjs.map +1 -1
  19. package/dist/esm/llm/openai/utils/index.mjs +19 -10
  20. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  21. package/dist/esm/messages/format.mjs +67 -10
  22. package/dist/esm/messages/format.mjs.map +1 -1
  23. package/dist/esm/tools/search/search.mjs +55 -66
  24. package/dist/esm/tools/search/search.mjs.map +1 -1
  25. package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
  26. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
  27. package/dist/esm/tools/search/tavily-search.mjs +370 -0
  28. package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
  29. package/dist/esm/tools/search/tool.mjs +26 -4
  30. package/dist/esm/tools/search/tool.mjs.map +1 -1
  31. package/dist/esm/tools/search/utils.mjs +10 -3
  32. package/dist/esm/tools/search/utils.mjs.map +1 -1
  33. package/dist/types/messages/format.d.ts +4 -1
  34. package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
  35. package/dist/types/tools/search/tavily-search.d.ts +4 -0
  36. package/dist/types/tools/search/types.d.ts +99 -5
  37. package/dist/types/tools/search/utils.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/src/llm/custom-chat-models.smoke.test.ts +175 -1
  40. package/src/llm/openai/index.ts +124 -0
  41. package/src/llm/openai/utils/index.ts +23 -14
  42. package/src/llm/openai/utils/messages.test.ts +159 -0
  43. package/src/messages/format.ts +90 -13
  44. package/src/messages/formatAgentMessages.test.ts +166 -1
  45. package/src/tools/search/search.ts +83 -73
  46. package/src/tools/search/tavily-scraper.ts +235 -0
  47. package/src/tools/search/tavily-search.ts +424 -0
  48. package/src/tools/search/tavily.test.ts +965 -0
  49. package/src/tools/search/tool.ts +36 -26
  50. package/src/tools/search/types.ts +134 -11
  51. package/src/tools/search/utils.ts +13 -5
@@ -14,6 +14,7 @@ import {
14
14
  } from './schema';
15
15
  import { createSearchAPI, createSourceProcessor } from './search';
16
16
  import { createSerperScraper } from './serper-scraper';
17
+ import { createTavilyScraper } from './tavily-scraper';
17
18
  import { createFirecrawlScraper } from './firecrawl';
18
19
  import { expandHighlights } from './highlights';
19
20
  import { formatResultsForLLM } from './format';
@@ -178,11 +179,13 @@ async function executeParallelSearches({
178
179
  function createSearchProcessor({
179
180
  searchAPI,
180
181
  safeSearch,
182
+ supportsVideos,
181
183
  sourceProcessor,
182
184
  onGetHighlights,
183
185
  logger,
184
186
  }: {
185
187
  safeSearch: t.SearchToolConfig['safeSearch'];
188
+ supportsVideos: boolean;
186
189
  searchAPI: ReturnType<typeof createSearchAPI>;
187
190
  sourceProcessor: ReturnType<typeof createSourceProcessor>;
188
191
  onGetHighlights: t.SearchToolConfig['onGetHighlights'];
@@ -218,7 +221,7 @@ function createSearchProcessor({
218
221
  country,
219
222
  safeSearch,
220
223
  images,
221
- videos,
224
+ videos: supportsVideos && videos,
222
225
  news,
223
226
  logger,
224
227
  });
@@ -306,32 +309,12 @@ function createTool({
306
309
  }
307
310
 
308
311
  /**
309
- * Creates a search tool with a schema that dynamically includes the country field
310
- * only when the searchProvider is 'serper'.
311
- *
312
- * Supports multiple scraper providers:
313
- * - Firecrawl (default): Full-featured web scraping with multiple formats
314
- * - Serper: Lightweight scraping using Serper's scrape API
315
- *
316
- * @example
317
- * ```typescript
318
- * // Using Firecrawl scraper (default)
319
- * const searchTool = createSearchTool({
320
- * searchProvider: 'serper',
321
- * scraperProvider: 'firecrawl',
322
- * firecrawlApiKey: 'your-firecrawl-key'
323
- * });
312
+ * Creates a search tool with configurable search and scraper providers.
324
313
  *
325
- * // Using Serper scraper
326
- * const searchTool = createSearchTool({
327
- * searchProvider: 'serper',
328
- * scraperProvider: 'serper',
329
- * serperApiKey: 'your-serper-key'
330
- * });
331
- * ```
314
+ * Search providers: Serper (Google results), SearXNG (self-hosted meta-search), Tavily (AI-optimized).
315
+ * Scraper providers: Firecrawl (default, full-featured), Serper (lightweight), Tavily (batch extraction).
332
316
  *
333
- * @param config - The search tool configuration
334
- * @returns A DynamicStructuredTool with a schema that depends on the searchProvider
317
+ * The country schema field is exposed to the LLM for providers that support localized results.
335
318
  */
336
319
  /** Input params type for search tool */
337
320
  interface SearchToolParams {
@@ -351,6 +334,10 @@ export const createSearchTool = (
351
334
  serperApiKey,
352
335
  searxngInstanceUrl,
353
336
  searxngApiKey,
337
+ tavilyApiKey,
338
+ tavilySearchUrl,
339
+ tavilyExtractUrl,
340
+ tavilySearchOptions,
354
341
  rerankerType = 'cohere',
355
342
  topResults = 5,
356
343
  strategies = ['no_extraction'],
@@ -362,6 +349,7 @@ export const createSearchTool = (
362
349
  firecrawlVersion,
363
350
  firecrawlOptions,
364
351
  serperScraperOptions,
352
+ tavilyScraperOptions,
365
353
  scraperTimeout,
366
354
  jinaApiKey,
367
355
  jinaApiUrl,
@@ -371,6 +359,13 @@ export const createSearchTool = (
371
359
  } = config;
372
360
 
373
361
  const logger = config.logger || createDefaultLogger();
362
+ const effectiveTavilySearchOptions =
363
+ searchProvider === 'tavily' && config.safeSearch != null
364
+ ? {
365
+ ...tavilySearchOptions,
366
+ safeSearch: config.safeSearch !== 0,
367
+ }
368
+ : tavilySearchOptions;
374
369
 
375
370
  const schemaProperties: Record<string, unknown> = {
376
371
  query: querySchema,
@@ -380,7 +375,7 @@ export const createSearchTool = (
380
375
  news: newsSchema,
381
376
  };
382
377
 
383
- if (searchProvider === 'serper') {
378
+ if (searchProvider === 'serper' || searchProvider === 'tavily') {
384
379
  schemaProperties.country = countrySchema;
385
380
  }
386
381
 
@@ -395,6 +390,9 @@ export const createSearchTool = (
395
390
  serperApiKey,
396
391
  searxngInstanceUrl,
397
392
  searxngApiKey,
393
+ tavilyApiKey,
394
+ tavilySearchUrl,
395
+ tavilySearchOptions: effectiveTavilySearchOptions,
398
396
  });
399
397
 
400
398
  /** Create scraper based on scraperProvider */
@@ -407,6 +405,17 @@ export const createSearchTool = (
407
405
  timeout: scraperTimeout ?? serperScraperOptions?.timeout,
408
406
  logger,
409
407
  });
408
+ } else if (scraperProvider === 'tavily') {
409
+ scraperInstance = createTavilyScraper({
410
+ ...tavilyScraperOptions,
411
+ apiKey:
412
+ tavilyScraperOptions?.apiKey ??
413
+ tavilyApiKey ??
414
+ process.env.TAVILY_API_KEY,
415
+ apiUrl: tavilyScraperOptions?.apiUrl ?? tavilyExtractUrl,
416
+ timeout: scraperTimeout ?? tavilyScraperOptions?.timeout,
417
+ logger,
418
+ });
410
419
  } else {
411
420
  scraperInstance = createFirecrawlScraper({
412
421
  ...firecrawlOptions,
@@ -445,6 +454,7 @@ export const createSearchTool = (
445
454
  const search = createSearchProcessor({
446
455
  searchAPI,
447
456
  safeSearch,
457
+ supportsVideos: searchProvider !== 'tavily',
448
458
  sourceProcessor,
449
459
  onGetHighlights,
450
460
  logger,
@@ -3,8 +3,8 @@ import type { RunnableConfig } from '@langchain/core/runnables';
3
3
  import type { BaseReranker } from './rerankers';
4
4
  import { DATE_RANGE } from './schema';
5
5
 
6
- export type SearchProvider = 'serper' | 'searxng';
7
- export type ScraperProvider = 'firecrawl' | 'serper';
6
+ export type SearchProvider = 'serper' | 'searxng' | 'tavily';
7
+ export type ScraperProvider = 'firecrawl' | 'serper' | 'tavily';
8
8
  export type RerankerType = 'infinity' | 'jina' | 'cohere' | 'none';
9
9
 
10
10
  export interface Highlight {
@@ -62,11 +62,59 @@ export interface Source {
62
62
  date?: string;
63
63
  }
64
64
 
65
+ export type TavilyTimeRange = 'day' | 'week' | 'month' | 'year';
66
+ export type TavilyTimeRangeInput =
67
+ | TavilyTimeRange
68
+ | 'h'
69
+ | 'd'
70
+ | 'w'
71
+ | 'm'
72
+ | 'y';
73
+
74
+ export interface TavilySearchOptions {
75
+ searchDepth?: 'basic' | 'advanced' | 'fast' | 'ultra-fast';
76
+ maxResults?: number;
77
+ includeImages?: boolean;
78
+ includeAnswer?: boolean | 'basic' | 'advanced';
79
+ includeRawContent?: boolean | 'markdown' | 'text';
80
+ includeDomains?: string[];
81
+ excludeDomains?: string[];
82
+ topic?: 'general' | 'news' | 'finance';
83
+ timeRange?: TavilyTimeRangeInput;
84
+ includeImageDescriptions?: boolean;
85
+ includeFavicon?: boolean;
86
+ chunksPerSource?: number;
87
+ safeSearch?: boolean;
88
+ timeout?: number;
89
+ }
90
+
91
+ export interface TavilySearchPayload {
92
+ query: string;
93
+ search_depth: NonNullable<TavilySearchOptions['searchDepth']>;
94
+ topic: NonNullable<TavilySearchOptions['topic']>;
95
+ max_results: number;
96
+ safe_search?: boolean;
97
+ time_range?: TavilyTimeRange;
98
+ country?: string;
99
+ include_images?: boolean;
100
+ include_answer?: NonNullable<TavilySearchOptions['includeAnswer']>;
101
+ include_raw_content?: NonNullable<TavilySearchOptions['includeRawContent']>;
102
+ include_domains?: string[];
103
+ exclude_domains?: string[];
104
+ include_image_descriptions?: boolean;
105
+ include_favicon?: boolean;
106
+ chunks_per_source?: number;
107
+ }
108
+
65
109
  export interface SearchConfig {
66
110
  searchProvider?: SearchProvider;
67
111
  serperApiKey?: string;
68
112
  searxngInstanceUrl?: string;
69
113
  searxngApiKey?: string;
114
+ tavilyApiKey?: string;
115
+ tavilySearchUrl?: string;
116
+ tavilyExtractUrl?: string;
117
+ tavilySearchOptions?: TavilySearchOptions;
70
118
  }
71
119
 
72
120
  export type References = {
@@ -106,6 +154,17 @@ export interface SerperScraperConfig {
106
154
  includeMarkdown?: boolean;
107
155
  }
108
156
 
157
+ export interface TavilyScraperConfig {
158
+ apiKey?: string;
159
+ apiUrl?: string;
160
+ timeout?: number;
161
+ logger?: Logger;
162
+ extractDepth?: 'basic' | 'advanced';
163
+ includeImages?: boolean;
164
+ includeFavicon?: boolean;
165
+ format?: 'markdown' | 'text';
166
+ }
167
+
109
168
  export interface ScraperContentResult {
110
169
  content: string;
111
170
  }
@@ -151,9 +210,8 @@ export type SafeSearchLevel = 0 | 1 | 2;
151
210
 
152
211
  export type Logger = WinstonLogger;
153
212
  export interface SearchToolConfig
154
- extends SearchConfig,
155
- ProcessSourcesConfig,
156
- FirecrawlConfig {
213
+ extends SearchConfig, ProcessSourcesConfig, FirecrawlConfig {
214
+ tavilyScraperOptions?: TavilyScraperConfig;
157
215
  logger?: Logger;
158
216
  safeSearch?: SafeSearchLevel;
159
217
  jinaApiKey?: string;
@@ -181,20 +239,27 @@ export type UsedReferences = {
181
239
  reference: MediaReference;
182
240
  }[];
183
241
 
242
+ export type AnyScraperResponse =
243
+ | FirecrawlScrapeResponse
244
+ | SerperScrapeResponse
245
+ | TavilyScrapeResponse;
246
+
184
247
  /** Base Scraper Interface */
185
248
  export interface BaseScraper {
186
249
  scrapeUrl(
187
250
  url: string,
188
251
  options?: unknown
189
- ): Promise<[string, FirecrawlScrapeResponse | SerperScrapeResponse]>;
252
+ ): Promise<[string, AnyScraperResponse]>;
253
+ scrapeUrls?(
254
+ urls: string[],
255
+ options?: unknown
256
+ ): Promise<Array<[string, AnyScraperResponse]>>;
190
257
  extractContent(
191
- response: FirecrawlScrapeResponse | SerperScrapeResponse
258
+ response: AnyScraperResponse
192
259
  ): [string, undefined | References];
193
260
  extractMetadata(
194
- response: FirecrawlScrapeResponse | SerperScrapeResponse
195
- ):
196
- | ScrapeMetadata
197
- | Record<string, string | number | boolean | null | undefined>;
261
+ response: AnyScraperResponse
262
+ ): ScrapeMetadata | GenericScrapeMetadata;
198
263
  }
199
264
 
200
265
  /** Firecrawl */
@@ -208,6 +273,25 @@ export type SerperScrapeOptions = Omit<
208
273
  'apiKey' | 'apiUrl' | 'logger'
209
274
  >;
210
275
 
276
+ export type TavilyScrapeOptions = Omit<
277
+ TavilyScraperConfig,
278
+ 'apiKey' | 'apiUrl' | 'logger'
279
+ >;
280
+
281
+ export interface TavilyExtractPayload {
282
+ urls: string[];
283
+ extract_depth: NonNullable<TavilyScraperConfig['extractDepth']>;
284
+ include_images: boolean;
285
+ include_favicon?: boolean;
286
+ format?: NonNullable<TavilyScraperConfig['format']>;
287
+ timeout?: number;
288
+ }
289
+
290
+ export type GenericScrapeMetadata = Record<
291
+ string,
292
+ string | number | boolean | null | undefined
293
+ >;
294
+
211
295
  export interface ScrapeMetadata {
212
296
  // Core source information
213
297
  sourceURL?: string;
@@ -294,6 +378,45 @@ export interface SerperScrapeResponse {
294
378
  error?: string;
295
379
  }
296
380
 
381
+ export interface TavilyScrapeResponse {
382
+ success: boolean;
383
+ data?: {
384
+ rawContent?: string;
385
+ images?: string[];
386
+ favicon?: string;
387
+ };
388
+ error?: string;
389
+ }
390
+
391
+ export interface TavilySearchResult {
392
+ title?: string;
393
+ url?: string;
394
+ content?: string;
395
+ score?: number;
396
+ published_date?: string;
397
+ }
398
+
399
+ export type TavilyImageResult =
400
+ | string
401
+ | {
402
+ url?: string;
403
+ description?: string;
404
+ };
405
+
406
+ export interface TavilySearchResponse {
407
+ answer?: string;
408
+ images?: TavilyImageResult[];
409
+ results?: TavilySearchResult[];
410
+ }
411
+
412
+ export interface TavilyExtractResult {
413
+ url: string;
414
+ raw_content?: string;
415
+ images?: string[];
416
+ favicon?: string;
417
+ error?: string;
418
+ }
419
+
297
420
  export interface FirecrawlScraperConfig {
298
421
  apiKey?: string;
299
422
  apiUrl?: string;
@@ -29,11 +29,18 @@ export const fileExtRegex =
29
29
 
30
30
  export const getDomainName = (
31
31
  link: string,
32
- metadata?: t.ScrapeMetadata,
32
+ metadata?: t.ScrapeMetadata | t.GenericScrapeMetadata,
33
33
  logger?: t.Logger
34
34
  ): string | undefined => {
35
35
  try {
36
- const url = metadata?.sourceURL ?? metadata?.url ?? (link || '');
36
+ const sourceUrl = metadata?.sourceURL;
37
+ const metadataUrl = metadata?.url;
38
+ const url =
39
+ typeof sourceUrl === 'string'
40
+ ? sourceUrl
41
+ : typeof metadataUrl === 'string'
42
+ ? metadataUrl
43
+ : link || '';
37
44
  const domain = new URL(url).hostname.replace(/^www\./, '');
38
45
  if (domain) {
39
46
  return domain;
@@ -52,7 +59,7 @@ export const getDomainName = (
52
59
 
53
60
  export function getAttribution(
54
61
  link: string,
55
- metadata?: t.ScrapeMetadata,
62
+ metadata?: t.ScrapeMetadata | t.GenericScrapeMetadata,
56
63
  logger?: t.Logger
57
64
  ): string | undefined {
58
65
  if (!metadata) return getDomainName(link, metadata, logger);
@@ -60,16 +67,17 @@ export function getAttribution(
60
67
  const twitterSite = metadata['twitter:site'];
61
68
  const twitterSiteFormatted =
62
69
  typeof twitterSite === 'string' ? twitterSite.replace(/^@/, '') : undefined;
70
+ const title = metadata.title;
63
71
 
64
72
  const possibleAttributions = [
65
73
  metadata.ogSiteName,
66
74
  metadata['og:site_name'],
67
- metadata.title?.split('|').pop()?.trim(),
75
+ typeof title === 'string' ? title.split('|').pop()?.trim() : undefined,
68
76
  twitterSiteFormatted,
69
77
  ];
70
78
 
71
79
  const attribution = possibleAttributions.find(
72
- (attr) => attr != null && typeof attr === 'string' && attr.trim() !== ''
80
+ (attr): attr is string => typeof attr === 'string' && attr.trim() !== ''
73
81
  );
74
82
  if (attribution != null) {
75
83
  return attribution;