mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-22 01:37:50 +00:00
196 lines
5.1 KiB
TypeScript
196 lines
5.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {
|
|
BaseDeclarativeTool,
|
|
BaseToolInvocation,
|
|
Kind,
|
|
ToolInvocation,
|
|
ToolResult,
|
|
} from './tools.js';
|
|
|
|
import { getErrorMessage } from '../utils/errors.js';
|
|
import { Config } from '../config/config.js';
|
|
|
|
interface TavilyResultItem {
|
|
title: string;
|
|
url: string;
|
|
content?: string;
|
|
score?: number;
|
|
published_date?: string;
|
|
}
|
|
|
|
interface TavilySearchResponse {
|
|
query: string;
|
|
answer?: string;
|
|
results: TavilyResultItem[];
|
|
}
|
|
|
|
/**
|
|
* Parameters for the WebSearchTool.
|
|
*/
|
|
export interface WebSearchToolParams {
|
|
/**
|
|
* The search query.
|
|
*/
|
|
query: string;
|
|
}
|
|
|
|
/**
|
|
* Extends ToolResult to include sources for web search.
|
|
*/
|
|
export interface WebSearchToolResult extends ToolResult {
|
|
sources?: Array<{ title: string; url: string }>;
|
|
}
|
|
|
|
class WebSearchToolInvocation extends BaseToolInvocation<
|
|
WebSearchToolParams,
|
|
WebSearchToolResult
|
|
> {
|
|
constructor(
|
|
private readonly config: Config,
|
|
params: WebSearchToolParams,
|
|
) {
|
|
super(params);
|
|
}
|
|
|
|
override getDescription(): string {
|
|
return `Searching the web for: "${this.params.query}"`;
|
|
}
|
|
|
|
async execute(signal: AbortSignal): Promise<WebSearchToolResult> {
|
|
const apiKey =
|
|
this.config.getTavilyApiKey() || process.env['TAVILY_API_KEY'];
|
|
if (!apiKey) {
|
|
return {
|
|
llmContent:
|
|
'Web search is disabled because TAVILY_API_KEY is not configured. Please set it in your settings.json, .env file, or via --tavily-api-key command line argument to enable web search.',
|
|
returnDisplay:
|
|
'Web search disabled. Configure TAVILY_API_KEY to enable Tavily search.',
|
|
};
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('https://api.tavily.com/search', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
api_key: apiKey,
|
|
query: this.params.query,
|
|
search_depth: 'advanced',
|
|
max_results: 5,
|
|
include_answer: true,
|
|
}),
|
|
signal,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const text = await response.text().catch(() => '');
|
|
throw new Error(
|
|
`Tavily API error: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`,
|
|
);
|
|
}
|
|
|
|
const data = (await response.json()) as TavilySearchResponse;
|
|
|
|
const sources = (data.results || []).map((r) => ({
|
|
title: r.title,
|
|
url: r.url,
|
|
}));
|
|
|
|
const sourceListFormatted = sources.map(
|
|
(s, i) => `[${i + 1}] ${s.title || 'Untitled'} (${s.url})`,
|
|
);
|
|
|
|
let content = data.answer?.trim() || '';
|
|
if (!content) {
|
|
// Fallback: build a concise summary from top results
|
|
content = sources
|
|
.slice(0, 3)
|
|
.map((s, i) => `${i + 1}. ${s.title} - ${s.url}`)
|
|
.join('\n');
|
|
}
|
|
|
|
if (sourceListFormatted.length > 0) {
|
|
content += `\n\nSources:\n${sourceListFormatted.join('\n')}`;
|
|
}
|
|
|
|
if (!content.trim()) {
|
|
return {
|
|
llmContent: `No search results or information found for query: "${this.params.query}"`,
|
|
returnDisplay: 'No information found.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
llmContent: `Web search results for "${this.params.query}":\n\n${content}`,
|
|
returnDisplay: `Search results for "${this.params.query}" returned.`,
|
|
sources,
|
|
};
|
|
} catch (error: unknown) {
|
|
const errorMessage = `Error during web search for query "${this.params.query}": ${getErrorMessage(
|
|
error,
|
|
)}`;
|
|
console.error(errorMessage, error);
|
|
return {
|
|
llmContent: `Error: ${errorMessage}`,
|
|
returnDisplay: `Error performing web search.`,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A tool to perform web searches using Google Search via the Gemini API.
|
|
*/
|
|
export class WebSearchTool extends BaseDeclarativeTool<
|
|
WebSearchToolParams,
|
|
WebSearchToolResult
|
|
> {
|
|
static readonly Name: string = 'web_search';
|
|
|
|
constructor(private readonly config: Config) {
|
|
super(
|
|
WebSearchTool.Name,
|
|
'WebSearch',
|
|
'Performs a web search using the Tavily API and returns a concise answer with sources. Requires the TAVILY_API_KEY environment variable.',
|
|
Kind.Search,
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
query: {
|
|
type: 'string',
|
|
description: 'The search query to find information on the web.',
|
|
},
|
|
},
|
|
required: ['query'],
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Validates the parameters for the WebSearchTool.
|
|
* @param params The parameters to validate
|
|
* @returns An error message string if validation fails, null if valid
|
|
*/
|
|
protected override validateToolParamValues(
|
|
params: WebSearchToolParams,
|
|
): string | null {
|
|
if (!params.query || params.query.trim() === '') {
|
|
return "The 'query' parameter cannot be empty.";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected createInvocation(
|
|
params: WebSearchToolParams,
|
|
): ToolInvocation<WebSearchToolParams, WebSearchToolResult> {
|
|
return new WebSearchToolInvocation(this.config, params);
|
|
}
|
|
}
|