feat(research): Implement research APIs.

This commit is contained in:
2025-10-03 12:50:42 +00:00
parent e34bf19698
commit fe8540c8ba
11 changed files with 367 additions and 114 deletions

View File

@@ -68,7 +68,7 @@ export class AnthropicProvider extends MultiModalModel {
// If we have a complete message, send it to Anthropic
if (currentMessage) {
const stream = await this.anthropicApiClient.messages.create({
model: 'claude-3-opus-20240229',
model: 'claude-sonnet-4-5-20250929',
messages: [{ role: currentMessage.role, content: currentMessage.content }],
system: '',
stream: true,
@@ -112,7 +112,7 @@ export class AnthropicProvider extends MultiModalModel {
}));
const result = await this.anthropicApiClient.messages.create({
model: 'claude-3-opus-20240229',
model: 'claude-sonnet-4-5-20250929',
system: optionsArg.systemMessage,
messages: [
...messages,
@@ -159,7 +159,7 @@ export class AnthropicProvider extends MultiModalModel {
];
const result = await this.anthropicApiClient.messages.create({
model: 'claude-3-opus-20240229',
model: 'claude-sonnet-4-5-20250929',
messages: [{
role: 'user',
content
@@ -218,7 +218,7 @@ export class AnthropicProvider extends MultiModalModel {
}
const result = await this.anthropicApiClient.messages.create({
model: 'claude-3-opus-20240229',
model: 'claude-sonnet-4-5-20250929',
system: optionsArg.systemMessage,
messages: [
...messages,
@@ -251,23 +251,27 @@ export class AnthropicProvider extends MultiModalModel {
try {
// Build the tool configuration for web search
const tools = this.options.enableWebSearch ? [
{
type: 'web_search_20250305' as const,
name: 'web_search',
description: 'Search the web for current information',
input_schema: {
type: 'object' as const,
properties: {
query: {
type: 'string',
description: 'The search query'
}
},
required: ['query']
}
const tools: any[] = [];
if (this.options.enableWebSearch) {
const webSearchTool: any = {
type: 'web_search_20250305',
name: 'web_search'
};
// Add optional parameters
if (optionsArg.maxSources) {
webSearchTool.max_uses = optionsArg.maxSources;
}
] : [];
if (this.options.searchDomainAllowList?.length) {
webSearchTool.allowed_domains = this.options.searchDomainAllowList;
} else if (this.options.searchDomainBlockList?.length) {
webSearchTool.blocked_domains = this.options.searchDomainBlockList;
}
tools.push(webSearchTool);
}
// Configure the request based on search depth
const maxTokens = optionsArg.searchDepth === 'deep' ? 8192 :
@@ -275,7 +279,7 @@ export class AnthropicProvider extends MultiModalModel {
// Create the research request
const requestParams: any = {
model: 'claude-3-opus-20240229',
model: 'claude-sonnet-4-5-20250929',
system: systemMessage,
messages: [
{
@@ -290,7 +294,6 @@ export class AnthropicProvider extends MultiModalModel {
// Add tools if web search is enabled
if (tools.length > 0) {
requestParams.tools = tools;
requestParams.tool_choice = { type: 'auto' };
}
// Execute the research request
@@ -304,54 +307,71 @@ export class AnthropicProvider extends MultiModalModel {
// Process content blocks
for (const block of result.content) {
if ('text' in block) {
// Accumulate text content
answer += block.text;
// Extract citations if present
if ('citations' in block && Array.isArray(block.citations)) {
for (const citation of block.citations) {
if (citation.type === 'web_search_result_location') {
sources.push({
title: citation.title || '',
url: citation.url || '',
snippet: citation.cited_text || ''
});
}
}
}
} else if ('type' in block && block.type === 'server_tool_use') {
// Extract search queries from server tool use
if (block.name === 'web_search' && block.input && typeof block.input === 'object' && 'query' in block.input) {
searchQueries.push((block.input as any).query);
}
} else if ('type' in block && block.type === 'web_search_tool_result') {
// Extract sources from web search results
if (Array.isArray(block.content)) {
for (const result of block.content) {
if (result.type === 'web_search_result') {
// Only add if not already in sources (avoid duplicates from citations)
if (!sources.some(s => s.url === result.url)) {
sources.push({
title: result.title || '',
url: result.url || '',
snippet: '' // Search results don't include snippets, only citations do
});
}
}
}
}
}
}
// Parse sources from the answer (Claude includes citations in various formats)
const urlRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let match: RegExpExecArray | null;
// Fallback: Parse markdown-style links if no citations found
if (sources.length === 0) {
const urlRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let match: RegExpExecArray | null;
while ((match = urlRegex.exec(answer)) !== null) {
sources.push({
title: match[1],
url: match[2],
snippet: ''
});
}
// Also look for plain URLs
const plainUrlRegex = /https?:\/\/[^\s\)]+/g;
const plainUrls = answer.match(plainUrlRegex) || [];
for (const url of plainUrls) {
// Check if this URL is already in sources
if (!sources.some(s => s.url === url)) {
while ((match = urlRegex.exec(answer)) !== null) {
sources.push({
title: new URL(url).hostname,
url: url,
title: match[1],
url: match[2],
snippet: ''
});
}
}
// Extract tool use information if available
if ('tool_use' in result && Array.isArray(result.tool_use)) {
for (const toolUse of result.tool_use) {
if (toolUse.name === 'web_search' && toolUse.input?.query) {
searchQueries.push(toolUse.input.query);
}
}
}
// Check if web search was used based on usage info
const webSearchCount = result.usage?.server_tool_use?.web_search_requests || 0;
return {
answer,
sources,
searchQueries: searchQueries.length > 0 ? searchQueries : undefined,
metadata: {
model: 'claude-3-opus-20240229',
model: 'claude-sonnet-4-5-20250929',
searchDepth: optionsArg.searchDepth || 'basic',
tokensUsed: result.usage?.output_tokens
tokensUsed: result.usage?.output_tokens,
webSearchesPerformed: webSearchCount
}
};
} catch (error) {