refactor(agent): Improve code formatting and readability across multiple components

This commit is contained in:
Willie Zutz 2025-06-17 00:20:05 -06:00
parent 74c3934aa5
commit 72c2ddc3a0
11 changed files with 302 additions and 223 deletions

View file

@ -2,7 +2,14 @@
import { useState } from 'react'; import { useState } from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ChevronDown, ChevronUp, Bot, Search, Zap, Microscope } from 'lucide-react'; import {
ChevronDown,
ChevronUp,
Bot,
Search,
Zap,
Microscope,
} from 'lucide-react';
import { AgentActionEvent } from './ChatWindow'; import { AgentActionEvent } from './ChatWindow';
interface AgentActionDisplayProps { interface AgentActionDisplayProps {
@ -48,7 +55,9 @@ const AgentActionDisplay = ({ events, messageId }: AgentActionDisplayProps) => {
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{getActionIcon(latestEvent.action)} {getActionIcon(latestEvent.action)}
<span className="font-medium text-base text-black/70 dark:text-white/70 tracking-wide capitalize"> <span className="font-medium text-base text-black/70 dark:text-white/70 tracking-wide capitalize">
{latestEvent.action === 'SYNTHESIZING_RESPONSE' ? 'Agent Log' : formatActionName(latestEvent.action)} {latestEvent.action === 'SYNTHESIZING_RESPONSE'
? 'Agent Log'
: formatActionName(latestEvent.action)}
</span> </span>
</div> </div>
{isExpanded ? ( {isExpanded ? (
@ -83,7 +92,11 @@ const AgentActionDisplay = ({ events, messageId }: AgentActionDisplayProps) => {
{event.details.sourceUrl && ( {event.details.sourceUrl && (
<div className="flex space-x-1"> <div className="flex space-x-1">
<span className="font-bold">Source:</span> <span className="font-bold">Source:</span>
<span className="truncate"><a href={event.details.sourceUrl} target='_blank'>{event.details.sourceUrl}</a></span> <span className="truncate">
<a href={event.details.sourceUrl} target="_blank">
{event.details.sourceUrl}
</a>
</span>
</div> </div>
)} )}
{event.details.skipReason && ( {event.details.skipReason && (
@ -92,12 +105,15 @@ const AgentActionDisplay = ({ events, messageId }: AgentActionDisplayProps) => {
<span>{event.details.skipReason}</span> <span>{event.details.skipReason}</span>
</div> </div>
)} )}
{event.details.searchQuery && event.details.searchQuery !== event.details.query && ( {event.details.searchQuery &&
<div className="flex space-x-1"> event.details.searchQuery !== event.details.query && (
<span className="font-bold">Search Query:</span> <div className="flex space-x-1">
<span className="italic">&quot;{event.details.searchQuery}&quot;</span> <span className="font-bold">Search Query:</span>
</div> <span className="italic">
)} &quot;{event.details.searchQuery}&quot;
</span>
</div>
)}
{event.details.sourcesFound !== undefined && ( {event.details.sourcesFound !== undefined && (
<div className="flex space-x-1"> <div className="flex space-x-1">
<span className="font-bold">Sources Found:</span> <span className="font-bold">Sources Found:</span>
@ -131,7 +147,9 @@ const AgentActionDisplay = ({ events, messageId }: AgentActionDisplayProps) => {
{event.details.processingType && ( {event.details.processingType && (
<div className="flex space-x-1"> <div className="flex space-x-1">
<span className="font-bold">Processing Type:</span> <span className="font-bold">Processing Type:</span>
<span className="capitalize">{event.details.processingType.replace('-', ' ')}</span> <span className="capitalize">
{event.details.processingType.replace('-', ' ')}
</span>
</div> </div>
)} )}
{event.details.insufficiencyReason && ( {event.details.insufficiencyReason && (

View file

@ -236,12 +236,11 @@ const Chat = ({
/> />
)} )}
{/* Show empty agent action display if this is the last user message and we're loading */} {/* Show empty agent action display if this is the last user message and we're loading */}
{loading && isLast && (!msg.agentActions || msg.agentActions.length === 0) && ( {loading &&
<AgentActionDisplay isLast &&
messageId={msg.messageId} (!msg.agentActions || msg.agentActions.length === 0) && (
events={[]} <AgentActionDisplay messageId={msg.messageId} events={[]} />
/> )}
)}
</> </>
)} )}
{!isLast && msg.role === 'assistant' && ( {!isLast && msg.role === 'assistant' && (

View file

@ -442,7 +442,10 @@ const ChatWindow = ({ id }: { id?: string }) => {
// Update the user message with agent actions // Update the user message with agent actions
setMessages((prev) => setMessages((prev) =>
prev.map((message) => { prev.map((message) => {
if (message.messageId === data.messageId && message.role === 'user') { if (
message.messageId === data.messageId &&
message.role === 'user'
) {
const updatedActions = [ const updatedActions = [
...(message.agentActions || []), ...(message.agentActions || []),
agentActionEvent, agentActionEvent,

View file

@ -40,12 +40,18 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
{/* Processing type indicator */} {/* Processing type indicator */}
{source.metadata.processingType === 'preview-only' && ( {source.metadata.processingType === 'preview-only' && (
<span title="Partial content analyzed" className="inline-flex"> <span title="Partial content analyzed" className="inline-flex">
<Zap size={14} className="text-black/40 dark:text-white/40 ml-1" /> <Zap
size={14}
className="text-black/40 dark:text-white/40 ml-1"
/>
</span> </span>
)} )}
{source.metadata.processingType === 'full-content' && ( {source.metadata.processingType === 'full-content' && (
<span title="Full content analyzed" className="inline-flex"> <span title="Full content analyzed" className="inline-flex">
<Microscope size={14} className="text-black/40 dark:text-white/40 ml-1" /> <Microscope
size={14}
className="text-black/40 dark:text-white/40 ml-1"
/>
</span> </span>
)} )}
</div> </div>

View file

@ -32,13 +32,14 @@ export class AnalyzerAgent {
type: 'agent_action', type: 'agent_action',
data: { data: {
action: 'ANALYZING_CONTEXT', action: 'ANALYZING_CONTEXT',
message: 'Analyzing the context to see if we have enough information to answer the query', message:
'Analyzing the context to see if we have enough information to answer the query',
details: { details: {
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
query: state.query, query: state.query,
searchIterations: state.searchInstructionHistory.length searchIterations: state.searchInstructionHistory.length,
} },
} },
}); });
console.log( console.log(
@ -94,7 +95,10 @@ Today's date is ${formatDateForLLM(new Date())}
).format({ ).format({
systemInstructions: this.systemInstructions, systemInstructions: this.systemInstructions,
context: state.relevantDocuments context: state.relevantDocuments
.map((doc, index) => `<source${index + 1}>${doc?.metadata?.title ? `<title>${doc?.metadata?.title}</title>` : ''}<content>${doc.pageContent}</content></source${index + 1}>`) .map(
(doc, index) =>
`<source${index + 1}>${doc?.metadata?.title ? `<title>${doc?.metadata?.title}</title>` : ''}<content>${doc.pageContent}</content></source${index + 1}>`,
)
.join('\n\n'), .join('\n\n'),
}); });
@ -115,9 +119,7 @@ Today's date is ${formatDateForLLM(new Date())}
const moreInfoQuestion = await moreInfoOutputParser.parse( const moreInfoQuestion = await moreInfoOutputParser.parse(
response.content as string, response.content as string,
); );
const reason = await reasonOutputParser.parse( const reason = await reasonOutputParser.parse(response.content as string);
response.content as string,
);
console.log('Analysis result:', analysisResult); console.log('Analysis result:', analysisResult);
console.log('More info question:', moreInfoQuestion); console.log('More info question:', moreInfoQuestion);
@ -129,15 +131,16 @@ Today's date is ${formatDateForLLM(new Date())}
type: 'agent_action', type: 'agent_action',
data: { data: {
action: 'MORE_DATA_NEEDED', action: 'MORE_DATA_NEEDED',
message: 'Current context is insufficient - gathering more information', message:
'Current context is insufficient - gathering more information',
details: { details: {
reason: reason, reason: reason,
nextSearchQuery: moreInfoQuestion, nextSearchQuery: moreInfoQuestion,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length, searchIterations: state.searchInstructionHistory.length,
query: state.query query: state.query,
} },
} },
}); });
return new Command({ return new Command({
@ -159,13 +162,14 @@ Today's date is ${formatDateForLLM(new Date())}
type: 'agent_action', type: 'agent_action',
data: { data: {
action: 'INFORMATION_GATHERING_COMPLETE', action: 'INFORMATION_GATHERING_COMPLETE',
message: 'Sufficient information gathered - ready to synthesize response', message:
'Sufficient information gathered - ready to synthesize response',
details: { details: {
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length, searchIterations: state.searchInstructionHistory.length,
query: state.query query: state.query,
} },
} },
}); });
return new Command({ return new Command({

View file

@ -37,9 +37,9 @@ export class SynthesizerAgent {
details: { details: {
query: state.query, query: state.query,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length searchIterations: state.searchInstructionHistory.length,
} },
} },
}); });
const synthesisPrompt = `You are an expert information synthesizer. Based on the search results and analysis provided, create a comprehensive, well-structured answer to the user's query. const synthesisPrompt = `You are an expert information synthesizer. Based on the search results and analysis provided, create a comprehensive, well-structured answer to the user's query.

View file

@ -9,7 +9,10 @@ import { webSearchRetrieverAgentPrompt } from '../prompts/webSearch';
import { searchSearxng } from '../searxng'; import { searchSearxng } from '../searxng';
import { formatDateForLLM } from '../utils'; import { formatDateForLLM } from '../utils';
import { summarizeWebContent } from '../utils/summarizeWebContent'; import { summarizeWebContent } from '../utils/summarizeWebContent';
import { analyzePreviewContent, PreviewContent } from '../utils/analyzePreviewContent'; import {
analyzePreviewContent,
PreviewContent,
} from '../utils/analyzePreviewContent';
import { AgentState } from './agentState'; import { AgentState } from './agentState';
export class WebSearchAgent { export class WebSearchAgent {
@ -44,9 +47,9 @@ export class WebSearchAgent {
query: state.query, query: state.query,
searchInstructions: state.searchInstructions || state.query, searchInstructions: state.searchInstructions || state.query,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length searchIterations: state.searchInstructionHistory.length,
} },
} },
}); });
const template = PromptTemplate.fromTemplate(webSearchRetrieverAgentPrompt); const template = PromptTemplate.fromTemplate(webSearchRetrieverAgentPrompt);
@ -70,7 +73,7 @@ export class WebSearchAgent {
try { try {
console.log(`Performing web search for query: "${searchQuery}"`); console.log(`Performing web search for query: "${searchQuery}"`);
// Emit executing web search event // Emit executing web search event
this.emitter.emit('agent_action', { this.emitter.emit('agent_action', {
type: 'agent_action', type: 'agent_action',
@ -81,9 +84,9 @@ export class WebSearchAgent {
query: state.query, query: state.query,
searchQuery: searchQuery, searchQuery: searchQuery,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length searchIterations: state.searchInstructionHistory.length,
} },
} },
}); });
const searchResults = await searchSearxng(searchQuery, { const searchResults = await searchSearxng(searchQuery, {
@ -102,30 +105,34 @@ export class WebSearchAgent {
searchQuery: searchQuery, searchQuery: searchQuery,
sourcesFound: searchResults.results.length, sourcesFound: searchResults.results.length,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length searchIterations: state.searchInstructionHistory.length,
} },
} },
}); });
let bannedUrls = state.bannedUrls || []; let bannedUrls = state.bannedUrls || [];
// Extract preview content from top 8 search results for analysis // Extract preview content from top 8 search results for analysis
const previewContents: PreviewContent[] = searchResults.results const previewContents: PreviewContent[] = searchResults.results
.filter(result => !bannedUrls.includes(result.url)) // Filter out banned URLs first .filter((result) => !bannedUrls.includes(result.url)) // Filter out banned URLs first
.slice(0, 8) // Then take top 8 results .slice(0, 8) // Then take top 8 results
.map(result => ({ .map((result) => ({
title: result.title || 'Untitled', title: result.title || 'Untitled',
snippet: result.content || '', snippet: result.content || '',
url: result.url url: result.url,
})); }));
console.log(`Extracted preview content from ${previewContents.length} search results for analysis`); console.log(
`Extracted preview content from ${previewContents.length} search results for analysis`,
);
// Perform preview analysis to determine if full content retrieval is needed // Perform preview analysis to determine if full content retrieval is needed
let previewAnalysisResult = null; let previewAnalysisResult = null;
if (previewContents.length > 0) { if (previewContents.length > 0) {
console.log('Starting preview content analysis to determine if full processing is needed'); console.log(
'Starting preview content analysis to determine if full processing is needed',
);
// Emit preview analysis event // Emit preview analysis event
this.emitter.emit('agent_action', { this.emitter.emit('agent_action', {
type: 'agent_action', type: 'agent_action',
@ -136,9 +143,9 @@ export class WebSearchAgent {
query: state.query, query: state.query,
previewCount: previewContents.length, previewCount: previewContents.length,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length searchIterations: state.searchInstructionHistory.length,
} },
} },
}); });
previewAnalysisResult = await analyzePreviewContent( previewAnalysisResult = await analyzePreviewContent(
@ -147,10 +154,12 @@ export class WebSearchAgent {
state.messages, state.messages,
this.llm, this.llm,
this.systemInstructions, this.systemInstructions,
this.signal this.signal,
); );
console.log(`Preview analysis result: ${previewAnalysisResult.isSufficient ? 'SUFFICIENT' : 'INSUFFICIENT'}${previewAnalysisResult.reason ? ` - ${previewAnalysisResult.reason}` : ''}`); console.log(
`Preview analysis result: ${previewAnalysisResult.isSufficient ? 'SUFFICIENT' : 'INSUFFICIENT'}${previewAnalysisResult.reason ? ` - ${previewAnalysisResult.reason}` : ''}`,
);
} }
let documents: Document[] = []; let documents: Document[] = [];
@ -159,8 +168,10 @@ export class WebSearchAgent {
// Conditional workflow based on preview analysis result // Conditional workflow based on preview analysis result
if (previewAnalysisResult && previewAnalysisResult.isSufficient) { if (previewAnalysisResult && previewAnalysisResult.isSufficient) {
// Preview content is sufficient - create documents from preview content // Preview content is sufficient - create documents from preview content
console.log('Preview content determined sufficient - skipping full content retrieval'); console.log(
'Preview content determined sufficient - skipping full content retrieval',
);
// Emit preview processing event // Emit preview processing event
this.emitter.emit('agent_action', { this.emitter.emit('agent_action', {
type: 'agent_action', type: 'agent_action',
@ -172,30 +183,38 @@ export class WebSearchAgent {
previewCount: previewContents.length, previewCount: previewContents.length,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length, searchIterations: state.searchInstructionHistory.length,
processingType: 'preview-only' processingType: 'preview-only',
} },
} },
}); });
// Create documents from preview content // Create documents from preview content
documents = previewContents.map((content, index) => new Document({ documents = previewContents.map(
pageContent: `# ${content.title}\n\n${content.snippet}`, (content, index) =>
metadata: { new Document({
title: content.title, pageContent: `# ${content.title}\n\n${content.snippet}`,
url: content.url, metadata: {
source: content.url, title: content.title,
processingType: 'preview-only', url: content.url,
snippet: content.snippet source: content.url,
} processingType: 'preview-only',
})); snippet: content.snippet,
},
}),
);
console.log(`Created ${documents.length} documents from preview content`); console.log(
`Created ${documents.length} documents from preview content`,
);
} else { } else {
// Preview content is insufficient - proceed with full content processing // Preview content is insufficient - proceed with full content processing
const insufficiencyReason = previewAnalysisResult?.reason || 'Preview content not available or insufficient'; const insufficiencyReason =
console.log(`Preview content insufficient: ${insufficiencyReason} - proceeding with full content retrieval`); previewAnalysisResult?.reason ||
'Preview content not available or insufficient';
console.log(
`Preview content insufficient: ${insufficiencyReason} - proceeding with full content retrieval`,
);
// Emit full processing event // Emit full processing event
this.emitter.emit('agent_action', { this.emitter.emit('agent_action', {
type: 'agent_action', type: 'agent_action',
@ -207,101 +226,106 @@ export class WebSearchAgent {
insufficiencyReason: insufficiencyReason, insufficiencyReason: insufficiencyReason,
documentCount: state.relevantDocuments.length, documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length, searchIterations: state.searchInstructionHistory.length,
processingType: 'full-content' processingType: 'full-content',
} },
} },
}); });
// Summarize the top 2 search results // Summarize the top 2 search results
for (const result of searchResults.results) { for (const result of searchResults.results) {
if (bannedUrls.includes(result.url)) { if (bannedUrls.includes(result.url)) {
console.log(`Skipping banned URL: ${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 // Note: We don't emit an agent_action event for banned URLs as this is an internal
// optimization that should be transparent to the user // optimization that should be transparent to the user
continue; // Skip banned URLs continue; // Skip banned URLs
}
if (attemptedUrlCount >= 5) {
console.warn(
'Too many attempts to summarize URLs, stopping further attempts.',
);
break; // Limit the number of attempts to summarize URLs
}
attemptedUrlCount++;
bannedUrls.push(result.url); // Add to banned URLs to avoid duplicates
if (documents.length >= 1) {
break; // Limit to top 1 document
}
// Emit analyzing source event
this.emitter.emit('agent_action', {
type: 'agent_action',
data: {
action: 'ANALYZING_SOURCE',
message: `Analyzing content from: ${result.title || result.url}`,
details: {
query: state.query,
sourceUrl: result.url,
sourceTitle: result.title || 'Untitled',
documentCount: state.relevantDocuments.length,
searchIterations: state.searchInstructionHistory.length
}
} }
}); if (attemptedUrlCount >= 5) {
console.warn(
'Too many attempts to summarize URLs, stopping further attempts.',
);
break; // Limit the number of attempts to summarize URLs
}
attemptedUrlCount++;
const summaryResult = await summarizeWebContent( bannedUrls.push(result.url); // Add to banned URLs to avoid duplicates
result.url,
state.query, if (documents.length >= 1) {
this.llm, break; // Limit to top 1 document
this.systemInstructions, }
this.signal,
); // Emit analyzing source event
if (summaryResult.document) {
documents.push(summaryResult.document);
// Emit context updated event
this.emitter.emit('agent_action', { this.emitter.emit('agent_action', {
type: 'agent_action', type: 'agent_action',
data: { data: {
action: 'CONTEXT_UPDATED', action: 'ANALYZING_SOURCE',
message: `Added information from ${summaryResult.document.metadata.title || result.url} to context`, message: `Analyzing content from: ${result.title || result.url}`,
details: {
query: state.query,
sourceUrl: result.url,
sourceTitle: summaryResult.document.metadata.title || 'Untitled',
contentLength: summaryResult.document.pageContent.length,
documentCount: state.relevantDocuments.length + documents.length,
searchIterations: state.searchInstructionHistory.length
}
}
});
console.log(
`Summarized content from ${result.url} to ${summaryResult.document.pageContent.length} characters. Content: ${summaryResult.document.pageContent}`,
);
} else {
console.warn(`No relevant content found for URL: ${result.url}`);
// Emit skipping irrelevant source event for non-relevant content
this.emitter.emit('agent_action', {
type: 'agent_action',
data: {
action: 'SKIPPING_IRRELEVANT_SOURCE',
message: `Source ${result.title || result.url} was not relevant - trying next`,
details: { details: {
query: state.query, query: state.query,
sourceUrl: result.url, sourceUrl: result.url,
sourceTitle: result.title || 'Untitled', sourceTitle: result.title || 'Untitled',
skipReason: summaryResult.notRelevantReason || 'Content was not relevant to the query', documentCount: state.relevantDocuments.length,
documentCount: state.relevantDocuments.length + documents.length, searchIterations: state.searchInstructionHistory.length,
searchIterations: state.searchInstructionHistory.length },
} },
}
}); });
const summaryResult = await summarizeWebContent(
result.url,
state.query,
this.llm,
this.systemInstructions,
this.signal,
);
if (summaryResult.document) {
documents.push(summaryResult.document);
// Emit context updated event
this.emitter.emit('agent_action', {
type: 'agent_action',
data: {
action: 'CONTEXT_UPDATED',
message: `Added information from ${summaryResult.document.metadata.title || result.url} to context`,
details: {
query: state.query,
sourceUrl: result.url,
sourceTitle:
summaryResult.document.metadata.title || 'Untitled',
contentLength: summaryResult.document.pageContent.length,
documentCount:
state.relevantDocuments.length + documents.length,
searchIterations: state.searchInstructionHistory.length,
},
},
});
console.log(
`Summarized content from ${result.url} to ${summaryResult.document.pageContent.length} characters. Content: ${summaryResult.document.pageContent}`,
);
} else {
console.warn(`No relevant content found for URL: ${result.url}`);
// Emit skipping irrelevant source event for non-relevant content
this.emitter.emit('agent_action', {
type: 'agent_action',
data: {
action: 'SKIPPING_IRRELEVANT_SOURCE',
message: `Source ${result.title || result.url} was not relevant - trying next`,
details: {
query: state.query,
sourceUrl: result.url,
sourceTitle: result.title || 'Untitled',
skipReason:
summaryResult.notRelevantReason ||
'Content was not relevant to the query',
documentCount:
state.relevantDocuments.length + documents.length,
searchIterations: state.searchInstructionHistory.length,
},
},
});
}
} }
}
} // Close the else block for full content processing } // Close the else block for full content processing
if (documents.length === 0) { if (documents.length === 0) {

View file

@ -5,18 +5,13 @@ import {
HumanMessage, HumanMessage,
SystemMessage, SystemMessage,
} from '@langchain/core/messages'; } from '@langchain/core/messages';
import { import { END, MemorySaver, START, StateGraph } from '@langchain/langgraph';
END,
MemorySaver,
START,
StateGraph,
} from '@langchain/langgraph';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { import {
AgentState, AgentState,
WebSearchAgent, WebSearchAgent,
AnalyzerAgent, AnalyzerAgent,
SynthesizerAgent SynthesizerAgent,
} from '../agents'; } from '../agents';
/** /**
@ -49,19 +44,19 @@ export class AgentSearch {
llm, llm,
emitter, emitter,
systemInstructions, systemInstructions,
signal signal,
); );
this.analyzerAgent = new AnalyzerAgent( this.analyzerAgent = new AnalyzerAgent(
llm, llm,
emitter, emitter,
systemInstructions, systemInstructions,
signal signal,
); );
this.synthesizerAgent = new SynthesizerAgent( this.synthesizerAgent = new SynthesizerAgent(
llm, llm,
emitter, emitter,
personaInstructions, personaInstructions,
signal signal,
); );
} }
@ -70,15 +65,27 @@ export class AgentSearch {
*/ */
private createWorkflow() { private createWorkflow() {
const workflow = new StateGraph(AgentState) const workflow = new StateGraph(AgentState)
.addNode('web_search', this.webSearchAgent.execute.bind(this.webSearchAgent), { .addNode(
ends: ['analyzer'], 'web_search',
}) this.webSearchAgent.execute.bind(this.webSearchAgent),
.addNode('analyzer', this.analyzerAgent.execute.bind(this.analyzerAgent), { {
ends: ['web_search', 'synthesizer'], ends: ['analyzer'],
}) },
.addNode('synthesizer', this.synthesizerAgent.execute.bind(this.synthesizerAgent), { )
ends: [END], .addNode(
}) 'analyzer',
this.analyzerAgent.execute.bind(this.analyzerAgent),
{
ends: ['web_search', 'synthesizer'],
},
)
.addNode(
'synthesizer',
this.synthesizerAgent.execute.bind(this.synthesizerAgent),
{
ends: [END],
},
)
.addEdge(START, 'analyzer'); .addEdge(START, 'analyzer');
return workflow.compile({ checkpointer: this.checkpointer }); return workflow.compile({ checkpointer: this.checkpointer });

View file

@ -180,8 +180,8 @@ class MetaSearchAgent implements MetaSearchAgentType {
? `${systemInstructions}\n\n` ? `${systemInstructions}\n\n`
: ''; : '';
const res = const res = await llm.invoke(
await llm.invoke(`${systemPrompt}You are a web search summarizer, tasked with summarizing a piece of text retrieved from a web search. Your job is to summarize the `${systemPrompt}You are a web search summarizer, tasked with summarizing a piece of text retrieved from a web search. Your job is to summarize the
text into a detailed, 2-4 paragraph explanation that captures the main ideas and provides a comprehensive answer to the query. text into a detailed, 2-4 paragraph explanation that captures the main ideas and provides a comprehensive answer to the query.
If the query is \"summarize\", you should provide a detailed summary of the text. If the query is a specific question, you should answer it in the summary. If the query is \"summarize\", you should provide a detailed summary of the text. If the query is a specific question, you should answer it in the summary.
@ -239,7 +239,9 @@ class MetaSearchAgent implements MetaSearchAgentType {
</text> </text>
Make sure to answer the query in the summary. Make sure to answer the query in the summary.
`, { signal }); `,
{ signal },
);
const document = new Document({ const document = new Document({
pageContent: res.content as string, pageContent: res.content as string,

View file

@ -24,37 +24,37 @@ export const analyzePreviewContent = async (
): Promise<PreviewAnalysisResult> => { ): Promise<PreviewAnalysisResult> => {
try { try {
console.log(`Analyzing preview content for query: "${query}"`); console.log(`Analyzing preview content for query: "${query}"`);
console.log(`Preview content being analyzed:`, previewContents.map(content => ({ console.log(
title: content.title, `Preview content being analyzed:`,
snippet: content.snippet.substring(0, 100) + '...', previewContents.map((content) => ({
url: content.url title: content.title,
}))); snippet: content.snippet.substring(0, 100) + '...',
url: content.url,
})),
);
// Format preview content for analysis // Format preview content for analysis
const formattedPreviewContent = previewContents const formattedPreviewContent = previewContents
.map((content, index) => .map(
`Source ${index + 1}: (content, index) =>
`Source ${index + 1}:
Title: ${content.title} Title: ${content.title}
Snippet: ${content.snippet} Snippet: ${content.snippet}
URL: ${content.url} URL: ${content.url}
---` ---`,
) )
.join('\n\n'); .join('\n\n');
// Format chat history for context // Format chat history for context
const formattedChatHistory = chatHistory const formattedChatHistory = chatHistory
.slice(-10) // Only include last 10 messages for context .slice(-10) // Only include last 10 messages for context
.map((message, index) => .map((message, index) => `${message._getType()}: ${message.content}`)
`${message._getType()}: ${message.content}`
)
.join('\n'); .join('\n');
const systemPrompt = systemInstructions const systemPrompt = systemInstructions ? `${systemInstructions}\n\n` : '';
? `${systemInstructions}\n\n`
: '';
console.log(`Invoking LLM for preview content analysis`); console.log(`Invoking LLM for preview content analysis`);
const analysisResponse = await llm.invoke( 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. `${systemPrompt}You are a preview content analyzer, tasked with determining if search result snippets contain sufficient information to answer a user's query.
@ -85,9 +85,10 @@ ${formattedPreviewContent}
if (!analysisResponse || !analysisResponse.content) { if (!analysisResponse || !analysisResponse.content) {
console.error('No analysis response returned from LLM'); console.error('No analysis response returned from LLM');
return { return {
isSufficient: false, isSufficient: false,
reason: 'No analysis response returned from LLM - falling back to full content processing' reason:
'No analysis response returned from LLM - falling back to full content processing',
}; };
} }
@ -99,30 +100,36 @@ ${formattedPreviewContent}
console.log(`LLM decision response:`, decision); console.log(`LLM decision response:`, decision);
if (decision.toLowerCase().trim() === 'sufficient') { if (decision.toLowerCase().trim() === 'sufficient') {
console.log('Preview content determined to be sufficient for answering the query'); console.log(
'Preview content determined to be sufficient for answering the query',
);
return { isSufficient: true }; return { isSufficient: true };
} else if (decision.toLowerCase().startsWith('not_needed')) { } else if (decision.toLowerCase().startsWith('not_needed')) {
// Extract the reason from the "not_needed" response // Extract the reason from the "not_needed" response
const reason = decision.startsWith('not_needed') const reason = decision.startsWith('not_needed')
? decision.substring('not_needed:'.length).trim() ? decision.substring('not_needed:'.length).trim()
: 'Preview content insufficient for complete answer'; : 'Preview content insufficient for complete answer';
console.log(`Preview content determined to be insufficient. Reason: ${reason}`); console.log(
`Preview content determined to be insufficient. Reason: ${reason}`,
);
return { isSufficient: false, reason }; return { isSufficient: false, reason };
} else { } else {
// Default to not sufficient if unclear response // Default to not sufficient if unclear response
console.log(`Unclear LLM response, defaulting to insufficient: ${decision}`); console.log(
return { `Unclear LLM response, defaulting to insufficient: ${decision}`,
isSufficient: false, );
reason: 'Unclear analysis response - falling back to full content processing' return {
isSufficient: false,
reason:
'Unclear analysis response - falling back to full content processing',
}; };
} }
} catch (error) { } catch (error) {
console.error('Error analyzing preview content:', error); console.error('Error analyzing preview content:', error);
return { return {
isSufficient: false, isSufficient: false,
reason: `Error during preview analysis: ${error instanceof Error ? error.message : 'Unknown error'} - falling back to full content processing` reason: `Error during preview analysis: ${error instanceof Error ? error.message : 'Unknown error'} - falling back to full content processing`,
}; };
} }
}; };

View file

@ -68,7 +68,10 @@ ${i === 0 ? content.metadata.html : content.pageContent},
if (!summary || !summary.content) { if (!summary || !summary.content) {
console.error(`No summary content returned for URL: ${url}`); console.error(`No summary content returned for URL: ${url}`);
return { document: null, notRelevantReason: 'No summary content returned from LLM' }; return {
document: null,
notRelevantReason: 'No summary content returned from LLM',
};
} }
const summaryParser = new LineOutputParser({ key: 'summary' }); const summaryParser = new LineOutputParser({ key: 'summary' });
@ -84,18 +87,18 @@ ${i === 0 ? content.metadata.html : content.pageContent},
`LLM response for URL "${url}" indicates it's not needed or is empty:`, `LLM response for URL "${url}" indicates it's not needed or is empty:`,
summarizedContent, summarizedContent,
); );
// Extract the reason from the "not_needed" response // Extract the reason from the "not_needed" response
const reason = summarizedContent.startsWith('not_needed') const reason = summarizedContent.startsWith('not_needed')
? summarizedContent.substring('not_needed:'.length).trim() ? summarizedContent.substring('not_needed:'.length).trim()
: summarizedContent.trim().length === 0 : summarizedContent.trim().length === 0
? 'Source content was empty or could not be processed' ? 'Source content was empty or could not be processed'
: 'Source content was not relevant to the query'; : 'Source content was not relevant to the query';
return { document: null, notRelevantReason: reason }; return { document: null, notRelevantReason: reason };
} }
return { return {
document: new Document({ document: new Document({
pageContent: summarizedContent, pageContent: summarizedContent,
metadata: { metadata: {
@ -104,7 +107,7 @@ ${i === 0 ? content.metadata.html : content.pageContent},
processingType: 'full-content', processingType: 'full-content',
}, },
}), }),
notRelevantReason: undefined notRelevantReason: undefined,
}; };
}; };
@ -138,10 +141,16 @@ ${i === 0 ? content.metadata.html : content.pageContent},
return await summarizeContent(webContent); return await summarizeContent(webContent);
} else { } else {
console.log(`No valid content found for URL: ${url}`); console.log(`No valid content found for URL: ${url}`);
return { document: null, notRelevantReason: 'No valid content found at the URL' }; return {
document: null,
notRelevantReason: 'No valid content found at the URL',
};
} }
} catch (error) { } catch (error) {
console.error(`Error processing URL ${url}:`, error); console.error(`Error processing URL ${url}:`, error);
return { document: null, notRelevantReason: `Error processing URL: ${error instanceof Error ? error.message : 'Unknown error'}` }; return {
document: null,
notRelevantReason: `Error processing URL: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
} }
}; };