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
- Set the value as a plain string: `additional_kwargs={"__openai_role__": "developer"}`.
- Validate/coerce the config value (`str(value)`) before constructing the SystemMessage.
- 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
- Validate config-driven additional_kwargs against a small schema
- Write __openai_role__ as a bare string in YAML/JSON templates
- Omit __openai_role__ unless you specifically need a non-system role
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
- Unrecognized content block at messages[{i}].content[{j}] has
- allowed_objects must contain Serializable subclasses.
- unsupported operand type(s) for +: "{self.__class__.__name__
- Unsupported source type. Only 'url' and 'base64' are support
- Keys base64, url, or file_id required for file blocks.
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/cd99c1f1f084750b.
Report an issue: GitHub.