mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-23 18:15:08 +02:00
fix(langgraph): Add warning for incorrect node signature with mistyped config param (#5798)
Fixes: #5787 Ensures that if `config` is not typed as one of `RunanbleConfig` or `Optional[RunnableConfig]` a warning is raised to help developers avoid unexpected results at invocation time. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
This commit is contained in:
co-authored by
open-swe[bot] <open-swe@users.noreply.github.com>
Sydney Runkle
parent
5152a96fce
commit
69dd20e523
@@ -4,6 +4,7 @@ import asyncio
|
||||
import enum
|
||||
import inspect
|
||||
import sys
|
||||
import warnings
|
||||
from collections.abc import (
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
@@ -303,6 +304,16 @@ class RunnableCallable(Runnable):
|
||||
if typ != (ANY_TYPE,) and p.annotation not in typ:
|
||||
# A specific type is required, but the function annotation does
|
||||
# not match the expected type.
|
||||
|
||||
# If this is a config parameter with incorrect typing, emit a warning
|
||||
# because we used to support any type but are moving towards more correct typing
|
||||
if kw == "config" and p.annotation != inspect.Parameter.empty:
|
||||
warnings.warn(
|
||||
f"The 'config' parameter should be typed as 'RunnableConfig' or "
|
||||
f"'RunnableConfig | None', not '{p.annotation}'. ",
|
||||
UserWarning,
|
||||
stacklevel=4,
|
||||
)
|
||||
continue
|
||||
|
||||
# If the kwarg is accepted by the function, store the key / runtime attribute to inject
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from typing import Any, Optional
|
||||
|
||||
import pytest
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from pytest_mock import MockerFixture
|
||||
@@ -188,7 +193,9 @@ def test_deprecated_import() -> None:
|
||||
from langgraph.constants import PREVIOUS # noqa: F401
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:`checkpoint_during` is deprecated")
|
||||
@pytest.mark.filterwarnings(
|
||||
"ignore:`durability` has no effect when no checkpointer is present"
|
||||
)
|
||||
def test_checkpoint_during_deprecation_state_graph() -> None:
|
||||
class CheckDurability(TypedDict):
|
||||
durability: NotRequired[str]
|
||||
@@ -228,3 +235,100 @@ def test_checkpoint_during_deprecation_state_graph() -> None:
|
||||
):
|
||||
for chunk in graph.stream({}, checkpoint_during=False): # type: ignore[arg-type]
|
||||
assert chunk["plain_node"]["durability"] == "exit"
|
||||
|
||||
|
||||
def test_config_parameter_incorrect_typing() -> None:
|
||||
"""Test that a warning is raised when config parameter is typed incorrectly."""
|
||||
builder = StateGraph(PlainState)
|
||||
|
||||
# Test sync function with config: dict
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*dict.*'. ",
|
||||
):
|
||||
|
||||
def sync_node_with_dict_config(state: PlainState, config: dict) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(sync_node_with_dict_config)
|
||||
|
||||
# Test async function with config: dict
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*dict.*'. ",
|
||||
):
|
||||
|
||||
async def async_node_with_dict_config(
|
||||
state: PlainState, config: dict
|
||||
) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(async_node_with_dict_config)
|
||||
|
||||
# Test with other incorrect types
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*Any.*'. ",
|
||||
):
|
||||
|
||||
def sync_node_with_any_config(state: PlainState, config: Any) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(sync_node_with_any_config)
|
||||
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*Any.*'. ",
|
||||
):
|
||||
|
||||
async def async_node_with_any_config(
|
||||
state: PlainState, config: Any
|
||||
) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(async_node_with_any_config)
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
|
||||
def node_with_correct_config(
|
||||
state: PlainState, config: RunnableConfig
|
||||
) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(node_with_correct_config)
|
||||
|
||||
def node_with_optional_config(
|
||||
state: PlainState,
|
||||
config: Optional[RunnableConfig], # noqa: UP045
|
||||
) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(node_with_optional_config)
|
||||
|
||||
def node_with_untyped_config(state: PlainState, config) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(node_with_untyped_config)
|
||||
|
||||
async def async_node_with_correct_config(
|
||||
state: PlainState, config: RunnableConfig
|
||||
) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(async_node_with_correct_config)
|
||||
|
||||
async def async_node_with_optional_config(
|
||||
state: PlainState,
|
||||
config: Optional[RunnableConfig], # noqa: UP045
|
||||
) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(async_node_with_optional_config)
|
||||
|
||||
async def async_node_with_untyped_config(
|
||||
state: PlainState, config
|
||||
) -> PlainState:
|
||||
return state
|
||||
|
||||
builder.add_node(async_node_with_untyped_config)
|
||||
assert len(w) == 0
|
||||
|
||||
Reference in New Issue
Block a user