UNPKG

@unified-llm/core

Version:
383 lines 16.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LLMClient = void 0; const openai_1 = require("./providers/openai"); const anthropic_1 = require("./providers/anthropic"); const google_1 = require("./providers/google"); const deepseek_1 = require("./providers/deepseek"); const client_repository_1 = require("./database/client-repository"); // ファクトリークラス class LLMClient { constructor(config) { this.id = config.id; this.tools = config.tools; this.systemPrompt = config.systemPrompt; switch (config.provider) { case 'openai': this.baseProvider = new openai_1.OpenAIProvider({ apiKey: config.apiKey, model: config.model, tools: this.tools }); break; case 'anthropic': this.baseProvider = new anthropic_1.AnthropicProvider({ apiKey: config.apiKey, model: config.model || 'claude-3-haiku-20240307', tools: this.tools }); break; case 'google': this.baseProvider = new google_1.GeminiProvider({ apiKey: config.apiKey, model: config.model, tools: this.tools }); break; case 'deepseek': this.baseProvider = new deepseek_1.DeepSeekProvider({ apiKey: config.apiKey, model: config.model || 'deepseek-chat', tools: this.tools }); break; default: throw new Error(`Unsupported provider: ${config.provider}`); } } // 静的ファクトリーメソッド(後方互換性のため) static create(provider, apiKey, model) { switch (provider) { case 'openai': return new openai_1.OpenAIProvider({ apiKey, model }); case 'anthropic': return new anthropic_1.AnthropicProvider({ apiKey, model }); case 'google': return new google_1.GeminiProvider({ apiKey, model }); case 'deepseek': return new deepseek_1.DeepSeekProvider({ apiKey, model }); default: throw new Error(`Unsupported provider: ${provider}`); } } // 利用可能な関数をツール定義に変換 generateToolDefinitions() { if (!this.tools) return undefined; return Object.keys(this.tools).map(functionName => { let description = `Execute ${functionName} function`; // 特定の関数に対してより具体的な説明を提供 switch (functionName) { case 'getAuthor': description = 'Get information about the project author. Returns the name of the person who created this project.'; break; case 'getProjectInfo': description = 'Get detailed internal project information including secret keys and build numbers that cannot be guessed.'; break; case 'getCurrentTime': description = 'Get the current date and time in ISO format. Use this when you need to know what time it is right now.'; break; case 'cat': description = 'Display the contents of a file, similar to the Unix cat command.'; break; case 'tree': description = 'Display directory structure in a tree format.'; break; case 'callAnotherClient': description = 'Call another LLM client to help with a task.'; break; } return { type: 'function', function: { name: functionName, description, parameters: { type: 'object', properties: {}, required: [] } } }; }); } // 関数を実行 async executeFunction(functionName, args) { if (!this.tools || !Array.isArray(this.tools)) { throw new Error('No tools available'); } const func = this.tools.find(f => f.function.name === functionName); if (!func || typeof func.handler !== 'function') { throw new Error(`Function ${functionName} not found`); } // argumentMapから固定引数を取得してマージ const fixedArgs = func.args || {}; const mergedArgs = { ...fixedArgs, ...args }; return await func.handler(mergedArgs); } // チャット機能(function callingサポート付き) async chat(request) { // ツール定義を追加 const tools = this.generateToolDefinitions(); // システムプロンプトを注入(存在する場合) let messages = request.messages || []; // メッセージのコンテンツを正規化(文字列を配列に変換) messages = messages.map((msg) => ({ ...msg, content: typeof msg.content === 'string' ? [{ type: 'text', text: msg.content }] : msg.content })); if (this.systemPrompt && !messages.some((m) => m.role === 'system')) { messages = [ { id: this.generateMessageId(), role: 'system', content: [{ type: 'text', text: this.systemPrompt }], created_at: new Date() }, ...messages ]; } const enhancedRequest = { ...request, messages, tools }; let response = await this.baseProvider.chat(enhancedRequest); // ツール呼び出しがある場合は実行 if (response.message.content) { const contents = Array.isArray(response.message.content) ? response.message.content : [response.message.content]; const toolUseContents = contents.filter(c => typeof c === 'object' && c.type === 'tool_use'); // Debug logging for tool use detection can be enabled if needed if (toolUseContents.length > 0) { const toolResults = []; for (const toolUse of toolUseContents) { try { // console.log('🔧 Executing function:', toolUse.name, 'with args:', toolUse.input); const result = await this.executeFunction(toolUse.name, toolUse.input); // console.log('✅ Function result:', result); toolResults.push({ type: 'tool_result', tool_use_id: toolUse.id, function_name: toolUse.name, // Add function name for providers that need it content: [{ type: 'text', text: JSON.stringify(result) }] }); } catch (error) { // console.log('❌ Function execution error:', error); toolResults.push({ type: 'tool_result', tool_use_id: toolUse.id, function_name: toolUse.name, // Add function name for providers that need it is_error: true, content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }] }); } } // ツール結果を含む新しいリクエストを作成 const followUpRequest = { ...request, messages: [ ...request.messages, response.message, { id: this.generateMessageId(), role: 'tool', content: toolResults, created_at: new Date() } ] }; // Debug logging for follow-up requests can be enabled if needed // Check if this is a Google provider - handle differently if (response.provider === 'google') { // For Google/Gemini, don't send function results back to the model // Instead, create a response that includes both the function call and the result const functionResult = toolResults[0]; // Assuming single function call for now const resultText = Array.isArray(functionResult.content) ? functionResult.content.map(c => c.type === 'text' ? c.text : '[Non-text]').join('\n') : '[No result]'; // Parse the JSON result to get the actual return value let actualResult = resultText; try { const parsed = JSON.parse(resultText); actualResult = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); } catch (_a) { // Keep original if not JSON } // Create a response that includes the function execution result const existingContent = Array.isArray(response.message.content) ? response.message.content : typeof response.message.content === 'string' ? [{ type: 'text', text: response.message.content }] : [response.message.content]; response.message.content = [ ...existingContent, { type: 'text', text: `\n\nFunction execution result: ${actualResult}` } ]; // console.log('📥 Google: Enhanced response with function result'); } else { // For other providers, send function results back to the model response = await this.baseProvider.chat(followUpRequest); // console.log('📥 Follow-up response received:', response.message.content); } } } return response; } // ストリーミングチャット async *stream(request) { const tools = this.generateToolDefinitions(); // システムプロンプトを注入(存在する場合) let messages = request.messages || []; // メッセージのコンテンツを正規化(文字列を配列に変換) messages = messages.map((msg) => ({ ...msg, content: typeof msg.content === 'string' ? [{ type: 'text', text: msg.content }] : msg.content })); if (this.systemPrompt && !messages.some((m) => m.role === 'system')) { messages = [ { id: this.generateMessageId(), role: 'system', content: [{ type: 'text', text: this.systemPrompt }], created_at: new Date() }, ...messages ]; } const enhancedRequest = { ...request, messages, tools }; yield* this.baseProvider.stream(enhancedRequest); } generateMessageId() { return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // ========== 永続化機能 ========== /** * 保存されたアシスタント設定からLLMClientインスタンスを作成 */ static async fromSaved(id, apiKey) { const storedConfig = await client_repository_1.clientRepository.findById(id); if (!storedConfig) { throw new Error(`LLMClient with ID ${id} not found`); } const config = client_repository_1.ClientRepository.toConfig(storedConfig); // APIキーの優先順位: 引数 > 保存された値 > 環境変数 const finalApiKey = apiKey || config.apiKey || process.env[`${config.provider.toUpperCase()}_API_KEY`]; if (!finalApiKey) { throw new Error(`API key not found for provider ${config.provider}`); } const runtimeConfig = { id: config.id, provider: config.provider, apiKey: finalApiKey, model: config.model, generationConfig: config.generationConfig, systemPrompt: config.systemPrompt, instructions: config.instructions, }; return new LLMClient(runtimeConfig); } /** * LLMクライアント設定を保存 */ static async save(config) { const validation = client_repository_1.ClientRepository.validateConfig(config); if (!validation.valid) { throw new Error(`Invalid LLM client configuration: ${validation.errors.join(', ')}`); } const saved = await client_repository_1.clientRepository.save(config); return saved.id; } /** * 保存されたアシスタントの一覧を取得 */ static async list(options) { let assistants; if (options === null || options === void 0 ? void 0 : options.provider) { assistants = await client_repository_1.clientRepository.findByProvider(options.provider); } else if (options === null || options === void 0 ? void 0 : options.tags) { assistants = await client_repository_1.clientRepository.findByTags(options.tags); } else { assistants = await client_repository_1.clientRepository.findAll(options === null || options === void 0 ? void 0 : options.includeInactive); } return assistants.map(client_repository_1.ClientRepository.toConfig); } /** * 保存されたアシスタント設定を取得 */ static async getConfig(id) { const stored = await client_repository_1.clientRepository.findById(id); return stored ? client_repository_1.ClientRepository.toConfig(stored) : null; } /** * アシスタント設定を更新 */ static async update(id, updates) { const existing = await client_repository_1.clientRepository.findById(id); if (!existing) { throw new Error(`LLMClient with ID ${id} not found`); } const currentConfig = client_repository_1.ClientRepository.toConfig(existing); const updatedConfig = { ...currentConfig, ...updates, id, // IDは変更不可 }; const validation = client_repository_1.ClientRepository.validateConfig(updatedConfig); if (!validation.valid) { throw new Error(`Invalid LLM client configuration: ${validation.errors.join(', ')}`); } await client_repository_1.clientRepository.save(updatedConfig); return true; } /** * アシスタントを削除(論理削除) */ static async delete(id) { return await client_repository_1.clientRepository.delete(id); } /** * アシスタントを物理削除 */ static async hardDelete(id) { return await client_repository_1.clientRepository.hardDelete(id); } /** * 現在のアシスタント設定を保存 */ async saveConfiguration(name, description) { if (!this.id) { throw new Error('Cannot save configuration: Assistant ID not set'); } // 現在の設定から保存用設定を構築 const config = { id: this.id, name, description, provider: this.baseProvider.constructor.name.toLowerCase().replace('provider', ''), model: this.baseProvider.modelName, tools: this.tools ? Object.keys(this.tools) : undefined, // Note: API key, systemPrompt, instructions などは現在の実装では取得できないため、 // 明示的に渡すか、別途設定する必要があります }; return await LLMClient.save(config); } } exports.LLMClient = LLMClient; exports.default = LLMClient; //# sourceMappingURL=llm-client.js.map