mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-14 12:15:25 +02:00
test(langgraph,prebuilt): cover wrapped ToolNodes and errors is_invalid_resume ignores
The nested-interrupt tests also run with a pass-through `wrap_tool_call` / `awrap_tool_call`, the path `create_agent` always takes. A new test checks that `is_invalid_resume` is False for an ordinary `ValidationError` and for other errors.
This commit is contained in:
@@ -212,6 +212,13 @@ def test_interrupt_response_schema_rejects_invalid_resume(
|
||||
}
|
||||
|
||||
|
||||
def test_is_invalid_resume_ignores_other_errors() -> None:
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Decision.model_validate({"approved": "nope"})
|
||||
assert not is_invalid_resume(exc_info.value)
|
||||
assert not is_invalid_resume(ValueError("nope"))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resume_style", ["null", "id_map"])
|
||||
def test_interrupt_response_schema_invalid_resume_after_earlier_interrupt(
|
||||
sync_checkpointer: BaseCheckpointSaver, resume_style: str
|
||||
|
||||
@@ -650,8 +650,9 @@ def _ask_human_call() -> dict[str, list[AnyMessage]]:
|
||||
return {"messages": [AIMessage("", tool_calls=[call])]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("wrapped", [False, True], ids=["plain", "wrapped"])
|
||||
def test_tool_node_reraises_invalid_resume_from_nested_interrupt(
|
||||
sync_checkpointer: BaseCheckpointSaver,
|
||||
sync_checkpointer: BaseCheckpointSaver, wrapped: bool
|
||||
) -> None:
|
||||
asker = _approval_graph()
|
||||
|
||||
@@ -660,9 +661,16 @@ def test_tool_node_reraises_invalid_resume_from_nested_interrupt(
|
||||
"""Ask a human for approval."""
|
||||
return asker.invoke({})["answer"]
|
||||
|
||||
# `create_agent` always runs tools through a wrapper (its middleware).
|
||||
def pass_through(request, handler):
|
||||
return handler(request)
|
||||
|
||||
graph = (
|
||||
StateGraph(MessagesState)
|
||||
.add_node("tools", ToolNode([ask_human]))
|
||||
.add_node(
|
||||
"tools",
|
||||
ToolNode([ask_human], wrap_tool_call=pass_through if wrapped else None),
|
||||
)
|
||||
.add_edge(START, "tools")
|
||||
.compile(checkpointer=sync_checkpointer)
|
||||
)
|
||||
@@ -679,8 +687,9 @@ def test_tool_node_reraises_invalid_resume_from_nested_interrupt(
|
||||
assert result["messages"][-1].content == "approved=True"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("wrapped", [False, True], ids=["plain", "wrapped"])
|
||||
async def test_tool_node_reraises_invalid_resume_from_nested_interrupt_async(
|
||||
async_checkpointer: BaseCheckpointSaver,
|
||||
async_checkpointer: BaseCheckpointSaver, wrapped: bool
|
||||
) -> None:
|
||||
asker = _approval_graph()
|
||||
|
||||
@@ -689,9 +698,15 @@ async def test_tool_node_reraises_invalid_resume_from_nested_interrupt_async(
|
||||
"""Ask a human for approval."""
|
||||
return (await asker.ainvoke({}))["answer"]
|
||||
|
||||
async def pass_through(request, handler):
|
||||
return await handler(request)
|
||||
|
||||
graph = (
|
||||
StateGraph(MessagesState)
|
||||
.add_node("tools", ToolNode([ask_human]))
|
||||
.add_node(
|
||||
"tools",
|
||||
ToolNode([ask_human], awrap_tool_call=pass_through if wrapped else None),
|
||||
)
|
||||
.add_edge(START, "tools")
|
||||
.compile(checkpointer=async_checkpointer)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user