diff --git a/src/index.ts b/src/index.ts index 9ceaba1..8767b8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,16 +7,15 @@ import { import { Runnable } from '@langchain/core/runnables'; import { DynamicStructuredTool } from '@langchain/core/tools'; import { ChatOpenAI } from '@langchain/openai'; -import dotenv from 'dotenv'; // Импортируем dotenv для загрузки переменных окружения +import dotenv from 'dotenv'; import Handlebars from 'handlebars'; import { z } from 'zod'; -// MCP SDK imports import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; import { Tool as McpTool, TextContent } from '@modelcontextprotocol/sdk/types'; -dotenv.config(); // Загружаем переменные окружения из .env файла +dotenv.config(); interface PipelineStep { name: string; @@ -29,9 +28,6 @@ interface Pipeline { steps: PipelineStep[]; } -/** - * Сервис для взаимодействия с MCP сервером и предоставления инструментов LangChain. - */ class McpToolsService { private mcpClient: Client | null = null; private transport: SSEClientTransport | null = null; @@ -41,12 +37,9 @@ class McpToolsService { this.serverUrl = serverUrl; } - /** - * Устанавливает соединение с MCP сервером. - */ async connect(): Promise { if (this.mcpClient && this.transport) { - return; // Уже подключено + return; } try { this.transport = new SSEClientTransport(new URL(this.serverUrl)); @@ -65,9 +58,6 @@ class McpToolsService { } } - /** - * Закрывает соединение с MCP сервером. - */ async disconnect(): Promise { if (this.mcpClient) { await this.mcpClient.close(); @@ -77,10 +67,6 @@ class McpToolsService { } } - /** - * Получает список инструментов с MCP сервера и преобразует их в формат LangChain. - * @returns Массив инструментов LangChain. - */ async getLangchainTools(): Promise { if (!this.mcpClient) { throw new Error('MCP клиент не подключен. Сначала вызовите .connect()'); @@ -90,8 +76,6 @@ class McpToolsService { const langchainTools: DynamicStructuredTool[] = []; for (const mcpTool of mcpTools) { - // Простая конвертация JSON Schema в Zod schema. - // Для сложных схем рекомендуется использовать специализированную библиотеку (например, json-schema-to-zod). const properties: Record = {}; for (const key in mcpTool.inputSchema.properties) { // biome-ignore lint/suspicious/noExplicitAny: @@ -108,11 +92,9 @@ class McpToolsService { schemaType = z.boolean(); break; case 'array': - // Для массивов без явного типа элементов, используем z.array(z.any()) schemaType = z.array(z.any()); break; case 'object': - // Для объектов без явной вложенной схемы, используем z.object({}) schemaType = z.object({}); break; default: @@ -149,7 +131,6 @@ class McpToolsService { ); } - // Предпочтительно возвращаем структурированный контент, если он есть if (result.structuredContent) { return result.structuredContent; } @@ -160,7 +141,7 @@ class McpToolsService { if (c.type === 'image') return `[Изображение: ${c.mimeType}]`; if (c.type === 'audio') return `[Аудио: ${c.mimeType}]`; if (c.type === 'resource') return `[Ресурс: ${c.uri}]`; - return JSON.stringify(c); // Запасной вариант для других типов + return JSON.stringify(c); }) .join('\n'); }, @@ -181,8 +162,8 @@ class PipelineExecutor { private async _getOrLoadTools(): Promise { if (!this.cachedTools) { - await this.mcpToolsService.connect(); // Устанавливаем соединение с MCP сервером - this.cachedTools = await this.mcpToolsService.getLangchainTools(); // Получаем инструменты LangChain + await this.mcpToolsService.connect(); + this.cachedTools = await this.mcpToolsService.getLangchainTools(); } return this.cachedTools; } @@ -225,25 +206,25 @@ class PipelineExecutor { const variables = options.variables ?? {}; const systemPrompts = options.systemPrompts ?? []; const chain = await this.initChain(options.openAIApiKey); - const initialMessages = this.buildMessages( - [options.pipeline.systemPrompt, ...systemPrompts], - variables, - ); + // Инициализируем массив сообщений, который будет накапливаться + const messages: (HumanMessage | SystemMessage | AIMessage | ToolMessage)[] = + this.buildMessages( + [options.pipeline.systemPrompt, ...systemPrompts], + variables, + ); const results: string[] = []; for (const { prompt } of options.pipeline.steps) { const renderedPrompt = this.renderPrompt(prompt, variables); - const currentMessages: ( - | HumanMessage - | SystemMessage - | AIMessage - | ToolMessage - )[] = [...initialMessages, new HumanMessage(renderedPrompt)]; + // Добавляем текущий промпт пользователя в общий массив сообщений + messages.push(new HumanMessage(renderedPrompt)); const finalResponse = await this._resolveToolCalls( chain, - currentMessages, + messages, // Передаем накопительный массив сообщений ); + // Добавляем ответ ИИ в общий массив сообщений для сохранения контекста + messages.push(finalResponse); results.push(finalResponse?.content.toString() ?? ''); } return results; @@ -260,7 +241,7 @@ class PipelineExecutor { for (const toolCall of response.tool_calls) { try { - const tools = await this._getOrLoadTools(); // Получаем LangChain-совместимые инструменты + const tools = await this._getOrLoadTools(); const tool = tools.find((t) => t.name === toolCall.name); if (!tool) { @@ -276,12 +257,11 @@ class PipelineExecutor { continue; } - // Выполняем функцию LangChain инструмента, которая внутри вызывает MCP сервер const toolResult = await tool.func(toolCall.args); messages.push( new ToolMessage({ tool_call_id: toolCall.id!, - content: JSON.stringify(toolResult), // LangChain ожидает строковый контент здесь + content: JSON.stringify(toolResult), }), ); } catch (error) { @@ -305,7 +285,6 @@ class PipelineExecutor { async function main() { const openAIApiKey = process.env.OPENAI_API_KEY; - // Используйте переменную окружения для URL сервера MCP, с запасным вариантом по умолчанию const mcpServerUrl = process.env.MCP_SERVER_URL || 'https://santiment-mcp.dev.mind-dev.com/sse'; @@ -316,7 +295,7 @@ async function main() { process.exit(1); } - const pipelineExecutor = new PipelineExecutor(mcpServerUrl); // Передаем URL сервера + const pipelineExecutor = new PipelineExecutor(mcpServerUrl); const testPipeline: Pipeline = { description: 'Тестовый пайплайн',