diff --git a/packages/langchain/src/CallbackHandler.ts b/packages/langchain/src/CallbackHandler.ts index 8bc539d8..a5cbfcdb 100644 --- a/packages/langchain/src/CallbackHandler.ts +++ b/packages/langchain/src/CallbackHandler.ts @@ -9,6 +9,7 @@ import { type UsageMetadata, type BaseMessageFields, type MessageContent, + type ToolMessage, } from "@langchain/core/messages"; import type { Generation, LLMResult } from "@langchain/core/outputs"; import type { ChainValues } from "@langchain/core/utils/types"; @@ -43,8 +44,10 @@ type LangfusePrompt = { export type LlmMessage = { role: string; + name?: string; content: BaseMessageFields["content"]; additional_kwargs?: BaseMessageFields["additional_kwargs"]; + tool_call_id?: string; }; export type AnonymousLlmMessage = { @@ -991,13 +994,16 @@ export class CallbackHandler extends BaseCallbackHandler { response = { content: message.content, additional_kwargs: message.additional_kwargs, - role: message.name, + role: "function", + name: message.name, }; } else if (message.getType() === "tool") { response = { content: message.content, additional_kwargs: message.additional_kwargs, - role: message.name, + role: "tool", + name: message.name, + tool_call_id: (message as ToolMessage).tool_call_id, }; } else if (!message.name) { response = { content: message.content }; diff --git a/tests/integration/langchain.integration.test.ts b/tests/integration/langchain.integration.test.ts index ebcb7a7b..eacf606f 100644 --- a/tests/integration/langchain.integration.test.ts +++ b/tests/integration/langchain.integration.test.ts @@ -11,7 +11,12 @@ import { StateGraph, } from "@langchain/langgraph"; import { BaseCallbackHandler } from "@langchain/core/callbacks/base"; -import { AIMessage, HumanMessage } from "@langchain/core/messages"; +import { + AIMessage, + FunctionMessage, + HumanMessage, + ToolMessage, +} from "@langchain/core/messages"; import { DynamicTool } from "@langchain/core/tools"; import { FakeStreamingChatModel } from "@langchain/core/utils/testing"; import { CallbackHandler } from "@langfuse/langchain"; @@ -374,4 +379,51 @@ describe("LangChain callback handler integration tests", () => { expect(completionStartTimes).not.toHaveProperty(runId); }); + + it("should serialize tool and function messages with role, name and tool_call_id", async () => { + const handler = new CallbackHandler(); + const runId = "generation-with-tool-results"; + + await handler.handleChatModelStart( + { id: ["ChatOpenAI"] }, + [ + [ + new HumanMessage("What is my updated debt?"), + new AIMessage({ + content: "", + tool_calls: [{ id: "call_1", name: "get_debt", args: {} }], + }), + new ToolMessage({ content: "1874.32", tool_call_id: "call_1" }), + new ToolMessage({ + content: "1874.32", + tool_call_id: "call_2", + name: "get_debt", + }), + new FunctionMessage({ content: "1874.32", name: "get_debt" }), + ], + ], + runId, + undefined, + { invocation_params: { model: "gpt-4.1-mini" } }, + ); + await handler.handleLLMEnd({ generations: [[{ text: "ok" }]] }, runId); + + await waitForSpanExport(testEnv.mockExporter, 1); + + assertions.expectSpanAttributeContains( + "ChatOpenAI", + LangfuseOtelSpanAttributes.OBSERVATION_INPUT, + '{"content":"1874.32","additional_kwargs":{},"role":"tool","tool_call_id":"call_1"}', + ); + assertions.expectSpanAttributeContains( + "ChatOpenAI", + LangfuseOtelSpanAttributes.OBSERVATION_INPUT, + '{"content":"1874.32","additional_kwargs":{},"role":"tool","name":"get_debt","tool_call_id":"call_2"}', + ); + assertions.expectSpanAttributeContains( + "ChatOpenAI", + LangfuseOtelSpanAttributes.OBSERVATION_INPUT, + '{"content":"1874.32","additional_kwargs":{},"role":"function","name":"get_debt"}', + ); + }); });