209 lines
6.1 KiB
TypeScript
209 lines
6.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { GroundingMetadata } from '@google/genai';
|
|
import {
|
|
BaseDeclarativeTool,
|
|
BaseToolInvocation,
|
|
Kind,
|
|
ToolInvocation,
|
|
ToolResult,
|
|
} from './tools.js';
|
|
|
|
import { getErrorMessage } from '../utils/errors.js';
|
|
import { Config } from '../config/config.js';
|
|
import { getResponseText } from '../utils/generateContentResponseUtilities.js';
|
|
|
|
interface GroundingChunkWeb {
|
|
uri?: string;
|
|
title?: string;
|
|
}
|
|
|
|
interface GroundingChunkItem {
|
|
web?: GroundingChunkWeb;
|
|
// Other properties might exist if needed in the future
|
|
}
|
|
|
|
interface GroundingSupportSegment {
|
|
startIndex: number;
|
|
endIndex: number;
|
|
text?: string; // text is optional as per the example
|
|
}
|
|
|
|
interface GroundingSupportItem {
|
|
segment?: GroundingSupportSegment;
|
|
groundingChunkIndices?: number[];
|
|
confidenceScores?: number[]; // Optional as per example
|
|
}
|
|
|
|
/**
|
|
* 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?: GroundingMetadata extends { groundingChunks: GroundingChunkItem[] }
|
|
? GroundingMetadata['groundingChunks']
|
|
: GroundingChunkItem[];
|
|
}
|
|
|
|
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 geminiClient = this.config.getGeminiClient();
|
|
|
|
try {
|
|
const response = await geminiClient.generateContent(
|
|
[{ role: 'user', parts: [{ text: this.params.query }] }],
|
|
{ tools: [{ googleSearch: {} }] },
|
|
signal,
|
|
);
|
|
|
|
const responseText = getResponseText(response);
|
|
const groundingMetadata = response.candidates?.[0]?.groundingMetadata;
|
|
const sources = groundingMetadata?.groundingChunks as
|
|
| GroundingChunkItem[]
|
|
| undefined;
|
|
const groundingSupports = groundingMetadata?.groundingSupports as
|
|
| GroundingSupportItem[]
|
|
| undefined;
|
|
|
|
if (!responseText || !responseText.trim()) {
|
|
return {
|
|
llmContent: `No search results or information found for query: "${this.params.query}"`,
|
|
returnDisplay: 'No information found.',
|
|
};
|
|
}
|
|
|
|
let modifiedResponseText = responseText;
|
|
const sourceListFormatted: string[] = [];
|
|
|
|
if (sources && sources.length > 0) {
|
|
sources.forEach((source: GroundingChunkItem, index: number) => {
|
|
const title = source.web?.title || 'Untitled';
|
|
const uri = source.web?.uri || 'No URI';
|
|
sourceListFormatted.push(`[${index + 1}] ${title} (${uri})`);
|
|
});
|
|
|
|
if (groundingSupports && groundingSupports.length > 0) {
|
|
const insertions: Array<{ index: number; marker: string }> = [];
|
|
groundingSupports.forEach((support: GroundingSupportItem) => {
|
|
if (support.segment && support.groundingChunkIndices) {
|
|
const citationMarker = support.groundingChunkIndices
|
|
.map((chunkIndex: number) => `[${chunkIndex + 1}]`)
|
|
.join('');
|
|
insertions.push({
|
|
index: support.segment.endIndex,
|
|
marker: citationMarker,
|
|
});
|
|
}
|
|
});
|
|
|
|
// Sort insertions by index in descending order to avoid shifting subsequent indices
|
|
insertions.sort((a, b) => b.index - a.index);
|
|
|
|
const responseChars = modifiedResponseText.split(''); // Use new variable
|
|
insertions.forEach((insertion) => {
|
|
responseChars.splice(insertion.index, 0, insertion.marker);
|
|
});
|
|
modifiedResponseText = responseChars.join(''); // Assign back to modifiedResponseText
|
|
}
|
|
|
|
if (sourceListFormatted.length > 0) {
|
|
modifiedResponseText +=
|
|
'\n\nSources:\n' + sourceListFormatted.join('\n');
|
|
}
|
|
}
|
|
|
|
return {
|
|
llmContent: `Web search results for "${this.params.query}":\n\n${modifiedResponseText}`,
|
|
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 = 'google_web_search';
|
|
|
|
constructor(private readonly config: Config) {
|
|
super(
|
|
WebSearchTool.Name,
|
|
'GoogleSearch',
|
|
'Performs a web search using Google Search (via the Gemini API) and returns the results. This tool is useful for finding information on the internet based on a query.',
|
|
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);
|
|
}
|
|
}
|