pack
This commit is contained in:
333
agent-system/agents/base-agent.ts
Normal file
333
agent-system/agents/base-agent.ts
Normal file
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* Base Agent Module
|
||||
*
|
||||
* Provides the foundation for creating specialized agents
|
||||
* with context management, memory, and tool integration.
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'crypto';
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import { ConversationContextManager, CompactionConfig } from '../core/context-manager';
|
||||
import { TokenCounter } from '../core/token-counter';
|
||||
|
||||
export interface AgentMemory {
|
||||
shortTerm: Map<string, unknown>;
|
||||
longTerm: Map<string, unknown>;
|
||||
conversationHistory: Array<{
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
timestamp: Date;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface AgentTool {
|
||||
name: string;
|
||||
description: string;
|
||||
execute: (params: unknown) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export interface AgentConfig {
|
||||
id?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
systemPrompt: string;
|
||||
tools?: AgentTool[];
|
||||
maxTokens?: number;
|
||||
contextConfig?: Partial<CompactionConfig>;
|
||||
}
|
||||
|
||||
export interface AgentResponse {
|
||||
content: string;
|
||||
tokens: {
|
||||
prompt: number;
|
||||
completion: number;
|
||||
total: number;
|
||||
};
|
||||
toolCalls?: Array<{
|
||||
name: string;
|
||||
params: unknown;
|
||||
result: unknown;
|
||||
}>;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* BaseAgent - Foundation class for all agents
|
||||
*/
|
||||
export abstract class BaseAgent {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
|
||||
protected systemPrompt: string;
|
||||
protected tools: Map<string, AgentTool>;
|
||||
protected memory: AgentMemory;
|
||||
protected contextManager: ConversationContextManager;
|
||||
protected tokenCounter: TokenCounter;
|
||||
protected zai: Awaited<ReturnType<typeof ZAI.create>> | null = null;
|
||||
protected initialized = false;
|
||||
|
||||
constructor(config: AgentConfig) {
|
||||
this.id = config.id || randomUUID();
|
||||
this.name = config.name;
|
||||
this.description = config.description;
|
||||
this.systemPrompt = config.systemPrompt;
|
||||
|
||||
this.tools = new Map();
|
||||
if (config.tools) {
|
||||
for (const tool of config.tools) {
|
||||
this.tools.set(tool.name, tool);
|
||||
}
|
||||
}
|
||||
|
||||
this.memory = {
|
||||
shortTerm: new Map(),
|
||||
longTerm: new Map(),
|
||||
conversationHistory: []
|
||||
};
|
||||
|
||||
this.tokenCounter = new TokenCounter(config.maxTokens);
|
||||
this.contextManager = new ConversationContextManager(config.contextConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the agent
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
this.zai = await ZAI.create();
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a user message
|
||||
*/
|
||||
async process(input: string, context?: string): Promise<AgentResponse> {
|
||||
await this.initialize();
|
||||
|
||||
// Add user message to context
|
||||
const userMessage = {
|
||||
role: 'user' as const,
|
||||
content: context ? `Context: ${context}\n\n${input}` : input,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
this.memory.conversationHistory.push(userMessage);
|
||||
|
||||
// Check if context compaction is needed
|
||||
await this.contextManager.getMessages();
|
||||
|
||||
// Build messages for LLM
|
||||
const messages = this.buildMessages();
|
||||
|
||||
// Get response from LLM
|
||||
const response = await this.zai!.chat.completions.create({
|
||||
messages,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const assistantContent = response.choices?.[0]?.message?.content || '';
|
||||
|
||||
// Add assistant response to history
|
||||
this.memory.conversationHistory.push({
|
||||
role: 'assistant',
|
||||
content: assistantContent,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
// Process any tool calls (if agent supports them)
|
||||
const toolCalls = await this.processToolCalls(assistantContent);
|
||||
|
||||
return {
|
||||
content: assistantContent,
|
||||
tokens: {
|
||||
prompt: 0, // Would need actual token counting
|
||||
completion: 0,
|
||||
total: 0
|
||||
},
|
||||
toolCalls,
|
||||
metadata: {
|
||||
conversationLength: this.memory.conversationHistory.length
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build messages array for LLM
|
||||
*/
|
||||
protected buildMessages(): Array<{ role: 'user' | 'assistant' | 'system'; content: string }> {
|
||||
const messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string }> = [];
|
||||
|
||||
// System prompt with tool descriptions
|
||||
let systemContent = this.systemPrompt;
|
||||
if (this.tools.size > 0) {
|
||||
const toolDescriptions = Array.from(this.tools.values())
|
||||
.map(t => `- ${t.name}: ${t.description}`)
|
||||
.join('\n');
|
||||
systemContent += `\n\nAvailable tools:\n${toolDescriptions}`;
|
||||
systemContent += `\n\nTo use a tool, include [TOOL:name]params[/TOOL] in your response.`;
|
||||
}
|
||||
|
||||
messages.push({ role: 'assistant', content: systemContent });
|
||||
|
||||
// Add conversation history
|
||||
for (const msg of this.memory.conversationHistory) {
|
||||
messages.push({
|
||||
role: msg.role,
|
||||
content: msg.content
|
||||
});
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process tool calls in the response
|
||||
*/
|
||||
protected async processToolCalls(content: string): Promise<Array<{
|
||||
name: string;
|
||||
params: unknown;
|
||||
result: unknown;
|
||||
}>> {
|
||||
const toolCalls: Array<{ name: string; params: unknown; result: unknown }> = [];
|
||||
|
||||
// Extract tool calls from content
|
||||
const toolRegex = /\[TOOL:(\w+)\]([\s\S]*?)\[\/TOOL\]/g;
|
||||
let match;
|
||||
|
||||
while ((match = toolRegex.exec(content)) !== null) {
|
||||
const toolName = match[1];
|
||||
const paramsStr = match[2].trim();
|
||||
|
||||
const tool = this.tools.get(toolName);
|
||||
if (tool) {
|
||||
try {
|
||||
let params = paramsStr;
|
||||
try {
|
||||
params = JSON.parse(paramsStr);
|
||||
} catch {
|
||||
// Keep as string if not valid JSON
|
||||
}
|
||||
|
||||
const result = await tool.execute(params);
|
||||
toolCalls.push({ name: toolName, params, result });
|
||||
} catch (error) {
|
||||
toolCalls.push({
|
||||
name: toolName,
|
||||
params: paramsStr,
|
||||
result: { error: String(error) }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toolCalls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tool to the agent
|
||||
*/
|
||||
addTool(tool: AgentTool): void {
|
||||
this.tools.set(tool.name, tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a tool from the agent
|
||||
*/
|
||||
removeTool(name: string): boolean {
|
||||
return this.tools.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a value in short-term memory
|
||||
*/
|
||||
remember(key: string, value: unknown): void {
|
||||
this.memory.shortTerm.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from memory
|
||||
*/
|
||||
recall(key: string): unknown | undefined {
|
||||
return this.memory.shortTerm.get(key) || this.memory.longTerm.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a value in long-term memory
|
||||
*/
|
||||
memorize(key: string, value: unknown): void {
|
||||
this.memory.longTerm.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear short-term memory
|
||||
*/
|
||||
forget(): void {
|
||||
this.memory.shortTerm.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear conversation history
|
||||
*/
|
||||
clearHistory(): void {
|
||||
this.memory.conversationHistory = [];
|
||||
this.contextManager.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation summary
|
||||
*/
|
||||
getSummary(): string {
|
||||
const messages = this.memory.conversationHistory;
|
||||
return messages.map(m => `[${m.role}]: ${m.content.substring(0, 100)}...`).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent stats
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
messageCount: this.memory.conversationHistory.length,
|
||||
toolCount: this.tools.size,
|
||||
memoryItems: this.memory.shortTerm.size + this.memory.longTerm.size,
|
||||
contextStats: this.contextManager.getStats()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method for agent-specific behavior
|
||||
*/
|
||||
abstract act(input: string, context?: string): Promise<AgentResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleAgent - A basic agent implementation
|
||||
*/
|
||||
export class SimpleAgent extends BaseAgent {
|
||||
async act(input: string, context?: string): Promise<AgentResponse> {
|
||||
return this.process(input, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple agent with custom system prompt
|
||||
*/
|
||||
export function createAgent(
|
||||
name: string,
|
||||
systemPrompt: string,
|
||||
options?: {
|
||||
description?: string;
|
||||
tools?: AgentTool[];
|
||||
maxTokens?: number;
|
||||
}
|
||||
): SimpleAgent {
|
||||
return new SimpleAgent({
|
||||
name,
|
||||
systemPrompt,
|
||||
description: options?.description || `Agent: ${name}`,
|
||||
tools: options?.tools,
|
||||
maxTokens: options?.maxTokens
|
||||
});
|
||||
}
|
||||
232
agent-system/agents/task-agent.ts
Normal file
232
agent-system/agents/task-agent.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Task Agent Module
|
||||
*
|
||||
* Specialized agent for executing structured tasks with
|
||||
* planning, execution, and verification phases.
|
||||
*/
|
||||
|
||||
import { BaseAgent, AgentConfig, AgentResponse, AgentTool } from './base-agent';
|
||||
|
||||
export interface TaskStep {
|
||||
id: string;
|
||||
description: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface TaskPlan {
|
||||
steps: TaskStep[];
|
||||
estimatedComplexity: 'low' | 'medium' | 'high';
|
||||
dependencies: Map<string, string[]>;
|
||||
}
|
||||
|
||||
export interface TaskResult {
|
||||
success: boolean;
|
||||
steps: TaskStep[];
|
||||
output: unknown;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskAgent - Agent specialized for structured task execution
|
||||
*/
|
||||
export class TaskAgent extends BaseAgent {
|
||||
private currentPlan: TaskPlan | null = null;
|
||||
private taskHistory: TaskResult[] = [];
|
||||
|
||||
constructor(config: AgentConfig) {
|
||||
super(config);
|
||||
|
||||
// Add default tools for task agents
|
||||
this.addTool({
|
||||
name: 'plan',
|
||||
description: 'Create a plan for a complex task',
|
||||
execute: async (params) => {
|
||||
const task = params as { description: string };
|
||||
return this.createPlan(task.description);
|
||||
}
|
||||
});
|
||||
|
||||
this.addTool({
|
||||
name: 'execute_step',
|
||||
description: 'Execute a single step of the plan',
|
||||
execute: async (params) => {
|
||||
const step = params as { stepId: string };
|
||||
return this.executeStep(step.stepId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a task with planning
|
||||
*/
|
||||
async act(input: string, context?: string): Promise<AgentResponse> {
|
||||
// First, create a plan
|
||||
this.currentPlan = await this.createPlan(input);
|
||||
|
||||
// Execute the plan
|
||||
const result = await this.executePlan();
|
||||
|
||||
this.taskHistory.push(result);
|
||||
|
||||
return {
|
||||
content: result.success
|
||||
? `Task completed successfully.\n${JSON.stringify(result.output, null, 2)}`
|
||||
: `Task failed. Errors: ${result.errors.join(', ')}`,
|
||||
tokens: { prompt: 0, completion: 0, total: 0 },
|
||||
metadata: {
|
||||
plan: this.currentPlan,
|
||||
result
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plan for a task
|
||||
*/
|
||||
private async createPlan(taskDescription: string): Promise<TaskPlan> {
|
||||
const planningPrompt = `Break down the following task into steps. For each step, provide a brief description.
|
||||
|
||||
Task: ${taskDescription}
|
||||
|
||||
Respond in JSON format:
|
||||
{
|
||||
"steps": [
|
||||
{ "id": "step1", "description": "First step description" },
|
||||
{ "id": "step2", "description": "Second step description" }
|
||||
],
|
||||
"complexity": "low|medium|high",
|
||||
"dependencies": {
|
||||
"step2": ["step1"]
|
||||
}
|
||||
}`;
|
||||
|
||||
const response = await this.process(planningPrompt);
|
||||
|
||||
try {
|
||||
// Extract JSON from response
|
||||
const jsonMatch = response.content.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
const plan = JSON.parse(jsonMatch[0]);
|
||||
return {
|
||||
steps: plan.steps.map((s: TaskStep) => ({ ...s, status: 'pending' as const })),
|
||||
estimatedComplexity: plan.complexity || 'medium',
|
||||
dependencies: new Map(Object.entries(plan.dependencies || {}))
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// Fall back to simple plan
|
||||
}
|
||||
|
||||
// Default simple plan
|
||||
return {
|
||||
steps: [{ id: 'step1', description: taskDescription, status: 'pending' }],
|
||||
estimatedComplexity: 'low',
|
||||
dependencies: new Map()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the current plan
|
||||
*/
|
||||
private async executePlan(): Promise<TaskResult> {
|
||||
if (!this.currentPlan) {
|
||||
return {
|
||||
success: false,
|
||||
steps: [],
|
||||
output: null,
|
||||
errors: ['No plan available']
|
||||
};
|
||||
}
|
||||
|
||||
const errors: string[] = [];
|
||||
const completedSteps = new Set<string>();
|
||||
|
||||
// Execute steps in order, respecting dependencies
|
||||
for (const step of this.currentPlan.steps) {
|
||||
// Check dependencies
|
||||
const deps = this.currentPlan.dependencies.get(step.id) || [];
|
||||
const depsMet = deps.every(depId => completedSteps.has(depId));
|
||||
|
||||
if (!depsMet) {
|
||||
step.status = 'failed';
|
||||
step.error = 'Dependencies not met';
|
||||
errors.push(`Step ${step.id}: Dependencies not met`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Execute step
|
||||
step.status = 'running';
|
||||
try {
|
||||
const result = await this.executeStep(step.id);
|
||||
step.status = 'completed';
|
||||
step.result = result;
|
||||
completedSteps.add(step.id);
|
||||
} catch (error) {
|
||||
step.status = 'failed';
|
||||
step.error = String(error);
|
||||
errors.push(`Step ${step.id}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
const success = errors.length === 0;
|
||||
const finalStep = this.currentPlan.steps[this.currentPlan.steps.length - 1];
|
||||
|
||||
return {
|
||||
success,
|
||||
steps: this.currentPlan.steps,
|
||||
output: finalStep.result,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single step
|
||||
*/
|
||||
private async executeStep(stepId: string): Promise<unknown> {
|
||||
if (!this.currentPlan) throw new Error('No plan available');
|
||||
|
||||
const step = this.currentPlan.steps.find(s => s.id === stepId);
|
||||
if (!step) throw new Error(`Step ${stepId} not found`);
|
||||
|
||||
const response = await this.process(
|
||||
`Execute the following step and provide the result:\n\n${step.description}`
|
||||
);
|
||||
|
||||
return response.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task history
|
||||
*/
|
||||
getTaskHistory(): TaskResult[] {
|
||||
return [...this.taskHistory];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current plan
|
||||
*/
|
||||
getCurrentPlan(): TaskPlan | null {
|
||||
return this.currentPlan;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a task agent
|
||||
*/
|
||||
export function createTaskAgent(
|
||||
name: string,
|
||||
systemPrompt: string,
|
||||
options?: {
|
||||
description?: string;
|
||||
tools?: AgentTool[];
|
||||
}
|
||||
): TaskAgent {
|
||||
return new TaskAgent({
|
||||
name,
|
||||
systemPrompt,
|
||||
description: options?.description || `Task Agent: ${name}`,
|
||||
tools: options?.tools
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user