@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.
- package/CHANGELOG.md +33 -0
- package/changelog/v1.json +12 -0
- package/package.json +1 -1
- package/packages/web-crawler/src/crawImpl/exa.ts +93 -0
- package/packages/web-crawler/src/crawImpl/firecrawl.ts +97 -0
- package/packages/web-crawler/src/crawImpl/index.ts +6 -0
- package/packages/web-crawler/src/crawImpl/tavily.ts +94 -0
- package/src/config/aiModels/modelscope.ts +3 -3
- package/src/config/modelProviders/modelscope.ts +3 -3
- package/src/database/client/migrations.json +10 -0
- package/src/database/migrations/0023_remove_param_and_doubao.sql +6 -0
- package/src/database/migrations/meta/0023_snapshot.json +5340 -0
- package/src/database/migrations/meta/_journal.json +7 -0
- package/src/server/services/search/impls/bocha/index.ts +124 -0
- package/src/server/services/search/impls/bocha/type.ts +47 -0
- package/src/server/services/search/impls/exa/index.ts +129 -0
- package/src/server/services/search/impls/exa/type.ts +39 -0
- package/src/server/services/search/impls/firecrawl/index.ts +128 -0
- package/src/server/services/search/impls/firecrawl/type.ts +35 -0
- package/src/server/services/search/impls/index.ts +31 -0
- package/src/server/services/search/impls/jina/index.ts +109 -0
- package/src/server/services/search/impls/jina/type.ts +26 -0
- package/src/server/services/search/impls/tavily/index.ts +124 -0
- package/src/server/services/search/impls/tavily/type.ts +36 -0
@@ -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
|
+
}
|