2025-06-09 23:00:25 -06:00
import { Embeddings } from '@langchain/core/embeddings' ;
import { BaseChatModel } from '@langchain/core/language_models/chat_models' ;
import {
BaseMessage ,
HumanMessage ,
SystemMessage ,
} from '@langchain/core/messages' ;
2025-06-19 12:49:37 -06:00
import {
BaseLangGraphError ,
END ,
2025-07-13 13:20:16 -06:00
GraphRecursionError ,
2025-06-19 12:49:37 -06:00
MemorySaver ,
START ,
StateGraph ,
} from '@langchain/langgraph' ;
2025-06-09 23:00:25 -06:00
import { EventEmitter } from 'events' ;
2025-06-17 00:20:05 -06:00
import {
2025-06-15 11:53:00 -06:00
AgentState ,
WebSearchAgent ,
AnalyzerAgent ,
2025-06-17 00:20:05 -06:00
SynthesizerAgent ,
2025-06-21 16:12:19 -06:00
TaskManagerAgent ,
2025-06-28 14:48:08 -06:00
FileSearchAgent ,
ContentRouterAgent ,
2025-06-29 13:59:52 -06:00
URLSummarizationAgent ,
2025-06-15 11:53:00 -06:00
} from '../agents' ;
2025-06-09 23:00:25 -06:00
/ * *
* Agent Search class implementing LangGraph Supervisor pattern
* /
export class AgentSearch {
private llm : BaseChatModel ;
private embeddings : Embeddings ;
private checkpointer : MemorySaver ;
private signal : AbortSignal ;
2025-06-21 16:12:19 -06:00
private taskManagerAgent : TaskManagerAgent ;
2025-06-15 11:53:00 -06:00
private webSearchAgent : WebSearchAgent ;
private analyzerAgent : AnalyzerAgent ;
private synthesizerAgent : SynthesizerAgent ;
2025-06-28 14:48:08 -06:00
private fileSearchAgent : FileSearchAgent ;
private contentRouterAgent : ContentRouterAgent ;
2025-06-29 13:59:52 -06:00
private urlSummarizationAgent : URLSummarizationAgent ;
2025-06-19 12:49:37 -06:00
private emitter : EventEmitter ;
2025-06-28 14:48:08 -06:00
private focusMode : string ;
2025-06-09 23:00:25 -06:00
constructor (
llm : BaseChatModel ,
embeddings : Embeddings ,
emitter : EventEmitter ,
systemInstructions : string = '' ,
personaInstructions : string = '' ,
signal : AbortSignal ,
2025-06-28 14:48:08 -06:00
focusMode : string = 'webSearch' ,
2025-06-09 23:00:25 -06:00
) {
this . llm = llm ;
this . embeddings = embeddings ;
this . checkpointer = new MemorySaver ( ) ;
this . signal = signal ;
2025-06-19 12:49:37 -06:00
this . emitter = emitter ;
2025-06-28 14:48:08 -06:00
this . focusMode = focusMode ;
2025-06-09 23:00:25 -06:00
2025-06-15 11:53:00 -06:00
// Initialize agents
2025-06-21 16:12:19 -06:00
this . taskManagerAgent = new TaskManagerAgent (
llm ,
emitter ,
systemInstructions ,
signal ,
) ;
2025-06-15 11:53:00 -06:00
this . webSearchAgent = new WebSearchAgent (
llm ,
emitter ,
systemInstructions ,
2025-06-17 00:20:05 -06:00
signal ,
2025-06-22 13:35:01 -06:00
embeddings ,
2025-06-09 23:00:25 -06:00
) ;
2025-06-15 11:53:00 -06:00
this . analyzerAgent = new AnalyzerAgent (
llm ,
emitter ,
systemInstructions ,
2025-06-17 00:20:05 -06:00
signal ,
2025-06-15 11:53:00 -06:00
) ;
this . synthesizerAgent = new SynthesizerAgent (
llm ,
emitter ,
personaInstructions ,
2025-06-17 00:20:05 -06:00
signal ,
2025-06-09 23:00:25 -06:00
) ;
2025-06-28 14:48:08 -06:00
this . fileSearchAgent = new FileSearchAgent (
llm ,
emitter ,
systemInstructions ,
signal ,
embeddings ,
) ;
this . contentRouterAgent = new ContentRouterAgent (
llm ,
emitter ,
systemInstructions ,
signal ,
) ;
2025-06-29 13:59:52 -06:00
this . urlSummarizationAgent = new URLSummarizationAgent (
llm ,
emitter ,
systemInstructions ,
signal ,
) ;
2025-06-09 23:00:25 -06:00
}
/ * *
* Create and compile the agent workflow graph
* /
private createWorkflow() {
const workflow = new StateGraph ( AgentState )
2025-06-29 13:59:52 -06:00
. addNode (
'url_summarization' ,
this . urlSummarizationAgent . execute . bind ( this . urlSummarizationAgent ) ,
{
ends : [ 'task_manager' , 'analyzer' ] ,
} ,
)
2025-06-21 16:12:19 -06:00
. addNode (
'task_manager' ,
this . taskManagerAgent . execute . bind ( this . taskManagerAgent ) ,
{
2025-06-28 14:48:08 -06:00
ends : [ 'content_router' , 'analyzer' ] ,
} ,
)
. addNode (
'content_router' ,
this . contentRouterAgent . execute . bind ( this . contentRouterAgent ) ,
{
ends : [ 'file_search' , 'web_search' , 'analyzer' ] ,
} ,
)
. addNode (
'file_search' ,
this . fileSearchAgent . execute . bind ( this . fileSearchAgent ) ,
{
ends : [ 'analyzer' ] ,
2025-06-21 16:12:19 -06:00
} ,
)
2025-06-17 00:20:05 -06:00
. addNode (
'web_search' ,
this . webSearchAgent . execute . bind ( this . webSearchAgent ) ,
{
2025-06-28 14:48:08 -06:00
ends : [ 'analyzer' ] ,
2025-06-17 00:20:05 -06:00
} ,
)
. addNode (
'analyzer' ,
this . analyzerAgent . execute . bind ( this . analyzerAgent ) ,
{
2025-06-29 13:59:52 -06:00
ends : [ 'url_summarization' , 'task_manager' , 'synthesizer' ] ,
2025-06-17 00:20:05 -06:00
} ,
)
. addNode (
'synthesizer' ,
this . synthesizerAgent . execute . bind ( this . synthesizerAgent ) ,
{
ends : [ END ] ,
} ,
)
2025-06-09 23:00:25 -06:00
. addEdge ( START , 'analyzer' ) ;
return workflow . compile ( { checkpointer : this.checkpointer } ) ;
}
/ * *
* Execute the agent search workflow
* /
2025-06-28 14:48:08 -06:00
async searchAndAnswer (
2025-06-28 17:59:12 -06:00
query : string ,
history : BaseMessage [ ] = [ ] ,
fileIds : string [ ] = [ ] ,
2025-06-28 14:48:08 -06:00
) {
2025-06-09 23:00:25 -06:00
const workflow = this . createWorkflow ( ) ;
2025-06-19 12:49:37 -06:00
const initialState = {
messages : [ . . . history , new HumanMessage ( query ) ] ,
query ,
2025-06-28 14:48:08 -06:00
fileIds ,
focusMode : this.focusMode ,
2025-06-19 12:49:37 -06:00
} ;
2025-06-09 23:00:25 -06:00
2025-07-13 13:20:16 -06:00
const threadId = ` agent_search_ ${ Date . now ( ) } ` ;
const config = {
configurable : { thread_id : threadId } ,
recursionLimit : 18 ,
signal : this.signal ,
} ;
2025-06-19 12:49:37 -06:00
try {
2025-07-13 13:20:16 -06:00
const result = await workflow . invoke ( initialState , config ) ;
} catch ( error : any ) {
if ( error instanceof GraphRecursionError ) {
console . warn (
'Graph recursion limit reached, attempting best-effort synthesis with gathered information' ,
) ;
// Emit agent action to explain what happened
this . emitter . emit (
'data' ,
JSON . stringify ( {
type : 'agent_action' ,
data : {
action : 'recursion_limit_recovery' ,
message :
'Search process reached complexity limits. Attempting to provide best-effort response with gathered information.' ,
details :
'The agent workflow exceeded the maximum number of steps allowed. Recovering by synthesizing available data.' ,
} ,
} ) ,
) ;
try {
// Get the latest state from the checkpointer to access gathered information
const latestState = await workflow . getState ( {
configurable : { thread_id : threadId } ,
} ) ;
if ( latestState && latestState . values ) {
// Create emergency synthesis state using gathered information
const stateValues = latestState . values ;
const emergencyState = {
messages : stateValues.messages || initialState . messages ,
query : stateValues.query || initialState . query ,
relevantDocuments : stateValues.relevantDocuments || [ ] ,
bannedSummaryUrls : stateValues.bannedSummaryUrls || [ ] ,
bannedPreviewUrls : stateValues.bannedPreviewUrls || [ ] ,
searchInstructionHistory :
stateValues . searchInstructionHistory || [ ] ,
searchInstructions : stateValues.searchInstructions || '' ,
next : 'synthesizer' ,
analysis : stateValues.analysis || '' ,
fullAnalysisAttempts : stateValues.fullAnalysisAttempts || 0 ,
tasks : stateValues.tasks || [ ] ,
currentTaskIndex : stateValues.currentTaskIndex || 0 ,
originalQuery :
stateValues . originalQuery ||
stateValues . query ||
initialState . query ,
fileIds : stateValues.fileIds || initialState . fileIds ,
focusMode : stateValues.focusMode || initialState . focusMode ,
urlsToSummarize : stateValues.urlsToSummarize || [ ] ,
summarizationIntent : stateValues.summarizationIntent || '' ,
recursionLimitReached : true ,
} ;
const documentsCount =
emergencyState . relevantDocuments ? . length || 0 ;
console . log (
` Attempting emergency synthesis with ${ documentsCount } gathered documents ` ,
) ;
// Emit detailed agent action about the recovery attempt
this . emitter . emit (
'data' ,
JSON . stringify ( {
type : 'agent_action' ,
data : {
action : 'emergency_synthesis' ,
message : ` Proceeding with available information: ${ documentsCount } documents gathered ${ emergencyState . analysis ? ', analysis available' : '' } ` ,
details : ` Recovered state contains: ${ documentsCount } relevant documents, ${ emergencyState . searchInstructionHistory ? . length || 0 } search attempts, ${ emergencyState . analysis ? 'analysis data' : 'no analysis' } ` ,
} ,
} ) ,
) ;
// Only proceed with synthesis if we have some useful information
if ( documentsCount > 0 || emergencyState . analysis ) {
await this . synthesizerAgent . execute ( emergencyState ) ;
} else {
// If we don't have any gathered information, provide a helpful message
this . emitter . emit (
'data' ,
JSON . stringify ( {
type : 'response' ,
data : "⚠️ **Search Process Incomplete** - The search process reached complexity limits before gathering sufficient information to provide a meaningful response. Please try:\n\n- Using more specific keywords\n- Breaking your question into smaller parts\n- Rephrasing your query to be more focused\n\nI apologize that I couldn't provide the information you were looking for." ,
} ) ,
) ;
this . emitter . emit ( 'end' ) ;
}
} else {
// Fallback if we can't retrieve state
this . emitter . emit (
'data' ,
JSON . stringify ( {
type : 'response' ,
data : '⚠️ **Limited Information Available** - The search process encountered complexity limits and was unable to gather sufficient information. Please try rephrasing your question or breaking it into smaller, more specific parts.' ,
} ) ,
) ;
this . emitter . emit ( 'end' ) ;
}
} catch ( synthError ) {
console . error ( 'Emergency synthesis failed:' , synthError ) ;
2025-06-19 12:49:37 -06:00
this . emitter . emit (
'data' ,
JSON . stringify ( {
type : 'response' ,
2025-07-13 13:20:16 -06:00
data : '⚠️ **Search Process Interrupted** - The search encountered complexity limits and could not complete successfully. Please try a simpler query or break your question into smaller parts.' ,
2025-06-19 12:49:37 -06:00
} ) ,
) ;
this . emitter . emit ( 'end' ) ;
}
} else if ( error . name === 'AbortError' ) {
console . warn ( 'Agent search was aborted:' , error . message ) ;
} else {
console . error ( 'Unexpected error during agent search:' , error ) ;
}
2025-06-09 23:00:25 -06:00
}
}
}