Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-laws-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@langchain/openai": patch
---

fix content in AIMessage for tool and function calls
2 changes: 0 additions & 2 deletions libs/providers/langchain-openai/src/converters/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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 = {
Expand Down
Loading