feat(agent): Add task manager. Agent feels pretty useful now?
This commit is contained in:
parent
60d36ab8f4
commit
7b127e5635
14 changed files with 634 additions and 55 deletions
187
src/lib/agents/taskManagerAgent.ts
Normal file
187
src/lib/agents/taskManagerAgent.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { AIMessage } from '@langchain/core/messages';
|
||||
import { PromptTemplate } from '@langchain/core/prompts';
|
||||
import { Command } from '@langchain/langgraph';
|
||||
import { EventEmitter } from 'events';
|
||||
import { taskBreakdownPrompt } from '../prompts/taskBreakdown';
|
||||
import { AgentState } from './agentState';
|
||||
import { setTemperature } from '../utils/modelUtils';
|
||||
|
||||
export class TaskManagerAgent {
|
||||
private llm: BaseChatModel;
|
||||
private emitter: EventEmitter;
|
||||
private systemInstructions: string;
|
||||
private signal: AbortSignal;
|
||||
|
||||
constructor(
|
||||
llm: BaseChatModel,
|
||||
emitter: EventEmitter,
|
||||
systemInstructions: string,
|
||||
signal: AbortSignal,
|
||||
) {
|
||||
this.llm = llm;
|
||||
this.emitter = emitter;
|
||||
this.systemInstructions = systemInstructions;
|
||||
this.signal = signal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task manager agent node - breaks down complex questions into smaller tasks
|
||||
*/
|
||||
async execute(state: typeof AgentState.State): Promise<Command> {
|
||||
try {
|
||||
setTemperature(this.llm, 0); // Set temperature to 0 for deterministic output
|
||||
|
||||
// Check if we're in task progression mode (tasks already exist and we're processing them)
|
||||
if (state.tasks && state.tasks.length > 0) {
|
||||
const currentTaskIndex = state.currentTaskIndex || 0;
|
||||
const hasMoreTasks = currentTaskIndex < state.tasks.length - 1;
|
||||
|
||||
if (hasMoreTasks) {
|
||||
// Move to next task
|
||||
const nextTaskIndex = currentTaskIndex + 1;
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'PROCEEDING_TO_NEXT_TASK',
|
||||
message: `Task ${currentTaskIndex + 1} completed. Moving to task ${nextTaskIndex + 1} of ${state.tasks.length}.`,
|
||||
details: {
|
||||
completedTask: state.tasks[currentTaskIndex],
|
||||
nextTask: state.tasks[nextTaskIndex],
|
||||
taskIndex: nextTaskIndex + 1,
|
||||
totalTasks: state.tasks.length,
|
||||
documentCount: state.relevantDocuments.length,
|
||||
query: state.originalQuery || state.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return new Command({
|
||||
goto: 'web_search',
|
||||
update: {
|
||||
messages: [
|
||||
new AIMessage(
|
||||
`Task ${currentTaskIndex + 1} completed. Processing task ${nextTaskIndex + 1} of ${state.tasks.length}: "${state.tasks[nextTaskIndex]}"`,
|
||||
),
|
||||
],
|
||||
currentTaskIndex: nextTaskIndex,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// All tasks completed, move to analysis
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'ALL_TASKS_COMPLETED',
|
||||
message: `All ${state.tasks.length} tasks completed. Ready for analysis.`,
|
||||
details: {
|
||||
totalTasks: state.tasks.length,
|
||||
documentCount: state.relevantDocuments.length,
|
||||
query: state.originalQuery || state.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return new Command({
|
||||
goto: 'analyzer',
|
||||
update: {
|
||||
messages: [
|
||||
new AIMessage(
|
||||
`All ${state.tasks.length} tasks completed. Moving to analysis phase.`,
|
||||
),
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Original task breakdown logic for new queries
|
||||
// Emit task analysis event
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'ANALYZING_TASK_COMPLEXITY',
|
||||
message: `Analyzing question to determine if it needs to be broken down into smaller tasks`,
|
||||
details: {
|
||||
query: state.query,
|
||||
currentTasks: state.tasks?.length || 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const template = PromptTemplate.fromTemplate(taskBreakdownPrompt);
|
||||
const prompt = await template.format({
|
||||
systemInstructions: this.systemInstructions,
|
||||
query: state.query,
|
||||
});
|
||||
|
||||
const taskBreakdownResult = await this.llm.invoke(
|
||||
[prompt],
|
||||
{ signal: this.signal },
|
||||
);
|
||||
|
||||
// Parse the response to extract tasks
|
||||
const responseContent = taskBreakdownResult.content as string;
|
||||
const taskLines = responseContent
|
||||
.split('\n')
|
||||
.filter(line => line.trim().startsWith('TASK:'))
|
||||
.map(line => line.replace('TASK:', '').trim())
|
||||
.filter(task => task.length > 0);
|
||||
|
||||
if (taskLines.length === 0) {
|
||||
// Fallback: if no tasks found, use the original query
|
||||
taskLines.push(state.query);
|
||||
}
|
||||
|
||||
console.log(`Task breakdown completed: ${taskLines.length} tasks identified`);
|
||||
taskLines.forEach((task, index) => {
|
||||
console.log(`Task ${index + 1}: ${task}`);
|
||||
});
|
||||
|
||||
// Emit task breakdown completion event
|
||||
this.emitter.emit('agent_action', {
|
||||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'TASK_BREAKDOWN_COMPLETED',
|
||||
message: `Question broken down into ${taskLines.length} focused ${taskLines.length === 1 ? 'task' : 'tasks'}`,
|
||||
details: {
|
||||
query: state.query,
|
||||
taskCount: taskLines.length,
|
||||
tasks: taskLines,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const responseMessage = taskLines.length === 1
|
||||
? 'Question is already focused and ready for processing'
|
||||
: `Question broken down into ${taskLines.length} focused tasks for parallel processing`;
|
||||
|
||||
return new Command({
|
||||
goto: 'web_search', // Next step would typically be web search for each task
|
||||
update: {
|
||||
messages: [new AIMessage(responseMessage)],
|
||||
tasks: taskLines,
|
||||
currentTaskIndex: 0,
|
||||
originalQuery: state.originalQuery || state.query, // Preserve original if not already set
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Task breakdown error:', error);
|
||||
const errorMessage = new AIMessage(
|
||||
`Task breakdown failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
);
|
||||
|
||||
return new Command({
|
||||
goto: 'web_search', // Fallback to web search with original query
|
||||
update: {
|
||||
messages: [errorMessage],
|
||||
tasks: [state.query], // Use original query as single task
|
||||
currentTaskIndex: 0,
|
||||
originalQuery: state.originalQuery || state.query, // Preserve original if not already set
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
setTemperature(this.llm, undefined); // Reset temperature to default
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue