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';
|
|
|
|
|
import {
|
|
|
|
|
END,
|
|
|
|
|
MemorySaver,
|
|
|
|
|
START,
|
|
|
|
|
StateGraph,
|
|
|
|
|
} from '@langchain/langgraph';
|
|
|
|
|
import { EventEmitter } from 'events';
|
2025-06-15 11:53:00 -06:00
|
|
|
import {
|
|
|
|
|
AgentState,
|
|
|
|
|
WebSearchAgent,
|
|
|
|
|
AnalyzerAgent,
|
|
|
|
|
SynthesizerAgent
|
|
|
|
|
} 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-15 11:53:00 -06:00
|
|
|
private webSearchAgent: WebSearchAgent;
|
|
|
|
|
private analyzerAgent: AnalyzerAgent;
|
|
|
|
|
private synthesizerAgent: SynthesizerAgent;
|
2025-06-09 23:00:25 -06:00
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
llm: BaseChatModel,
|
|
|
|
|
embeddings: Embeddings,
|
|
|
|
|
emitter: EventEmitter,
|
|
|
|
|
systemInstructions: string = '',
|
|
|
|
|
personaInstructions: string = '',
|
|
|
|
|
signal: AbortSignal,
|
|
|
|
|
) {
|
|
|
|
|
this.llm = llm;
|
|
|
|
|
this.embeddings = embeddings;
|
|
|
|
|
this.checkpointer = new MemorySaver();
|
|
|
|
|
this.signal = signal;
|
|
|
|
|
|
2025-06-15 11:53:00 -06:00
|
|
|
// Initialize agents
|
|
|
|
|
this.webSearchAgent = new WebSearchAgent(
|
|
|
|
|
llm,
|
|
|
|
|
emitter,
|
|
|
|
|
systemInstructions,
|
|
|
|
|
signal
|
2025-06-09 23:00:25 -06:00
|
|
|
);
|
2025-06-15 11:53:00 -06:00
|
|
|
this.analyzerAgent = new AnalyzerAgent(
|
|
|
|
|
llm,
|
|
|
|
|
emitter,
|
|
|
|
|
systemInstructions,
|
|
|
|
|
signal
|
|
|
|
|
);
|
|
|
|
|
this.synthesizerAgent = new SynthesizerAgent(
|
|
|
|
|
llm,
|
|
|
|
|
emitter,
|
|
|
|
|
personaInstructions,
|
|
|
|
|
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-15 11:53:00 -06:00
|
|
|
.addNode('web_search', this.webSearchAgent.execute.bind(this.webSearchAgent), {
|
2025-06-09 23:00:25 -06:00
|
|
|
ends: ['analyzer'],
|
|
|
|
|
})
|
2025-06-15 11:53:00 -06:00
|
|
|
.addNode('analyzer', this.analyzerAgent.execute.bind(this.analyzerAgent), {
|
2025-06-09 23:00:25 -06:00
|
|
|
ends: ['web_search', 'synthesizer'],
|
|
|
|
|
})
|
2025-06-15 11:53:00 -06:00
|
|
|
.addNode('synthesizer', this.synthesizerAgent.execute.bind(this.synthesizerAgent), {
|
2025-06-09 23:00:25 -06:00
|
|
|
ends: [END],
|
|
|
|
|
})
|
|
|
|
|
.addEdge(START, 'analyzer');
|
|
|
|
|
|
|
|
|
|
return workflow.compile({ checkpointer: this.checkpointer });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Execute the agent search workflow
|
|
|
|
|
*/
|
|
|
|
|
async searchAndAnswer(query: string, history: BaseMessage[] = []) {
|
|
|
|
|
const workflow = this.createWorkflow();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const initialState = {
|
|
|
|
|
messages: [...history, new HumanMessage(query)],
|
|
|
|
|
query,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await workflow.invoke(initialState, {
|
|
|
|
|
configurable: { thread_id: `agent_search_${Date.now()}` },
|
2025-06-10 00:12:45 -06:00
|
|
|
recursionLimit: 20,
|
2025-06-17 00:14:22 -06:00
|
|
|
signal: this.signal,
|
2025-06-09 23:00:25 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Agent workflow error:', error);
|
|
|
|
|
|
|
|
|
|
// Fallback to a simple response
|
|
|
|
|
const fallbackResponse = await this.llm.invoke(
|
|
|
|
|
[
|
|
|
|
|
new SystemMessage(
|
|
|
|
|
"You are a helpful assistant. The advanced agent workflow failed, so please provide a basic response to the user's query based on your knowledge.",
|
|
|
|
|
),
|
|
|
|
|
new HumanMessage(query),
|
|
|
|
|
],
|
|
|
|
|
{ signal: this.signal },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
messages: [...history, new HumanMessage(query), fallbackResponse],
|
|
|
|
|
query,
|
|
|
|
|
searchResults: [],
|
|
|
|
|
next: END,
|
|
|
|
|
analysis: '',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|