@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
JavaScript
"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;