Pydantic populate_by_name not respected in StateGraph input validation #1042

Closed
opened 2026-02-20 17:42:51 -05:00 by yindo · 1 comment
Owner

Originally created by @smart-cau on GitHub (Nov 6, 2025).

Bug Description

Pydantic's populate_by_name=True configuration is not respected during LangGraph's StateGraph input validation, causing validation errors when using field aliases (camelCase) instead of original field names (snake_case).

Environment

  • LangGraph version: 1.0.1
  • Python version: 3.13.5
  • Pydantic version: 2.x

Steps to Reproduce

from pydantic import BaseModel, ConfigDict, Field
from langgraph.graph import StateGraph, START, END

def to_camel(string: str) -> str:
    """Convert snake_case to camelCase."""
    return "".join(
        word.capitalize() if i != 0 else word
        for i, word in enumerate(string.split("_"))
    )

class BaseStateConfig(BaseModel):
    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,  # Should allow both snake_case and camelCase
        serialize_by_alias=True,
    )

class InputState(BaseStateConfig):
    user_name: str = Field(description="User name")
    user_age: int | None = Field(default=None, description="User age")

class OutputState(BaseStateConfig):
    result: str | None = Field(default=None, description="Result")

class GraphState(InputState, OutputState):
    pass

def process_node(state: GraphState) -> dict:
    return {"result": f"Hello, {state.user_name}!"}

# Create graph
workflow = StateGraph(GraphState, input_schema=InputState, output_schema=OutputState)
workflow.add_node("process", process_node)
workflow.add_edge(START, "process")
workflow.add_edge("process", END)
graph = workflow.compile()

# Test 1: snake_case input (works)
result1 = graph.invoke({"user_name": "Alice", "user_age": 30})
print("✅ Test 1 passed:", result1)

# Test 2: camelCase input (fails, but should work due to populate_by_name=True)
result2 = graph.invoke({"userName": "Bob", "userAge": 25})
print("✅ Test 2 passed:", result2)

Expected Behavior

Both Test 1 (snake_case) and Test 2 (camelCase) should succeed, because:

  1. alias_generator=to_camel creates camelCase aliases for all fields
  2. populate_by_name=True allows both original names (snake_case) and aliases (camelCase) during validation

Actual Behavior

Test 2 fails with:

pydantic_core._pydantic_core.ValidationError: 1 validation error for GraphState
userName
  Field required [type=missing, input_value={'userName': 'Bob', 'userAge': 25}, input_type=dict]

The camelCase fields are not recognized, even though Pydantic's populate_by_name=True should enable this.

Root Cause Analysis

The issue appears to be in langgraph/graph/state.py around line 1237 in the _coerce_state function:

def _coerce_state(schema, input):
    return schema(**input)

LangGraph's state coercion logic may be bypassing Pydantic's normal validation flow, which respects populate_by_name. The direct field mapping or optimization in LangGraph's state handling might not honor Pydantic's model_config settings.

Verification: Pydantic Works Correctly

Direct Pydantic validation works as expected:

# This works perfectly:
state1 = InputState(user_name="Alice", user_age=30)  # snake_case
state2 = InputState(userName="Bob", userAge=25)      # camelCase
# Both succeed ✅

The issue only occurs when going through LangGraph's StateGraph.invoke().

Suggested Fix

LangGraph's state validation should respect Pydantic's model_config settings, particularly populate_by_name. This would allow users to:

  1. Define schemas with snake_case (Python convention)
  2. Use camelCase aliases for external APIs (JavaScript/JSON convention)
  3. Accept both formats during validation (thanks to populate_by_name=True)

Workaround

Currently, users must manually convert camelCase to snake_case before passing input to LangGraph:

# Manual conversion required
def camel_to_snake(data: dict) -> dict:
    # Convert all keys from camelCase to snake_case
    ...

graph.invoke(camel_to_snake(input_data))

This workaround defeats the purpose of Pydantic's populate_by_name feature.

Impact

This issue affects any LangGraph application that:

  • Receives input from external systems using different naming conventions (e.g., JavaScript/TypeScript frontends)
  • Wants to use Pydantic's alias features for field name mapping
  • Expects populate_by_name=True to work as documented

Thank you for your consideration!

