UNPKG

@langchain/openai

Version:
620 lines (618 loc) 21 kB
import { wrapOpenAIClientError } from "../utils/client.js"; import { _convertToOpenAITool, convertResponsesCustomTool, formatFunctionDefinitions, hasProviderToolDefinition, isBuiltInTool, isCustomTool } from "../utils/tools.js"; import { isReasoningModel, messageToOpenAIRole } from "../utils/misc.js"; import { getEndpoint, getHeadersWithUserAgent } from "../utils/azure.js"; import { getStructuredOutputMethod, interopZodResponseFormat } from "../utils/output.js"; import profiles_default from "./profiles.js"; import { OpenAI as OpenAI$1 } from "openai"; import { getSchemaDescription, isInteropZodSchema } from "@langchain/core/utils/types"; import { toJsonSchema } from "@langchain/core/utils/json_schema"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { BaseChatModel } from "@langchain/core/language_models/chat_models"; import { isOpenAITool } from "@langchain/core/language_models/base"; import { RunnableLambda, RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables"; import { JsonOutputParser, StructuredOutputParser } from "@langchain/core/output_parsers"; import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools"; //#region src/chat_models/base.ts /** @internal */ var BaseChatOpenAI = class extends BaseChatModel { temperature; topP; frequencyPenalty; presencePenalty; n; logitBias; model = "gpt-3.5-turbo"; modelKwargs; stop; stopSequences; user; timeout; streaming = false; streamUsage = true; maxTokens; logprobs; topLogprobs; apiKey; organization; __includeRawResponse; /** @internal */ client; /** @internal */ clientConfig; /** * Whether the model supports the `strict` argument when passing in tools. * If `undefined` the `strict` argument will not be passed to OpenAI. */ supportsStrictToolCalling; audio; modalities; reasoning; /** * Must be set to `true` in tenancies with Zero Data Retention. Setting to `true` will disable * output storage in the Responses API, but this DOES NOT enable Zero Data Retention in your * OpenAI organization or project. This must be configured directly with OpenAI. * * See: * https://platform.openai.com/docs/guides/your-data * https://platform.openai.com/docs/api-reference/responses/create#responses-create-store * * @default false */ zdrEnabled; /** * Service tier to use for this request. Can be "auto", "default", or "flex" or "priority". * Specifies the service tier for prioritization and latency optimization. */ service_tier; /** * Used by OpenAI to cache responses for similar requests to optimize your cache * hit rates. * [Learn more](https://platform.openai.com/docs/guides/prompt-caching). */ promptCacheKey; /** * Used by OpenAI to set cache retention time */ promptCacheRetention; /** * The verbosity of the model's response. */ verbosity; defaultOptions; _llmType() { return "openai"; } static lc_name() { return "ChatOpenAI"; } get callKeys() { return [ ...super.callKeys, "options", "function_call", "functions", "tools", "tool_choice", "promptIndex", "response_format", "seed", "reasoning", "service_tier" ]; } lc_serializable = true; get lc_secrets() { return { apiKey: "OPENAI_API_KEY", organization: "OPENAI_ORGANIZATION" }; } get lc_aliases() { return { apiKey: "openai_api_key", modelName: "model" }; } get lc_serializable_keys() { return [ "configuration", "logprobs", "topLogprobs", "prefixMessages", "supportsStrictToolCalling", "modalities", "audio", "temperature", "maxTokens", "topP", "frequencyPenalty", "presencePenalty", "n", "logitBias", "user", "streaming", "streamUsage", "model", "modelName", "modelKwargs", "stop", "stopSequences", "timeout", "apiKey", "cache", "maxConcurrency", "maxRetries", "verbose", "callbacks", "tags", "metadata", "disableStreaming", "zdrEnabled", "reasoning", "promptCacheKey", "promptCacheRetention", "verbosity" ]; } getLsParams(options) { const params = this.invocationParams(options); return { ls_provider: "openai", ls_model_name: this.model, ls_model_type: "chat", ls_temperature: params.temperature ?? void 0, ls_max_tokens: params.max_tokens ?? void 0, ls_stop: options.stop }; } /** @ignore */ _identifyingParams() { return { model_name: this.model, ...this.invocationParams(), ...this.clientConfig }; } /** * Get the identifying parameters for the model */ identifyingParams() { return this._identifyingParams(); } constructor(fields) { super(fields ?? {}); const configApiKey = typeof fields?.configuration?.apiKey === "string" || typeof fields?.configuration?.apiKey === "function" ? fields?.configuration?.apiKey : void 0; this.apiKey = fields?.apiKey ?? configApiKey ?? getEnvironmentVariable("OPENAI_API_KEY"); this.organization = fields?.configuration?.organization ?? getEnvironmentVariable("OPENAI_ORGANIZATION"); this.model = fields?.model ?? fields?.modelName ?? this.model; this.modelKwargs = fields?.modelKwargs ?? {}; this.timeout = fields?.timeout; this.temperature = fields?.temperature ?? this.temperature; this.topP = fields?.topP ?? this.topP; this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty; this.presencePenalty = fields?.presencePenalty ?? this.presencePenalty; this.logprobs = fields?.logprobs; this.topLogprobs = fields?.topLogprobs; this.n = fields?.n ?? this.n; this.logitBias = fields?.logitBias; this.stop = fields?.stopSequences ?? fields?.stop; this.stopSequences = this.stop; this.user = fields?.user; this.__includeRawResponse = fields?.__includeRawResponse; this.audio = fields?.audio; this.modalities = fields?.modalities; this.reasoning = fields?.reasoning; this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens; this.promptCacheKey = fields?.promptCacheKey ?? this.promptCacheKey; this.promptCacheRetention = fields?.promptCacheRetention ?? this.promptCacheRetention; this.verbosity = fields?.verbosity ?? this.verbosity; this.disableStreaming = fields?.disableStreaming === true; this.streaming = fields?.streaming === true; if (this.disableStreaming) this.streaming = false; if (fields?.streaming === false) this.disableStreaming = true; this.streamUsage = fields?.streamUsage ?? this.streamUsage; if (this.disableStreaming) this.streamUsage = false; this.clientConfig = { apiKey: this.apiKey, organization: this.organization, dangerouslyAllowBrowser: true, ...fields?.configuration }; if (fields?.supportsStrictToolCalling !== void 0) this.supportsStrictToolCalling = fields.supportsStrictToolCalling; if (fields?.service_tier !== void 0) this.service_tier = fields.service_tier; this.zdrEnabled = fields?.zdrEnabled ?? false; } /** * Returns backwards compatible reasoning parameters from constructor params and call options * @internal */ _getReasoningParams(options) { if (!isReasoningModel(this.model)) return; let reasoning; if (this.reasoning !== void 0) reasoning = { ...reasoning, ...this.reasoning }; if (options?.reasoning !== void 0) reasoning = { ...reasoning, ...options.reasoning }; return reasoning; } /** * Returns an openai compatible response format from a set of options * @internal */ _getResponseFormat(resFormat) { if (resFormat && resFormat.type === "json_schema" && resFormat.json_schema.schema && isInteropZodSchema(resFormat.json_schema.schema)) return interopZodResponseFormat(resFormat.json_schema.schema, resFormat.json_schema.name, { description: resFormat.json_schema.description }); return resFormat; } _combineCallOptions(additionalOptions) { return { ...this.defaultOptions, ...additionalOptions ?? {} }; } /** @internal */ _getClientOptions(options) { if (!this.client) { const openAIEndpointConfig = { baseURL: this.clientConfig.baseURL }; const endpoint = getEndpoint(openAIEndpointConfig); const params = { ...this.clientConfig, baseURL: endpoint, timeout: this.timeout, maxRetries: 0 }; if (!params.baseURL) delete params.baseURL; params.defaultHeaders = getHeadersWithUserAgent(params.defaultHeaders); this.client = new OpenAI$1(params); } const requestOptions = { ...this.clientConfig, ...options }; return requestOptions; } _convertChatOpenAIToolToCompletionsTool(tool, fields) { if (isCustomTool(tool)) return convertResponsesCustomTool(tool.metadata.customTool); if (isOpenAITool(tool)) { if (fields?.strict !== void 0) return { ...tool, function: { ...tool.function, strict: fields.strict } }; return tool; } return _convertToOpenAITool(tool, fields); } bindTools(tools, kwargs) { let strict; if (kwargs?.strict !== void 0) strict = kwargs.strict; else if (this.supportsStrictToolCalling !== void 0) strict = this.supportsStrictToolCalling; return this.withConfig({ tools: tools.map((tool) => { if (isBuiltInTool(tool) || isCustomTool(tool)) return tool; if (hasProviderToolDefinition(tool)) return tool.extras.providerToolDefinition; return this._convertChatOpenAIToolToCompletionsTool(tool, { strict }); }), ...kwargs }); } async stream(input, options) { return super.stream(input, this._combineCallOptions(options)); } async invoke(input, options) { return super.invoke(input, this._combineCallOptions(options)); } /** @ignore */ _combineLLMOutput(...llmOutputs) { return llmOutputs.reduce((acc, llmOutput) => { if (llmOutput && llmOutput.tokenUsage) { acc.tokenUsage.completionTokens += llmOutput.tokenUsage.completionTokens ?? 0; acc.tokenUsage.promptTokens += llmOutput.tokenUsage.promptTokens ?? 0; acc.tokenUsage.totalTokens += llmOutput.tokenUsage.totalTokens ?? 0; } return acc; }, { tokenUsage: { completionTokens: 0, promptTokens: 0, totalTokens: 0 } }); } async getNumTokensFromMessages(messages) { let totalCount = 0; let tokensPerMessage = 0; let tokensPerName = 0; if (this.model === "gpt-3.5-turbo-0301") { tokensPerMessage = 4; tokensPerName = -1; } else { tokensPerMessage = 3; tokensPerName = 1; } const countPerMessage = await Promise.all(messages.map(async (message) => { const textCount = await this.getNumTokens(message.content); const roleCount = await this.getNumTokens(messageToOpenAIRole(message)); const nameCount = message.name !== void 0 ? tokensPerName + await this.getNumTokens(message.name) : 0; let count = textCount + tokensPerMessage + roleCount + nameCount; const openAIMessage = message; if (openAIMessage._getType() === "function") count -= 2; if (openAIMessage.additional_kwargs?.function_call) count += 3; if (openAIMessage?.additional_kwargs.function_call?.name) count += await this.getNumTokens(openAIMessage.additional_kwargs.function_call?.name); if (openAIMessage.additional_kwargs.function_call?.arguments) try { count += await this.getNumTokens(JSON.stringify(JSON.parse(openAIMessage.additional_kwargs.function_call?.arguments))); } catch (error) { console.error("Error parsing function arguments", error, JSON.stringify(openAIMessage.additional_kwargs.function_call)); count += await this.getNumTokens(openAIMessage.additional_kwargs.function_call?.arguments); } totalCount += count; return count; })); totalCount += 3; return { totalCount, countPerMessage }; } /** @internal */ async _getNumTokensFromGenerations(generations) { const generationUsages = await Promise.all(generations.map(async (generation) => { if (generation.message.additional_kwargs?.function_call) return (await this.getNumTokensFromMessages([generation.message])).countPerMessage[0]; else return await this.getNumTokens(generation.message.content); })); return generationUsages.reduce((a, b) => a + b, 0); } /** @internal */ async _getEstimatedTokenCountFromPrompt(messages, functions, function_call) { let tokens = (await this.getNumTokensFromMessages(messages)).totalCount; if (functions && function_call !== "auto") { const promptDefinitions = formatFunctionDefinitions(functions); tokens += await this.getNumTokens(promptDefinitions); tokens += 9; } if (functions && messages.find((m) => m._getType() === "system")) tokens -= 4; if (function_call === "none") tokens += 1; else if (typeof function_call === "object") tokens += await this.getNumTokens(function_call.name) + 4; return tokens; } /** * Moderate content using OpenAI's Moderation API. * * This method checks whether content violates OpenAI's content policy by * analyzing text for categories such as hate, harassment, self-harm, * sexual content, violence, and more. * * @param input - The text or array of texts to moderate * @param params - Optional parameters for the moderation request * @param params.model - The moderation model to use. Defaults to "omni-moderation-latest". * @param params.options - Additional options to pass to the underlying request * @returns A promise that resolves to the moderation response containing results for each input * * @example * ```typescript * const model = new ChatOpenAI({ model: "gpt-4o-mini" }); * * // Moderate a single text * const result = await model.moderateContent("This is a test message"); * console.log(result.results[0].flagged); // false * console.log(result.results[0].categories); // { hate: false, harassment: false, ... } * * // Moderate multiple texts * const results = await model.moderateContent([ * "Hello, how are you?", * "This is inappropriate content" * ]); * results.results.forEach((result, index) => { * console.log(`Text ${index + 1} flagged:`, result.flagged); * }); * * // Use a specific moderation model * const stableResult = await model.moderateContent( * "Test content", * { model: "omni-moderation-latest" } * ); * ``` */ async moderateContent(input, params) { const clientOptions = this._getClientOptions(params?.options); const moderationModel = params?.model ?? "omni-moderation-latest"; const moderationRequest = { input, model: moderationModel }; return this.caller.call(async () => { try { const response = await this.client.moderations.create(moderationRequest, clientOptions); return response; } catch (e) { const error = wrapOpenAIClientError(e); throw error; } }); } /** * Return profiling information for the model. * * Provides information about the model's capabilities and constraints, * including token limits, multimodal support, and advanced features like * tool calling and structured output. * * @returns {ModelProfile} An object describing the model's capabilities and constraints * * @example * ```typescript * const model = new ChatOpenAI({ model: "gpt-4o" }); * const profile = model.profile; * console.log(profile.maxInputTokens); // 128000 * console.log(profile.imageInputs); // true * ``` */ get profile() { return profiles_default[this.model] ?? {}; } /** @internal */ _getStructuredOutputMethod(config) { const ensuredConfig = { ...config }; if (!this.model.startsWith("gpt-3") && !this.model.startsWith("gpt-4-") && this.model !== "gpt-4") { if (ensuredConfig?.method === void 0) return "jsonSchema"; } else if (ensuredConfig.method === "jsonSchema") console.warn(`[WARNING]: JSON Schema is not supported for model "${this.model}". Falling back to tool calling.`); return ensuredConfig.method; } /** * Add structured output to the model. * * The OpenAI model family supports the following structured output methods: * - `jsonSchema`: Use the `response_format` field in the response to return a JSON schema. Only supported with the `gpt-4o-mini`, * `gpt-4o-mini-2024-07-18`, and `gpt-4o-2024-08-06` model snapshots and later. * - `functionCalling`: Function calling is useful when you are building an application that bridges the models and functionality * of your application. * - `jsonMode`: JSON mode is a more basic version of the Structured Outputs feature. While JSON mode ensures that model * output is valid JSON, Structured Outputs reliably matches the model's output to the schema you specify. * We recommend you use `functionCalling` or `jsonSchema` if it is supported for your use case. * * The default method is `functionCalling`. * * @see https://platform.openai.com/docs/guides/structured-outputs * @param outputSchema - The schema to use for structured output. * @param config - The structured output method options. * @returns The model with structured output. */ withStructuredOutput(outputSchema, config) { let llm; let outputParser; const { schema, name, includeRaw } = { ...config, schema: outputSchema }; if (config?.strict !== void 0 && config.method === "jsonMode") throw new Error("Argument `strict` is only supported for `method` = 'function_calling'"); const method = getStructuredOutputMethod(this.model, config?.method); if (method === "jsonMode") { if (isInteropZodSchema(schema)) outputParser = StructuredOutputParser.fromZodSchema(schema); else outputParser = new JsonOutputParser(); const asJsonSchema = toJsonSchema(schema); llm = this.withConfig({ outputVersion: "v0", response_format: { type: "json_object" }, ls_structured_output_format: { kwargs: { method: "json_mode" }, schema: { title: name ?? "extract", ...asJsonSchema } } }); } else if (method === "jsonSchema") { const openaiJsonSchemaParams = { name: name ?? "extract", description: getSchemaDescription(schema), schema, strict: config?.strict }; const asJsonSchema = toJsonSchema(openaiJsonSchemaParams.schema); llm = this.withConfig({ outputVersion: "v0", response_format: { type: "json_schema", json_schema: openaiJsonSchemaParams }, ls_structured_output_format: { kwargs: { method: "json_schema" }, schema: { title: openaiJsonSchemaParams.name, description: openaiJsonSchemaParams.description, ...asJsonSchema } } }); if (isInteropZodSchema(schema)) { const altParser = StructuredOutputParser.fromZodSchema(schema); outputParser = RunnableLambda.from((aiMessage) => { if ("parsed" in aiMessage.additional_kwargs) return aiMessage.additional_kwargs.parsed; return altParser; }); } else outputParser = new JsonOutputParser(); } else { let functionName = name ?? "extract"; if (isInteropZodSchema(schema)) { const asJsonSchema = toJsonSchema(schema); llm = this.withConfig({ outputVersion: "v0", tools: [{ type: "function", function: { name: functionName, description: asJsonSchema.description, parameters: asJsonSchema } }], tool_choice: { type: "function", function: { name: functionName } }, ls_structured_output_format: { kwargs: { method: "function_calling" }, schema: { title: functionName, ...asJsonSchema } }, ...config?.strict !== void 0 ? { strict: config.strict } : {} }); outputParser = new JsonOutputKeyToolsParser({ returnSingle: true, keyName: functionName, zodSchema: schema }); } else { let openAIFunctionDefinition; if (typeof schema.name === "string" && typeof schema.parameters === "object" && schema.parameters != null) { openAIFunctionDefinition = schema; functionName = schema.name; } else { functionName = schema.title ?? functionName; openAIFunctionDefinition = { name: functionName, description: schema.description ?? "", parameters: schema }; } const asJsonSchema = toJsonSchema(schema); llm = this.withConfig({ outputVersion: "v0", tools: [{ type: "function", function: openAIFunctionDefinition }], tool_choice: { type: "function", function: { name: functionName } }, ls_structured_output_format: { kwargs: { method: "function_calling" }, schema: { title: functionName, ...asJsonSchema } }, ...config?.strict !== void 0 ? { strict: config.strict } : {} }); outputParser = new JsonOutputKeyToolsParser({ returnSingle: true, keyName: functionName }); } } if (!includeRaw) return llm.pipe(outputParser); const parserAssign = RunnablePassthrough.assign({ parsed: (input, config$1) => outputParser.invoke(input.raw, config$1) }); const parserNone = RunnablePassthrough.assign({ parsed: () => null }); const parsedWithFallback = parserAssign.withFallbacks({ fallbacks: [parserNone] }); return RunnableSequence.from([{ raw: llm }, parsedWithFallback]); } }; //#endregion export { BaseChatOpenAI }; //# sourceMappingURL=base.js.map