UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

155 lines (133 loc) 4.96 kB
import { TRPCError } from '@trpc/server'; import debug from 'debug'; import urlJoin from 'url-join'; import { SearchParams, UniformSearchResponse, UniformSearchResult } from '@/types/tool/search'; import { SearchServiceImpl } from '../type'; import { Search1ApiResponse } from './type'; interface Search1APIQueryParams { crawl_results?: 0 | 1; exclude_sites?: string[]; image?: boolean; include_sites?: string[]; language?: string; max_results: number; query: string; search_service?: string; time_range?: string; } const log = debug('lobe-search:search1api'); /** * Search1API implementation of the search service * Primarily used for web crawling */ export class Search1APIImpl implements SearchServiceImpl { private get apiKey(): string | undefined { return process.env.SEARCH1API_SEARCH_API_KEY || process.env.SEARCH1API_API_KEY; } private get baseUrl(): string { // Assuming the base URL is consistent with the crawl endpoint return 'https://api.search1api.com'; } async query(query: string, params: SearchParams = {}): Promise<UniformSearchResponse> { log('Starting Search1API query with query: "%s", params: %o', query, params); const endpoint = urlJoin(this.baseUrl, '/search'); const { searchEngines } = params; const defaultQueryParams: Search1APIQueryParams = { crawl_results: 0, // 默认不做抓取 image: false, max_results: 15, // Default max results query, }; let body: Search1APIQueryParams[] = [ { ...defaultQueryParams, time_range: params?.searchTimeRange && params.searchTimeRange !== 'anytime' ? params.searchTimeRange : undefined, }, ]; if (searchEngines && searchEngines.length > 0) { body = searchEngines.map((searchEngine) => ({ ...defaultQueryParams, max_results: parseInt((20 / searchEngines.length).toFixed(0)), search_service: searchEngine, time_range: params?.searchTimeRange && params.searchTimeRange !== 'anytime' ? params.searchTimeRange : undefined, })); } // Note: Other SearchParams like searchCategories, searchEngines (beyond the first one) // and Search1API specific params like include_sites, exclude_sites, language // are not currently mapped. log('Constructed request body: %o', body); let response: Response; const startAt = Date.now(); let costTime = 0; try { log('Sending request to endpoint: %s', endpoint); response = await fetch(endpoint, { body: JSON.stringify(body), headers: { 'Authorization': this.apiKey ? `Bearer ${this.apiKey}` : '', 'Content-Type': 'application/json', }, method: 'POST', }); log('Received response with status: %d', response.status); costTime = Date.now() - startAt; } catch (error) { log.extend('error')('Search1API fetch error: %o', error); throw new TRPCError({ cause: error, code: 'SERVICE_UNAVAILABLE', message: 'Failed to connect to Search1API.', }); } if (!response.ok) { const errorBody = await response.text(); log.extend('error')( `Search1API request failed with status ${response.status}: %s`, errorBody.length > 200 ? `${errorBody.slice(0, 200)}...` : errorBody, ); throw new TRPCError({ cause: errorBody, code: 'SERVICE_UNAVAILABLE', message: `Search1API request failed: ${response.statusText}`, }); } try { const search1ApiResponse = (await response.json()) as Search1ApiResponse[]; // Use a specific type if defined elsewhere log('Parsed Search1API response: %o', search1ApiResponse); const mappedResults = search1ApiResponse.flatMap((response) => { // Map Search1API response to SearchResponse return (response.results || []).map( (result): UniformSearchResult => ({ category: 'general', // Default category content: result.content || result.snippet || '', // Prioritize content, fallback to snippet engines: [response.searchParameters?.search_service || ''], parsedUrl: result.link ? new URL(result.link).hostname : '', // Basic URL parsing score: 1, // Default score title: result.title || '', url: result.link, }), ); }); log('Mapped %d results to SearchResult format', mappedResults.length); return { costTime, query: query, resultNumbers: mappedResults.length, results: mappedResults, }; } catch (error) { log.extend('error')('Error parsing Search1API response: %o', error); throw new TRPCError({ cause: error, code: 'INTERNAL_SERVER_ERROR', message: 'Failed to parse Search1API response.', }); } } }