feat(openrouter): surface parallel_tool_calls on bind_tools (#38214)

`ChatOpenRouter.bind_tools()` now accepts a `parallel_tool_calls`
argument to disable parallel tool use.

---

Users binding tools to `ChatOpenRouter` previously had to know that
arbitrary kwargs were forwarded to the OpenRouter SDK in order to
disable parallel tool use (e.g.
`model.bind(parallel_tool_calls=False)`). This made the option hard to
discover.

This exposes `parallel_tool_calls` as an explicit keyword-only argument
on `ChatOpenRouter.bind_tools()`, matching the ergonomics of
`langchain-openai`. When set, it is forwarded to the request exactly as
before; when left as `None` it is omitted, preserving existing behavior.
No change to the underlying request payload or SDK floor — purely an
API/discoverability improvement.

Made by [Open
SWE](https://openswe.vercel.app/agents/053b59c8-baf4-84e1-c003-425e349e014d)

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-06-22 23:01:15 -04:00
committed by GitHub
parent 3b48f48458
commit dae4e5de9d
2 changed files with 20 additions and 0 deletions

View File

@@ -837,6 +837,7 @@ class ChatOpenRouter(BaseChatModel):
*,
tool_choice: dict | str | bool | None = None,
strict: bool | None = None,
parallel_tool_calls: bool | None = None,
**kwargs: Any,
) -> Runnable[LanguageModelInput, AIMessage]:
"""Bind tool-like objects to this chat model.
@@ -852,8 +853,13 @@ class ChatOpenRouter(BaseChatModel):
If `None`, the `strict` argument will not be passed to
the model.
parallel_tool_calls: Set to `False` to disable parallel tool use.
Defaults to `None` (no specification, which allows parallel
tool use).
**kwargs: Any additional parameters.
"""
if parallel_tool_calls is not None:
kwargs["parallel_tool_calls"] = parallel_tool_calls
formatted_tools = [
convert_to_openai_tool(tool, strict=strict) for tool in tools
]

View File

@@ -1044,6 +1044,20 @@ class TestBindTools:
tools = bound.kwargs["tools"]
assert "strict" not in tools[0]["function"]
def test_bind_tools_parallel_tool_calls_forwarded(self) -> None:
"""Test that parallel_tool_calls is forwarded to the request kwargs."""
model = _make_model()
bound = model.bind_tools([GetWeather], parallel_tool_calls=False)
assert isinstance(bound, RunnableBinding)
assert bound.kwargs["parallel_tool_calls"] is False
def test_bind_tools_parallel_tool_calls_none_omits_key(self) -> None:
"""Test that parallel_tool_calls=None does not set the key in kwargs."""
model = _make_model()
bound = model.bind_tools([GetWeather])
assert isinstance(bound, RunnableBinding)
assert "parallel_tool_calls" not in bound.kwargs
# ===========================================================================
# with_structured_output tests