UNPKG

@nomyx/assistant

Version:

A powerful assistant library and cli for your AI projects. works with Vertex AI (Claude and Gemini)

226 lines (225 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskExecutor = exports.TaskExecutionError = void 0; const MessageCollationMiddleware_1 = require("./middleware/MessageCollationMiddleware"); const LoggingMiddleware_1 = require("./middleware/LoggingMiddleware"); class TaskExecutionError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'TaskExecutionError'; } } exports.TaskExecutionError = TaskExecutionError; class TaskExecutor { constructor(provider, state, history, context, tools, prompts, logger) { this.provider = provider; this.state = state; this.history = history; this.context = context; this.tools = tools; this.prompts = prompts; this.logger = logger; // Apply middlewares this.provider.use(new MessageCollationMiddleware_1.MessageCollationMiddleware()); this.provider.use(new LoggingMiddleware_1.LoggingMiddleware(this.logger)); this.logPrompts(); // Log prompts immediately after construction } async execute(input, promptName) { try { const selectedPrompt = promptName ? this.prompts[promptName] : this.selectPrompt(input); const messages = this.prepareMessages(input, selectedPrompt); const toolsForPrompt = this.prepareTools(selectedPrompt); this.logger.debug('Executing task with input', { input, selectedPrompt: selectedPrompt.name, toolsForPrompt: toolsForPrompt.map(t => t.name) }); const response = await this.provider.chat(messages, this.getChatOptions(selectedPrompt), toolsForPrompt); this.logger.debug('Received response from provider', { content: response.content, toolCalls: response.toolCalls }); if (response.toolCalls && response.toolCalls.length > 0) { const toolResults = await this.executeToolCalls(response.toolCalls); const finalResponse = await this.processFinalResponse(input, toolResults); return finalResponse; } return response.content; } catch (error) { this.logger.error('Error executing task', { error, input }); throw new TaskExecutionError('Failed to execute task', error); } } async executeToolCalls(toolCalls) { if (!toolCalls) return []; const toolResponses = []; for (const toolCall of toolCalls) { const tool = this.tools[toolCall.name]; if (tool) { this.logger.debug('Executing tool', { toolName: toolCall.name, arguments: toolCall.arguments }); try { const toolResult = await tool.execute(toolCall.arguments, {}); toolResponses.push(`Tool ${toolCall.name} result: ${JSON.stringify(toolResult)}`); this.logger.debug('Tool execution result', { toolName: toolCall.name, result: toolResult }); } catch (error) { this.logger.error('Error executing tool', { toolName: toolCall.name, error }); toolResponses.push(`Error executing ${toolCall.name}: ${error}`); } } else { this.logger.warn('Tool not found for tool call', { toolCall }); toolResponses.push(`Tool ${toolCall.name} not found`); } } return toolResponses; } async processFinalResponse(originalInput, toolResults) { const processToolResultPrompt = this.prompts['processToolResult']; if (!processToolResultPrompt) { throw new Error('processToolResult prompt not found'); } const finalInput = { toolResult: { result: toolResults.join('\n') }, originalQuery: typeof originalInput === 'string' ? originalInput : JSON.stringify(originalInput) }; const finalResponse = await this.execute(finalInput, 'processToolResult'); this.logger.debug('Received final response from provider', { content: finalResponse }); return finalResponse; } async *executeStream(input) { try { const selectedPrompt = this.selectPrompt(input); const messages = this.prepareMessages(input, selectedPrompt); const toolsForPrompt = this.prepareTools(selectedPrompt); const stream = this.provider.streamChat(messages, this.getChatOptions(selectedPrompt), toolsForPrompt); for await (const chunk of stream) { yield chunk.content; } } catch (error) { this.logger.error('Error executing streaming task', { error, input }); throw new TaskExecutionError('Failed to execute streaming task', error); } } async *executeStreamWithDetails(input) { try { const selectedPrompt = this.selectPrompt(input); const messages = this.prepareMessages(input, selectedPrompt); const toolsForPrompt = this.prepareTools(selectedPrompt); const stream = this.provider.streamChat(messages, this.getChatOptions(selectedPrompt), toolsForPrompt); for await (const chunk of stream) { yield { content: chunk.content, toolCalls: chunk.toolCalls }; } } catch (error) { this.logger.error('Error executing detailed streaming task', { error, input }); throw new TaskExecutionError('Failed to execute detailed streaming task', error); } } prepareMessages(input, prompt) { const systemPrompt = this.createSystemPrompt(prompt); const history = this.history.getFormattedHistory(); const context = this.context.getFormattedContext(); const userMessage = this.replaceRequestPlaceholder(prompt.user_message, input, prompt.input_schema); return [ { role: 'system', content: systemPrompt }, { role: 'system', content: `Current context:\n${context}` }, { role: 'system', content: `Recent conversation history:\n${history}` }, { role: 'user', content: userMessage } ]; } replaceRequestPlaceholder(message, input, inputSchema) { this.logger.debug('Replacing placeholders', { message, input, inputSchema }); if (typeof input === 'string') { const schemaKeys = Object.keys(inputSchema); if (schemaKeys.length === 1 && inputSchema[schemaKeys[0]] === 'string') { return message.replace(`{${schemaKeys[0]}}`, input); } else { throw new Error('Input is a string, but input_schema does not match a single string field'); } } else { return message.replace(/{(\w+)}/g, (match, key) => { if (key in input && key in inputSchema) { const value = input[key]; if (typeof value === inputSchema[key] || (inputSchema[key] === 'object' && typeof value === 'object')) { return JSON.stringify(value); } else { throw new Error(`Type mismatch for property '${key}': expected ${inputSchema[key]}, got ${typeof value}`); } } else { throw new Error(`Property '${key}' not found in input object or input_schema`); } }); } } createSystemPrompt(prompt) { return `${this.getDefaultSystemPrompt()}\n\n${prompt.system_message}`; } getDefaultSystemPrompt() { return `You are an AI assistant capable of performing complex multi-task activities. You have access to persistent state and contextual information. Always consider the provided context and history when formulating your responses. If you need to perform a specific action or retrieve information, clearly state it in your response.`; } getChatOptions(prompt) { return { maxTokens: prompt.config?.max_tokens || 3600, temperature: prompt.config?.temperature || 0.3, topP: 1 }; } selectPrompt(input) { this.logger.debug('Selecting prompt for input', { inputType: typeof input }); const matchingPrompts = Object.values(this.prompts).filter(prompt => this.inputMatchesSchema(input, prompt.input_schema)); if (matchingPrompts.length === 0) { this.logger.warn('No matching prompt found for input', { inputType: typeof input }); throw new Error('No matching prompt found for the given input'); } // If multiple prompts match, select the one with the highest priority (if set) or the first one const selectedPrompt = matchingPrompts.reduce((prev, current) => (current.priority ?? 0) > (prev.priority ?? 0) ? current : prev); this.logger.debug('Selected prompt', { promptName: selectedPrompt.name }); return selectedPrompt; } inputMatchesSchema(input, schema) { if (typeof input === 'string') { return Object.keys(schema).length === 1 && Object.values(schema)[0] === 'string'; } const inputKeys = Object.keys(input); const schemaKeys = Object.keys(schema); return inputKeys.length === schemaKeys.length && inputKeys.every(key => { return key in schema && (typeof input[key] === schema[key] || (schema[key] === 'object' && typeof input[key] === 'object')); }); } prepareTools(prompt) { if (!prompt.tools || prompt.tools.length === 0) { return []; } return prompt.tools .map(toolName => this.tools[toolName]) .filter((tool) => tool !== undefined); } logPrompts() { this.logger.debug('Prompts in TaskExecutor:', { promptCount: Object.keys(this.prompts).length, promptNames: Object.keys(this.prompts) }); Object.entries(this.prompts).forEach(([promptName, prompt]) => { this.logger.debug(`Prompt details for ${promptName}:`, { name: prompt.name, description: prompt.description, inputSchema: prompt.input_schema, toolCount: prompt.tools.length, toolNames: prompt.tools, priority: prompt.priority ?? 'Not set' }); }); if (!('executeTask' in this.prompts)) { this.logger.warn('executeTask prompt not found in TaskExecutor'); } } } exports.TaskExecutor = TaskExecutor;