mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 22:43:45 +08:00
dev: add deepseek AI to backend
This commit is contained in:
parent
40030ebc94
commit
e9f6266062
266
src/backend/src/modules/puterai/DeepSeekService.js
Normal file
266
src/backend/src/modules/puterai/DeepSeekService.js
Normal file
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user