chore(prebuilt): remove v1 (#5960)

This commit is contained in:
Sydney Runkle
2025-08-19 14:30:32 -04:00
committed by GitHub
parent 5239184ba6
commit 4151861ca2
2 changed files with 46 additions and 140 deletions
@@ -5,7 +5,6 @@ from typing import (
Any,
Awaitable,
Callable,
Literal,
Optional,
Sequence,
Type,
@@ -204,7 +203,6 @@ class _AgentBuilder:
post_model_hook: Optional[RunnableLike] = None,
state_schema: Optional[StateSchemaType] = None,
context_schema: Optional[Type[Any]] = None,
version: Literal["v1", "v2"] = "v2",
name: Optional[str] = None,
store: Optional[BaseStore] = None,
):
@@ -216,7 +214,6 @@ class _AgentBuilder:
self.post_model_hook = post_model_hook
self.state_schema = state_schema
self.context_schema = context_schema
self.version = version
self.name = name
self.store = store
@@ -604,16 +601,13 @@ class _AgentBuilder:
else:
return END
else:
if self.version == "v1":
return "tools"
elif self.version == "v2":
if self.post_model_hook is not None:
return "post_model_hook"
tool_calls = [
self._tool_node.inject_tool_args(call, state, self.store) # type: ignore[arg-type]
for call in last_message.tool_calls
]
return [Send("tools", [tool_call]) for tool_call in tool_calls]
if self.post_model_hook is not None:
return "post_model_hook"
tool_calls = [
self._tool_node.inject_tool_args(call, state, self.store) # type: ignore[arg-type]
for call in last_message.tool_calls
]
return [Send("tools", [tool_call]) for tool_call in tool_calls]
return should_continue
@@ -794,7 +788,6 @@ def create_react_agent(
interrupt_before: Optional[list[str]] = None,
interrupt_after: Optional[list[str]] = None,
debug: bool = False,
version: Literal["v1", "v2"] = "v2",
name: Optional[str] = None,
**deprecated_kwargs: Any,
) -> CompiledStateGraph:
@@ -906,9 +899,6 @@ def create_react_agent(
post_model_hook: An optional node to add after the `agent` node (i.e., the node that calls the LLM).
Useful for implementing human-in-the-loop, guardrails, validation, or other post-processing.
Post-model hook must be a callable or a runnable that takes in current graph state and returns a state update.
!!! Note
Only available with `version="v2"`.
state_schema: An optional state schema that defines graph state.
Must have `messages` and `remaining_steps` keys.
Defaults to `AgentState` that defines those two keys.
@@ -924,15 +914,6 @@ def create_react_agent(
Should be one of the following: "agent", "tools".
This is useful if you want to return directly or run additional processing on an output.
debug: A flag indicating whether to enable debug mode.
version: Determines the version of the graph to create.
Can be one of:
- `"v1"`: The tool node processes a single message. All tool
calls in the message are executed in parallel within the tool node.
- `"v2"`: The tool node processes a tool call.
Tool calls are distributed across multiple instances of the tool
node using the [Send](https://langchain-ai.github.io/langgraph/concepts/low_level/#send)
API.
name: An optional name for the CompiledStateGraph.
This name will be automatically used when adding ReAct agent graph to another graph as a subgraph node -
particularly useful for building multi-agent systems.
@@ -1000,11 +981,6 @@ def create_react_agent(
f"create_react_agent() got unexpected keyword arguments: {deprecated_kwargs}"
)
if version not in ("v1", "v2"):
raise ValueError(
f"Invalid version {version}. Supported versions are 'v1' and 'v2'."
)
if response_format and not isinstance(response_format, ToolOutput):
# Then it's a pydantic model or JSONSchema. We'll automatically convert
# it to the tool output strategy as it is widely supported.
@@ -1031,7 +1007,6 @@ def create_react_agent(
post_model_hook=post_model_hook,
state_schema=state_schema,
context_schema=context_schema,
version=version,
name=name,
store=store,
)
+39 -108
View File
@@ -2,7 +2,6 @@ import dataclasses
import inspect
from typing import (
Annotated,
Literal,
Optional,
Union,
)
@@ -53,18 +52,14 @@ from tests.model import FakeToolCallingModel
pytestmark = pytest.mark.anyio
REACT_TOOL_CALL_VERSIONS = ["v1", "v2"]
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_no_prompt(sync_checkpointer: BaseCheckpointSaver, version: str) -> None:
def test_no_prompt(sync_checkpointer: BaseCheckpointSaver) -> None:
model = FakeToolCallingModel()
agent = create_react_agent(
model,
[],
checkpointer=sync_checkpointer,
version=version,
)
inputs = [HumanMessage("hi?")]
thread = {"configurable": {"thread_id": "123"}}
@@ -172,8 +167,7 @@ def test_runnable_prompt():
assert response == expected_response
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_prompt_with_store(version: Literal["v1", "v2"]):
def test_prompt_with_store():
def add(a: int, b: int):
"""Adds a and b"""
return a + b
@@ -198,7 +192,6 @@ def test_prompt_with_store(version: Literal["v1", "v2"]):
[add],
prompt=prompt,
store=in_memory_store,
version=version,
)
response = agent.invoke(
{"messages": [("user", "hi")]}, {"configurable": {"user_id": "1"}}
@@ -211,7 +204,6 @@ def test_prompt_with_store(version: Literal["v1", "v2"]):
[add],
prompt=prompt_no_store,
store=in_memory_store,
version=version,
)
response = agent.invoke(
{"messages": [("user", "hi")]}, {"configurable": {"user_id": "2"}}
@@ -262,9 +254,8 @@ async def test_prompt_with_store_async():
@pytest.mark.parametrize("tool_style", ["openai", "anthropic"])
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
@pytest.mark.parametrize("include_builtin", [True, False])
def test_model_with_tools(tool_style: str, version: str, include_builtin: bool) -> None:
def test_model_with_tools(tool_style: str, include_builtin: bool) -> None:
model = FakeToolCallingModel(tool_style=tool_style)
@dec_tool
@@ -299,7 +290,6 @@ def test_model_with_tools(tool_style: str, version: str, include_builtin: bool)
create_react_agent(
model.bind_tools(tools),
tools,
version=version,
)
@@ -429,8 +419,7 @@ def test__infer_handled_types() -> None:
_infer_handled_types(handler)
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_react_agent_with_structured_response(version: str) -> None:
def test_react_agent_with_structured_response() -> None:
class WeatherResponse(BaseModel):
temperature: float = Field(description="The temperature in fahrenheit")
@@ -451,7 +440,6 @@ def test_react_agent_with_structured_response(version: str) -> None:
model,
[get_weather],
response_format=WeatherResponse,
version=version,
)
response = agent.invoke({"messages": [HumanMessage("What's the weather?")]})
assert response["structured_response"] == expected_structured_response
@@ -484,11 +472,9 @@ class CustomStatePydantic(AgentStatePydantic):
user_name: Optional[str] = None
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
@pytest.mark.parametrize("state_schema", [CustomState, CustomStatePydantic])
def test_react_agent_update_state(
sync_checkpointer: BaseCheckpointSaver,
version: Literal["v1", "v2"],
state_schema: StateSchemaType,
) -> None:
@dec_tool
@@ -533,7 +519,6 @@ def test_react_agent_update_state(
state_schema=state_schema,
prompt=prompt,
checkpointer=sync_checkpointer,
version=version,
)
config = {"configurable": {"thread_id": "1"}}
# Run until interrupted
@@ -549,9 +534,8 @@ def test_react_agent_update_state(
assert tool_message.name == "get_user_name"
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_react_agent_parallel_tool_calls(
sync_checkpointer: BaseCheckpointSaver, version: str
sync_checkpointer: BaseCheckpointSaver,
) -> None:
human_assistance_execution_count = 0
@@ -584,7 +568,6 @@ def test_react_agent_parallel_tool_calls(
model,
[human_assistance, get_weather],
checkpointer=sync_checkpointer,
version=version,
)
config = {"configurable": {"thread_id": "1"}}
query = "Get user assistance and also check the weather"
@@ -595,17 +578,11 @@ def test_react_agent_parallel_tool_calls(
if messages := event.get("messages"):
message_types.append([m.type for m in messages])
if version == "v1":
assert message_types == [
["human"],
["human", "ai"],
]
elif version == "v2":
assert message_types == [
["human"],
["human", "ai"],
["human", "ai", "tool"],
]
assert message_types == [
["human"],
["human", "ai"],
["human", "ai", "tool"],
]
# Resume
message_types = []
@@ -621,12 +598,8 @@ def test_react_agent_parallel_tool_calls(
["human", "ai", "tool", "tool", "ai"],
]
if version == "v1":
assert human_assistance_execution_count == 1
assert get_weather_execution_count == 2
elif version == "v2":
assert human_assistance_execution_count == 1
assert get_weather_execution_count == 1
assert human_assistance_execution_count == 1
assert get_weather_execution_count == 1
class AgentStateExtraKey(AgentState):
@@ -637,13 +610,10 @@ class AgentStateExtraKeyPydantic(AgentStatePydantic):
foo: int
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
@pytest.mark.parametrize(
"state_schema", [AgentStateExtraKey, AgentStateExtraKeyPydantic]
)
def test_create_react_agent_inject_vars(
version: Literal["v1", "v2"], state_schema: StateSchemaType
) -> None:
def test_create_react_agent_inject_vars(state_schema: StateSchemaType) -> None:
"""Test that the agent can inject state and store into tool functions."""
store = InMemoryStore()
namespace = ("test",)
@@ -682,7 +652,6 @@ def test_create_react_agent_inject_vars(
ToolNode([tool1], handle_tool_errors=False),
state_schema=state_schema,
store=store,
version=version,
)
result = agent.invoke({"messages": [{"role": "user", "content": "hi"}], "foo": 2})
assert result["messages"] == [
@@ -694,8 +663,7 @@ def test_create_react_agent_inject_vars(
assert result["foo"] == 2
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
async def test_return_direct(version: str) -> None:
async def test_return_direct() -> None:
@dec_tool(return_direct=True)
def tool_return_direct(input: str) -> str:
"""A tool that returns directly."""
@@ -722,7 +690,6 @@ async def test_return_direct(version: str) -> None:
agent = create_react_agent(
model,
[tool_return_direct, tool_normal],
version=version,
)
# Test direct return for tool_return_direct
@@ -747,9 +714,7 @@ async def test_return_direct(version: str) -> None:
),
]
model = FakeToolCallingModel(tool_calls=[second_tool_call, []])
agent = create_react_agent(
model, [tool_return_direct, tool_normal], version=version
)
agent = create_react_agent(model, [tool_return_direct, tool_normal])
result = agent.invoke(
{"messages": [HumanMessage(content="Test normal", id="hum1")]}
)
@@ -778,9 +743,7 @@ async def test_return_direct(version: str) -> None:
),
]
model = FakeToolCallingModel(tool_calls=[both_tool_calls, []])
agent = create_react_agent(
model, [tool_return_direct, tool_normal], version=version
)
agent = create_react_agent(model, [tool_return_direct, tool_normal])
result = agent.invoke({"messages": [HumanMessage(content="Test both", id="hum2")]})
assert result["messages"] == [
HumanMessage(content="Test both", id="hum2"),
@@ -821,9 +784,8 @@ def test_inspect_react() -> None:
inspect.getclosurevars(agent.nodes["agent"].bound.func)
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_react_with_subgraph_tools(
sync_checkpointer: BaseCheckpointSaver, version: Literal["v1", "v2"]
sync_checkpointer: BaseCheckpointSaver,
) -> None:
class State(TypedDict):
a: int
@@ -879,7 +841,6 @@ def test_react_with_subgraph_tools(
model,
tool_node,
checkpointer=sync_checkpointer,
version=version,
)
result = agent.invoke(
{"messages": [HumanMessage(content="What's 2 + 3 and 2 * 3?")]},
@@ -910,8 +871,7 @@ def test_react_with_subgraph_tools(
]
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_react_agent_subgraph_streaming_sync(version: Literal["v1", "v2"]) -> None:
def test_react_agent_subgraph_streaming_sync() -> None:
"""Test React agent streaming when used as a subgraph node sync version"""
@dec_tool
@@ -931,7 +891,6 @@ def test_react_agent_subgraph_streaming_sync(version: Literal["v1", "v2"]) -> No
model,
tools=[get_weather],
prompt="You are a helpful travel assistant.",
version=version,
)
# Create a subgraph that uses the React agent as a node
@@ -1001,8 +960,7 @@ def test_react_agent_subgraph_streaming_sync(version: Literal["v1", "v2"]) -> No
assert msg.content.startswith("The weather of Tokyo is sunny.")
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
async def test_react_agent_subgraph_streaming(version: Literal["v1", "v2"]) -> None:
async def test_react_agent_subgraph_streaming() -> None:
"""Test React agent streaming when used as a subgraph node."""
@dec_tool
@@ -1022,7 +980,6 @@ async def test_react_agent_subgraph_streaming(version: Literal["v1", "v2"]) -> N
model,
tools=[get_weather],
prompt="You are a helpful travel assistant.",
version=version,
)
# Create a subgraph that uses the React agent as a node
@@ -1094,9 +1051,8 @@ async def test_react_agent_subgraph_streaming(version: Literal["v1", "v2"]) -> N
assert msg.content.startswith("The weather of Tokyo is sunny.")
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_tool_node_node_interrupt(
sync_checkpointer: BaseCheckpointSaver, version: str
sync_checkpointer: BaseCheckpointSaver,
) -> None:
def tool_normal(some_val: int) -> str:
"""Tool docstring."""
@@ -1122,7 +1078,6 @@ def test_tool_node_node_interrupt(
model,
[tool_interrupt, tool_normal],
checkpointer=sync_checkpointer,
version=version,
)
result = agent.invoke({"messages": [HumanMessage("hi?")]}, config)
expected_messages = [
@@ -1147,11 +1102,7 @@ def test_tool_node_node_interrupt(
),
_AnyIdToolMessage(content="normal", name="tool_normal", tool_call_id="2"),
]
if version == "v1":
# Interrupt blocks second tool result
assert result["messages"] == expected_messages[:-1]
elif version == "v2":
assert result["messages"] == expected_messages
assert result["messages"] == expected_messages
state = agent.get_state(config)
assert state.next == ("tools",)
@@ -1165,8 +1116,7 @@ def test_tool_node_node_interrupt(
)
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_basic(version: str) -> None:
def test_dynamic_model_basic() -> None:
"""Test basic dynamic model functionality."""
def dynamic_model(state, runtime: Runtime):
@@ -1176,7 +1126,7 @@ def test_dynamic_model_basic(version: str) -> None:
else:
return FakeToolCallingModel(tool_calls=[])
agent = create_react_agent(dynamic_model, [], version=version)
agent = create_react_agent(dynamic_model, [])
result = agent.invoke({"messages": [HumanMessage("hello")]})
assert len(result["messages"]) == 2
@@ -1187,8 +1137,7 @@ def test_dynamic_model_basic(version: str) -> None:
assert result["messages"][-1].content == "urgent help"
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_with_tools(version: Literal["v1", "v2"]) -> None:
def test_dynamic_model_with_tools() -> None:
"""Test dynamic model with tool calling."""
@dec_tool
@@ -1215,9 +1164,7 @@ def test_dynamic_model_with_tools(version: Literal["v1", "v2"]) -> None:
tool_calls=[[{"args": {"x": 1}, "id": "1", "name": "basic_tool"}], []]
)
agent = create_react_agent(
dynamic_model, [basic_tool, advanced_tool], version=version
)
agent = create_react_agent(dynamic_model, [basic_tool, advanced_tool])
# Test basic tool usage
result = agent.invoke({"messages": [HumanMessage("basic request")]})
@@ -1239,8 +1186,7 @@ class Context:
user_id: str
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_with_context(version: str) -> None:
def test_dynamic_model_with_context() -> None:
"""Test dynamic model using config parameters."""
def dynamic_model(state, runtime: Runtime[Context]):
@@ -1251,9 +1197,7 @@ def test_dynamic_model_with_context(version: str) -> None:
else:
return FakeToolCallingModel(tool_calls=[])
agent = create_react_agent(
dynamic_model, [], context_schema=Context, version=version
)
agent = create_react_agent(dynamic_model, [], context_schema=Context)
# Test with basic user
result = agent.invoke(
@@ -1270,8 +1214,7 @@ def test_dynamic_model_with_context(version: str) -> None:
assert len(result["messages"]) == 2
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_with_state_schema(version: Literal["v1", "v2"]) -> None:
def test_dynamic_model_with_state_schema() -> None:
"""Test dynamic model with custom state schema."""
class CustomDynamicState(AgentState):
@@ -1284,9 +1227,7 @@ def test_dynamic_model_with_state_schema(version: Literal["v1", "v2"]) -> None:
else:
return FakeToolCallingModel(tool_calls=[])
agent = create_react_agent(
dynamic_model, [], state_schema=CustomDynamicState, version=version
)
agent = create_react_agent(dynamic_model, [], state_schema=CustomDynamicState)
result = agent.invoke(
{"messages": [HumanMessage("hello")], "model_preference": "advanced"}
@@ -1295,15 +1236,14 @@ def test_dynamic_model_with_state_schema(version: Literal["v1", "v2"]) -> None:
assert result["model_preference"] == "advanced"
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_with_prompt(version: Literal["v1", "v2"]) -> None:
def test_dynamic_model_with_prompt() -> None:
"""Test dynamic model with different prompt types."""
def dynamic_model(state: AgentState, runtime: Runtime) -> BaseChatModel:
return FakeToolCallingModel(tool_calls=[])
# Test with string prompt
agent = create_react_agent(dynamic_model, [], prompt="system_msg", version=version)
agent = create_react_agent(dynamic_model, [], prompt="system_msg")
result = agent.invoke({"messages": [HumanMessage("human_msg")]})
assert result["messages"][-1].content == "system_msg-human_msg"
@@ -1312,9 +1252,7 @@ def test_dynamic_model_with_prompt(version: Literal["v1", "v2"]) -> None:
"""Generate a dynamic system message based on state."""
return [{"role": "system", "content": "system_msg"}] + list(state["messages"])
agent = create_react_agent(
dynamic_model, [], prompt=dynamic_prompt, version=version
)
agent = create_react_agent(dynamic_model, [], prompt=dynamic_prompt)
result = agent.invoke({"messages": [HumanMessage("human_msg")]})
assert result["messages"][-1].content == "system_msg-human_msg"
@@ -1332,8 +1270,7 @@ async def test_dynamic_model_async() -> None:
assert result["messages"][-1].content == "hello async"
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_with_structured_response(version: str) -> None:
def test_dynamic_model_with_structured_response() -> None:
"""Test dynamic model with structured response format."""
class TestResponse(BaseModel):
@@ -1354,9 +1291,7 @@ def test_dynamic_model_with_structured_response(version: str) -> None:
],
)
agent = create_react_agent(
dynamic_model, [], response_format=TestResponse, version=version
)
agent = create_react_agent(dynamic_model, [], response_format=TestResponse)
result = agent.invoke({"messages": [HumanMessage("hello")]})
assert "structured_response" in result
@@ -1395,8 +1330,7 @@ def test_dynamic_model_with_checkpointer(sync_checkpointer):
assert call_count >= 2
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_state_dependent_tools(version: Literal["v1", "v2"]) -> None:
def test_dynamic_model_state_dependent_tools() -> None:
"""Test dynamic model that changes available tools based on state."""
@dec_tool
@@ -1420,7 +1354,7 @@ def test_dynamic_model_state_dependent_tools(version: Literal["v1", "v2"]) -> No
tool_calls=[[{"args": {"x": 1}, "id": "1", "name": "tool_a"}], []]
)
agent = create_react_agent(dynamic_model, [tool_a, tool_b], version=version)
agent = create_react_agent(dynamic_model, [tool_a, tool_b])
# Ask to use tool B
result = agent.invoke({"messages": [HumanMessage("use_b please")]})
@@ -1435,8 +1369,7 @@ def test_dynamic_model_state_dependent_tools(version: Literal["v1", "v2"]) -> No
assert last_message.content == "A: 1"
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_error_handling(version: Literal["v1", "v2"]) -> None:
def test_dynamic_model_error_handling() -> None:
"""Test error handling in dynamic model."""
def failing_dynamic_model(state, runtime: Runtime):
@@ -1444,7 +1377,7 @@ def test_dynamic_model_error_handling(version: Literal["v1", "v2"]) -> None:
raise ValueError("Dynamic model failed")
return FakeToolCallingModel(tool_calls=[])
agent = create_react_agent(failing_dynamic_model, [], version=version)
agent = create_react_agent(failing_dynamic_model, [])
# Normal operation should work
result = agent.invoke({"messages": [HumanMessage("hello")]})
@@ -1788,8 +1721,7 @@ def test_create_react_agent_inject_vars_with_post_model_hook(
assert result["foo"] == 2
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_response_format_using_tool_choice(version: Literal["v1", "v2"]) -> None:
def test_response_format_using_tool_choice() -> None:
"""Test response format using tool choice."""
class WeatherResponse(BaseModel):
@@ -1810,7 +1742,6 @@ def test_response_format_using_tool_choice(version: Literal["v1", "v2"]) -> None
model,
[get_weather],
response_format=WeatherResponse,
version=version,
)
response = agent.invoke(
{