UNPKG

ai-utils.js

Version:

Build AI applications, chatbots, and agents with JavaScript and TypeScript.

289 lines (288 loc) 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenAIChatResponseFormat = exports.OpenAIChatModel = exports.calculateOpenAIChatCostInMillicents = exports.isOpenAIChatModel = exports.OPENAI_CHAT_MODELS = void 0; const zod_1 = __importDefault(require("zod")); const AbstractModel_js_1 = require("../../../model-function/AbstractModel.cjs"); const PromptMappingTextGenerationModel_js_1 = require("../../../prompt/PromptMappingTextGenerationModel.cjs"); const callWithRetryAndThrottle_js_1 = require("../../../util/api/callWithRetryAndThrottle.cjs"); const postToApi_js_1 = require("../../../util/api/postToApi.cjs"); const OpenAIError_js_1 = require("../OpenAIError.cjs"); const TikTokenTokenizer_js_1 = require("../TikTokenTokenizer.cjs"); const OpenAIChatStreamIterable_js_1 = require("./OpenAIChatStreamIterable.cjs"); const countOpenAIChatMessageTokens_js_1 = require("./countOpenAIChatMessageTokens.cjs"); /* * Available OpenAI chat models, their token limits, and pricing. * * @see https://platform.openai.com/docs/models/ * @see https://openai.com/pricing */ exports.OPENAI_CHAT_MODELS = { "gpt-4": { contextWindowSize: 8192, promptTokenCostInMillicents: 3, completionTokenCostInMillicents: 6, }, "gpt-4-0314": { contextWindowSize: 8192, promptTokenCostInMillicents: 3, completionTokenCostInMillicents: 6, }, "gpt-4-0613": { contextWindowSize: 8192, promptTokenCostInMillicents: 3, completionTokenCostInMillicents: 6, }, "gpt-4-32k": { contextWindowSize: 32768, promptTokenCostInMillicents: 6, completionTokenCostInMillicents: 12, }, "gpt-4-32k-0314": { contextWindowSize: 32768, promptTokenCostInMillicents: 6, completionTokenCostInMillicents: 12, }, "gpt-4-32k-0613": { contextWindowSize: 32768, promptTokenCostInMillicents: 6, completionTokenCostInMillicents: 12, }, "gpt-3.5-turbo": { contextWindowSize: 4096, promptTokenCostInMillicents: 0.15, completionTokenCostInMillicents: 0.2, }, "gpt-3.5-turbo-0301": { contextWindowSize: 4096, promptTokenCostInMillicents: 0.15, completionTokenCostInMillicents: 0.2, }, "gpt-3.5-turbo-0613": { contextWindowSize: 4096, promptTokenCostInMillicents: 0.15, completionTokenCostInMillicents: 0.2, }, "gpt-3.5-turbo-16k": { contextWindowSize: 16384, promptTokenCostInMillicents: 0.3, completionTokenCostInMillicents: 0.4, }, "gpt-3.5-turbo-16k-0613": { contextWindowSize: 16384, promptTokenCostInMillicents: 0.3, completionTokenCostInMillicents: 0.4, }, }; const isOpenAIChatModel = (model) => model in exports.OPENAI_CHAT_MODELS; exports.isOpenAIChatModel = isOpenAIChatModel; const calculateOpenAIChatCostInMillicents = ({ model, response, }) => response.usage.prompt_tokens * exports.OPENAI_CHAT_MODELS[model].promptTokenCostInMillicents + response.usage.completion_tokens * exports.OPENAI_CHAT_MODELS[model].completionTokenCostInMillicents; exports.calculateOpenAIChatCostInMillicents = calculateOpenAIChatCostInMillicents; /** * Create a text generation model that calls the OpenAI chat completion API. * * @see https://platform.openai.com/docs/api-reference/chat/create * * @example * const model = new OpenAIChatModel({ * model: "gpt-3.5-turbo", * temperature: 0.7, * maxTokens: 500, * }); * * const { text } = await generateText([ * model, * OpenAIChatMessage.system( * "Write a short story about a robot learning to love:" * ), * ]); */ class OpenAIChatModel extends AbstractModel_js_1.AbstractModel { constructor(settings) { super({ settings }); Object.defineProperty(this, "provider", { enumerable: true, configurable: true, writable: true, value: "openai" }); Object.defineProperty(this, "contextWindowSize", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "tokenizer", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.tokenizer = new TikTokenTokenizer_js_1.TikTokenTokenizer({ model: this.settings.model }); this.contextWindowSize = exports.OPENAI_CHAT_MODELS[this.settings.model].contextWindowSize; } get modelName() { return this.settings.model; } get apiKey() { const apiKey = this.settings.apiKey ?? process.env.OPENAI_API_KEY; if (apiKey == null) { throw new Error(`OpenAI API key is missing. Pass it as an argument to the constructor or set it as an environment variable named OPENAI_API_KEY.`); } return apiKey; } /** * Counts the prompt tokens required for the messages. This includes the message base tokens * and the prompt base tokens. */ countPromptTokens(messages) { return (0, countOpenAIChatMessageTokens_js_1.countOpenAIChatPromptTokens)({ messages, model: this.modelName, }); } async callAPI(messages, options) { const { run, settings, responseFormat } = options; const callSettings = Object.assign({ apiKey: this.apiKey, user: this.settings.isUserIdForwardingEnabled ? run?.userId : undefined, }, this.settings, settings, { abortSignal: run?.abortSignal, messages, responseFormat, }); return (0, callWithRetryAndThrottle_js_1.callWithRetryAndThrottle)({ retry: callSettings.retry, throttle: callSettings.throttle, call: async () => callOpenAIChatCompletionAPI(callSettings), }); } generateTextResponse(prompt, options) { return this.callAPI(prompt, { ...options, responseFormat: exports.OpenAIChatResponseFormat.json, }); } extractText(response) { return response.choices[0].message.content; } generateDeltaStreamResponse(prompt, options) { return this.callAPI(prompt, { ...options, responseFormat: exports.OpenAIChatResponseFormat.deltaIterable, }); } extractTextDelta(fullDelta) { return fullDelta[0]?.delta.content ?? undefined; } /** * JSON generation uses the OpenAI GPT function calling API. * It provides a single function specification and instructs the model to provide parameters for calling the function. * The result is returned as parsed JSON. * * @see https://platform.openai.com/docs/guides/gpt/function-calling */ generateJsonResponse(prompt, options) { const settingsWithFunctionCall = Object.assign({}, options, { functionCall: prompt.functionCall, functions: prompt.functions, }); return this.callAPI(prompt.messages, { responseFormat: exports.OpenAIChatResponseFormat.json, functionId: options?.functionId, settings: settingsWithFunctionCall, run: options?.run, }); } mapPrompt(promptMapping) { return new PromptMappingTextGenerationModel_js_1.PromptMappingTextGenerationModel({ model: this.withStopTokens(promptMapping.stopTokens), promptMapping, }); } withSettings(additionalSettings) { return new OpenAIChatModel(Object.assign({}, this.settings, additionalSettings)); } get maxCompletionTokens() { return this.settings.maxTokens; } withMaxCompletionTokens(maxCompletionTokens) { return this.withSettings({ maxTokens: maxCompletionTokens }); } withStopTokens(stopTokens) { return this.withSettings({ stop: stopTokens }); } } exports.OpenAIChatModel = OpenAIChatModel; const openAIChatResponseSchema = zod_1.default.object({ id: zod_1.default.string(), object: zod_1.default.literal("chat.completion"), created: zod_1.default.number(), model: zod_1.default.string(), choices: zod_1.default.array(zod_1.default.object({ message: zod_1.default.object({ role: zod_1.default.literal("assistant"), content: zod_1.default.string().nullable(), function_call: zod_1.default .object({ name: zod_1.default.string(), arguments: zod_1.default.string(), }) .optional(), }), index: zod_1.default.number(), logprobs: zod_1.default.nullable(zod_1.default.any()), finish_reason: zod_1.default.string(), })), usage: zod_1.default.object({ prompt_tokens: zod_1.default.number(), completion_tokens: zod_1.default.number(), total_tokens: zod_1.default.number(), }), }); async function callOpenAIChatCompletionAPI({ baseUrl = "https://api.openai.com/v1", abortSignal, responseFormat, apiKey, model, messages, functions, functionCall, temperature, topP, n, stop, maxTokens, presencePenalty, frequencyPenalty, user, }) { return (0, postToApi_js_1.postJsonToApi)({ url: `${baseUrl}/chat/completions`, apiKey, body: { stream: responseFormat.stream, model, messages, functions, function_call: functionCall, temperature, top_p: topP, n, stop, max_tokens: maxTokens, presence_penalty: presencePenalty, frequency_penalty: frequencyPenalty, user, }, failedResponseHandler: OpenAIError_js_1.failedOpenAICallResponseHandler, successfulResponseHandler: responseFormat.handler, abortSignal, }); } exports.OpenAIChatResponseFormat = { /** * Returns the response as a JSON object. */ json: { stream: false, handler: (0, postToApi_js_1.createJsonResponseHandler)(openAIChatResponseSchema), }, /** * Returns an async iterable over the text deltas (only the tex different of the first choice). */ deltaIterable: { stream: true, handler: async ({ response }) => (0, OpenAIChatStreamIterable_js_1.createOpenAIChatFullDeltaIterableQueue)(response.body), }, };