"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SimpleChatModel = exports.BaseChatModel = exports.createChatMessageChunkEncoderStream = void 0;
const zod_to_json_schema_1 = require("zod-to-json-schema");
const index_js_1 = require("../messages/index.cjs");
const outputs_js_1 = require("../outputs.cjs");
const base_js_1 = require("./base.cjs");
const manager_js_1 = require("../callbacks/manager.cjs");
const base_js_2 = require("../runnables/base.cjs");
const event_stream_js_1 = require("../tracers/event_stream.cjs");
const log_stream_js_1 = require("../tracers/log_stream.cjs");
const stream_js_1 = require("../utils/stream.cjs");
const passthrough_js_1 = require("../runnables/passthrough.cjs");
const is_zod_schema_js_1 = require("../utils/types/is_zod_schema.cjs");
/**
 * Creates a transform stream for encoding chat message chunks.
 * @deprecated Use {@link BytesOutputParser} instead
 * @returns A TransformStream instance that encodes chat message chunks.
 */
function createChatMessageChunkEncoderStream() {
    const textEncoder = new TextEncoder();
    return new TransformStream({
        transform(chunk, controller) {
            controller.enqueue(textEncoder.encode(typeof chunk.content === "string"
                ? chunk.content
                : JSON.stringify(chunk.content)));
        },
    });
}
exports.createChatMessageChunkEncoderStream = createChatMessageChunkEncoderStream;
/**
 * Base class for chat models. It extends the BaseLanguageModel class and
 * provides methods for generating chat based on input messages.
 */
