diff --git a/src/components/AgentActionDisplay.tsx b/src/components/AgentActionDisplay.tsx
index 510fc54..d3d38f1 100644
--- a/src/components/AgentActionDisplay.tsx
+++ b/src/components/AgentActionDisplay.tsx
@@ -81,7 +81,7 @@ const AgentActionDisplay = ({ events, messageId }: AgentActionDisplayProps) => {
{event.details.searchQuery && event.details.searchQuery !== event.details.query && (
Search Query:
- "{event.details.searchQuery}"
+ "{event.details.searchQuery}"
)}
{event.details.sourcesFound !== undefined && (
diff --git a/src/lib/agents/analyzerAgent.ts b/src/lib/agents/analyzerAgent.ts
index 704fcc1..9d028bf 100644
--- a/src/lib/agents/analyzerAgent.ts
+++ b/src/lib/agents/analyzerAgent.ts
@@ -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',
diff --git a/src/lib/agents/webSearchAgent.ts b/src/lib/agents/webSearchAgent.ts
index bc03f89..585f6ad 100644
--- a/src/lib/agents/webSearchAgent.ts
+++ b/src/lib/agents/webSearchAgent.ts
@@ -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({
diff --git a/src/lib/utils/analyzePreviewContent.ts b/src/lib/utils/analyzePreviewContent.ts
new file mode 100644
index 0000000..2f1367a
--- /dev/null
+++ b/src/lib/utils/analyzePreviewContent.ts
@@ -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 => {
+ 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`
+ };
+ }
+};