feat(agent): Start agent speed optimizations.
This commit is contained in:
parent
df1a8b6cd2
commit
3437c6522f
4 changed files with 238 additions and 5 deletions
|
|
@ -81,7 +81,7 @@ const AgentActionDisplay = ({ events, messageId }: AgentActionDisplayProps) => {
|
|||
{event.details.searchQuery && event.details.searchQuery !== event.details.query && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Search Query:</span>
|
||||
<span className="italic">"{event.details.searchQuery}"</span>
|
||||
<span className="italic">"{event.details.searchQuery}"</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.sourcesFound !== undefined && (
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ Today's date is ${formatDateForLLM(new Date())}
|
|||
{ signal: this.signal },
|
||||
);
|
||||
|
||||
console.log('Analysis response:', response.content);
|
||||
// Parse the response to extract the analysis result
|
||||
const analysisOutputParser = new LineOutputParser({ key: 'answer' });
|
||||
const moreInfoOutputParser = new LineOutputParser({ key: 'question' });
|
||||
|
|
@ -122,7 +123,7 @@ Today's date is ${formatDateForLLM(new Date())}
|
|||
console.log('More info question:', moreInfoQuestion);
|
||||
console.log('Reason for insufficiency:', reason);
|
||||
|
||||
if (analysisResult.startsWith('need_more_info')) {
|
||||
if (!analysisResult.startsWith('good_content')) {
|
||||
// Emit reanalyzing event when we need more information
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { webSearchRetrieverAgentPrompt } from '../prompts/webSearch';
|
|||
import { searchSearxng } from '../searxng';
|
||||
import { formatDateForLLM } from '../utils';
|
||||
import { summarizeWebContent } from '../utils/summarizeWebContent';
|
||||
import { analyzePreviewContent, PreviewContent } from '../utils/analyzePreviewContent';
|
||||
import { AgentState } from './agentState';
|
||||
|
||||
export class WebSearchAgent {
|
||||
|
|
@ -107,10 +108,112 @@ export class WebSearchAgent {
|
|||
});
|
||||
|
||||
let bannedUrls = state.bannedUrls || [];
|
||||
let attemptedUrlCount = 0;
|
||||
// Summarize the top 2 search results
|
||||
|
||||
// Extract preview content from top 8 search results for analysis
|
||||
const previewContents: PreviewContent[] = searchResults.results
|
||||
.filter(result => !bannedUrls.includes(result.url)) // Filter out banned URLs first
|
||||
.slice(0, 8) // Then take top 8 results
|
||||
.map(result => ({
|
||||
title: result.title || 'Untitled',
|
||||
snippet: result.content || '',
|
||||
url: result.url
|
||||
}));
|
||||
|
||||
console.log(`Extracted preview content from ${previewContents.length} search results for analysis`);
|
||||
|
||||
// Perform preview analysis to determine if full content retrieval is needed
|
||||
let previewAnalysisResult = null;
|
||||
if (previewContents.length > 0) {
|
||||
console.log('Starting preview content analysis to determine if full processing is needed');
|
||||
|
||||
// Emit preview analysis event
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'ANALYZING_PREVIEW_CONTENT',
|
||||
message: `Analyzing ${previewContents.length} search result previews to determine processing approach`,
|
||||
details: {
|
||||
query: state.query,
|
||||
previewCount: previewContents.length,
|
||||
documentCount: state.relevantDocuments.length,
|
||||
searchIterations: state.searchInstructionHistory.length
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
previewAnalysisResult = await analyzePreviewContent(
|
||||
previewContents,
|
||||
state.query,
|
||||
state.messages,
|
||||
this.llm,
|
||||
this.systemInstructions,
|
||||
this.signal
|
||||
);
|
||||
|
||||
console.log(`Preview analysis result: ${previewAnalysisResult.isSufficient ? 'SUFFICIENT' : 'INSUFFICIENT'}${previewAnalysisResult.reason ? ` - ${previewAnalysisResult.reason}` : ''}`);
|
||||
}
|
||||
|
||||
let documents: Document[] = [];
|
||||
for (const result of searchResults.results) {
|
||||
let attemptedUrlCount = 0; // Declare outside conditional blocks
|
||||
|
||||
// Conditional workflow based on preview analysis result
|
||||
if (previewAnalysisResult && previewAnalysisResult.isSufficient) {
|
||||
// Preview content is sufficient - create documents from preview content
|
||||
console.log('Preview content determined sufficient - skipping full content retrieval');
|
||||
|
||||
// Emit preview processing event
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'PROCESSING_PREVIEW_CONTENT',
|
||||
message: `Using preview content from ${previewContents.length} sources - no full content retrieval needed`,
|
||||
details: {
|
||||
query: state.query,
|
||||
previewCount: previewContents.length,
|
||||
documentCount: state.relevantDocuments.length,
|
||||
searchIterations: state.searchInstructionHistory.length,
|
||||
processingType: 'preview-only'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create documents from preview content
|
||||
documents = previewContents.map((content, index) => new Document({
|
||||
pageContent: `# ${content.title}\n\n${content.snippet}`,
|
||||
metadata: {
|
||||
title: content.title,
|
||||
url: content.url,
|
||||
source: content.url,
|
||||
processingType: 'preview-only',
|
||||
snippet: content.snippet
|
||||
}
|
||||
}));
|
||||
|
||||
console.log(`Created ${documents.length} documents from preview content`);
|
||||
|
||||
} else {
|
||||
// Preview content is insufficient - proceed with full content processing
|
||||
const insufficiencyReason = previewAnalysisResult?.reason || 'Preview content not available or insufficient';
|
||||
console.log(`Preview content insufficient: ${insufficiencyReason} - proceeding with full content retrieval`);
|
||||
|
||||
// Emit full processing event
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'PROCEEDING_WITH_FULL_ANALYSIS',
|
||||
message: `Preview content insufficient - proceeding with detailed content analysis`,
|
||||
details: {
|
||||
query: state.query,
|
||||
insufficiencyReason: insufficiencyReason,
|
||||
documentCount: state.relevantDocuments.length,
|
||||
searchIterations: state.searchInstructionHistory.length,
|
||||
processingType: 'full-content'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Summarize the top 2 search results
|
||||
for (const result of searchResults.results) {
|
||||
if (bannedUrls.includes(result.url)) {
|
||||
console.log(`Skipping banned URL: ${result.url}`);
|
||||
// Note: We don't emit an agent_action event for banned URLs as this is an internal
|
||||
|
|
@ -199,6 +302,7 @@ export class WebSearchAgent {
|
|||
});
|
||||
}
|
||||
}
|
||||
} // Close the else block for full content processing
|
||||
|
||||
if (documents.length === 0) {
|
||||
return new Command({
|
||||
|
|
|
|||
128
src/lib/utils/analyzePreviewContent.ts
Normal file
128
src/lib/utils/analyzePreviewContent.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import LineOutputParser from '../outputParsers/lineOutputParser';
|
||||
import { formatDateForLLM } from '../utils';
|
||||
|
||||
export type PreviewAnalysisResult = {
|
||||
isSufficient: boolean;
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
export type PreviewContent = {
|
||||
title: string;
|
||||
snippet: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const analyzePreviewContent = async (
|
||||
previewContents: PreviewContent[],
|
||||
query: string,
|
||||
chatHistory: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
systemInstructions: string,
|
||||
signal: AbortSignal,
|
||||
): Promise<PreviewAnalysisResult> => {
|
||||
try {
|
||||
console.log(`Analyzing preview content for query: "${query}"`);
|
||||
console.log(`Preview content being analyzed:`, previewContents.map(content => ({
|
||||
title: content.title,
|
||||
snippet: content.snippet.substring(0, 100) + '...',
|
||||
url: content.url
|
||||
})));
|
||||
|
||||
// Format preview content for analysis
|
||||
const formattedPreviewContent = previewContents
|
||||
.map((content, index) =>
|
||||
`Source ${index + 1}:
|
||||
Title: ${content.title}
|
||||
Snippet: ${content.snippet}
|
||||
URL: ${content.url}
|
||||
---`
|
||||
)
|
||||
.join('\n\n');
|
||||
|
||||
// Format chat history for context
|
||||
const formattedChatHistory = chatHistory
|
||||
.slice(-10) // Only include last 10 messages for context
|
||||
.map((message, index) =>
|
||||
`${message._getType()}: ${message.content}`
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
const systemPrompt = systemInstructions
|
||||
? `${systemInstructions}\n\n`
|
||||
: '';
|
||||
|
||||
console.log(`Invoking LLM for preview content analysis`);
|
||||
|
||||
const analysisResponse = await llm.invoke(
|
||||
`${systemPrompt}You are a preview content analyzer, tasked with determining if search result snippets contain sufficient information to answer a user's query.
|
||||
|
||||
# Instructions
|
||||
- Analyze the provided search result previews (titles + snippets) to determine if they collectively contain enough information to provide a complete and accurate answer to the user's query
|
||||
- Consider the chat history context when making your decision
|
||||
- You must make a binary decision: either the preview content is sufficient OR it is not sufficient
|
||||
- If the preview content can provide a complete answer to the query, respond with "sufficient"
|
||||
- If the preview content lacks important details, requires deeper analysis, or cannot fully answer the query, respond with "not_needed: [specific reason why full content analysis is required]"
|
||||
- Be specific in your reasoning when the content is not sufficient
|
||||
- Consider query complexity: simple factual questions may be answerable from snippets, while complex research questions typically need full content
|
||||
- Consider information completeness: if key details are missing from the snippets that would be needed for a complete answer, full analysis is required
|
||||
- Output your decision inside a \`decision\` XML tag
|
||||
|
||||
Today's date is ${formatDateForLLM(new Date())}
|
||||
|
||||
# Chat History Context:
|
||||
${formattedChatHistory ? formattedChatHistory : 'No previous conversation context'}
|
||||
|
||||
# User Query:
|
||||
${query}
|
||||
|
||||
# Search Result Previews to Analyze:
|
||||
${formattedPreviewContent}
|
||||
`,
|
||||
{ signal },
|
||||
);
|
||||
|
||||
if (!analysisResponse || !analysisResponse.content) {
|
||||
console.error('No analysis response returned from LLM');
|
||||
return {
|
||||
isSufficient: false,
|
||||
reason: 'No analysis response returned from LLM - falling back to full content processing'
|
||||
};
|
||||
}
|
||||
|
||||
const decisionParser = new LineOutputParser({ key: 'decision' });
|
||||
const decision = await decisionParser.parse(
|
||||
analysisResponse.content as string,
|
||||
);
|
||||
|
||||
console.log(`LLM decision response:`, decision);
|
||||
|
||||
if (decision.toLowerCase().trim() === 'sufficient') {
|
||||
console.log('Preview content determined to be sufficient for answering the query');
|
||||
return { isSufficient: true };
|
||||
} else if (decision.toLowerCase().startsWith('not_needed')) {
|
||||
// Extract the reason from the "not_needed" response
|
||||
const reason = decision.startsWith('not_needed')
|
||||
? decision.substring('not_needed:'.length).trim()
|
||||
: 'Preview content insufficient for complete answer';
|
||||
|
||||
console.log(`Preview content determined to be insufficient. Reason: ${reason}`);
|
||||
return { isSufficient: false, reason };
|
||||
} else {
|
||||
// Default to not sufficient if unclear response
|
||||
console.log(`Unclear LLM response, defaulting to insufficient: ${decision}`);
|
||||
return {
|
||||
isSufficient: false,
|
||||
reason: 'Unclear analysis response - falling back to full content processing'
|
||||
};
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error analyzing preview content:', error);
|
||||
return {
|
||||
isSufficient: false,
|
||||
reason: `Error during preview analysis: ${error instanceof Error ? error.message : 'Unknown error'} - falling back to full content processing`
|
||||
};
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue