@librechat/agents 2.4.321 → 2.4.322
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.
- package/dist/cjs/tools/search/firecrawl.cjs +6 -4
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/format.cjs +6 -0
- package/dist/cjs/tools/search/format.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +43 -36
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +70 -0
- package/dist/cjs/tools/search/schema.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +62 -25
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +162 -47
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +34 -5
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +6 -4
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/format.mjs +7 -1
- package/dist/esm/tools/search/format.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +43 -36
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +61 -0
- package/dist/esm/tools/search/schema.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +63 -26
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +161 -46
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +33 -6
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/types/tools/search/firecrawl.d.ts +1 -0
- package/dist/types/tools/search/rerankers.d.ts +8 -4
- package/dist/types/tools/search/schema.d.ts +16 -0
- package/dist/types/tools/search/tool.d.ts +13 -0
- package/dist/types/tools/search/types.d.ts +34 -0
- package/dist/types/tools/search/utils.d.ts +9 -2
- package/package.json +3 -2
- package/src/scripts/search.ts +3 -3
- package/src/tools/search/firecrawl.ts +9 -4
- package/src/tools/search/format.ts +8 -1
- package/src/tools/search/rerankers.ts +57 -36
- package/src/tools/search/schema.ts +63 -0
- package/src/tools/search/search.ts +74 -22
- package/src/tools/search/tool.ts +217 -44
- package/src/tools/search/types.ts +35 -0
- package/src/tools/search/utils.ts +37 -5
- package/src/utils/llmConfig.ts +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
1
|
import axios from 'axios';
|
|
3
2
|
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
|
|
4
3
|
import type * as t from './types';
|
|
4
|
+
import { getAttribution, createDefaultLogger } from './utils';
|
|
5
5
|
import { FirecrawlScraper } from './firecrawl';
|
|
6
6
|
import { BaseReranker } from './rerankers';
|
|
7
|
-
import { getAttribution } from './utils';
|
|
8
7
|
|
|
9
8
|
const chunker = {
|
|
10
9
|
cleanText: (text: string): string => {
|
|
@@ -52,12 +51,14 @@ const chunker = {
|
|
|
52
51
|
chunkSize?: number;
|
|
53
52
|
chunkOverlap?: number;
|
|
54
53
|
separators?: string[];
|
|
55
|
-
}
|
|
54
|
+
},
|
|
55
|
+
logger?: t.Logger
|
|
56
56
|
): Promise<string[][]> => {
|
|
57
57
|
// Split multiple texts
|
|
58
|
+
const logger_ = logger || createDefaultLogger();
|
|
58
59
|
const promises = texts.map((text) =>
|
|
59
60
|
chunker.splitText(text, options).catch((error) => {
|
|
60
|
-
|
|
61
|
+
logger_.error('Error splitting text:', error);
|
|
61
62
|
return [text];
|
|
62
63
|
})
|
|
63
64
|
);
|
|
@@ -82,18 +83,22 @@ const getHighlights = async ({
|
|
|
82
83
|
content,
|
|
83
84
|
reranker,
|
|
84
85
|
topResults = 5,
|
|
86
|
+
logger,
|
|
85
87
|
}: {
|
|
86
88
|
content: string;
|
|
87
89
|
query: string;
|
|
88
90
|
reranker?: BaseReranker;
|
|
89
91
|
topResults?: number;
|
|
92
|
+
logger?: t.Logger;
|
|
90
93
|
}): Promise<t.Highlight[] | undefined> => {
|
|
94
|
+
const logger_ = logger || createDefaultLogger();
|
|
95
|
+
|
|
91
96
|
if (!content) {
|
|
92
|
-
|
|
97
|
+
logger_.warn('No content provided for highlights');
|
|
93
98
|
return;
|
|
94
99
|
}
|
|
95
100
|
if (!reranker) {
|
|
96
|
-
|
|
101
|
+
logger_.warn('No reranker provided for highlights');
|
|
97
102
|
return;
|
|
98
103
|
}
|
|
99
104
|
|
|
@@ -102,14 +107,14 @@ const getHighlights = async ({
|
|
|
102
107
|
if (Array.isArray(documents)) {
|
|
103
108
|
return await reranker.rerank(query, documents, topResults);
|
|
104
109
|
} else {
|
|
105
|
-
|
|
110
|
+
logger_.error(
|
|
106
111
|
'Expected documents to be an array, got:',
|
|
107
112
|
typeof documents
|
|
108
113
|
);
|
|
109
114
|
return;
|
|
110
115
|
}
|
|
111
116
|
} catch (error) {
|
|
112
|
-
|
|
117
|
+
logger_.error('Error in content processing:', error);
|
|
113
118
|
return;
|
|
114
119
|
}
|
|
115
120
|
};
|
|
@@ -131,25 +136,49 @@ const createSerperAPI = (
|
|
|
131
136
|
|
|
132
137
|
const getSources = async ({
|
|
133
138
|
query,
|
|
139
|
+
date,
|
|
134
140
|
country,
|
|
141
|
+
safeSearch,
|
|
135
142
|
numResults = 8,
|
|
143
|
+
type,
|
|
136
144
|
}: t.GetSourcesParams): Promise<t.SearchResult> => {
|
|
137
145
|
if (!query.trim()) {
|
|
138
146
|
return { success: false, error: 'Query cannot be empty' };
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
try {
|
|
150
|
+
const safe = ['off', 'moderate', 'active'] as const;
|
|
142
151
|
const payload: t.SerperSearchPayload = {
|
|
143
152
|
q: query,
|
|
153
|
+
safe: safe[safeSearch ?? 1],
|
|
144
154
|
num: Math.min(Math.max(1, numResults), 10),
|
|
145
155
|
};
|
|
146
156
|
|
|
157
|
+
// Set the search type if provided
|
|
158
|
+
if (type) {
|
|
159
|
+
payload.type = type;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (date != null) {
|
|
163
|
+
payload.tbs = `qdr:${date}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
147
166
|
if (country != null && country !== '') {
|
|
148
167
|
payload['gl'] = country.toLowerCase();
|
|
149
168
|
}
|
|
150
169
|
|
|
170
|
+
// Determine the API endpoint based on the search type
|
|
171
|
+
let apiEndpoint = config.apiUrl;
|
|
172
|
+
if (type === 'images') {
|
|
173
|
+
apiEndpoint = 'https://google.serper.dev/images';
|
|
174
|
+
} else if (type === 'videos') {
|
|
175
|
+
apiEndpoint = 'https://google.serper.dev/videos';
|
|
176
|
+
} else if (type === 'news') {
|
|
177
|
+
apiEndpoint = 'https://google.serper.dev/news';
|
|
178
|
+
}
|
|
179
|
+
|
|
151
180
|
const response = await axios.post<t.SerperResultData>(
|
|
152
|
-
|
|
181
|
+
apiEndpoint,
|
|
153
182
|
payload,
|
|
154
183
|
{
|
|
155
184
|
headers: {
|
|
@@ -169,6 +198,8 @@ const createSerperAPI = (
|
|
|
169
198
|
peopleAlsoAsk: data.peopleAlsoAsk,
|
|
170
199
|
knowledgeGraph: data.knowledgeGraph,
|
|
171
200
|
relatedSearches: data.relatedSearches,
|
|
201
|
+
videos: data.videos ?? [],
|
|
202
|
+
news: data.news ?? [],
|
|
172
203
|
};
|
|
173
204
|
|
|
174
205
|
return { success: true, data: results };
|
|
@@ -202,6 +233,7 @@ const createSearXNGAPI = (
|
|
|
202
233
|
const getSources = async ({
|
|
203
234
|
query,
|
|
204
235
|
numResults = 8,
|
|
236
|
+
type,
|
|
205
237
|
}: t.GetSourcesParams): Promise<t.SearchResult> => {
|
|
206
238
|
if (!query.trim()) {
|
|
207
239
|
return { success: false, error: 'Query cannot be empty' };
|
|
@@ -218,12 +250,22 @@ const createSearXNGAPI = (
|
|
|
218
250
|
searchUrl = searchUrl.replace(/\/$/, '') + '/search';
|
|
219
251
|
}
|
|
220
252
|
|
|
253
|
+
// Determine the search category based on the type
|
|
254
|
+
let category = 'general';
|
|
255
|
+
if (type === 'images') {
|
|
256
|
+
category = 'images';
|
|
257
|
+
} else if (type === 'videos') {
|
|
258
|
+
category = 'videos';
|
|
259
|
+
} else if (type === 'news') {
|
|
260
|
+
category = 'news';
|
|
261
|
+
}
|
|
262
|
+
|
|
221
263
|
// Prepare parameters for SearXNG
|
|
222
264
|
const params: t.SearxNGSearchPayload = {
|
|
223
265
|
q: query,
|
|
224
266
|
format: 'json',
|
|
225
267
|
pageno: 1,
|
|
226
|
-
categories:
|
|
268
|
+
categories: category,
|
|
227
269
|
language: 'all',
|
|
228
270
|
safesearch: 0,
|
|
229
271
|
engines: 'google,bing,duckduckgo',
|
|
@@ -271,6 +313,8 @@ const createSearXNGAPI = (
|
|
|
271
313
|
topStories: [],
|
|
272
314
|
// Use undefined instead of null for optional properties
|
|
273
315
|
relatedSearches: data.suggestions ?? [],
|
|
316
|
+
videos: [],
|
|
317
|
+
news: [],
|
|
274
318
|
};
|
|
275
319
|
|
|
276
320
|
return { success: true, data: results };
|
|
@@ -327,8 +371,10 @@ export const createSourceProcessor = (
|
|
|
327
371
|
// strategies = ['no_extraction'],
|
|
328
372
|
// filterContent = true,
|
|
329
373
|
reranker,
|
|
374
|
+
logger,
|
|
330
375
|
} = config;
|
|
331
376
|
|
|
377
|
+
const logger_ = logger || createDefaultLogger();
|
|
332
378
|
const firecrawlScraper = scraperInstance;
|
|
333
379
|
|
|
334
380
|
const webScraper = {
|
|
@@ -341,7 +387,7 @@ export const createSourceProcessor = (
|
|
|
341
387
|
links: string[];
|
|
342
388
|
onGetHighlights: t.SearchToolConfig['onGetHighlights'];
|
|
343
389
|
}): Promise<Array<t.ScrapeResult>> => {
|
|
344
|
-
|
|
390
|
+
logger_.debug(`Scraping ${links.length} links with Firecrawl`);
|
|
345
391
|
const promises: Array<Promise<t.ScrapeResult>> = [];
|
|
346
392
|
try {
|
|
347
393
|
for (let i = 0; i < links.length; i++) {
|
|
@@ -349,7 +395,11 @@ export const createSourceProcessor = (
|
|
|
349
395
|
const promise: Promise<t.ScrapeResult> = firecrawlScraper
|
|
350
396
|
.scrapeUrl(currentLink, {})
|
|
351
397
|
.then(([url, response]) => {
|
|
352
|
-
const attribution = getAttribution(
|
|
398
|
+
const attribution = getAttribution(
|
|
399
|
+
url,
|
|
400
|
+
response.data?.metadata,
|
|
401
|
+
logger_
|
|
402
|
+
);
|
|
353
403
|
if (response.success && response.data) {
|
|
354
404
|
const [content, references] =
|
|
355
405
|
firecrawlScraper.extractContent(response);
|
|
@@ -371,8 +421,9 @@ export const createSourceProcessor = (
|
|
|
371
421
|
.then(async (result) => {
|
|
372
422
|
try {
|
|
373
423
|
if (result.error != null) {
|
|
374
|
-
|
|
375
|
-
`Error scraping ${result.url}: ${result.content}
|
|
424
|
+
logger_.error(
|
|
425
|
+
`Error scraping ${result.url}: ${result.content}`,
|
|
426
|
+
result.error
|
|
376
427
|
);
|
|
377
428
|
return {
|
|
378
429
|
...result,
|
|
@@ -382,6 +433,7 @@ export const createSourceProcessor = (
|
|
|
382
433
|
query,
|
|
383
434
|
reranker,
|
|
384
435
|
content: result.content,
|
|
436
|
+
logger: logger_,
|
|
385
437
|
});
|
|
386
438
|
if (onGetHighlights) {
|
|
387
439
|
onGetHighlights(result.url);
|
|
@@ -391,14 +443,14 @@ export const createSourceProcessor = (
|
|
|
391
443
|
highlights,
|
|
392
444
|
};
|
|
393
445
|
} catch (error) {
|
|
394
|
-
|
|
446
|
+
logger_.error('Error processing scraped content:', error);
|
|
395
447
|
return {
|
|
396
448
|
...result,
|
|
397
449
|
};
|
|
398
450
|
}
|
|
399
451
|
})
|
|
400
452
|
.catch((error) => {
|
|
401
|
-
|
|
453
|
+
logger_.error(`Error scraping ${currentLink}:`, error);
|
|
402
454
|
return {
|
|
403
455
|
url: currentLink,
|
|
404
456
|
error: true,
|
|
@@ -409,7 +461,7 @@ export const createSourceProcessor = (
|
|
|
409
461
|
}
|
|
410
462
|
return await Promise.all(promises);
|
|
411
463
|
} catch (error) {
|
|
412
|
-
|
|
464
|
+
logger_.error('Error in scrapeMany:', error);
|
|
413
465
|
return [];
|
|
414
466
|
}
|
|
415
467
|
},
|
|
@@ -453,6 +505,7 @@ export const createSourceProcessor = (
|
|
|
453
505
|
result,
|
|
454
506
|
numElements,
|
|
455
507
|
query,
|
|
508
|
+
news,
|
|
456
509
|
proMode = true,
|
|
457
510
|
onGetHighlights,
|
|
458
511
|
}: t.ProcessSourcesFields): Promise<t.SearchResultData> => {
|
|
@@ -520,7 +573,7 @@ export const createSourceProcessor = (
|
|
|
520
573
|
organicLinksSet
|
|
521
574
|
);
|
|
522
575
|
|
|
523
|
-
if (organicLinks.length === 0 && topStoryLinks.length === 0) {
|
|
576
|
+
if (organicLinks.length === 0 && (topStoryLinks.length === 0 || !news)) {
|
|
524
577
|
return result.data;
|
|
525
578
|
}
|
|
526
579
|
|
|
@@ -541,7 +594,7 @@ export const createSourceProcessor = (
|
|
|
541
594
|
}
|
|
542
595
|
|
|
543
596
|
// Process top story links
|
|
544
|
-
if (topStoryLinks.length > 0) {
|
|
597
|
+
if (news && topStoryLinks.length > 0) {
|
|
545
598
|
promises.push(
|
|
546
599
|
fetchContents({
|
|
547
600
|
query,
|
|
@@ -555,18 +608,17 @@ export const createSourceProcessor = (
|
|
|
555
608
|
|
|
556
609
|
await Promise.all(promises);
|
|
557
610
|
|
|
558
|
-
// Update sources with scraped content
|
|
559
611
|
if (result.data.organic.length > 0) {
|
|
560
612
|
updateSourcesWithContent(result.data.organic, sourceMap);
|
|
561
613
|
}
|
|
562
614
|
|
|
563
|
-
if (topStories.length > 0) {
|
|
615
|
+
if (news && topStories.length > 0) {
|
|
564
616
|
updateSourcesWithContent(topStories, sourceMap);
|
|
565
617
|
}
|
|
566
618
|
|
|
567
619
|
return result.data;
|
|
568
620
|
} catch (error) {
|
|
569
|
-
|
|
621
|
+
logger_.error('Error in processSources:', error);
|
|
570
622
|
return {
|
|
571
623
|
organic: [],
|
|
572
624
|
topStories: [],
|
package/src/tools/search/tool.ts
CHANGED
|
@@ -1,88 +1,246 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
4
3
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
5
4
|
import type * as t from './types';
|
|
5
|
+
import {
|
|
6
|
+
DATE_RANGE,
|
|
7
|
+
querySchema,
|
|
8
|
+
dateSchema,
|
|
9
|
+
countrySchema,
|
|
10
|
+
imagesSchema,
|
|
11
|
+
videosSchema,
|
|
12
|
+
newsSchema,
|
|
13
|
+
} from './schema';
|
|
6
14
|
import { createSearchAPI, createSourceProcessor } from './search';
|
|
7
15
|
import { createFirecrawlScraper } from './firecrawl';
|
|
8
16
|
import { expandHighlights } from './highlights';
|
|
9
17
|
import { formatResultsForLLM } from './format';
|
|
18
|
+
import { createDefaultLogger } from './utils';
|
|
10
19
|
import { createReranker } from './rerankers';
|
|
11
20
|
import { Constants } from '@/common';
|
|
12
21
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Executes parallel searches and merges the results
|
|
24
|
+
*/
|
|
25
|
+
async function executeParallelSearches({
|
|
26
|
+
searchAPI,
|
|
27
|
+
query,
|
|
28
|
+
date,
|
|
29
|
+
country,
|
|
30
|
+
safeSearch,
|
|
31
|
+
images,
|
|
32
|
+
videos,
|
|
33
|
+
news,
|
|
34
|
+
logger,
|
|
35
|
+
}: {
|
|
36
|
+
searchAPI: ReturnType<typeof createSearchAPI>;
|
|
37
|
+
query: string;
|
|
38
|
+
date?: DATE_RANGE;
|
|
39
|
+
country?: string;
|
|
40
|
+
safeSearch: t.SearchToolConfig['safeSearch'];
|
|
41
|
+
images: boolean;
|
|
42
|
+
videos: boolean;
|
|
43
|
+
news: boolean;
|
|
44
|
+
logger: t.Logger;
|
|
45
|
+
}): Promise<t.SearchResult> {
|
|
46
|
+
// Prepare all search tasks to run in parallel
|
|
47
|
+
const searchTasks: Promise<t.SearchResult>[] = [
|
|
48
|
+
// Main search
|
|
49
|
+
searchAPI.getSources({
|
|
50
|
+
query,
|
|
51
|
+
date,
|
|
52
|
+
country,
|
|
53
|
+
safeSearch,
|
|
54
|
+
}),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
if (images) {
|
|
58
|
+
searchTasks.push(
|
|
59
|
+
searchAPI
|
|
60
|
+
.getSources({
|
|
61
|
+
query,
|
|
62
|
+
date,
|
|
63
|
+
country,
|
|
64
|
+
safeSearch,
|
|
65
|
+
type: 'images',
|
|
66
|
+
})
|
|
67
|
+
.catch((error) => {
|
|
68
|
+
logger.error('Error fetching images:', error);
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: `Images search failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
72
|
+
};
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (videos) {
|
|
77
|
+
searchTasks.push(
|
|
78
|
+
searchAPI
|
|
79
|
+
.getSources({
|
|
80
|
+
query,
|
|
81
|
+
date,
|
|
82
|
+
country,
|
|
83
|
+
safeSearch,
|
|
84
|
+
type: 'videos',
|
|
85
|
+
})
|
|
86
|
+
.catch((error) => {
|
|
87
|
+
logger.error('Error fetching videos:', error);
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: `Videos search failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (news) {
|
|
96
|
+
searchTasks.push(
|
|
97
|
+
searchAPI
|
|
98
|
+
.getSources({
|
|
99
|
+
query,
|
|
100
|
+
date,
|
|
101
|
+
country,
|
|
102
|
+
safeSearch,
|
|
103
|
+
type: 'news',
|
|
104
|
+
})
|
|
105
|
+
.catch((error) => {
|
|
106
|
+
logger.error('Error fetching news:', error);
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
error: `News search failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Run all searches in parallel
|
|
116
|
+
const results = await Promise.all(searchTasks);
|
|
117
|
+
|
|
118
|
+
// Get the main search result (first result)
|
|
119
|
+
const mainResult = results[0];
|
|
120
|
+
if (!mainResult.success) {
|
|
121
|
+
throw new Error(mainResult.error ?? 'Search failed');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Merge additional results with the main results
|
|
125
|
+
const mergedResults = { ...mainResult.data };
|
|
126
|
+
|
|
127
|
+
// Convert existing news to topStories if present
|
|
128
|
+
if (mergedResults.news !== undefined && mergedResults.news.length > 0) {
|
|
129
|
+
const existingNewsAsTopStories = mergedResults.news
|
|
130
|
+
.filter((newsItem) => newsItem.link !== undefined && newsItem.link !== '')
|
|
131
|
+
.map((newsItem) => ({
|
|
132
|
+
title: newsItem.title ?? '',
|
|
133
|
+
link: newsItem.link ?? '',
|
|
134
|
+
source: newsItem.source ?? '',
|
|
135
|
+
date: newsItem.date ?? '',
|
|
136
|
+
imageUrl: newsItem.imageUrl ?? '',
|
|
137
|
+
processed: false,
|
|
138
|
+
}));
|
|
139
|
+
mergedResults.topStories = [
|
|
140
|
+
...(mergedResults.topStories ?? []),
|
|
141
|
+
...existingNewsAsTopStories,
|
|
142
|
+
];
|
|
143
|
+
delete mergedResults.news;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
results.slice(1).forEach((result) => {
|
|
147
|
+
if (result.success && result.data !== undefined) {
|
|
148
|
+
if (result.data.images !== undefined && result.data.images.length > 0) {
|
|
149
|
+
mergedResults.images = [
|
|
150
|
+
...(mergedResults.images ?? []),
|
|
151
|
+
...result.data.images,
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
if (result.data.videos !== undefined && result.data.videos.length > 0) {
|
|
155
|
+
mergedResults.videos = [
|
|
156
|
+
...(mergedResults.videos ?? []),
|
|
157
|
+
...result.data.videos,
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
if (result.data.news !== undefined && result.data.news.length > 0) {
|
|
161
|
+
const newsAsTopStories = result.data.news.map((newsItem) => ({
|
|
162
|
+
...newsItem,
|
|
163
|
+
link: newsItem.link ?? '',
|
|
164
|
+
}));
|
|
165
|
+
mergedResults.topStories = [
|
|
166
|
+
...(mergedResults.topStories ?? []),
|
|
167
|
+
...newsAsTopStories,
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return { success: true, data: mergedResults };
|
|
174
|
+
}
|
|
41
175
|
|
|
42
176
|
function createSearchProcessor({
|
|
43
177
|
searchAPI,
|
|
178
|
+
safeSearch,
|
|
44
179
|
sourceProcessor,
|
|
45
180
|
onGetHighlights,
|
|
181
|
+
logger,
|
|
46
182
|
}: {
|
|
183
|
+
safeSearch: t.SearchToolConfig['safeSearch'];
|
|
47
184
|
searchAPI: ReturnType<typeof createSearchAPI>;
|
|
48
185
|
sourceProcessor: ReturnType<typeof createSourceProcessor>;
|
|
49
186
|
onGetHighlights: t.SearchToolConfig['onGetHighlights'];
|
|
187
|
+
logger: t.Logger;
|
|
50
188
|
}) {
|
|
51
189
|
return async function ({
|
|
52
190
|
query,
|
|
191
|
+
date,
|
|
53
192
|
country,
|
|
54
193
|
proMode = true,
|
|
55
194
|
maxSources = 5,
|
|
56
195
|
onSearchResults,
|
|
196
|
+
images = false,
|
|
197
|
+
videos = false,
|
|
198
|
+
news = false,
|
|
57
199
|
}: {
|
|
58
200
|
query: string;
|
|
59
201
|
country?: string;
|
|
60
|
-
|
|
202
|
+
date?: DATE_RANGE;
|
|
61
203
|
proMode?: boolean;
|
|
204
|
+
maxSources?: number;
|
|
62
205
|
onSearchResults: t.SearchToolConfig['onSearchResults'];
|
|
206
|
+
images?: boolean;
|
|
207
|
+
videos?: boolean;
|
|
208
|
+
news?: boolean;
|
|
63
209
|
}): Promise<t.SearchResultData> {
|
|
64
210
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
211
|
+
// Execute parallel searches and merge results
|
|
212
|
+
const searchResult = await executeParallelSearches({
|
|
213
|
+
searchAPI,
|
|
214
|
+
query,
|
|
215
|
+
date,
|
|
216
|
+
country,
|
|
217
|
+
safeSearch,
|
|
218
|
+
images,
|
|
219
|
+
videos,
|
|
220
|
+
news,
|
|
221
|
+
logger,
|
|
222
|
+
});
|
|
67
223
|
|
|
68
|
-
|
|
69
|
-
throw new Error(result.error ?? 'Search failed');
|
|
70
|
-
}
|
|
224
|
+
onSearchResults?.(searchResult);
|
|
71
225
|
|
|
72
226
|
const processedSources = await sourceProcessor.processSources({
|
|
73
227
|
query,
|
|
74
|
-
|
|
228
|
+
news,
|
|
229
|
+
result: searchResult,
|
|
75
230
|
proMode,
|
|
76
231
|
onGetHighlights,
|
|
77
232
|
numElements: maxSources,
|
|
78
233
|
});
|
|
234
|
+
|
|
79
235
|
return expandHighlights(processedSources);
|
|
80
236
|
} catch (error) {
|
|
81
|
-
|
|
237
|
+
logger.error('Error in search:', error);
|
|
82
238
|
return {
|
|
83
239
|
organic: [],
|
|
84
240
|
topStories: [],
|
|
85
241
|
images: [],
|
|
242
|
+
videos: [],
|
|
243
|
+
news: [],
|
|
86
244
|
relatedSearches: [],
|
|
87
245
|
error: error instanceof Error ? error.message : String(error),
|
|
88
246
|
};
|
|
@@ -116,11 +274,15 @@ function createTool({
|
|
|
116
274
|
}): DynamicStructuredTool<typeof schema> {
|
|
117
275
|
return tool<typeof schema>(
|
|
118
276
|
async (params, runnableConfig) => {
|
|
119
|
-
const { query, country: _c } = params;
|
|
277
|
+
const { query, date, country: _c, images, videos, news } = params;
|
|
120
278
|
const country = typeof _c === 'string' && _c ? _c : undefined;
|
|
121
279
|
const searchResult = await search({
|
|
122
280
|
query,
|
|
281
|
+
date,
|
|
123
282
|
country,
|
|
283
|
+
images,
|
|
284
|
+
videos,
|
|
285
|
+
news,
|
|
124
286
|
onSearchResults: createOnSearchResults({
|
|
125
287
|
runnableConfig,
|
|
126
288
|
onSearchResults: _onSearchResults,
|
|
@@ -181,6 +343,7 @@ export const createSearchTool = (
|
|
|
181
343
|
topResults = 5,
|
|
182
344
|
strategies = ['no_extraction'],
|
|
183
345
|
filterContent = true,
|
|
346
|
+
safeSearch = 1,
|
|
184
347
|
firecrawlApiKey,
|
|
185
348
|
firecrawlApiUrl,
|
|
186
349
|
firecrawlFormats = ['markdown', 'html'],
|
|
@@ -190,19 +353,25 @@ export const createSearchTool = (
|
|
|
190
353
|
onGetHighlights,
|
|
191
354
|
} = config;
|
|
192
355
|
|
|
193
|
-
const
|
|
356
|
+
const logger = config.logger || createDefaultLogger();
|
|
357
|
+
|
|
194
358
|
const schemaObject: {
|
|
195
359
|
query: z.ZodString;
|
|
360
|
+
date: z.ZodOptional<z.ZodNativeEnum<typeof DATE_RANGE>>;
|
|
196
361
|
country?: z.ZodOptional<z.ZodString>;
|
|
362
|
+
images: z.ZodOptional<z.ZodBoolean>;
|
|
363
|
+
videos: z.ZodOptional<z.ZodBoolean>;
|
|
364
|
+
news: z.ZodOptional<z.ZodBoolean>;
|
|
197
365
|
} = {
|
|
198
366
|
query: querySchema,
|
|
367
|
+
date: dateSchema,
|
|
368
|
+
images: imagesSchema,
|
|
369
|
+
videos: videosSchema,
|
|
370
|
+
news: newsSchema,
|
|
199
371
|
};
|
|
200
372
|
|
|
201
373
|
if (searchProvider === 'serper') {
|
|
202
|
-
schemaObject.country =
|
|
203
|
-
.string()
|
|
204
|
-
.optional()
|
|
205
|
-
.describe(DEFAULT_COUNTRY_DESCRIPTION);
|
|
374
|
+
schemaObject.country = countrySchema;
|
|
206
375
|
}
|
|
207
376
|
|
|
208
377
|
const toolSchema = z.object(schemaObject);
|
|
@@ -224,10 +393,11 @@ export const createSearchTool = (
|
|
|
224
393
|
rerankerType,
|
|
225
394
|
jinaApiKey,
|
|
226
395
|
cohereApiKey,
|
|
396
|
+
logger,
|
|
227
397
|
});
|
|
228
398
|
|
|
229
399
|
if (!selectedReranker) {
|
|
230
|
-
|
|
400
|
+
logger.warn('No reranker selected. Using default ranking.');
|
|
231
401
|
}
|
|
232
402
|
|
|
233
403
|
const sourceProcessor = createSourceProcessor(
|
|
@@ -236,14 +406,17 @@ export const createSearchTool = (
|
|
|
236
406
|
topResults,
|
|
237
407
|
strategies,
|
|
238
408
|
filterContent,
|
|
409
|
+
logger,
|
|
239
410
|
},
|
|
240
411
|
firecrawlScraper
|
|
241
412
|
);
|
|
242
413
|
|
|
243
414
|
const search = createSearchProcessor({
|
|
244
415
|
searchAPI,
|
|
416
|
+
safeSearch,
|
|
245
417
|
sourceProcessor,
|
|
246
418
|
onGetHighlights,
|
|
419
|
+
logger,
|
|
247
420
|
});
|
|
248
421
|
|
|
249
422
|
return createTool({
|