Originally created by @smart-cau on GitHub (Nov 6, 2025). ## Bug Description Pydantic's `populate_by_name=True` configuration is not respected during LangGraph's `StateGraph` input validation, causing validation errors when using field aliases (camelCase) instead of original field names (snake_case). ## Environment - **LangGraph version**: 1.0.1 - **Python version**: 3.13.5 - **Pydantic version**: 2.x ## Steps to Reproduce ```python from pydantic import BaseModel, ConfigDict, Field from langgraph.graph import StateGraph, START, END def to_camel(string: str) -> str: """Convert snake_case to camelCase.""" return "".join( word.capitalize() if i != 0 else word for i, word in enumerate(string.split("_")) ) class BaseStateConfig(BaseModel): model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, # Should allow both snake_case and camelCase serialize_by_alias=True, ) class InputState(BaseStateConfig): user_name: str = Field(description="User name") user_age: int | None = Field(default=None, description="User age") class OutputState(BaseStateConfig): result: str | None = Field(default=None, description="Result") class GraphState(InputState, OutputState): pass def process_node(state: GraphState) -> dict: return {"result": f"Hello, {state.user_name}!"} # Create graph workflow = StateGraph(GraphState, input_schema=InputState, output_schema=OutputState) workflow.add_node("process", process_node) workflow.add_edge(START, "process") workflow.add_edge("process", END) graph = workflow.compile() # Test 1: snake_case input (works) result1 = graph.invoke({"user_name": "Alice", "user_age": 30}) print("✅ Test 1 passed:", result1) # Test 2: camelCase input (fails, but should work due to populate_by_name=True) result2 = graph.invoke({"userName": "Bob", "userAge": 25}) print("✅ Test 2 passed:", result2) ``` ## Expected Behavior Both Test 1 (snake_case) and Test 2 (camelCase) should succeed, because: 1. `alias_generator=to_camel` creates camelCase aliases for all fields 2. `populate_by_name=True` allows **both** original names (snake_case) **and** aliases (camelCase) during validation ## Actual Behavior Test 2 fails with: ``` pydantic_core._pydantic_core.ValidationError: 1 validation error for GraphState userName Field required [type=missing, input_value={'userName': 'Bob', 'userAge': 25}, input_type=dict] ``` The camelCase fields are not recognized, even though Pydantic's `populate_by_name=True` should enable this. ## Root Cause Analysis The issue appears to be in `langgraph/graph/state.py` around line 1237 in the `_coerce_state` function: ```python def _coerce_state(schema, input): return schema(**input) ``` LangGraph's state coercion logic may be bypassing Pydantic's normal validation flow, which respects `populate_by_name`. The direct field mapping or optimization in LangGraph's state handling might not honor Pydantic's `model_config` settings. ## Verification: Pydantic Works Correctly Direct Pydantic validation works as expected: ```python # This works perfectly: state1 = InputState(user_name="Alice", user_age=30) # snake_case state2 = InputState(userName="Bob", userAge=25) # camelCase # Both succeed ✅ ``` The issue only occurs when going through LangGraph's `StateGraph.invoke()`. ## Suggested Fix LangGraph's state validation should respect Pydantic's `model_config` settings, particularly `populate_by_name`. This would allow users to: 1. Define schemas with snake_case (Python convention) 2. Use camelCase aliases for external APIs (JavaScript/JSON convention) 3. Accept both formats during validation (thanks to `populate_by_name=True`) ## Workaround Currently, users must manually convert camelCase to snake_case before passing input to LangGraph: ```python # Manual conversion required def camel_to_snake(data: dict) -> dict: # Convert all keys from camelCase to snake_case ... graph.invoke(camel_to_snake(input_data)) ``` This workaround defeats the purpose of Pydantic's `populate_by_name` feature. ## Impact This issue affects any LangGraph application that: - Receives input from external systems using different naming conventions (e.g., JavaScript/TypeScript frontends) - Wants to use Pydantic's alias features for field name mapping - Expects `populate_by_name=True` to work as documented Thank you for your consideration!
yindo closed this issue 2026-02-20 17:42:51 -05:00
Author
Owner

@sydney-runkle commented on GitHub (Nov 7, 2025):

Hiya, this is a dupe of https://github.com/langchain-ai/langgraph/issues/2555, closing as such

@sydney-runkle commented on GitHub (Nov 7, 2025): Hiya, this is a dupe of https://github.com/langchain-ai/langgraph/issues/2555, closing as such
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#1042