@lobehub/chat 1.90.4 → 1.91.0

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.
@@ -0,0 +1,26 @@
1
+ export interface JinaSearchParameters {
2
+ q: string;
3
+ }
4
+
5
+ interface JinaUsage {
6
+ tokens: number;
7
+ }
8
+
9
+ interface JinaMeta {
10
+ usage: JinaUsage;
11
+ }
12
+
13
+ interface JinaData {
14
+ content?: string;
15
+ description?: string;
16
+ title: string;
17
+ url: string;
18
+ usage?: JinaUsage;
19
+ }
20
+
21
+ export interface JinaResponse {
22
+ code?: number;
23
+ data: JinaData[];
24
+ meta?: JinaMeta;
25
+ status?: number;
26
+ }
@@ -0,0 +1,124 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import debug from 'debug';
3
+ import urlJoin from 'url-join';
4
+
5
+ import { SearchParams, UniformSearchResponse, UniformSearchResult } from '@/types/tool/search';
6
+
7
+ import { SearchServiceImpl } from '../type';
8
+ import { TavilySearchParameters, TavilyResponse } from './type';
9
+
10
+ const log = debug('lobe-search:Tavily');
11
+
12
+ /**
13
+ * Tavily implementation of the search service
14
+ * Primarily used for web crawling
15
+ */
16
+ export class TavilyImpl implements SearchServiceImpl {
17
+ private get apiKey(): string | undefined {
18
+ return process.env.TAVILY_API_KEY;
19
+ }
20
+
21
+ private get baseUrl(): string {
22
+ // Assuming the base URL is consistent with the crawl endpoint
23
+ return 'https://api.tavily.com';
24
+ }
25
+
26
+ async query(query: string, params: SearchParams = {}): Promise<UniformSearchResponse> {
27
+ log('Starting Tavily query with query: "%s", params: %o', query, params);
28
+ const endpoint = urlJoin(this.baseUrl, '/search');
29
+
30
+ const defaultQueryParams: TavilySearchParameters = {
31
+ include_answer: false,
32
+ include_image_descriptions: true,
33
+ include_images: false,
34
+ include_raw_content: false,
35
+ max_results: 15,
36
+ query,
37
+ search_depth: process.env.TAVILY_SEARCH_DEPTH || 'basic' // basic or advanced
38
+ };
39
+
40
+ let body: TavilySearchParameters = {
41
+ ...defaultQueryParams,
42
+ time_range:
43
+ params?.searchTimeRange && params.searchTimeRange !== 'anytime'
44
+ ? params.searchTimeRange
45
+ : undefined,
46
+ topic:
47
+ // Tavily 只支持 news 和 general 两种类型
48
+ params?.searchCategories?.filter(cat => ['news', 'general'].includes(cat))?.[0],
49
+ };
50
+
51
+ log('Constructed request body: %o', body);
52
+
53
+ let response: Response;
54
+ const startAt = Date.now();
55
+ let costTime = 0;
56
+ try {
57
+ log('Sending request to endpoint: %s', endpoint);
58
+ response = await fetch(endpoint, {
59
+ body: JSON.stringify(body),
60
+ headers: {
61
+ 'Authorization': this.apiKey ? `Bearer ${this.apiKey}` : '',
62
+ 'Content-Type': 'application/json',
63
+ },
64
+ method: 'POST',
65
+ });
66
+ log('Received response with status: %d', response.status);
67
+ costTime = Date.now() - startAt;
68
+ } catch (error) {
69
+ log.extend('error')('Tavily fetch error: %o', error);
70
+ throw new TRPCError({
71
+ cause: error,
72
+ code: 'SERVICE_UNAVAILABLE',
73
+ message: 'Failed to connect to Tavily.',
74
+ });
75
+ }
76
+
77
+ if (!response.ok) {
78
+ const errorBody = await response.text();
79
+ log.extend('error')(
80
+ `Tavily request failed with status ${response.status}: %s`,
81
+ errorBody.length > 200 ? `${errorBody.slice(0, 200)}...` : errorBody,
82
+ );
83
+ throw new TRPCError({
84
+ cause: errorBody,
85
+ code: 'SERVICE_UNAVAILABLE',
86
+ message: `Tavily request failed: ${response.statusText}`,
87
+ });
88
+ }
89
+
90
+ try {
91
+ const tavilyResponse = (await response.json()) as TavilyResponse;
92
+
93
+ log('Parsed Tavily response: %o', tavilyResponse);
94
+
95
+ const mappedResults = (tavilyResponse.results || []).map(
96
+ (result): UniformSearchResult => ({
97
+ category: body.topic || 'general', // Default category
98
+ content: result.content || '', // Prioritize content, fallback to snippet
99
+ engines: ['tavily'], // Use 'tavily' as the engine name
100
+ parsedUrl: result.url ? new URL(result.url).hostname : '', // Basic URL parsing
101
+ score: result.score || 0, // Default score to 0 if undefined
102
+ title: result.title || '',
103
+ url: result.url,
104
+ }),
105
+ );
106
+
107
+ log('Mapped %d results to SearchResult format', mappedResults.length);
108
+
109
+ return {
110
+ costTime,
111
+ query: query,
112
+ resultNumbers: mappedResults.length,
113
+ results: mappedResults,
114
+ };
115
+ } catch (error) {
116
+ log.extend('error')('Error parsing Tavily response: %o', error);
117
+ throw new TRPCError({
118
+ cause: error,
119
+ code: 'INTERNAL_SERVER_ERROR',
120
+ message: 'Failed to parse Tavily response.',
121
+ });
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,36 @@
1
+ export interface TavilySearchParameters {
2
+ chunks_per_source?: number;
3
+ days?: number;
4
+ exclude_domains?: string[];
5
+ include_answer?: boolean | string;
6
+ include_domains?: string[];
7
+ include_image_descriptions?: boolean;
8
+ include_images?: boolean;
9
+ include_raw_content?: boolean;
10
+ max_results?: number;
11
+ query: string;
12
+ search_depth?: string;
13
+ time_range?: string;
14
+ topic?: string;
15
+ }
16
+
17
+ interface TavilyImages {
18
+ description?: string;
19
+ url: string;
20
+ }
21
+
22
+ interface TavilyResults {
23
+ content?: string;
24
+ raw_content?: string | null;
25
+ score?: number;
26
+ title?: string;
27
+ url: string;
28
+ }
29
+
30
+ export interface TavilyResponse {
31
+ answer?: string;
32
+ images?: TavilyImages[];
33
+ query: string;
34
+ response_time: number;
35
+ results: TavilyResults[];
36
+ }