UNPKG

chatgpt-optimized-official

Version:
429 lines 18.7 kB
import axios from "axios"; import { randomUUID } from "crypto"; import { encode } from "gpt-3-encoder"; import MessageType from "../enums/message-type.js"; import AppDbContext from "./app-dbcontext.js"; class ChatGPT { options; db; onUsage; constructor(key, options) { this.db = new AppDbContext(); this.db.WaitForLoad().then(() => { if (typeof key === "string") { if (this.db.keys.Any((x) => x.key === key)) return; this.db.keys.Add({ key: key, queries: 0, balance: 0, tokens: 0, }); } else if (Array.isArray(key)) { key.forEach((k) => { if (this.db.keys.Any((x) => x.key === k)) return; this.db.keys.Add({ key: k, queries: 0, balance: 0, tokens: 0, }); }); } }); this.options = { model: options?.model || "gpt-3.5-turbo", temperature: options?.temperature || 0.7, max_tokens: options?.max_tokens || 100, top_p: options?.top_p || 0.9, frequency_penalty: options?.frequency_penalty || 0, presence_penalty: options?.presence_penalty || 0, instructions: options?.instructions || `You are ChatGPT, a language model developed by OpenAI. You are designed to respond to user input in a conversational manner, Answer as concisely as possible. Your training data comes from a diverse range of internet text and You have been trained to generate human-like responses to various questions and prompts. You can provide information on a wide range of topics, but your knowledge is limited to what was present in your training data, which has a cutoff date of 2021. You strive to provide accurate and helpful information to the best of your ability.\nKnowledge cutoff: 2021-09`, price: options?.price || 0.002, max_conversation_tokens: options?.max_conversation_tokens || 4097, endpoint: options?.endpoint || "https://api.openai.com/v1/chat/completions", moderation: options?.moderation || false, functions: options?.functions || null, function_call: options?.function_call || null, tools: options?.tools || null, tool_choice: options?.tool_choice || 'auto', parallel_tool_calls: options?.parallel_tool_calls || false, response_format: options?.response_format || null, }; } getOpenAIKey() { let key = this.db.keys.OrderBy((x) => x.balance).FirstOrDefault(); if (key == null) { key = this.db.keys.FirstOrDefault(); } if (key == null) { throw new Error("No keys available."); } return key; } async *chunksToLines(chunksAsync) { let previous = ""; for await (const chunk of chunksAsync) { const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); previous += bufferChunk; let eolIndex; while ((eolIndex = previous.indexOf("\n")) >= 0) { const line = previous.slice(0, eolIndex + 1).trimEnd(); if (line === "data: [DONE]") break; if (line.startsWith("data: ")) yield line; previous = previous.slice(eolIndex + 1); } } } async *linesToMessages(linesAsync) { for await (const line of linesAsync) { const message = line.substring("data :".length); yield message; } } async *streamCompletion(data) { yield* this.linesToMessages(this.chunksToLines(data)); } getInstructions(username) { return `${this.options.instructions} Current date: ${this.getToday()} Current time: ${this.getTime()}${username !== "User" ? `\nName of the user talking to: ${username}` : ""}`; } addConversation(conversationId, userName = "User") { let conversation = { id: conversationId, userName: userName, messages: [], }; this.db.conversations.Add(conversation); return conversation; } getConversation(conversationId, userName = "User") { let conversation = this.db.conversations.Where((conversation) => conversation.id === conversationId).FirstOrDefault(); if (!conversation) { conversation = this.addConversation(conversationId, userName); } else { conversation.lastActive = Date.now(); } conversation.userName = userName; return conversation; } resetConversation(conversationId) { let conversation = this.db.conversations.Where((conversation) => conversation.id == conversationId).FirstOrDefault(); if (conversation) { conversation.messages = []; conversation.lastActive = Date.now(); } return conversation; } async ask(prompt, conversationId = "default", userName = "User") { return await this.askStream((data) => { }, (data) => { }, prompt, conversationId, userName); } async askStream(data, usage, prompt, conversationId = "default", userName = "User") { let oAIKey = this.getOpenAIKey(); let conversation = this.getConversation(conversationId, userName); if (this.options.moderation) { let flagged = await this.moderate(prompt, oAIKey.key); if (flagged) { for (let chunk in "Your message was flagged as inappropriate and was not sent.".split("")) { data(chunk); await this.wait(100); } return "Your message was flagged as inappropriate and was not sent."; } } let promptStr = this.generatePrompt(conversation, prompt); let prompt_tokens = this.countTokens(promptStr); try { const response = await axios.post(this.options.endpoint, { model: this.options.model, messages: promptStr, temperature: this.options.temperature, max_tokens: this.options.max_tokens, top_p: this.options.top_p, frequency_penalty: this.options.frequency_penalty, presence_penalty: this.options.presence_penalty, stream: true }, { responseType: "stream", headers: { Accept: "text/event-stream", "Content-Type": "application/json", Authorization: `Bearer ${oAIKey.key}`, }, }); let responseStr = ""; let responseArg = ""; let responseNameFunction = ""; for await (const message of this.streamCompletion(response.data)) { try { const parsed = JSON.parse(message); const { delta, finish_reason } = parsed.choices[0]; const { content, function_call } = delta; if (function_call) { responseNameFunction += function_call.name; responseArg += function_call.arguments; } if (finish_reason === "function_call") { responseStr = JSON.stringify({ "name": responseNameFunction, "arguments": responseArg }); data(responseStr); } else if (content) { responseStr += content; data(content); } } catch (error) { console.error("Could not JSON parse stream message", message, error); } } let completion_tokens = encode(responseStr).length; let usageData = { key: oAIKey.key, prompt_tokens: prompt_tokens, completion_tokens: completion_tokens, total_tokens: prompt_tokens + completion_tokens, }; usage(usageData); if (this.onUsage) this.onUsage(usageData); oAIKey.tokens += usageData.total_tokens; oAIKey.balance = (oAIKey.tokens / 1000) * this.options.price; oAIKey.queries++; conversation.messages.push({ id: randomUUID(), content: responseStr, type: MessageType.Assistant, date: Date.now(), }); return responseStr; } catch (error) { if (error.response && error.response.data && error.response.headers["content-type"] === "application/json") { let errorResponseStr = ""; for await (const message of error.response.data) { errorResponseStr += message; } const errorResponseJson = JSON.parse(errorResponseStr); throw new Error(errorResponseJson.error.message); } else { throw new Error(error.message); } } } async askV1(prompt, conversationId = "default", type = 1, function_name, tool_call_id, userName = "User", response_format) { return await this.askPost((data) => { }, (data) => { }, prompt, conversationId, function_name, userName, type, tool_call_id, response_format); } async askPost(data, usage, prompt, conversationId = "default", function_name, userName = "User", type = MessageType.User, tool_call_id, response_format) { let oAIKey = this.getOpenAIKey(); let conversation = this.getConversation(conversationId, userName); let promptStr = this.generatePrompt(conversation, prompt, type, function_name, tool_call_id); let prompt_tokens = this.countTokens(promptStr); try { let auxOptions = { model: this.options.model, messages: promptStr, temperature: this.options.temperature, max_tokens: this.options.max_tokens, top_p: this.options.top_p, frequency_penalty: this.options.frequency_penalty, presence_penalty: this.options.presence_penalty, stream: false, tools: this.options.tools, tool_choice: this.options.tool_choice, parallel_tool_calls: this.options.parallel_tool_calls, response_format: response_format, }; if (this.options.functions) { auxOptions["functions"] = this.options.functions; auxOptions["function_call"] = this.options.function_call ? this.options.function_call : "auto"; } const response = await axios.post(this.options.endpoint, auxOptions, { responseType: "json", headers: { Accept: "application/json", "Content-Type": "application/json", Authorization: `Bearer ${oAIKey.key}`, }, }); let completion_tokens = response.data.usage['completion_tokens']; let usageData = { key: oAIKey.key, prompt_tokens: prompt_tokens, completion_tokens: completion_tokens, total_tokens: prompt_tokens + completion_tokens, }; if (this.onUsage) this.onUsage(usageData); oAIKey.tokens += usageData.total_tokens; oAIKey.balance = (oAIKey.tokens / 1000) * this.options.price; oAIKey.queries++; if (response.data.choices[0].finish_reason == "tool_calls") { conversation.messages.push({ id: randomUUID(), content: response.data.choices[0]['message']['content'], type: MessageType.Assistant, date: Date.now(), name: function_name, tool_calls: response.data.choices[0]['message']['tool_calls'], }); } else if (response.data.choices[0]['message']['content']) { conversation.messages.push({ id: randomUUID(), content: response.data.choices[0]['message']['content'] ? response.data.choices[0]['message']['content'] : "", type: MessageType.Assistant, date: Date.now(), }); } data(JSON.stringify(response.data.choices[0])); return response.data.choices[0]; } catch (error) { if (error.response && error.response.data && error.response.headers["content-type"] === "application/json") { throw new Error(error.response.data.error.message); } else { throw new Error(error.message); } } } async moderate(prompt, key) { return false; } generatePrompt(conversation, prompt, type = MessageType.User, function_name, tool_call_id) { if (Array.isArray(prompt) && prompt.length > 0 && 'tool_call_id' in prompt[0]) { const toolOutputs = prompt; for (const toolOutput of toolOutputs) { conversation.messages.push({ id: randomUUID(), content: toolOutput.content, type: MessageType.Function, date: Date.now(), name: toolOutput.name, tool_call_id: toolOutput.tool_call_id, }); } } else { let message = { id: randomUUID(), content: prompt, type: type, date: Date.now(), }; if (type === MessageType.Function && function_name) { message["name"] = function_name; message["tool_call_id"] = tool_call_id; } conversation.messages.push(message); } let messages = this.generateMessages(conversation); let promptEncodedLength = this.countTokens(messages); let totalLength = promptEncodedLength + this.options.max_tokens; while (totalLength > this.options.max_conversation_tokens) { conversation.messages.shift(); messages = this.generateMessages(conversation); promptEncodedLength = this.countTokens(messages); totalLength = promptEncodedLength + this.options.max_tokens; } conversation.lastActive = Date.now(); return messages; } generateMessages(conversation) { let messages = []; messages.push({ role: "system", content: this.getInstructions(conversation.userName), }); for (let message of conversation.messages) { if (message.type === MessageType.Function) { messages.push({ tool_call_id: message.tool_call_id, role: "tool", name: message.name || "unknownFunction", content: message.content, }); } else if (message.type === MessageType.User) { messages.push({ role: "user", content: message.content, }); } else { if (message.tool_calls) { messages.push({ role: "assistant", content: message.content, tool_calls: message.tool_calls, }); } else { messages.push({ role: "assistant", content: message.content, }); } } } return messages; } countTokens(messages) { let tokens = 0; for (let message of messages) { if (message.content) { if (typeof message.content === "string") { tokens += encode(message.content).length; } else if (Array.isArray(message.content)) { for (let contentBlock of message.content) { if (contentBlock.type === "text") { tokens += encode(contentBlock.text).length; } else if (contentBlock.type === "image_url") { if (contentBlock.image_url.detail === "low" || !contentBlock.image_url.detail) { tokens += 85; } else if (contentBlock.image_url.detail === "high") { tokens += 765; } else { tokens += 85; } } } } } } return tokens; } getToday() { let today = new Date(); let dd = String(today.getDate()).padStart(2, "0"); let mm = String(today.getMonth() + 1).padStart(2, "0"); let yyyy = today.getFullYear(); return `${yyyy}-${mm}-${dd}`; } getTime() { let today = new Date(); let hours = today.getHours(); let minutes = today.getMinutes(); let ampm = hours >= 12 ? "PM" : "AM"; hours = hours % 12; hours = hours ? hours : 12; minutes = minutes < 10 ? `0${minutes}` : minutes; return `${hours}:${minutes} ${ampm}`; } wait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } } export default ChatGPT; //# sourceMappingURL=chatgpt.js.map