ai-utils.js
Version:
Build AI applications, chatbots, and agents with JavaScript and TypeScript.
289 lines (288 loc) • 10.7 kB
JavaScript
;
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),
},
};