UNPKG

@langchain/core

Version:
515 lines (514 loc) • 19.9 kB
const require_data = require("./data.cjs"); const require_utils = require("./utils.cjs"); // src/messages/block_translators/openai.ts /** The refusal Chat Completions and Responses keep in `additional_kwargs`. */ function refusalFromAdditionalKwargs(message) { const refusal = message.additional_kwargs.refusal; return require_utils._isString(refusal) && refusal.length > 0 ? { type: "refusal", refusal } : void 0; } /** * Converts a ChatOpenAICompletions message to an array of v1 standard content blocks. * * This function processes an AI message from ChatOpenAICompletions API format * and converts it to the standardized v1 content block format. It handles both * string content and structured content blocks, as well as tool calls. * * @param message - The AI message containing ChatOpenAICompletions formatted content * @returns Array of content blocks in v1 standard format * * @example * ```typescript * const message = new AIMessage("Hello world"); * const standardBlocks = convertToV1FromChatCompletions(message); * // Returns: [{ type: "text", text: "Hello world" }] * ``` * * @example * ```typescript * const message = new AIMessage([ * { type: "text", text: "Hello" }, * { type: "image_url", image_url: { url: "https://example.com/image.png" } } * ]); * message.tool_calls = [ * { id: "call_123", name: "calculator", args: { a: 1, b: 2 } } * ]; * * const standardBlocks = convertToV1FromChatCompletions(message); * // Returns: * // [ * // { type: "text", text: "Hello" }, * // { type: "image", url: "https://example.com/image.png" }, * // { type: "tool_call", id: "call_123", name: "calculator", args: { a: 1, b: 2 } } * // ] * ``` */ function convertToV1FromChatCompletions(message) { const blocks = []; if (typeof message.content === "string") { if (message.content.length > 0) blocks.push({ type: "text", text: message.content }); } else blocks.push(...convertToV1FromChatCompletionsInput(message.content)); for (const toolCall of message.tool_calls ?? []) blocks.push({ type: "tool_call", id: toolCall.id, name: toolCall.name, args: toolCall.args }); const refusal = refusalFromAdditionalKwargs(message); if (refusal) blocks.push(refusal); return blocks; } /** * Converts a ChatOpenAICompletions message chunk to an array of v1 standard content blocks. * * This function processes an AI message chunk from OpenAI's chat completions API and converts * it to the standardized v1 content block format. It handles both string and array content, * as well as tool calls that may be present in the chunk. * * @param message - The AI message chunk containing OpenAI-formatted content blocks * @returns Array of content blocks in v1 standard format * * @example * ```typescript * const chunk = new AIMessage("Hello"); * const standardBlocks = convertToV1FromChatCompletionsChunk(chunk); * // Returns: [{ type: "text", text: "Hello" }] * ``` * * @example * ```typescript * const chunk = new AIMessage([ * { type: "text", text: "Processing..." } * ]); * chunk.tool_calls = [ * { id: "call_456", name: "search", args: { query: "test" } } * ]; * * const standardBlocks = convertToV1FromChatCompletionsChunk(chunk); * // Returns: * // [ * // { type: "text", text: "Processing..." }, * // { type: "tool_call", id: "call_456", name: "search", args: { query: "test" } } * // ] * ``` */ function convertToV1FromChatCompletionsChunk(message) { const blocks = []; if (typeof message.content === "string") { if (message.content.length > 0) blocks.push({ type: "text", text: message.content }); } else blocks.push(...convertToV1FromChatCompletionsInput(message.content)); for (const toolCall of message.tool_calls ?? []) blocks.push({ type: "tool_call", id: toolCall.id, name: toolCall.name, args: toolCall.args }); const refusal = refusalFromAdditionalKwargs(message); if (refusal) blocks.push(refusal); return blocks; } /** * Converts an array of ChatOpenAICompletions content blocks to v1 standard content blocks. * * This function processes content blocks from OpenAI's Chat Completions API format * and converts them to the standardized v1 content block format. It handles both * OpenAI-specific data blocks (which require conversion) and standard blocks * (which are passed through with type assertion). * * @param blocks - Array of content blocks in ChatOpenAICompletions format * @returns Array of content blocks in v1 standard format * * @example * ```typescript * const openaiBlocks = [ * { type: "text", text: "Hello world" }, * { type: "image_url", image_url: { url: "https://example.com/image.png" } } * ]; * * const standardBlocks = convertToV1FromChatCompletionsInput(openaiBlocks); * // Returns: * // [ * // { type: "text", text: "Hello world" }, * // { type: "image", url: "https://example.com/image.png" } * // ] * ``` */ function convertToV1FromChatCompletionsInput(blocks) { const convertedBlocks = []; for (const block of blocks) if (require_data.isOpenAIDataBlock(block)) convertedBlocks.push(require_data.convertToV1FromOpenAIDataBlock(block)); else convertedBlocks.push(block); return convertedBlocks; } function convertResponsesAnnotation(annotation) { if (annotation.type === "url_citation") { const { url, title, start_index, end_index } = annotation; return { type: "citation", url, title, startIndex: start_index, endIndex: end_index }; } if (annotation.type === "file_citation") { const { file_id, filename, index } = annotation; return { type: "citation", title: filename, startIndex: index, endIndex: index, fileId: file_id }; } return annotation; } /** Builds a reasoning content block, including id/encrypted_content when present. */ function mapReasoningItemToV1(item) { if (!require_utils._isArray(item.summary)) return void 0; return { type: "reasoning", reasoning: item.summary.reduce((acc, part) => { if (require_utils._isObject(part) && require_utils._isString(part.text)) return `${acc}${part.text}`; return acc; }, ""), ...require_utils._isString(item.id) ? { id: item.id } : {}, ...require_utils._isString(item.encrypted_content) ? { encrypted_content: item.encrypted_content } : {} }; } function* mapToolOutputToV1Blocks(toolOutput) { if (require_utils._isContentBlock(toolOutput, "web_search_call")) { const webSearchArgs = {}; if (require_utils._isObject(toolOutput.action) && require_utils._isString(toolOutput.action.query)) webSearchArgs.query = toolOutput.action.query; yield { id: toolOutput.id, type: "server_tool_call", name: "web_search", args: webSearchArgs }; if (toolOutput.status === "completed" || toolOutput.status === "failed") { const output = {}; if (require_utils._isObject(toolOutput.action)) output.action = toolOutput.action; yield { type: "server_tool_call_result", toolCallId: require_utils._isString(toolOutput.id) ? toolOutput.id : "", status: toolOutput.status === "completed" ? "success" : "error", output }; } return; } if (require_utils._isContentBlock(toolOutput, "file_search_call")) { yield { id: toolOutput.id, type: "server_tool_call", name: "file_search", args: { queries: require_utils._isArray(toolOutput.queries) ? toolOutput.queries : [] } }; if (toolOutput.status === "completed" || toolOutput.status === "failed") yield { type: "server_tool_call_result", toolCallId: require_utils._isString(toolOutput.id) ? toolOutput.id : "", status: toolOutput.status === "completed" ? "success" : "error", output: require_utils._isArray(toolOutput.results) ? { results: toolOutput.results } : {} }; return; } if (require_utils._isContentBlock(toolOutput, "computer_call")) { yield { type: "non_standard", value: toolOutput }; return; } if (require_utils._isContentBlock(toolOutput, "code_interpreter_call")) { if (require_utils._isString(toolOutput.code)) yield { id: toolOutput.id, type: "server_tool_call", name: "code_interpreter", args: { code: toolOutput.code } }; if (require_utils._isArray(toolOutput.outputs)) { const returnCode = require_utils.iife(() => { if (toolOutput.status === "in_progress") return void 0; if (toolOutput.status === "completed") return 0; if (toolOutput.status === "incomplete") return 127; if (toolOutput.status === "interpreting") return void 0; if (toolOutput.status === "failed") return 1; }); for (const output of toolOutput.outputs) if (require_utils._isContentBlock(output, "logs")) yield { type: "server_tool_call_result", toolCallId: toolOutput.id ?? "", status: "success", output: { type: "code_interpreter_output", returnCode: returnCode ?? 0, stderr: [0, void 0].includes(returnCode) ? void 0 : String(output.logs), stdout: [0, void 0].includes(returnCode) ? String(output.logs) : void 0 } }; } return; } if (require_utils._isContentBlock(toolOutput, "mcp_call")) { yield { id: toolOutput.id, type: "server_tool_call", name: "mcp_call", args: toolOutput.input }; return; } if (require_utils._isContentBlock(toolOutput, "mcp_list_tools")) { yield { id: toolOutput.id, type: "server_tool_call", name: "mcp_list_tools", args: toolOutput.input }; return; } if (require_utils._isContentBlock(toolOutput, "mcp_approval_request")) { yield { type: "non_standard", value: toolOutput }; return; } if (require_utils._isContentBlock(toolOutput, "tool_search_call")) { const toolSearchArgs = {}; if (require_utils._isObject(toolOutput.arguments)) Object.assign(toolSearchArgs, toolOutput.arguments); const toolSearchCallExtras = {}; if (require_utils._isString(toolOutput.execution)) toolSearchCallExtras.execution = toolOutput.execution; if (require_utils._isString(toolOutput.status)) toolSearchCallExtras.status = toolOutput.status; if (require_utils._isString(toolOutput.call_id)) toolSearchCallExtras.call_id = toolOutput.call_id; yield { id: require_utils._isString(toolOutput.id) ? toolOutput.id : "", type: "server_tool_call", name: "tool_search", args: toolSearchArgs, ...Object.keys(toolSearchCallExtras).length > 0 ? { extras: toolSearchCallExtras } : {} }; return; } if (require_utils._isContentBlock(toolOutput, "tool_search_output")) { const toolSearchOutputExtras = { name: "tool_search" }; if (require_utils._isString(toolOutput.execution)) toolSearchOutputExtras.execution = toolOutput.execution; yield { type: "server_tool_call_result", toolCallId: require_utils._isString(toolOutput.id) ? toolOutput.id : "", status: toolOutput.status === "completed" ? "success" : toolOutput.status === "failed" ? "error" : "success", output: { tools: require_utils._isArray(toolOutput.tools) ? toolOutput.tools : [] }, extras: toolSearchOutputExtras }; return; } if (require_utils._isContentBlock(toolOutput, "image_generation_call")) { if (require_utils._isString(toolOutput.result)) yield { type: "image", mimeType: "image/png", data: toolOutput.result, id: require_utils._isString(toolOutput.id) ? toolOutput.id : void 0, metadata: { status: require_utils._isString(toolOutput.status) ? toolOutput.status : void 0 } }; yield { type: "non_standard", value: toolOutput }; return; } if (require_utils._isObject(toolOutput)) yield { type: "non_standard", value: toolOutput }; } /** * Converts a ChatOpenAIResponses message to an array of v1 standard content blocks. * * This function processes an AI message containing OpenAI Responses-specific content blocks * and converts them to the standardized v1 content block format. It handles reasoning summaries, * text content with annotations, tool calls, and various tool outputs including code interpreter, * web search, file search, computer calls, and MCP-related blocks. * * A response can hold more than one reasoning item (e.g. multiple tool calls * in one turn, common on GPT-5.6-class models). When `response_metadata.output` * is available, each item's own `id`/`encrypted_content` is preserved and * blocks are sorted back into original order, so replay under Zero Data * Retention interleaves reasoning and tool calls correctly. Without it (e.g. * a hand-built message), falls back to a single reasoning block from * `additional_kwargs.reasoning`, as before. * * @param message - The AI message containing OpenAI Responses-formatted content blocks * @returns Array of content blocks in v1 standard format * * @example * ```typescript * const message = new AIMessage({ * content: [{ type: "text", text: "Hello world", annotations: [] }], * tool_calls: [{ id: "123", name: "calculator", args: { a: 1, b: 2 } }], * additional_kwargs: { * reasoning: { summary: [{ text: "Let me calculate this..." }] }, * tool_outputs: [ * { * type: "code_interpreter_call", * code: "print('hello')", * outputs: [{ type: "logs", logs: "hello" }] * } * ] * } * }); * * const standardBlocks = convertToV1FromResponses(message); * // Returns: * // [ * // { type: "reasoning", reasoning: "Let me calculate this..." }, * // { type: "text", text: "Hello world", annotations: [] }, * // { type: "tool_call", id: "123", name: "calculator", args: { a: 1, b: 2 } }, * // { type: "server_tool_call", name: "code_interpreter", args: { code: "print('hello')" } }, * // { type: "server_tool_call_result", toolCallId: "", status: "success", output: { type: "code_interpreter_output", returnCode: 0, stdout: "hello" } } * // ] * ``` */ function convertToV1FromResponses(message) { const rawOutput = message.response_metadata?.output; const hasRawOutput = require_utils._isArray(rawOutput) && rawOutput.length > 0; const positionByKey = /* @__PURE__ */ new Map(); let messageItemCount = 0; let onlyMessageItemIndex; if (hasRawOutput) rawOutput.forEach((rawItem, index) => { if (!require_utils._isObject(rawItem) || !require_utils._isString(rawItem.type)) return; if (rawItem.type === "reasoning" && require_utils._isString(rawItem.id)) positionByKey.set(`reasoning:${rawItem.id}`, index); else if ((rawItem.type === "function_call" || rawItem.type === "custom_tool_call" || rawItem.type === "computer_call") && require_utils._isString(rawItem.call_id)) positionByKey.set(`tool_call:${rawItem.call_id}`, index); else if (rawItem.type === "message") { messageItemCount += 1; onlyMessageItemIndex = index; } else if (require_utils._isString(rawItem.id)) positionByKey.set(`other:${rawItem.type}:${rawItem.id}`, index); }); const textBlockKey = messageItemCount === 1 && onlyMessageItemIndex != null ? `message:${onlyMessageItemIndex}` : void 0; if (textBlockKey != null) positionByKey.set(textBlockKey, onlyMessageItemIndex); function* iterateContent() { if (hasRawOutput) { for (const rawItem of rawOutput) if (require_utils._isObject(rawItem) && rawItem.type === "reasoning") { const block = mapReasoningItemToV1(rawItem); if (block) yield [block, require_utils._isString(rawItem.id) ? `reasoning:${rawItem.id}` : void 0]; } } else if (require_utils._isObject(message.additional_kwargs?.reasoning) && require_utils._isArray(message.additional_kwargs.reasoning.summary)) { const block = mapReasoningItemToV1(message.additional_kwargs.reasoning); if (block) yield [block, void 0]; } const content = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content; for (const block of content) if (require_utils._isContentBlock(block, "text")) { const { text, annotations, phase, extras: existingExtras, ...rest } = block; const extras = require_utils._isObject(existingExtras) ? { ...existingExtras } : {}; if (require_utils._isString(phase)) extras.phase = phase; const extrasSpread = Object.keys(extras).length > 0 ? { extras } : {}; yield [{ ...rest, ...extrasSpread, type: "text", text: String(text), ...Array.isArray(annotations) ? { annotations: annotations.map(convertResponsesAnnotation) } : {} }, textBlockKey]; } let hasRefusalPart = false; for (const block of content) if (require_utils._isContentBlock(block, "refusal") && require_utils._isString(block.refusal)) { hasRefusalPart = true; yield [{ type: "refusal", refusal: block.refusal, ...require_utils._isString(block.id) ? { id: block.id } : {} }, textBlockKey]; } if (!hasRefusalPart) { const refusal = refusalFromAdditionalKwargs(message); if (refusal) yield [refusal, textBlockKey]; } for (const toolCall of message.tool_calls ?? []) yield [{ type: "tool_call", id: toolCall.id, name: toolCall.name, args: toolCall.args }, toolCall.id ? `tool_call:${toolCall.id}` : void 0]; if (require_utils._isObject(message.additional_kwargs) && require_utils._isArray(message.additional_kwargs.tool_outputs)) for (const toolOutput of message.additional_kwargs.tool_outputs) { const key = require_utils._isObject(toolOutput) && require_utils._isString(toolOutput.type) && require_utils._isString(toolOutput.id) ? `other:${toolOutput.type}:${toolOutput.id}` : void 0; for (const block of mapToolOutputToV1Blocks(toolOutput)) yield [block, key]; } } const withKeys = Array.from(iterateContent()); if (!hasRawOutput) return withKeys.map(([block]) => block); return withKeys.map(([block, key], pushIndex) => ({ block, pushIndex, position: key != null ? positionByKey.get(key) : void 0 })).sort((a, b) => { const aPos = a.position ?? Number.POSITIVE_INFINITY; const bPos = b.position ?? Number.POSITIVE_INFINITY; if (aPos !== bPos) return aPos - bPos; return a.pushIndex - b.pushIndex; }).map((entry) => entry.block); } /** * Converts a ChatOpenAIResponses message chunk to an array of v1 standard content blocks. * * This function processes an AI message chunk containing OpenAI-specific content blocks * and converts them to the standardized v1 content block format. It handles both the * regular message content and tool call chunks that are specific to streaming responses. * * @param message - The AI message chunk containing OpenAI-formatted content blocks * @returns Array of content blocks in v1 standard format * * @example * ```typescript * const messageChunk = new AIMessageChunk({ * content: [{ type: "text", text: "Hello" }], * tool_call_chunks: [ * { id: "call_123", name: "calculator", args: '{"a": 1' } * ] * }); * * const standardBlocks = convertToV1FromResponsesChunk(messageChunk); * // Returns: * // [ * // { type: "text", text: "Hello" }, * // { type: "tool_call_chunk", id: "call_123", name: "calculator", args: '{"a": 1' } * // ] * ``` */ function convertToV1FromResponsesChunk(message) { function* iterateContent() { yield* convertToV1FromResponses(message); for (const toolCallChunk of message.tool_call_chunks ?? []) yield { type: "tool_call_chunk", id: toolCallChunk.id, name: toolCallChunk.name, args: toolCallChunk.args }; } return Array.from(iterateContent()); } const ChatOpenAITranslator = { translateContent: (message) => { if (typeof message.content === "string") return convertToV1FromChatCompletions(message); return convertToV1FromResponses(message); }, translateContentChunk: (message) => { if (typeof message.content === "string") return convertToV1FromChatCompletionsChunk(message); return convertToV1FromResponsesChunk(message); } }; exports.ChatOpenAITranslator = ChatOpenAITranslator; exports.convertToV1FromChatCompletions = convertToV1FromChatCompletions; exports.convertToV1FromChatCompletionsChunk = convertToV1FromChatCompletionsChunk; exports.convertToV1FromChatCompletionsInput = convertToV1FromChatCompletionsInput; exports.convertToV1FromResponses = convertToV1FromResponses; exports.convertToV1FromResponsesChunk = convertToV1FromResponsesChunk; //# sourceMappingURL=openai.cjs.map