[PR #5789] fix(langgraph): support Pydantic default values with reducer functions #4647

Closed
opened 2026-02-20 17:50:31 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/langchain-ai/langgraph/pull/5789

State: closed
Merged: No


Description: This PR fixes a bug where Pydantic Field(default=...) and Field(default_factory=...) values were not being applied when used with Annotated reducer functions in LangGraph state schemas. The issue was that default values defined in Pydantic fields were being ignored, causing state fields to start with empty values instead of the specified defaults.

The fix updates the get_field_default function to handle Pydantic models, updates the BinaryOperatorAggregate class to accept and use default values, and connects the schema context through the state management system.

Issue: Fix #5225

Dependencies: None - this is a bug fix that updates existing functionality without adding new dependencies.

Changes Made

Core Implementation:

  1. Updated get_field_default function (langgraph/_internal/_fields.py):

    • Added support for Pydantic BaseModel default value extraction
    • Handles both Field(default_factory=...) and Field(default=...)
    • Properly processes PydanticUndefinedType markers
  2. Updated BinaryOperatorAggregate class (langgraph/channels/binop.py):

    • Added default parameter to constructor
    • Modified initialization to use provided defaults instead of empty instances
    • Updated copy() and from_checkpoint() methods to preserve defaults
  3. Connected schema context (langgraph/graph/state.py):

    • Modified _is_field_binop, _get_channel, and _get_channels functions
    • Added schema context passing for proper default value resolution

Testing:

Created comprehensive test suite in tests/test_pydantic_defaults_with_reducers.py with 12 test cases:

  • 5 core functionality tests for Pydantic defaults with reducers
  • 7 backward compatibility tests ensuring existing behavior is preserved

Example Usage After Fix

from pydantic import BaseModel, Field
from typing import Annotated
import operator
from langgraph.graph import StateGraph, START, END

class State(BaseModel):
    messages: Annotated[list[str], operator.add] = Field(default_factory=lambda: ["initial"])
    count: Annotated[int, operator.add] = Field(default=0)

def my_node(state: State) -> dict:
    return {"messages": ["new"], "count": 1}

graph = StateGraph(State)
graph.add_node("process", my_node)
graph.add_edge(START, "process")
graph.add_edge("process", END)

result = graph.compile().invoke({})
# result["messages"] == ["initial", "new"]  ✅ Now works!
# result["count"] == 1  ✅ Default + reducer

Test Results

All tests pass:

$ python -m pytest tests/test_pydantic_defaults_with_reducers.py -v
=============================================== 12 passed, 12 warnings in 0.11s ================================================

**Original Pull Request:** https://github.com/langchain-ai/langgraph/pull/5789 **State:** closed **Merged:** No --- **Description:** This PR fixes a bug where Pydantic `Field(default=...)` and `Field(default_factory=...)` values were not being applied when used with `Annotated` reducer functions in LangGraph state schemas. The issue was that default values defined in Pydantic fields were being ignored, causing state fields to start with empty values instead of the specified defaults. The fix updates the `get_field_default` function to handle Pydantic models, updates the `BinaryOperatorAggregate` class to accept and use default values, and connects the schema context through the state management system. **Issue:** Fix #5225 **Dependencies:** None - this is a bug fix that updates existing functionality without adding new dependencies. ## Changes Made ### Core Implementation: 1. **Updated `get_field_default` function** (`langgraph/_internal/_fields.py`): - Added support for Pydantic BaseModel default value extraction - Handles both `Field(default_factory=...)` and `Field(default=...)` - Properly processes `PydanticUndefinedType` markers 2. **Updated `BinaryOperatorAggregate` class** (`langgraph/channels/binop.py`): - Added `default` parameter to constructor - Modified initialization to use provided defaults instead of empty instances - Updated `copy()` and `from_checkpoint()` methods to preserve defaults 3. **Connected schema context** (`langgraph/graph/state.py`): - Modified `_is_field_binop`, `_get_channel`, and `_get_channels` functions - Added schema context passing for proper default value resolution ### Testing: Created comprehensive test suite in `tests/test_pydantic_defaults_with_reducers.py` with 12 test cases: - 5 core functionality tests for Pydantic defaults with reducers - 7 backward compatibility tests ensuring existing behavior is preserved ## Example Usage After Fix ```python from pydantic import BaseModel, Field from typing import Annotated import operator from langgraph.graph import StateGraph, START, END class State(BaseModel): messages: Annotated[list[str], operator.add] = Field(default_factory=lambda: ["initial"]) count: Annotated[int, operator.add] = Field(default=0) def my_node(state: State) -> dict: return {"messages": ["new"], "count": 1} graph = StateGraph(State) graph.add_node("process", my_node) graph.add_edge(START, "process") graph.add_edge("process", END) result = graph.compile().invoke({}) # result["messages"] == ["initial", "new"] ✅ Now works! # result["count"] == 1 ✅ Default + reducer ``` ## Test Results All tests pass: ```bash $ python -m pytest tests/test_pydantic_defaults_with_reducers.py -v =============================================== 12 passed, 12 warnings in 0.11s ================================================ ```
yindo added the pull-request label 2026-02-20 17:50:31 -05:00
yindo closed this issue 2026-02-20 17:50:31 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#4647