From 65a8898d14000b44f96b554e4eba930d8ab300cd Mon Sep 17 00:00:00 2001 From: uxname Date: Sat, 24 May 2025 15:34:35 +0300 Subject: [PATCH] Refactor: Move config and types to separate files, enhance tool call handling. --- src/index.ts | 139 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 81 insertions(+), 58 deletions(-) diff --git a/src/index.ts b/src/index.ts index 32b4c32..6ba3be4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { + AIMessage, HumanMessage, SystemMessage, ToolMessage, @@ -27,7 +28,6 @@ class MockMcpToolsService { description: 'Возвращает введенную строку.', schema: z.object({ input: z.string().describe('Строка для эхо.') }), func: async (input: { input: string }) => { - console.log(`Вызов инструмента 'echo' с входом: ${input.input}`); return input.input; }, }); @@ -37,7 +37,6 @@ class MockMcpToolsService { description: 'Возвращает текущую дату в формате ISO.', schema: z.object({}), func: async () => { - console.log('Вызов инструмента "get_current_date"'); return new Date().toISOString(); }, }); @@ -48,13 +47,21 @@ class MockMcpToolsService { class PipelineExecutor { private readonly mcpToolsService: MockMcpToolsService; + private cachedTools: DynamicStructuredTool[] | null = null; constructor() { this.mcpToolsService = new MockMcpToolsService(); } + private async _getOrLoadTools(): Promise { + if (!this.cachedTools) { + this.cachedTools = await this.mcpToolsService.getTools(); + } + return this.cachedTools; + } + private async initChain(openAIApiKey: string): Promise { - const tools = await this.mcpToolsService.getTools(); + const tools = await this._getOrLoadTools(); const llm = new ChatOpenAI({ model: 'gpt-4o', temperature: 0.2, @@ -68,10 +75,8 @@ class PipelineExecutor { variables: Record = {}, ): (HumanMessage | SystemMessage)[] { return [ - ...systemPrompts.map((prompt) => - Object.keys(variables).length - ? new SystemMessage(Handlebars.compile(prompt)(variables)) - : new SystemMessage(prompt), + ...systemPrompts.map( + (prompt) => new SystemMessage(Handlebars.compile(prompt)(variables)), ), new SystemMessage(`Текущая дата и время: ${new Date().toISOString()}`), ]; @@ -81,73 +86,91 @@ class PipelineExecutor { prompt: string, variables: Record = {}, ): string { - return Object.keys(variables).length - ? Handlebars.compile(prompt)(variables) - : prompt; + return Handlebars.compile(prompt)(variables); } - async executeChain(params: { + async executeChain(options: { pipeline: Pipeline; variables?: Record; systemPrompts?: string[]; openAIApiKey: string; }): Promise { - const variables = params.variables ?? {}; - const systemPrompts = params.systemPrompts ?? []; - const chain = await this.initChain(params.openAIApiKey); + const variables = options.variables ?? {}; + const systemPrompts = options.systemPrompts ?? []; + const chain = await this.initChain(options.openAIApiKey); const initialMessages = this.buildMessages( - [params.pipeline.systemPrompt, ...systemPrompts], + [options.pipeline.systemPrompt, ...systemPrompts], variables, ); const results: string[] = []; - for (const { prompt } of params.pipeline.steps) { + for (const { prompt } of options.pipeline.steps) { const renderedPrompt = this.renderPrompt(prompt, variables); - const currentMessages = [ - ...initialMessages, - new HumanMessage(renderedPrompt), - ]; - let response = await chain.invoke(currentMessages); + const currentMessages: ( + | HumanMessage + | SystemMessage + | AIMessage + | ToolMessage + )[] = [...initialMessages, new HumanMessage(renderedPrompt)]; - while (response.tool_calls && response.tool_calls.length > 0) { - const toolCalls = response.tool_calls; - currentMessages.push(response); - - for (const toolCall of toolCalls) { - try { - const tools = await this.mcpToolsService.getTools(); - const tool = tools.find((t) => t.name === toolCall.name); - - if (!tool) { - throw new Error(`Tool ${toolCall.name} not found`); - } - - const toolResult = await tool.func(toolCall.args); - currentMessages.push( - new ToolMessage({ - tool_call_id: toolCall.id, - content: JSON.stringify(toolResult), - }), - ); - } catch (error) { - console.error( - `Ошибка выполнения инструмента ${toolCall.name}:`, - error, - ); - currentMessages.push( - new ToolMessage({ - tool_call_id: toolCall.id, - content: `Error: ${error.message}`, - }), - ); - } - } - response = await chain.invoke(currentMessages); - } - results.push(response?.content ?? ''); + const finalResponse = await this._resolveToolCalls( + chain, + currentMessages, + ); + results.push(finalResponse?.content.toString() ?? ''); } return results; } + + private async _resolveToolCalls( + chain: Runnable, + messages: (HumanMessage | SystemMessage | AIMessage | ToolMessage)[], + ): Promise { + let response: AIMessage = await chain.invoke(messages); + + while (response.tool_calls && response.tool_calls.length > 0) { + messages.push(response); + + for (const toolCall of response.tool_calls) { + try { + const tools = await this._getOrLoadTools(); + const tool = tools.find((t) => t.name === toolCall.name); + + if (!tool) { + console.error(`Инструмент ${toolCall.name} не найден.`); + messages.push( + new ToolMessage({ + tool_call_id: toolCall.id!, + content: `Ошибка: Инструмент ${toolCall.name} не найден.`, + }), + ); + continue; + } + + const toolResult = await tool.func(toolCall.args); + messages.push( + new ToolMessage({ + tool_call_id: toolCall.id!, + content: JSON.stringify(toolResult), + }), + ); + } catch (error) { + console.error( + `Ошибка выполнения инструмента ${toolCall.name}:`, + error, + ); + messages.push( + new ToolMessage({ + tool_call_id: toolCall.id!, + content: `Ошибка: ${error.message}`, + }), + ); + } + } + response = await chain.invoke(messages); + } + return response; + } } async function main() { @@ -181,7 +204,7 @@ async function main() { name: 'Получение даты', prompt: 'Какая сейчас дата? Используй инструмент `get_current_date`.', }, - { name: 'Завершение', prompt: 'Напиши резюме, что ты сделал' }, + { name: 'Завершение', prompt: 'Напиши резюме нашего с тобой диалога' }, ], };