langchain-ai/langchain · error · ValueError

Unknown BaseMessage type {message.__class__}.

Error message

Unknown BaseMessage type {message.__class__}.

What it means

`_get_message_openai_role` maps known message classes to OpenAI roles (AIMessage->assistant, HumanMessage->user, ToolMessage->tool, SystemMessage->system/override, FunctionMessage->function, ChatMessage->its own role). A BaseMessage subclass that is none of these cannot be assigned a role, so a ValueError with the class name is raised. Unlike error 170/171 this is about role assignment during OpenAI-format conversion, not chunk coercion.

Source

Thrown at libs/core/langchain_core/messages/utils.py:2227

def _get_message_openai_role(message: BaseMessage) -> str:
    if isinstance(message, AIMessage):
        return "assistant"
    if isinstance(message, HumanMessage):
        return "user"
    if isinstance(message, ToolMessage):
        return "tool"
    if isinstance(message, SystemMessage):
        role = message.additional_kwargs.get("__openai_role__", "system")
        if not isinstance(role, str):
            msg = f"Expected '__openai_role__' to be a str, got {type(role).__name__}"
            raise TypeError(msg)
        return role
    if isinstance(message, FunctionMessage):
        return "function"
    if isinstance(message, ChatMessage):
        return message.role
    msg = f"Unknown BaseMessage type {message.__class__}."
    raise ValueError(msg)


def _convert_to_openai_tool_calls(tool_calls: list[ToolCall]) -> list[dict[str, Any]]:
    return [
        {
            "type": "function",
            "id": tool_call["id"],
            "function": {
                "name": tool_call["name"],
                "arguments": json.dumps(tool_call["args"], ensure_ascii=False),
            },
        }
        for tool_call in tool_calls
    ]


def count_tokens_approximately(
    messages: Iterable[MessageLikeRepresentation],

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Subclass ChatMessage and set its `role` field — ChatMessage is explicitly supported for custom roles: `ChatMessage(content="hi", role="user")`.
  2. Or subclass one of the standard classes (HumanMessage/AIMessage/SystemMessage) so a role is inferable.
  3. Or convert custom messages to a standard type before invoking OpenAI-format conversion.

Example fix

// before
class CustomMessage(BaseMessage): ...

// after
from langchain_core.messages import ChatMessage
ChatMessage(content="hi", role="user")
Defensive patterns

Strategy: type-guard

Validate before calling

from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage, FunctionMessage, ChatMessage

def has_openai_role(m) -> bool:
    return isinstance(m, (AIMessage, HumanMessage, SystemMessage, ToolMessage, FunctionMessage, ChatMessage))

Type guard

def is_role_assignable(m) -> bool:
    return isinstance(m, (AIMessage, HumanMessage, SystemMessage, ToolMessage, FunctionMessage, ChatMessage))

Try / catch

try:
    convert_to_openai_messages([msg])
except ValueError as e:
    if "Unknown BaseMessage type" in str(e):
        msg = ChatMessage(content=msg.content, role="user")  # choose an explicit role
        convert_to_openai_messages([msg])

Prevention

When it happens

Trigger: Passing a `class CustomMessage(BaseMessage)` instance into code that converts messages to OpenAI request payloads; exotic message types from older LangChain versions or third-party packages that no longer match the isinstance chain.

Common situations: Migrating legacy `Chain` code that defined its own message types; combining langchain-core with frameworks whose adapters leak their own BaseMessage subclasses.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/f26cadbd12c7e30d. Report an issue: GitHub.