ソースを参照

dev: add deepseek AI to backend

KernelDeimos 4 ヶ月 前
コミット
e9f6266062

+ 266 - 0
src/backend/src/modules/puterai/DeepSeekService.js

@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2024-present Puter Technologies Inc.
+ * 
+ * This file is part of Puter.
+ * 
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+// METADATA // {"ai-commented":{"service":"claude"}}
+const BaseService = require("../../services/BaseService");
+const { whatis, nou } = require("../../util/langutil");
+const { PassThrough } = require("stream");
+const { TypedValue } = require("../../services/drivers/meta/Runtime");
+const { TeePromise } = require('@heyputer/putility').libs.promise;
+
+const PUTER_PROMPT = `
+    You are running on an open-source platform called Puter,
+    as the DeepSeek implementation for a driver interface
+    called puter-chat-completion.
+    
+    The following JSON contains system messages from the
+    user of the driver interface (typically an app on Puter):
+`.replace('\n', ' ').trim();
+
+
+/**
+* DeepSeekService class - Provides integration with X.AI's API for chat completions
+* Extends BaseService to implement the puter-chat-completion interface.
+* Handles model management, message adaptation, streaming responses,
+* and usage tracking for X.AI's language models like Grok.
+* @extends BaseService
+*/
+class DeepSeekService extends BaseService {
+    static MODULES = {
+        openai: require('openai'),
+    }
+
+
+    /**
+    * Gets the system prompt used for AI interactions
+    * @returns {string} The base system prompt that identifies the AI as running on Puter
+    */
+    get_system_prompt () {
+        return PUTER_PROMPT;
+    }
+
+    adapt_model (model) {
+        return model;
+    }
+    
+
+    /**
+    * Initializes the XAI service by setting up the OpenAI client and registering with the AI chat provider
+    * @private
+    * @returns {Promise<void>} Resolves when initialization is complete
+    */
+    async _init () {
+        this.openai = new this.modules.openai.OpenAI({
+            apiKey: this.global_config.services.deepseek.apiKey,
+            baseURL: 'https://api.deepseek.com',
+        });
+
+        const svc_aiChat = this.services.get('ai-chat');
+        svc_aiChat.register_provider({
+            service_name: this.service_name,
+            alias: true,
+        });
+    }
+
+
+    /**
+    * Returns the default model identifier for the XAI service
+    * @returns {string} The default model ID 'grok-beta'
+    */
+    get_default_model () {
+        return 'grok-beta';
+    }
+
+    static IMPLEMENTS = {
+        ['puter-chat-completion']: {
+            /**
+             * Returns a list of available models and their details.
+             * See AIChatService for more information.
+             * 
+             * @returns Promise<Array<Object>> Array of model details
+             */
+            async models () {
+                return await this.models_();
+            },
+            /**
+            * Returns a list of available model names including their aliases
+            * @returns {Promise<string[]>} Array of model identifiers and their aliases
+            * @description Retrieves all available model IDs and their aliases,
+            * flattening them into a single array of strings that can be used for model selection
+            */
+            async list () {
+                const models = await this.models_();
+                const model_names = [];
+                for ( const model of models ) {
+                    model_names.push(model.id);
+                    if ( model.aliases ) {
+                        model_names.push(...model.aliases);
+                    }
+                }
+                return model_names;
+            },
+
+            /**
+             * AI Chat completion method.
+             * See AIChatService for more details.
+             */
+            async complete ({ messages, stream, model }) {
+                model = this.adapt_model(model);
+                const adapted_messages = [];
+                
+                const system_prompts = [];
+                let previous_was_user = false;
+                for ( const message of messages ) {
+                    if ( typeof message.content === 'string' ) {
+                        message.content = {
+                            type: 'text',
+                            text: message.content,
+                        };
+                    }
+                    if ( whatis(message.content) !== 'array' ) {
+                        message.content = [message.content];
+                    }
+                    if ( ! message.role ) message.role = 'user';
+                    if ( message.role === 'user' && previous_was_user ) {
+                        const last_msg = adapted_messages[adapted_messages.length-1];
+                        last_msg.content.push(
+                            ...(Array.isArray ? message.content : [message.content])
+                        );
+                        continue;
+                    }
+                    if ( message.role === 'system' ) {
+                        system_prompts.push(...message.content);
+                        continue;
+                    }
+                    adapted_messages.push(message);
+                    if ( message.role === 'user' ) {
+                        previous_was_user = true;
+                    }
+                }
+
+                adapted_messages.unshift({
+                    role: 'system',
+                    content: this.get_system_prompt() +
+                        JSON.stringify(system_prompts),
+                })
+
+                const completion = await this.openai.chat.completions.create({
+                    messages: adapted_messages,
+                    model: model ?? this.get_default_model(),
+                    max_tokens: 1000,
+                    stream,
+                    ...(stream ? {
+                        stream_options: { include_usage: true },
+                    } : {}),
+                });
+                
+                if ( stream ) {
+                    let usage_promise = new TeePromise();
+
+                    const stream = new PassThrough();
+                    const retval = new TypedValue({
+                        $: 'stream',
+                        content_type: 'application/x-ndjson',
+                        chunked: true,
+                    }, stream);
+                    (async () => {
+                        let last_usage = null;
+                        for await ( const chunk of completion ) {
+                            if ( chunk.usage ) last_usage = chunk.usage;
+                            // if (
+                            //     event.type !== 'content_block_delta' ||
+                            //     event.delta.type !== 'text_delta'
+                            // ) continue;
+                            // const str = JSON.stringify({
+                            //     text: event.delta.text,
+                            // });
+                            // stream.write(str + '\n');
+                            if ( chunk.choices.length < 1 ) continue;
+                            if ( nou(chunk.choices[0].delta.content) ) continue;
+                            const str = JSON.stringify({
+                                text: chunk.choices[0].delta.content
+                            });
+                            stream.write(str + '\n');
+                        }
+                        usage_promise.resolve({
+                            input_tokens: last_usage.prompt_tokens,
+                            output_tokens: last_usage.completion_tokens,
+                        });
+                        stream.end();
+                    })();
+
+                    return new TypedValue({ $: 'ai-chat-intermediate' }, {
+                        stream: true,
+                        response: retval,
+                        usage_promise: usage_promise,
+                    });
+                }
+
+                const ret = completion.choices[0];
+                ret.usage = {
+                    input_tokens: completion.usage.prompt_tokens,
+                    output_tokens: completion.usage.completion_tokens,
+                };
+                return ret;
+            }
+        }
+    }
+
+
+    /**
+    * Retrieves available AI models and their specifications
+    * @returns {Promise<Array>} Array of model objects containing:
+    *   - id: Model identifier string
+    *   - name: Human readable model name
+    *   - context: Maximum context window size
+    *   - cost: Pricing information object with currency and rates
+    * @private
+    */
+    async models_ () {
+        return [
+            {
+                id: 'deepseek-chat',
+                name: 'DeepSeek Chat',
+                context: 64000,
+                cost: {
+                    currency: 'usd-cents',
+                    tokens: 1_000_000,
+                    input: 14,
+                    output: 28,
+                },
+            },
+            {
+                id: 'deepseek-reasoner',
+                name: 'DeepSeek Reasoner',
+                context: 64000,
+                cost: {
+                    currency: 'usd-cents',
+                    tokens: 1_000_000,
+                    input: 55,
+                    output: 219,
+                },
+            }
+        ];
+    }
+}
+
+module.exports = {
+    DeepSeekService,
+};
+

+ 8 - 0
src/backend/src/modules/puterai/PuterAIModule.js

@@ -92,6 +92,14 @@ class PuterAIModule extends AdvancedBase {
             // services.registerService('claude', ClaudeEnoughService);
         }
 
+        if ( !! config?.services?.['deepseek'] ) {
+            const { DeepSeekService } = require('./DeepSeekService');
+            services.registerService('deepseek', DeepSeekService);
+
+            // const { ClaudeEnoughService } = require('./ClaudeEnoughService');
+            // services.registerService('claude', ClaudeEnoughService);
+        }
+
         const { AIChatService } = require('./AIChatService');
         services.registerService('ai-chat', AIChatService);