Skip to content

fix(langchain): preserve custom ChatMessage role instead of hardcoding "human"#833

Open
i-anubhav-anand wants to merge 1 commit into
langfuse:mainfrom
i-anubhav-anand:fix/langchain-chatmessage-role
Open

fix(langchain): preserve custom ChatMessage role instead of hardcoding "human"#833
i-anubhav-anand wants to merge 1 commit into
langfuse:mainfrom
i-anubhav-anand:fix/langchain-chatmessage-role

Conversation

@i-anubhav-anand

@i-anubhav-anand i-anubhav-anand commented Jun 14, 2026

Copy link
Copy Markdown

LangChain ChatMessage (getType() === "generic") is the message class for arbitrary custom roles (e.g. "developer", "user_proxy"). extractChatMessageContent hardcodes the role for it:

} else if (message.getType() === "generic") {
  response = { content: message.content, role: "human" };  // loses the real role

So new ChatMessage({ content: "hi", role: "developer" }) is traced as role: "human". Every other branch (human/ai/system/function/tool) maps the role correctly.

This is a regression from #680, which swapped instanceof ChatMessage for getType() === "generic" and replaced the original role: message.role with the literal "human".

Fix restores message.role (falling back to "human" only when absent).

Test

Adds a unit test in tests/integration/langchain.integration.test.ts mapping a ChatMessage with role "developer". Fails before (role: "human"), passes after. prettier/eslint clean.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Greptile Summary

Fixes a regression introduced in PR #680 where ChatMessage objects with custom roles (e.g. "developer", "user_proxy") were always traced as role: "human" instead of their actual role.

  • CallbackHandler.ts: In extractChatMessageContent, the "generic" branch now reads (message as any).role ?? "human" instead of hardcoding "human". Importing ChatMessage from the already-used @langchain/core/messages package would replace the as any cast with a properly typed assertion.
  • langchain.integration.test.ts: Adds a test verifying a ChatMessage with role: "developer" round-trips correctly; the test is self-contained (no network/server needed) and would be better placed in a unit test file.

Confidence Score: 4/5

The fix is safe to merge — it corrects a clear role-preservation bug with a minimal, targeted change and a covering test.

The core logic change is correct and well-scoped. The only rough edge is using as any when a proper ChatMessage type is already available from the imported package, which leaves the .role access unchecked at compile time. The new test lives in the integration suite despite requiring no external dependencies, so it may not run in fast unit-test CI jobs. Neither issue affects runtime correctness of the fix.

The as any cast in CallbackHandler.ts line 936 is the only spot worth a second look; the test file placement is a minor workflow concern.

Sequence Diagram

sequenceDiagram
    participant LC as LangChain
    participant CB as CallbackHandler
    participant LF as Langfuse

    LC->>CB: onLLMStart / onChatModelStart(messages: BaseMessage[])
    CB->>CB: extractChatMessageContent(message)
    alt "message.getType() === "human""
        CB-->>CB: "role = "user""
    else "message.getType() === "generic" (ChatMessage)"
        Note over CB: BEFORE: role hardcoded to "human"<br/>AFTER: role = message.role ?? "human"
        CB-->>CB: "role = message.role ?? "human""
    else "message.getType() === "ai""
        CB-->>CB: "role = "assistant""
    else "message.getType() === "system""
        CB-->>CB: "role = "system""
    end
    CB->>LF: trace with correct role preserved
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/langchain/src/CallbackHandler.ts:5-12
The `as any` cast is unnecessary here since `ChatMessage` (the only concrete `BaseMessage` subclass that returns `"generic"` from `getType()`) is already available from the same `@langchain/core/messages` package that's already being imported. Using `ChatMessage` directly gives compile-time type safety and makes the `.role` access explicit.

```suggestion
import {
  AIMessage,
  AIMessageChunk,
  BaseMessage,
  ChatMessage,
  type UsageMetadata,
  type BaseMessageFields,
  type MessageContent,
} from "@langchain/core/messages";
```

### Issue 2 of 3
packages/langchain/src/CallbackHandler.ts:936
With `ChatMessage` imported, the `as any` cast can be replaced with a proper type assertion, making the `.role` access statically type-checked and self-documenting.

```suggestion
        role: (message as ChatMessage).role ?? "human",
```

### Issue 3 of 3
tests/integration/langchain.integration.test.ts:27-35
**Test belongs in a unit test file, not integration**

This test exercises `extractChatMessageContent` in isolation — no network calls, no Langfuse server, no external services. Placing it in the integration suite means it will be skipped or slow in environments where integration tests are gated behind a running backend, and it won't run in fast CI unit-test jobs. Consider moving it to a dedicated unit test file (e.g. `packages/langchain/tests/unit/CallbackHandler.test.ts`).

Reviews (1): Last reviewed commit: "fix(langchain): preserve custom ChatMess..." | Re-trigger Greptile

…g "human"

LangChain ChatMessage (getType() === "generic") is the message class for
arbitrary custom roles (e.g. "developer"). extractChatMessageContent
hardcoded role: "human" for it, discarding the real role — a regression
from langfuse#680, which replaced the prior `role: message.role` with the literal
"human" when switching from instanceof ChatMessage to getType() checks.

Restores `message.role` (falling back to "human" only when absent). Adds a
unit test that maps a ChatMessage with role "developer" (fails before:
role was "human").

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

@i-anubhav-anand is attempting to deploy a commit to the langfuse Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Jun 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants