UNPKG

@langchain/core

Version:
412 lines (411 loc) 12.4 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_runtime = require("../_virtual/_rolldown/runtime.cjs"); const require_ai = require("../messages/ai.cjs"); //#region src/language_models/compat.ts /** * Compatibility bridge: converts legacy `_streamResponseChunks` * (`ChatGenerationChunk` / `AIMessageChunk`) output to the new * `ChatModelStreamEvent` protocol. * * @module */ var compat_exports = /* @__PURE__ */ require_runtime.__exportAll({ convertChunksToEvents: () => convertChunksToEvents, finalizeContentBlock: () => finalizeContentBlock }); const MIME_TYPE_BY_AUDIO_FORMAT = { wav: "audio/wav", mp3: "audio/mpeg", flac: "audio/flac", opus: "audio/opus", aac: "audio/aac", pcm16: "audio/pcm" }; const MIME_TYPE_BY_IMAGE_FORMAT = { png: "image/png", jpeg: "image/jpeg", jpg: "image/jpeg", webp: "image/webp", gif: "image/gif" }; function nextBlockIndex(activeBlocks) { let next = 0; for (const index of activeBlocks.keys()) if (index >= next) next = index + 1; return next; } function getAdditionalKwargs(message) { const additional = message.additional_kwargs; return additional != null && typeof additional === "object" ? additional : {}; } function extractImageBlocksFromToolOutputs(message) { const toolOutputs = getAdditionalKwargs(message).tool_outputs; if (!Array.isArray(toolOutputs)) return []; const blocks = []; for (const entry of toolOutputs) { if (entry == null || typeof entry !== "object") continue; const record = entry; if (record.type !== "image_generation_call") continue; const data = typeof record.result === "string" ? record.result : void 0; const url = typeof record.url === "string" ? record.url : void 0; if (data == null && url == null) continue; const outputFormat = typeof record.output_format === "string" ? record.output_format.toLowerCase() : void 0; const mimeType = (outputFormat != null ? MIME_TYPE_BY_IMAGE_FORMAT[outputFormat] : void 0) ?? "image/png"; blocks.push({ type: "image", ...typeof record.id === "string" ? { id: record.id } : {}, ...url != null ? { url } : {}, ...data != null ? { data } : {}, mimeType }); } return blocks; } /** * Get the audio payload from the message. * * This handles the OpenAI-shaped `additional_kwargs.audio` payload used by * legacy chunk streams; other providers must normalize into this shape first. * * @param message - The message to get the audio payload from. * @returns The audio payload. * @internal */ function getAudioPayload(message) { const audio = getAdditionalKwargs(message).audio; if (audio == null || typeof audio !== "object") return void 0; const record = audio; const data = typeof record.data === "string" ? record.data : void 0; const url = typeof record.url === "string" ? record.url : void 0; const transcript = typeof record.transcript === "string" ? record.transcript : void 0; if (data == null && url == null && transcript == null) return void 0; const explicitMimeType = typeof record.mime_type === "string" ? record.mime_type : typeof record.mimeType === "string" ? record.mimeType : void 0; const format = typeof record.format === "string" ? record.format.toLowerCase() : void 0; const mimeType = explicitMimeType ?? (format != null ? MIME_TYPE_BY_AUDIO_FORMAT[format] : void 0) ?? (data != null ? "audio/wav" : "audio/pcm"); return { ...typeof record.id === "string" ? { id: record.id } : {}, ...data != null ? { data } : {}, ...url != null ? { url } : {}, ...transcript != null ? { transcript } : {}, mimeType }; } /** * Convert an async iterable of legacy `ChatGenerationChunk`s into * `ChatModelStreamEvent`s with typed deltas. */ async function* convertChunksToEvents(chunks, options) { const activeBlocks = /* @__PURE__ */ new Map(); let messageStarted = false; let lastUsage; let audioStream; const emittedImageKeys = /* @__PURE__ */ new Set(); for await (const chunk of chunks) { options?.signal?.throwIfAborted(); const msg = chunk.message; let usageHandledInStart = false; if (!messageStarted) { messageStarted = true; const startEvent = { event: "message-start", id: msg.id ?? void 0 }; if (require_ai.AIMessageChunk.isInstance(msg) && msg.usage_metadata) { startEvent.usage = msg.usage_metadata; lastUsage = { ...msg.usage_metadata }; usageHandledInStart = true; } yield startEvent; } const content = msg.content; if (typeof content === "string") { if (content !== "") { const blockIndex = 0; if (!activeBlocks.has(blockIndex)) { const initial = { type: "text", text: "" }; activeBlocks.set(blockIndex, { type: "text", accumulated: initial }); yield { event: "content-block-start", index: blockIndex, content: initial }; } const block = activeBlocks.get(blockIndex); block.accumulated = { ...block.accumulated, text: (block.accumulated.text ?? "") + content }; yield { event: "content-block-delta", index: blockIndex, delta: { type: "text-delta", text: content } }; } } else if (Array.isArray(content)) for (const part of content) { const blockIndex = typeof part.index === "number" ? part.index : activeBlocks.size; if (!activeBlocks.has(blockIndex)) { activeBlocks.set(blockIndex, { type: part.type, accumulated: { ...part } }); yield { event: "content-block-start", index: blockIndex, content: { ...part } }; } else { const block = activeBlocks.get(blockIndex); const delta = contentBlockToDelta(part); block.accumulated = applyDeltaToBlock(block.accumulated, delta); yield { event: "content-block-delta", index: blockIndex, delta }; } } if (require_ai.AIMessageChunk.isInstance(msg) && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) for (const toolChunk of msg.tool_call_chunks) { const blockIndex = typeof toolChunk.index === "number" ? toolChunk.index : activeBlocks.size; if (!activeBlocks.has(blockIndex)) { const initial = { type: "tool_call_chunk", id: toolChunk.id, name: toolChunk.name, args: "", index: blockIndex }; activeBlocks.set(blockIndex, { type: "tool_call_chunk", accumulated: initial }); yield { event: "content-block-start", index: blockIndex, content: initial }; } const acc = activeBlocks.get(blockIndex).accumulated; if (toolChunk.id != null) acc.id = toolChunk.id; if (toolChunk.name != null) acc.name = toolChunk.name; acc.args = (acc.args ?? "") + (toolChunk.args ?? ""); yield { event: "content-block-delta", index: blockIndex, delta: { type: "block-delta", fields: { type: "tool_call_chunk", ..."id" in acc && acc.id != null ? { id: acc.id } : {}, ..."name" in acc && acc.name != null ? { name: acc.name } : {}, args: acc.args } } }; } const audioPayload = getAudioPayload(msg); if (audioPayload != null) { if (audioStream == null) { const index = nextBlockIndex(activeBlocks); audioStream = { index, id: audioPayload.id, mimeType: audioPayload.mimeType, transcript: "" }; const initial = { type: "audio", ...audioPayload.id != null ? { id: audioPayload.id } : {}, ...audioPayload.url != null ? { url: audioPayload.url } : {}, data: "", mimeType: audioPayload.mimeType }; activeBlocks.set(index, { type: "audio", accumulated: initial }); yield { event: "content-block-start", index, content: initial }; } const activeAudio = activeBlocks.get(audioStream.index); if (activeAudio != null) { const accumulated = activeAudio.accumulated; if (audioPayload.id != null && audioStream.id == null) { audioStream.id = audioPayload.id; accumulated.id = audioPayload.id; } if (audioPayload.transcript != null) { audioStream.transcript += audioPayload.transcript; accumulated.transcript = audioStream.transcript; yield { event: "content-block-delta", index: audioStream.index, delta: { type: "block-delta", fields: { type: "audio", transcript: audioStream.transcript } } }; } if (audioPayload.data != null && audioPayload.data.length > 0) { accumulated.data = (accumulated.data ?? "") + audioPayload.data; yield { event: "content-block-delta", index: audioStream.index, delta: { type: "data-delta", data: audioPayload.data, encoding: "base64" } }; } } } for (const imageBlock of extractImageBlocksFromToolOutputs(msg)) { const imageRecord = imageBlock; const imageKey = imageRecord.id ?? imageRecord.url ?? (imageRecord.data != null ? `${imageRecord.data.length}:${imageRecord.data.slice(0, 32)}` : void 0); if (imageKey != null && emittedImageKeys.has(imageKey)) continue; if (imageKey != null) emittedImageKeys.add(imageKey); const index = nextBlockIndex(activeBlocks); activeBlocks.set(index, { type: "image", accumulated: imageBlock }); yield { event: "content-block-start", index, content: imageBlock }; } if (!usageHandledInStart && require_ai.AIMessageChunk.isInstance(msg) && msg.usage_metadata) { const chunkUsage = msg.usage_metadata; if (!lastUsage) lastUsage = { ...chunkUsage }; else lastUsage = { input_tokens: lastUsage.input_tokens + chunkUsage.input_tokens, output_tokens: lastUsage.output_tokens + chunkUsage.output_tokens, total_tokens: lastUsage.total_tokens + chunkUsage.total_tokens }; yield { event: "usage", usage: { ...lastUsage } }; } } for (const [index, block] of activeBlocks) yield { event: "content-block-finish", index, content: finalizeContentBlock(block.accumulated) }; yield { event: "message-finish", reason: "stop", ...lastUsage ? { usage: lastUsage } : {} }; } /** * Apply a typed delta to an accumulated content block. * @internal */ function applyDeltaToBlock(block, delta) { switch (delta.type) { case "text-delta": if (block.type === "text") return { ...block, text: (block.text ?? "") + delta.text }; return block; case "reasoning-delta": if (block.type === "thinking") return { ...block, thinking: (block.thinking ?? "") + delta.reasoning }; if (block.type === "reasoning") return { ...block, reasoning: (block.reasoning ?? "") + delta.reasoning }; return block; case "data-delta": return { ...block, data: (block.data ?? "") + delta.data }; case "block-delta": return { ...block, ...delta.fields }; default: throw new Error(`Unknown delta type: ${JSON.stringify(delta)}`); } } function contentBlockToDelta(block) { if (block.type === "text") return { type: "text-delta", text: block.text }; if (block.type === "reasoning") return { type: "reasoning-delta", reasoning: block.reasoning }; if (block.type === "thinking" && typeof block.thinking === "string") return { type: "reasoning-delta", reasoning: block.thinking }; if (typeof block.data === "string") return { type: "data-delta", data: block.data, encoding: "base64" }; if (typeof block.type === "string") return { type: "block-delta", fields: { ...block } }; throw new Error(`Unsupported content block delta: ${JSON.stringify(block)}`); } /** * Finalize a content block for the finish event. * For tool calls, parse the accumulated JSON args string. */ function finalizeContentBlock(block) { if (block.type === "tool_call_chunk") { const chunk = block; let parsedArgs; try { parsedArgs = JSON.parse(chunk.args ?? "{}"); } catch { return { type: "invalid_tool_call", id: chunk.id, name: chunk.name, args: chunk.args, error: "Failed to parse tool call arguments as JSON" }; } return { type: "tool_call", id: chunk.id, name: chunk.name, args: parsedArgs }; } return block; } //#endregion Object.defineProperty(exports, "compat_exports", { enumerable: true, get: function() { return compat_exports; } }); exports.convertChunksToEvents = convertChunksToEvents; exports.finalizeContentBlock = finalizeContentBlock; //# sourceMappingURL=compat.cjs.map