class BaseChatModel extends base_js_1.BaseLanguageModel {
    constructor(fields) {
        super(fields);
        // Only ever instantiated in main LangChain
        Object.defineProperty(this, "lc_namespace", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: ["langchain", "chat_models", this._llmType()]
        });
    }
    _separateRunnableConfigFromCallOptionsCompat(options) {
        // For backwards compat, keep `signal` in both runnableConfig and callOptions
        const [runnableConfig, callOptions] = super._separateRunnableConfigFromCallOptions(options);
        callOptions.signal = runnableConfig.signal;
        return [runnableConfig, callOptions];
    }
    /**
     * Invokes the chat model with a single input.
     * @param input The input for the language model.
     * @param options The call options.
     * @returns A Promise that resolves to a BaseMessageChunk.
     */
    async invoke(input, options) {
        const promptValue = BaseChatModel._convertInputToPromptValue(input);
        const result = await this.generatePrompt([promptValue], options, options?.callbacks);
        const chatGeneration = result.generations[0][0];
        // TODO: Remove cast after figuring out inheritance
        return chatGeneration.message;
    }
    // eslint-disable-next-line require-yield
    async *_streamResponseChunks(_messages, _options, _runManager) {
        throw new Error("Not implemented.");
    }
    async *_streamIterator(input, options) {
        // Subclass check required to avoid double callbacks with default implementation
        if (this._streamResponseChunks ===
            BaseChatModel.prototype._streamResponseChunks) {
            yield this.invoke(input, options);
        }
        else {
            const prompt = BaseChatModel._convertInputToPromptValue(input);
            const messages = prompt.toChatMessages();
            const [runnableConfig, callOptions] = this._separateRunnableConfigFromCallOptionsCompat(options);
            const inheritableMetadata = {
                ...runnableConfig.metadata,
                ...this.getLsParams(callOptions),
            };
            const callbackManager_ = await manager_js_1.CallbackManager.configure(runnableConfig.callbacks, this.callbacks, runnableConfig.tags, this.tags, inheritableMetadata, this.metadata, { verbose: this.verbose });
            const extra = {
                options: callOptions,
                invocation_params: this?.invocationParams(callOptions),
                batch_size: 1,
            };
            const runManagers = await callbackManager_?.handleChatModelStart(this.toJSON(), [messages], runnableConfig.runId, undefined, extra, undefined, undefined, runnableConfig.runName);
            let generationChunk;
            try {
                for await (const chunk of this._streamResponseChunks(messages, callOptions, runManagers?.[0])) {
                    if (chunk.message.id == null) {
                        const runId = runManagers?.at(0)?.runId;
                        if (runId != null)
                            chunk.message._updateId(`run-${runId}`);
                    }
                    chunk.message.response_metadata = {
                        ...chunk.generationInfo,
                        ...chunk.message.response_metadata,
                    };
                    yield chunk.message;
                    if (!generationChunk) {
                        generationChunk = chunk;
                    }
                    else {
                        generationChunk = generationChunk.concat(chunk);
                    }
                }
            }
            catch (err) {
                await Promise.all((runManagers ?? []).map((runManager) => runManager?.handleLLMError(err)));
                throw err;
            }
            await Promise.all((runManagers ?? []).map((runManager) => runManager?.handleLLMEnd({
                // TODO: Remove cast after figuring out inheritance
                generations: [[generationChunk]],
            })));
        }
    }
    getLsParams(options) {
        return {
            ls_model_type: "chat",
            ls_stop: options.stop,
        };
    }
    /** @ignore */
    async _generateUncached(messages, parsedOptions, handledOptions) {
        const baseMessages = messages.map((messageList) => messageList.map(index_js_1.coerceMessageLikeToMessage));
        const inheritableMetadata = {
            ...handledOptions.metadata,
            ...this.getLsParams(parsedOptions),
        };
        // create callback manager and start run
        const callbackManager_ = await manager_js_1.CallbackManager.configure(handledOptions.callbacks, this.callbacks, handledOptions.tags, this.tags, inheritableMetadata, this.metadata, { verbose: this.verbose });
        const extra = {
            options: parsedOptions,
            invocation_params: this?.invocationParams(parsedOptions),
            batch_size: 1,
        };
        const runManagers = await callbackManager_?.handleChatModelStart(this.toJSON(), baseMessages, handledOptions.runId, undefined, extra, undefined, undefined, handledOptions.runName);
        const generations = [];
        const llmOutputs = [];
        // Even if stream is not explicitly called, check if model is implicitly
        // called from streamEvents() or streamLog() to get all streamed events.
        // Bail out if _streamResponseChunks not overridden
        const hasStreamingHandler = !!runManagers?.[0].handlers.find((handler) => {
            return (0, event_stream_js_1.isStreamEventsHandler)(handler) || (0, log_stream_js_1.isLogStreamHandler)(handler);
        });
        if (hasStreamingHandler &&
            baseMessages.length === 1 &&
            this._streamResponseChunks !==
                BaseChatModel.prototype._streamResponseChunks) {
            try {
                const stream = await this._streamResponseChunks(baseMessages[0], parsedOptions, runManagers?.[0]);
                let aggregated;
                for await (const chunk of stream) {
                    if (chunk.message.id == null) {
                        const runId = runManagers?.at(0)?.runId;
                        if (runId != null)
                            chunk.message._updateId(`run-${runId}`);
                    }
                    if (aggregated === undefined) {
                        aggregated = chunk;
                    }
                    else {
                        aggregated = (0, stream_js_1.concat)(aggregated, chunk);
                    }
                }
                if (aggregated === undefined) {
                    throw new Error("Received empty response from chat model call.");
                }
                generations.push([aggregated]);
                await runManagers?.[0].handleLLMEnd({
                    generations,
                    llmOutput: {},
                });
            }
            catch (e) {
                await runManagers?.[0].handleLLMError(e);
                throw e;
            }
        }
        else {
            // generate results
            const results = await Promise.allSettled(baseMessages.map((messageList, i) => this._generate(messageList, { ...parsedOptions, promptIndex: i }, runManagers?.[i])));
            // handle results
            await Promise.all(results.map(async (pResult, i) => {
                if (pResult.status === "fulfilled") {
                    const result = pResult.value;
                    for (const generation of result.generations) {
                        if (generation.message.id == null) {
                            const runId = runManagers?.at(0)?.runId;
                            if (runId != null)
                                generation.message._updateId(`run-${runId}`);
                        }
                        generation.message.response_metadata = {
                            ...generation.generationInfo,
                            ...generation.message.response_metadata,
                        };
                    }
                    if (result.generations.length === 1) {
                        result.generations[0].message.response_metadata = {
                            ...result.llmOutput,
                            ...result.generations[0].message.response_metadata,
                        };
                    }
                    generations[i] = result.generations;
                    llmOutputs[i] = result.llmOutput;
                    return runManagers?.[i]?.handleLLMEnd({
                        generations: [result.generations],
                        llmOutput: result.llmOutput,
                    });
                }
                else {
                    // status === "rejected"
                    await runManagers?.[i]?.handleLLMError(pResult.reason);
                    return Promise.reject(pResult.reason);
                }
            }));
        }
        // create combined output
        const output = {
            generations,
            llmOutput: llmOutputs.length
                ? this._combineLLMOutput?.(...llmOutputs)
                : undefined,
        };
        Object.defineProperty(output, outputs_js_1.RUN_KEY, {
            value: runManagers
                ? { runIds: runManagers?.map((manager) => manager.runId) }
                : undefined,
            configurable: true,
        });
        return output;
    }
    async _generateCached({ messages, cache, llmStringKey, parsedOptions, handledOptions, }) {
        const baseMessages = messages.map((messageList) => messageList.map(index_js_1.coerceMessageLikeToMessage));
        const inheritableMetadata = {
            ...handledOptions.metadata,
            ...this.getLsParams(parsedOptions),
        };
        // create callback manager and start run
        const callbackManager_ = await manager_js_1.CallbackManager.configure(handledOptions.callbacks, this.callbacks, handledOptions.tags, this.tags, inheritableMetadata, this.metadata, { verbose: this.verbose });
        const extra = {
            options: parsedOptions,
            invocation_params: this?.invocationParams(parsedOptions),
            batch_size: 1,
            cached: true,
        };
        const runManagers = await callbackManager_?.handleChatModelStart(this.toJSON(), baseMessages, handledOptions.runId, undefined, extra, undefined, undefined, handledOptions.runName);
        // generate results
        const missingPromptIndices = [];
        const results = await Promise.allSettled(baseMessages.map(async (baseMessage, index) => {
            // Join all content into one string for the prompt index
            const prompt = BaseChatModel._convertInputToPromptValue(baseMessage).toString();
            const result = await cache.lookup(prompt, llmStringKey);
            if (result == null) {
                missingPromptIndices.push(index);
            }
            return result;
        }));
        // Map run managers to the results before filtering out null results
        // Null results are just absent from the cache.
        const cachedResults = results
            .map((result, index) => ({ result, runManager: runManagers?.[index] }))
            .filter(({ result }) => (result.status === "fulfilled" && result.value != null) ||
            result.status === "rejected");
        // Handle results and call run managers
        const generations = [];
        await Promise.all(cachedResults.map(async ({ result: promiseResult, runManager }, i) => {
            if (promiseResult.status === "fulfilled") {
                const result = promiseResult.value;
                generations[i] = result;
                if (result.length) {
                    await runManager?.handleLLMNewToken(result[0].text);
                }
                return runManager?.handleLLMEnd({
                    generations: [result],
                });
            }
            else {
                // status === "rejected"
                await runManager?.handleLLMError(promiseResult.reason);
                return Promise.reject(promiseResult.reason);
            }
        }));
        const output = {
            generations,
            missingPromptIndices,
        };
        // This defines RUN_KEY as a non-enumerable property on the output object
        // so that it is not serialized when the output is stringified, and so that
        // it isnt included when listing the keys of the output object.
        Object.defineProperty(output, outputs_js_1.RUN_KEY, {
            value: runManagers
                ? { runIds: runManagers?.map((manager) => manager.runId) }
                : undefined,
            configurable: true,
        });
        return output;
    }
    /**
     * Generates chat based on the input messages.
     * @param messages An array of arrays of BaseMessage instances.
     * @param options The call options or an array of stop sequences.
     * @param callbacks The callbacks for the language model.
     * @returns A Promise that resolves to an LLMResult.
     */
    async generate(messages, options, callbacks) {
        // parse call options
        let parsedOptions;
        if (Array.isArray(options)) {
            parsedOptions = { stop: options };
        }
        else {
            parsedOptions = options;
        }
        const baseMessages = messages.map((messageList) => messageList.map(index_js_1.coerceMessageLikeToMessage));
        const [runnableConfig, callOptions] = this._separateRunnableConfigFromCallOptionsCompat(parsedOptions);
        runnableConfig.callbacks = runnableConfig.callbacks ?? callbacks;
        if (!this.cache) {
            return this._generateUncached(baseMessages, callOptions, runnableConfig);
        }
        const { cache } = this;
        const llmStringKey = this._getSerializedCacheKeyParametersForCall(callOptions);
        const { generations, missingPromptIndices } = await this._generateCached({
            messages: baseMessages,
            cache,
            llmStringKey,
            parsedOptions: callOptions,
            handledOptions: runnableConfig,
        });
        let llmOutput = {};
        if (missingPromptIndices.length > 0) {
            const results = await this._generateUncached(missingPromptIndices.map((i) => baseMessages[i]), callOptions, runnableConfig);
            await Promise.all(results.generations.map(async (generation, index) => {
                const promptIndex = missingPromptIndices[index];
                generations[promptIndex] = generation;
                // Join all content into one string for the prompt index
                const prompt = BaseChatModel._convertInputToPromptValue(baseMessages[promptIndex]).toString();
                return cache.update(prompt, llmStringKey, generation);
            }));
            llmOutput = results.llmOutput ?? {};
        }
        return { generations, llmOutput };
    }
    /**
     * Get the parameters used to invoke the model
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    invocationParams(_options) {
        return {};
    }
    _modelType() {
        return "base_chat_model";
    }
    /**
     * @deprecated
     * Return a json-like object representing this LLM.
     */
    serialize() {
        return {
            ...this.invocationParams(),
            _type: this._llmType(),
            _model: this._modelType(),
        };
    }
    /**
     * Generates a prompt based on the input prompt values.
     * @param promptValues An array of BasePromptValue instances.
     * @param options The call options or an array of stop sequences.
     * @param callbacks The callbacks for the language model.
     * @returns A Promise that resolves to an LLMResult.
     */
    async generatePrompt(promptValues, options, callbacks) {
        const promptMessages = promptValues.map((promptValue) => promptValue.toChatMessages());
        return this.generate(promptMessages, options, callbacks);
    }
    /**
     * @deprecated Use .invoke() instead. Will be removed in 0.2.0.
     *
     * Makes a single call to the chat model.
     * @param messages An array of BaseMessage instances.
     * @param options The call options or an array of stop sequences.
     * @param callbacks The callbacks for the language model.
     * @returns A Promise that resolves to a BaseMessage.
     */
    async call(messages, options, callbacks) {
        const result = await this.generate([messages.map(index_js_1.coerceMessageLikeToMessage)], options, callbacks);
        const generations = result.generations;
        return generations[0][0].message;
    }
    /**
     * @deprecated Use .invoke() instead. Will be removed in 0.2.0.
     *
     * Makes a single call to the chat model with a prompt value.
     * @param promptValue The value of the prompt.
     * @param options The call options or an array of stop sequences.
     * @param callbacks The callbacks for the language model.
     * @returns A Promise that resolves to a BaseMessage.
     */
    async callPrompt(promptValue, options, callbacks) {
        const promptMessages = promptValue.toChatMessages();
        return this.call(promptMessages, options, callbacks);
    }
    /**
     * @deprecated Use .invoke() instead. Will be removed in 0.2.0.
     *
     * Predicts the next message based on the input messages.
     * @param messages An array of BaseMessage instances.
     * @param options The call options or an array of stop sequences.
     * @param callbacks The callbacks for the language model.
     * @returns A Promise that resolves to a BaseMessage.
     */
    async predictMessages(messages, options, callbacks) {
        return this.call(messages, options, callbacks);
    }
    /**
     * @deprecated Use .invoke() instead. Will be removed in 0.2.0.
     *
     * Predicts the next message based on a text input.
     * @param text The text input.
     * @param options The call options or an array of stop sequences.
     * @param callbacks The callbacks for the language model.
     * @returns A Promise that resolves to a string.
     */
    async predict(text, options, callbacks) {
        const message = new index_js_1.HumanMessage(text);
        const result = await this.call([message], options, callbacks);
        if (typeof result.content !== "string") {
            throw new Error("Cannot use predict when output is not a string.");
        }
        return result.content;
    }
    withStructuredOutput(outputSchema, config) {
        if (typeof this.bindTools !== "function") {
            throw new Error(`Chat model must implement ".bindTools()" to use withStructuredOutput.`);
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const schema = outputSchema;
        const name = config?.name;
        const description = schema.description ?? "A function available to call.";
        const method = config?.method;
        const includeRaw = config?.includeRaw;
        if (method === "jsonMode") {
            throw new Error(`Base withStructuredOutput implementation only supports "functionCalling" as a method.`);
        }
        let functionName = name ?? "extract";
        let tools;
        if ((0, is_zod_schema_js_1.isZodSchema)(schema)) {
            tools = [
                {
                    type: "function",
                    function: {
                        name: functionName,
                        description,
                        parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(schema),
                    },
                },
            ];
        }
        else {
            if ("name" in schema) {
                functionName = schema.name;
            }
            tools = [
                {
                    type: "function",
                    function: {
                        name: functionName,
                        description,
                        parameters: schema,
                    },
                },
            ];
        }
        const llm = this.bindTools(tools);
        const outputParser = base_js_2.RunnableLambda.from((input) => {
            if (!input.tool_calls || input.tool_calls.length === 0) {
                throw new Error("No tool calls found in the response.");
            }
            const toolCall = input.tool_calls.find((tc) => tc.name === functionName);
            if (!toolCall) {
                throw new Error(`No tool call found with name ${functionName}.`);
            }
            return toolCall.args;
        });
        if (!includeRaw) {
            return llm.pipe(outputParser).withConfig({
                runName: "StructuredOutput",
            });
        }
        const parserAssign = passthrough_js_1.RunnablePassthrough.assign({
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            parsed: (input, config) => outputParser.invoke(input.raw, config),
        });
        const parserNone = passthrough_js_1.RunnablePassthrough.assign({
            parsed: () => null,
        });
        const parsedWithFallback = parserAssign.withFallbacks({
            fallbacks: [parserNone],
        });
        return base_js_2.RunnableSequence.from([
            {
                raw: llm,
            },
            parsedWithFallback,
        ]).withConfig({
            runName: "StructuredOutputRunnable",
        });
    }
}
exports.BaseChatModel = BaseChatModel;
/**
 * An abstract class that extends BaseChatModel and provides a simple
 * implementation of _generate.
 */
class SimpleChatModel extends BaseChatModel {
    async _generate(messages, options, runManager) {
        const text = await this._call(messages, options, runManager);
        const message = new index_js_1.AIMessage(text);
        if (typeof message.content !== "string") {
            throw new Error("Cannot generate with a simple chat model when output is not a string.");
        }
        return {
            generations: [
                {
                    text: message.content,
                    message,
                },
            ],
        };
    }
}
exports.SimpleChatModel = SimpleChatModel;
