diff --git a/.changeset/cold-laws-matter.md b/.changeset/cold-laws-matter.md new file mode 100644 index 000000000000..9c3b82803196 --- /dev/null +++ b/.changeset/cold-laws-matter.md @@ -0,0 +1,5 @@ +--- +"@langchain/openai": patch +--- + +fix content in AIMessage for tool and function calls diff --git a/libs/providers/langchain-openai/src/converters/completions.ts b/libs/providers/langchain-openai/src/converters/completions.ts index 4a5304df904d..ea08e95c5408 100644 --- a/libs/providers/langchain-openai/src/converters/completions.ts +++ b/libs/providers/langchain-openai/src/converters/completions.ts @@ -811,13 +811,11 @@ export const convertMessagesToCompletionsMessageParams: Converter< } if (message.additional_kwargs.function_call != null) { completionParam.function_call = message.additional_kwargs.function_call; - completionParam.content = ""; } if (AIMessage.isInstance(message) && !!message.tool_calls?.length) { completionParam.tool_calls = message.tool_calls.map( convertLangChainToolCallToOpenAI ); - completionParam.content = ""; } else { if (message.additional_kwargs.tool_calls != null) { completionParam.tool_calls = message.additional_kwargs.tool_calls; diff --git a/libs/providers/langchain-openai/src/converters/tests/completions.test.ts b/libs/providers/langchain-openai/src/converters/tests/completions.test.ts index 8e52a6a50677..d587b91b789c 100644 --- a/libs/providers/langchain-openai/src/converters/tests/completions.test.ts +++ b/libs/providers/langchain-openai/src/converters/tests/completions.test.ts @@ -1,9 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, it, expect } from "vitest"; import { ChatCompletionMessage } from "openai/resources"; +import { AIMessage } from "@langchain/core/messages"; import { completionsApiContentBlockConverter, convertCompletionsMessageToBaseMessage, + convertMessagesToCompletionsMessageParams, convertStandardContentBlockToCompletionsContentPart, } from "../completions.js"; @@ -194,6 +196,102 @@ describe("convertCompletionsMessageToBaseMessage", () => { }); }); + describe("convertMessagesToCompletionsMessageParams", () => { + it("should preserve AIMessage content when tool_calls are present", () => { + const message = new AIMessage({ + content: + "I'll check the status of item 730 for identifier X1110 to find out why it's not active.", + tool_calls: [ + { + id: "call_zGKlzVl2Ee3Lyob4AsyqfGXb", + name: "getStatus", + args: { identifier: "X1110", itemId: "730" }, + }, + ], + }); + + const result = convertMessagesToCompletionsMessageParams({ + messages: [message], + }); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + role: "assistant", + content: + "I'll check the status of item 730 for identifier X1110 to find out why it's not active.", + tool_calls: [ + { + id: "call_zGKlzVl2Ee3Lyob4AsyqfGXb", + type: "function", + function: { + name: "getStatus", + arguments: '{"identifier":"X1110","itemId":"730"}', + }, + }, + ], + }); + }); + + it("should handle AIMessage with empty content and tool_calls", () => { + const message = new AIMessage({ + content: "", + tool_calls: [ + { + id: "call_123", + name: "someFunction", + args: { key: "value" }, + }, + ], + }); + + const result = convertMessagesToCompletionsMessageParams({ + messages: [message], + }); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + role: "assistant", + content: "", + tool_calls: [ + { + id: "call_123", + type: "function", + function: { + name: "someFunction", + arguments: '{"key":"value"}', + }, + }, + ], + }); + }); + + it("should preserve content with function_call in additional_kwargs", () => { + const message = new AIMessage({ + content: "Let me call a function for you.", + additional_kwargs: { + function_call: { + name: "myFunction", + arguments: '{"arg":"value"}', + }, + }, + }); + + const result = convertMessagesToCompletionsMessageParams({ + messages: [message], + }); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + role: "assistant", + content: "Let me call a function for you.", + function_call: { + name: "myFunction", + arguments: '{"arg":"value"}', + }, + }); + }); + }); + describe("completionsApiContentBlockConverter.fromStandardFileBlock", () => { it("throws when base64 file block is missing filename/name/title metadata", () => { const block = {