mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat: Optimize the code
This commit is contained in:
@@ -908,29 +908,8 @@ export class Config {
|
|||||||
return this.tavilyApiKey;
|
return this.tavilyApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWebSearchConfig():
|
getWebSearchConfig() {
|
||||||
| {
|
return this.webSearch;
|
||||||
provider: Array<{
|
|
||||||
type: 'tavily' | 'google' | 'dashscope';
|
|
||||||
config: Record<string, unknown>;
|
|
||||||
}>;
|
|
||||||
default: string;
|
|
||||||
}
|
|
||||||
| undefined {
|
|
||||||
if (!this.webSearch) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
provider: this.webSearch.provider.map((p) => ({
|
|
||||||
type: p.type,
|
|
||||||
config: {
|
|
||||||
apiKey: p.apiKey,
|
|
||||||
searchEngineId: p.searchEngineId,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
default: this.webSearch.default,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getIdeMode(): boolean {
|
getIdeMode(): boolean {
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import type {
|
|||||||
WebSearchResult,
|
WebSearchResult,
|
||||||
WebSearchResultItem,
|
WebSearchResultItem,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { getErrorMessage } from '../../utils/errors.js';
|
import { WebSearchError } from './errors.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base implementation for web search providers.
|
* Base implementation for web search providers.
|
||||||
|
* Provides common functionality for error handling and result formatting.
|
||||||
*/
|
*/
|
||||||
export abstract class BaseWebSearchProvider implements WebSearchProvider {
|
export abstract class BaseWebSearchProvider implements WebSearchProvider {
|
||||||
abstract readonly name: string;
|
abstract readonly name: string;
|
||||||
@@ -41,16 +42,19 @@ export abstract class BaseWebSearchProvider implements WebSearchProvider {
|
|||||||
*/
|
*/
|
||||||
async search(query: string, signal: AbortSignal): Promise<WebSearchResult> {
|
async search(query: string, signal: AbortSignal): Promise<WebSearchResult> {
|
||||||
if (!this.isAvailable()) {
|
if (!this.isAvailable()) {
|
||||||
throw new Error(
|
throw new WebSearchError(
|
||||||
`${this.name} provider is not available. Please check your configuration.`,
|
this.name,
|
||||||
|
'Provider is not available. Please check your configuration.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.performSearch(query, signal);
|
return await this.performSearch(query, signal);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = getErrorMessage(error);
|
if (error instanceof WebSearchError) {
|
||||||
throw new Error(`Error during ${this.name} search: ${errorMessage}`);
|
throw error;
|
||||||
|
}
|
||||||
|
throw new WebSearchError(this.name, 'Search failed', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +62,7 @@ export abstract class BaseWebSearchProvider implements WebSearchProvider {
|
|||||||
* Format search results into a consistent format.
|
* Format search results into a consistent format.
|
||||||
* @param results Raw results from the provider
|
* @param results Raw results from the provider
|
||||||
* @param query The original search query
|
* @param query The original search query
|
||||||
|
* @param answer Optional answer from the provider
|
||||||
* @returns Formatted search results
|
* @returns Formatted search results
|
||||||
*/
|
*/
|
||||||
protected formatResults(
|
protected formatResults(
|
||||||
@@ -71,31 +76,4 @@ export abstract class BaseWebSearchProvider implements WebSearchProvider {
|
|||||||
results,
|
results,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a formatted source list for display.
|
|
||||||
* @param results Search result items
|
|
||||||
* @returns Formatted source list
|
|
||||||
*/
|
|
||||||
protected createSourceList(results: WebSearchResultItem[]): string {
|
|
||||||
return results
|
|
||||||
.map((r, i) => `[${i + 1}] ${r.title || 'Untitled'} (${r.url})`)
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a concise summary from search results.
|
|
||||||
* @param results Search result items
|
|
||||||
* @param maxResults Maximum number of results to include
|
|
||||||
* @returns Concise summary string
|
|
||||||
*/
|
|
||||||
protected buildSummary(
|
|
||||||
results: WebSearchResultItem[],
|
|
||||||
maxResults: number = 3,
|
|
||||||
): string {
|
|
||||||
return results
|
|
||||||
.slice(0, maxResults)
|
|
||||||
.map((r, i) => `${i + 1}. ${r.title} - ${r.url}`)
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
packages/core/src/tools/web-search/errors.ts
Normal file
19
packages/core/src/tools/web-search/errors.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom error class for web search operations.
|
||||||
|
*/
|
||||||
|
export class WebSearchError extends Error {
|
||||||
|
constructor(
|
||||||
|
readonly provider: string,
|
||||||
|
message: string,
|
||||||
|
readonly originalError?: unknown,
|
||||||
|
) {
|
||||||
|
super(`[${provider}] ${message}`);
|
||||||
|
this.name = 'WebSearchError';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,16 +13,21 @@ import {
|
|||||||
type ToolInfoConfirmationDetails,
|
type ToolInfoConfirmationDetails,
|
||||||
ToolConfirmationOutcome,
|
ToolConfirmationOutcome,
|
||||||
} from '../tools.js';
|
} from '../tools.js';
|
||||||
|
import { ToolErrorType } from '../tool-error.js';
|
||||||
|
|
||||||
import type { Config } from '../../config/config.js';
|
import type { Config } from '../../config/config.js';
|
||||||
import { ApprovalMode } from '../../config/config.js';
|
import { ApprovalMode } from '../../config/config.js';
|
||||||
import { getErrorMessage } from '../../utils/errors.js';
|
import { getErrorMessage } from '../../utils/errors.js';
|
||||||
import { WebSearchProviderFactory } from './provider-factory.js';
|
import { buildContentWithSources, buildSummary } from './utils.js';
|
||||||
|
import { TavilyProvider } from './providers/tavily-provider.js';
|
||||||
|
import { GoogleProvider } from './providers/google-provider.js';
|
||||||
|
import { DashScopeProvider } from './providers/dashscope-provider.js';
|
||||||
import type {
|
import type {
|
||||||
WebSearchToolParams,
|
WebSearchToolParams,
|
||||||
WebSearchToolResult,
|
WebSearchToolResult,
|
||||||
WebSearchProvider,
|
WebSearchProvider,
|
||||||
WebSearchResultItem,
|
WebSearchResultItem,
|
||||||
|
WebSearchProviderConfig,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
class WebSearchToolInvocation extends BaseToolInvocation<
|
class WebSearchToolInvocation extends BaseToolInvocation<
|
||||||
@@ -37,7 +42,6 @@ class WebSearchToolInvocation extends BaseToolInvocation<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override getDescription(): string {
|
override getDescription(): string {
|
||||||
// Try to determine which provider will be used
|
|
||||||
const webSearchConfig = this.config.getWebSearchConfig();
|
const webSearchConfig = this.config.getWebSearchConfig();
|
||||||
const provider =
|
const provider =
|
||||||
this.params.provider || webSearchConfig?.default || 'tavily';
|
this.params.provider || webSearchConfig?.default || 'tavily';
|
||||||
@@ -64,6 +68,103 @@ class WebSearchToolInvocation extends BaseToolInvocation<
|
|||||||
return confirmationDetails;
|
return confirmationDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a provider instance from configuration.
|
||||||
|
*/
|
||||||
|
private createProvider(config: WebSearchProviderConfig): WebSearchProvider {
|
||||||
|
switch (config.type) {
|
||||||
|
case 'tavily':
|
||||||
|
return new TavilyProvider(config);
|
||||||
|
case 'google':
|
||||||
|
return new GoogleProvider(config);
|
||||||
|
case 'dashscope':
|
||||||
|
return new DashScopeProvider(config);
|
||||||
|
default:
|
||||||
|
throw new Error('Unknown provider type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create all configured providers.
|
||||||
|
*/
|
||||||
|
private createProviders(
|
||||||
|
configs: WebSearchProviderConfig[],
|
||||||
|
): Map<string, WebSearchProvider> {
|
||||||
|
const providers = new Map<string, WebSearchProvider>();
|
||||||
|
|
||||||
|
for (const config of configs) {
|
||||||
|
try {
|
||||||
|
const provider = this.createProvider(config);
|
||||||
|
if (provider.isAvailable()) {
|
||||||
|
providers.set(config.type, provider);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to create ${config.type} provider:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the appropriate provider based on configuration and parameters.
|
||||||
|
*/
|
||||||
|
private selectProvider(
|
||||||
|
providers: Map<string, WebSearchProvider>,
|
||||||
|
requestedProvider?: string,
|
||||||
|
defaultProvider?: string,
|
||||||
|
): { provider: WebSearchProvider | null; error?: string } {
|
||||||
|
// Use requested provider if specified
|
||||||
|
if (requestedProvider) {
|
||||||
|
const provider = providers.get(requestedProvider);
|
||||||
|
if (!provider) {
|
||||||
|
const availableProviders = Array.from(providers.keys()).join(', ');
|
||||||
|
return {
|
||||||
|
provider: null,
|
||||||
|
error: `The specified provider "${requestedProvider}" is not available or not configured. Available providers: ${availableProviders}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { provider };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use default provider if specified and available
|
||||||
|
if (defaultProvider && providers.has(defaultProvider)) {
|
||||||
|
const provider = providers.get(defaultProvider)!;
|
||||||
|
return { provider };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to first available provider
|
||||||
|
const firstProvider = providers.values().next().value;
|
||||||
|
return { provider: firstProvider || null };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format search results into a content string.
|
||||||
|
*/
|
||||||
|
private formatSearchResults(searchResult: {
|
||||||
|
answer?: string;
|
||||||
|
results: WebSearchResultItem[];
|
||||||
|
}): {
|
||||||
|
content: string;
|
||||||
|
sources: Array<{ title: string; url: string }>;
|
||||||
|
} {
|
||||||
|
const sources = searchResult.results.map((r) => ({
|
||||||
|
title: r.title,
|
||||||
|
url: r.url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let content = searchResult.answer?.trim() || '';
|
||||||
|
if (!content) {
|
||||||
|
// Fallback: build a concise summary from top results
|
||||||
|
content = buildSummary(sources, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sources section
|
||||||
|
content = buildContentWithSources(content, sources);
|
||||||
|
|
||||||
|
return { content, sources };
|
||||||
|
}
|
||||||
|
|
||||||
async execute(signal: AbortSignal): Promise<WebSearchToolResult> {
|
async execute(signal: AbortSignal): Promise<WebSearchToolResult> {
|
||||||
const webSearchConfig = this.config.getWebSearchConfig();
|
const webSearchConfig = this.config.getWebSearchConfig();
|
||||||
if (!webSearchConfig) {
|
if (!webSearchConfig) {
|
||||||
@@ -75,37 +176,35 @@ class WebSearchToolInvocation extends BaseToolInvocation<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const providers = WebSearchProviderFactory.createProviders(
|
const providers = this.createProviders(webSearchConfig.provider);
|
||||||
webSearchConfig.provider,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Determine which provider to use
|
const { provider: selectedProvider, error } = this.selectProvider(
|
||||||
let selectedProvider: WebSearchProvider | null = null;
|
|
||||||
|
|
||||||
if (this.params.provider) {
|
|
||||||
// Use the specified provider if available
|
|
||||||
const provider = providers.get(this.params.provider);
|
|
||||||
if (provider && provider.isAvailable()) {
|
|
||||||
selectedProvider = provider;
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
llmContent: `The specified provider "${this.params.provider}" is not available or not configured. Available providers: ${Array.from(providers.keys()).join(', ')}`,
|
|
||||||
returnDisplay: `The WebSearch Provider "${this.params.provider}" not available.`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use default provider
|
|
||||||
selectedProvider = WebSearchProviderFactory.getDefaultProvider(
|
|
||||||
providers,
|
providers,
|
||||||
|
this.params.provider,
|
||||||
webSearchConfig.default,
|
webSearchConfig.default,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return {
|
||||||
|
llmContent: error,
|
||||||
|
returnDisplay: `Provider "${this.params.provider}" not available.`,
|
||||||
|
error: {
|
||||||
|
message: error,
|
||||||
|
type: ToolErrorType.INVALID_TOOL_PARAMS,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedProvider) {
|
if (!selectedProvider) {
|
||||||
|
const errorMsg =
|
||||||
|
'Web search is disabled because no web search providers are available. Please check your configuration.';
|
||||||
return {
|
return {
|
||||||
llmContent:
|
llmContent: errorMsg,
|
||||||
'Web search is disabled because no web search providers are available. Please check your configuration.',
|
|
||||||
returnDisplay: 'Web search disabled. No available providers.',
|
returnDisplay: 'Web search disabled. No available providers.',
|
||||||
|
error: {
|
||||||
|
message: errorMsg,
|
||||||
|
type: ToolErrorType.EXECUTION_FAILED,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,31 +214,7 @@ class WebSearchToolInvocation extends BaseToolInvocation<
|
|||||||
signal,
|
signal,
|
||||||
);
|
);
|
||||||
|
|
||||||
const sources = searchResult.results.map((r: WebSearchResultItem) => ({
|
const { content, sources } = this.formatSearchResults(searchResult);
|
||||||
title: r.title,
|
|
||||||
url: r.url,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const sourceListFormatted = sources.map(
|
|
||||||
(s: { title: string; url: string }, i: number) =>
|
|
||||||
`[${i + 1}] ${s.title || 'Untitled'} (${s.url})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
let content = searchResult.answer?.trim() || '';
|
|
||||||
if (!content) {
|
|
||||||
// Fallback: build a concise summary from top results
|
|
||||||
content = sources
|
|
||||||
.slice(0, 3)
|
|
||||||
.map(
|
|
||||||
(s: { title: string; url: string }, i: number) =>
|
|
||||||
`${i + 1}. ${s.title} - ${s.url}`,
|
|
||||||
)
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceListFormatted.length > 0) {
|
|
||||||
content += `\n\nSources:\n${sourceListFormatted.join('\n')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
return {
|
return {
|
||||||
@@ -161,6 +236,10 @@ class WebSearchToolInvocation extends BaseToolInvocation<
|
|||||||
return {
|
return {
|
||||||
llmContent: `Error: ${errorMessage}`,
|
llmContent: `Error: ${errorMessage}`,
|
||||||
returnDisplay: `Error performing web search.`,
|
returnDisplay: `Error performing web search.`,
|
||||||
|
error: {
|
||||||
|
message: errorMessage,
|
||||||
|
type: ToolErrorType.EXECUTION_FAILED,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Qwen
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { WebSearchProvider, WebSearchProviderConfig } from './types.js';
|
|
||||||
import {
|
|
||||||
TavilyProvider,
|
|
||||||
type TavilyConfig,
|
|
||||||
} from './providers/tavily-provider.js';
|
|
||||||
import {
|
|
||||||
GoogleProvider,
|
|
||||||
type GoogleConfig,
|
|
||||||
} from './providers/google-provider.js';
|
|
||||||
import {
|
|
||||||
DashScopeProvider,
|
|
||||||
type DashScopeConfig,
|
|
||||||
} from './providers/dashscope-provider.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for creating web search providers based on configuration.
|
|
||||||
*/
|
|
||||||
export class WebSearchProviderFactory {
|
|
||||||
/**
|
|
||||||
* Create a web search provider from configuration.
|
|
||||||
* @param config Provider configuration
|
|
||||||
* @returns Web search provider instance
|
|
||||||
*/
|
|
||||||
static createProvider(config: WebSearchProviderConfig): WebSearchProvider {
|
|
||||||
switch (config.type) {
|
|
||||||
case 'tavily': {
|
|
||||||
const tavilyConfig = config.config as unknown as TavilyConfig;
|
|
||||||
if (!tavilyConfig?.apiKey) {
|
|
||||||
throw new Error('Tavily provider requires apiKey in configuration');
|
|
||||||
}
|
|
||||||
return new TavilyProvider(tavilyConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'google': {
|
|
||||||
const googleConfig = config.config as unknown as GoogleConfig;
|
|
||||||
if (!googleConfig?.apiKey || !googleConfig?.searchEngineId) {
|
|
||||||
throw new Error(
|
|
||||||
'Google provider requires apiKey and searchEngineId in configuration',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new GoogleProvider(googleConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'dashscope': {
|
|
||||||
const dashscopeConfig = config.config as unknown as DashScopeConfig;
|
|
||||||
return new DashScopeProvider(dashscopeConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`Unsupported web search provider type: ${(config as WebSearchProviderConfig).type}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create multiple providers from configuration list.
|
|
||||||
* @param configs List of provider configurations
|
|
||||||
* @returns Map of provider name to provider instance
|
|
||||||
*/
|
|
||||||
static createProviders(
|
|
||||||
configs: WebSearchProviderConfig[],
|
|
||||||
): Map<string, WebSearchProvider> {
|
|
||||||
const providers = new Map<string, WebSearchProvider>();
|
|
||||||
|
|
||||||
for (const config of configs) {
|
|
||||||
try {
|
|
||||||
const provider = this.createProvider(config);
|
|
||||||
providers.set(config.type, provider);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Failed to create ${config.type} provider:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default provider from a list of providers.
|
|
||||||
* @param providers Map of available providers
|
|
||||||
* @param defaultProviderName Name of the default provider
|
|
||||||
* @returns Default provider or the first available provider
|
|
||||||
*/
|
|
||||||
static getDefaultProvider(
|
|
||||||
providers: Map<string, WebSearchProvider>,
|
|
||||||
defaultProviderName?: string,
|
|
||||||
): WebSearchProvider | null {
|
|
||||||
if (defaultProviderName && providers.has(defaultProviderName)) {
|
|
||||||
const provider = providers.get(defaultProviderName)!;
|
|
||||||
if (provider.isAvailable()) {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to first available provider
|
|
||||||
for (const provider of providers.values()) {
|
|
||||||
if (provider.isAvailable()) {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseWebSearchProvider } from '../base-provider.js';
|
import { BaseWebSearchProvider } from '../base-provider.js';
|
||||||
import type { WebSearchResult, WebSearchResultItem } from '../types.js';
|
import { WebSearchError } from '../errors.js';
|
||||||
|
import type {
|
||||||
|
WebSearchResult,
|
||||||
|
WebSearchResultItem,
|
||||||
|
DashScopeProviderConfig,
|
||||||
|
} from '../types.js';
|
||||||
|
|
||||||
interface DashScopeSearchItem {
|
interface DashScopeSearchItem {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -50,30 +55,19 @@ interface DashScopeSearchResponse {
|
|||||||
success: boolean;
|
success: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for DashScope provider.
|
|
||||||
*/
|
|
||||||
export interface DashScopeConfig {
|
|
||||||
apiKey: string;
|
|
||||||
uid: string;
|
|
||||||
appId: string;
|
|
||||||
maxResults?: number;
|
|
||||||
scene?: string;
|
|
||||||
timeout?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web search provider using Alibaba Cloud DashScope API.
|
* Web search provider using Alibaba Cloud DashScope API.
|
||||||
*/
|
*/
|
||||||
export class DashScopeProvider extends BaseWebSearchProvider {
|
export class DashScopeProvider extends BaseWebSearchProvider {
|
||||||
readonly name = 'DashScope';
|
readonly name = 'DashScope';
|
||||||
|
|
||||||
constructor(private readonly config: DashScopeConfig) {
|
constructor(private readonly config: DashScopeProviderConfig) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
isAvailable(): boolean {
|
isAvailable(): boolean {
|
||||||
return !!(this.config.apiKey && this.config.uid && this.config.appId);
|
return true;
|
||||||
|
// return !!(this.config.apiKey && this.config.uid && this.config.appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async performSearch(
|
protected async performSearch(
|
||||||
@@ -82,7 +76,7 @@ export class DashScopeProvider extends BaseWebSearchProvider {
|
|||||||
): Promise<WebSearchResult> {
|
): Promise<WebSearchResult> {
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
rid: '',
|
rid: '',
|
||||||
uid: this.config.uid,
|
uid: this.config.uid!,
|
||||||
scene: this.config.scene || 'dolphin_search_inner_turbo',
|
scene: this.config.scene || 'dolphin_search_inner_turbo',
|
||||||
uq: query,
|
uq: query,
|
||||||
fields: [],
|
fields: [],
|
||||||
@@ -91,7 +85,7 @@ export class DashScopeProvider extends BaseWebSearchProvider {
|
|||||||
customConfigInfo: {},
|
customConfigInfo: {},
|
||||||
headers: {
|
headers: {
|
||||||
__d_head_qto: this.config.timeout || 8000,
|
__d_head_qto: this.config.timeout || 8000,
|
||||||
__d_head_app: this.config.appId,
|
__d_head_app: this.config.appId!,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,7 +95,7 @@ export class DashScopeProvider extends BaseWebSearchProvider {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: this.config.apiKey,
|
Authorization: this.config.apiKey!,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
signal,
|
signal,
|
||||||
@@ -110,16 +104,18 @@ export class DashScopeProvider extends BaseWebSearchProvider {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text().catch(() => '');
|
const text = await response.text().catch(() => '');
|
||||||
throw new Error(
|
throw new WebSearchError(
|
||||||
`DashScope API error: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`,
|
this.name,
|
||||||
|
`API error: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.json()) as DashScopeSearchResponse;
|
const data = (await response.json()) as DashScopeSearchResponse;
|
||||||
|
|
||||||
if (data.status !== 0) {
|
if (data.status !== 0) {
|
||||||
throw new Error(
|
throw new WebSearchError(
|
||||||
`DashScope API error: ${data.message || 'Unknown error'}`,
|
this.name,
|
||||||
|
`API error: ${data.message || 'Unknown error'}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseWebSearchProvider } from '../base-provider.js';
|
import { BaseWebSearchProvider } from '../base-provider.js';
|
||||||
import type { WebSearchResult, WebSearchResultItem } from '../types.js';
|
import { WebSearchError } from '../errors.js';
|
||||||
|
import type {
|
||||||
|
WebSearchResult,
|
||||||
|
WebSearchResultItem,
|
||||||
|
GoogleProviderConfig,
|
||||||
|
} from '../types.js';
|
||||||
|
|
||||||
interface GoogleSearchItem {
|
interface GoogleSearchItem {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -23,25 +28,13 @@ interface GoogleSearchResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for Google provider.
|
|
||||||
*/
|
|
||||||
export interface GoogleConfig {
|
|
||||||
apiKey: string;
|
|
||||||
searchEngineId: string;
|
|
||||||
maxResults?: number;
|
|
||||||
safeSearch?: 'off' | 'medium' | 'high';
|
|
||||||
language?: string;
|
|
||||||
country?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web search provider using Google Custom Search API.
|
* Web search provider using Google Custom Search API.
|
||||||
*/
|
*/
|
||||||
export class GoogleProvider extends BaseWebSearchProvider {
|
export class GoogleProvider extends BaseWebSearchProvider {
|
||||||
readonly name = 'Google';
|
readonly name = 'Google';
|
||||||
|
|
||||||
constructor(private readonly config: GoogleConfig) {
|
constructor(private readonly config: GoogleProviderConfig) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +47,8 @@ export class GoogleProvider extends BaseWebSearchProvider {
|
|||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
): Promise<WebSearchResult> {
|
): Promise<WebSearchResult> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
key: this.config.apiKey,
|
key: this.config.apiKey!,
|
||||||
cx: this.config.searchEngineId,
|
cx: this.config.searchEngineId!,
|
||||||
q: query,
|
q: query,
|
||||||
num: String(this.config.maxResults || 10),
|
num: String(this.config.maxResults || 10),
|
||||||
safe: this.config.safeSearch || 'medium',
|
safe: this.config.safeSearch || 'medium',
|
||||||
@@ -78,8 +71,9 @@ export class GoogleProvider extends BaseWebSearchProvider {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text().catch(() => '');
|
const text = await response.text().catch(() => '');
|
||||||
throw new Error(
|
throw new WebSearchError(
|
||||||
`Google Search API error: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`,
|
this.name,
|
||||||
|
`API error: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseWebSearchProvider } from '../base-provider.js';
|
import { BaseWebSearchProvider } from '../base-provider.js';
|
||||||
import type { WebSearchResult, WebSearchResultItem } from '../types.js';
|
import { WebSearchError } from '../errors.js';
|
||||||
|
import type {
|
||||||
|
WebSearchResult,
|
||||||
|
WebSearchResultItem,
|
||||||
|
TavilyProviderConfig,
|
||||||
|
} from '../types.js';
|
||||||
|
|
||||||
interface TavilyResultItem {
|
interface TavilyResultItem {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -21,23 +26,13 @@ interface TavilySearchResponse {
|
|||||||
results: TavilyResultItem[];
|
results: TavilyResultItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for Tavily provider.
|
|
||||||
*/
|
|
||||||
export interface TavilyConfig {
|
|
||||||
apiKey: string;
|
|
||||||
searchDepth?: 'basic' | 'advanced';
|
|
||||||
maxResults?: number;
|
|
||||||
includeAnswer?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web search provider using Tavily API.
|
* Web search provider using Tavily API.
|
||||||
*/
|
*/
|
||||||
export class TavilyProvider extends BaseWebSearchProvider {
|
export class TavilyProvider extends BaseWebSearchProvider {
|
||||||
readonly name = 'Tavily';
|
readonly name = 'Tavily';
|
||||||
|
|
||||||
constructor(private readonly config: TavilyConfig) {
|
constructor(private readonly config: TavilyProviderConfig) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +61,9 @@ export class TavilyProvider extends BaseWebSearchProvider {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text().catch(() => '');
|
const text = await response.text().catch(() => '');
|
||||||
throw new Error(
|
throw new WebSearchError(
|
||||||
`Tavily API error: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`,
|
this.name,
|
||||||
|
`API error: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export interface WebSearchConfig {
|
|||||||
/**
|
/**
|
||||||
* List of available providers with their configurations.
|
* List of available providers with their configurations.
|
||||||
*/
|
*/
|
||||||
providers: WebSearchProviderConfig[];
|
provider: WebSearchProviderConfig[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default provider to use.
|
* The default provider to use.
|
||||||
@@ -104,16 +104,47 @@ export interface WebSearchConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base configuration for a web search provider.
|
* Base configuration for Tavily provider.
|
||||||
*/
|
*/
|
||||||
export interface WebSearchProviderConfig {
|
export interface TavilyProviderConfig {
|
||||||
/**
|
type: 'tavily';
|
||||||
* The type of provider.
|
apiKey?: string;
|
||||||
*/
|
searchDepth?: 'basic' | 'advanced';
|
||||||
type: 'tavily' | 'google' | 'dashscope';
|
maxResults?: number;
|
||||||
|
includeAnswer?: boolean;
|
||||||
/**
|
|
||||||
* Provider-specific configuration.
|
|
||||||
*/
|
|
||||||
config?: Record<string, unknown>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base configuration for Google provider.
|
||||||
|
*/
|
||||||
|
export interface GoogleProviderConfig {
|
||||||
|
type: 'google';
|
||||||
|
apiKey?: string;
|
||||||
|
searchEngineId?: string;
|
||||||
|
maxResults?: number;
|
||||||
|
safeSearch?: 'off' | 'medium' | 'high';
|
||||||
|
language?: string;
|
||||||
|
country?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base configuration for DashScope provider.
|
||||||
|
*/
|
||||||
|
export interface DashScopeProviderConfig {
|
||||||
|
type: 'dashscope';
|
||||||
|
apiKey?: string;
|
||||||
|
uid?: string;
|
||||||
|
appId?: string;
|
||||||
|
maxResults?: number;
|
||||||
|
scene?: string;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discriminated union type for web search provider configurations.
|
||||||
|
* This ensures type safety when working with different provider configs.
|
||||||
|
*/
|
||||||
|
export type WebSearchProviderConfig =
|
||||||
|
| TavilyProviderConfig
|
||||||
|
| GoogleProviderConfig
|
||||||
|
| DashScopeProviderConfig;
|
||||||
|
|||||||
52
packages/core/src/tools/web-search/utils.ts
Normal file
52
packages/core/src/tools/web-search/utils.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions for web search formatting and processing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format sources into a numbered list with titles and URLs.
|
||||||
|
* @param sources Array of source objects with title and url
|
||||||
|
* @returns Formatted source list string
|
||||||
|
*/
|
||||||
|
export function formatSources(
|
||||||
|
sources: Array<{ title: string; url: string }>,
|
||||||
|
): string {
|
||||||
|
return sources
|
||||||
|
.map((s, i) => `[${i + 1}] ${s.title || 'Untitled'} (${s.url})`)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build content string with appended sources section.
|
||||||
|
* @param content Main content text
|
||||||
|
* @param sources Array of source objects
|
||||||
|
* @returns Combined content with sources
|
||||||
|
*/
|
||||||
|
export function buildContentWithSources(
|
||||||
|
content: string,
|
||||||
|
sources: Array<{ title: string; url: string }>,
|
||||||
|
): string {
|
||||||
|
if (!sources.length) return content;
|
||||||
|
return `${content}\n\nSources:\n${formatSources(sources)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a concise summary from top search results.
|
||||||
|
* @param sources Array of source objects
|
||||||
|
* @param maxResults Maximum number of results to include
|
||||||
|
* @returns Concise summary string
|
||||||
|
*/
|
||||||
|
export function buildSummary(
|
||||||
|
sources: Array<{ title: string; url: string }>,
|
||||||
|
maxResults: number = 3,
|
||||||
|
): string {
|
||||||
|
return sources
|
||||||
|
.slice(0, maxResults)
|
||||||
|
.map((s, i) => `${i + 1}. ${s.title} - ${s.url}`)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user