langchain-ai/langchain · error · TypeError

Expected '__openai_role__' to be a str, got {type(role).__na

Error message

Expected '__openai_role__' to be a str, got {type(role).__name__}

What it means

When determining an OpenAI role, langchain-core lets a SystemMessage override its default `system` role via `additional_kwargs["__openai_role__"]` (used e.g. to emit `developer` messages). This value must be a string; any other type raises TypeError immediately. The strictness exists because the role is placed verbatim into the request payload, and a non-string would corrupt the API call.

Source

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

    return message.type in types_str or isinstance(message, types_types)


def _bytes_to_b64_str(bytes_: bytes) -> str:
    return base64.b64encode(bytes_).decode("utf-8")


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),
            },

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Set the value as a plain string: `additional_kwargs={"__openai_role__": "developer"}`.
  2. Validate/coerce the config value (`str(value)`) before constructing the SystemMessage.
  3. If you do not need a custom role, remove `__openai_role__` entirely — `system` is the default.

Example fix

// before
SystemMessage("You are helpful.", additional_kwargs={"__openai_role__": ["developer"]})

// after
SystemMessage("You are helpful.", additional_kwargs={"__openai_role__": "developer"})
Defensive patterns

Strategy: type-guard

Validate before calling

role = msg.additional_kwargs.get("__openai_role__")
if role is not None and not isinstance(role, str):
    raise TypeError(f"__openai_role__ must be str, got {type(role).__name__}")
# or coerce: msg.additional_kwargs["__openai_role__"] = str(role)

Type guard

def has_valid_openai_role(msg) -> bool:
    role = msg.additional_kwargs.get("__openai_role__")
    return role is None or isinstance(role, str)

Try / catch

try:
    _get_message_openai_role(msg)  # or the conversion call
except TypeError as e:
    if "__openai_role__" in str(e):
        msg.additional_kwargs["__openai_role__"] = str(msg.additional_kwargs["__openai_role__"])
        retry()

Prevention

When it happens

Trigger: `SystemMessage("...", additional_kwargs={"__openai_role__": ["developer"]})` or `"__openai_role__": 1}`; programmatically setting the kwarg from unvalidated config/JSON where it arrives as a list or None.

Common situations: Loading prompt templates from YAML/JSON where `__openai_role__` was written as a one-element array; threading user-supplied options into additional_kwargs without type checks.

Related errors


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