Improves the default `reject` `ToolMessage` so models see that the human
denied the action, the tool was not executed, and the same call should
not be retried unless the user asks. Also documents that clients can
provide a custom `reject` message for domain-specific guidance.
[Docs](https://github.com/langchain-ai/docs/pull/4269)
_Opened collaboratively by Mason Daugherty and open-swe._
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Adds an optional `when` predicate to `InterruptOnConfig`, allowing
dynamic per-tool-call control over whether a HITL interrupt fires.
---
**`when` predicate in `InterruptOnConfig`**
```python
class InterruptOnConfig(TypedDict):
allowed_decisions: list[DecisionType]
description: NotRequired[str | _DescriptionFactory]
args_schema: NotRequired[dict[str, Any]]
when: NotRequired[Callable[[ToolCallRequest], bool]] # new
```
When provided, `when` is called before adding a tool call to the batch
interrupt. If it returns `False`, the call is auto-approved and
excluded. If it returns `True` (or `when` is absent), existing behaviour
is unchanged.
The predicate receives a `ToolCallRequest` with:
- `tool_call` — the raw tool call dict (name, args, id)
- `tool` — `None` (no `BaseTool` instance is available at the
`after_model` stage)
- `state` — current agent state
- `runtime` — a `ToolRuntime` constructed from the node-level `Runtime`,
with `tool_call_id` populated
Example:
```python
HumanInTheLoopMiddleware(
interrupt_on={
"delete_file": InterruptOnConfig(
allowed_decisions=["approve", "reject"],
when=lambda req: req.tool_call["args"].get("path", "").startswith("/etc"),
)
}
)
```
This change is fully backwards-compatible — `when` is `NotRequired` and
existing configs without it behave identically.
> This PR was developed with AI-agent assistance.
- Bumps `langchain` to **1.3.2** (patch)
- Raises minimum `langgraph` requirement from `>=1.2.1` to `>=1.2.2`
langgraph 1.2.2 fixes a race condition where DeltaChannel checkpoint
writes
could serialize `BaseMessage` objects with `id=None` before
`apply_writes()`
ran the reducer, causing the same message to appear with a different ID
on
every `get_state()` call and across resumed invocations
(langchain-ai/langgraph#7913).
The lockfile will be updated once langgraph 1.2.2 is published to PyPI
(langchain-ai/langgraph#7914).
`PIIMiddleware` previously scrubbed detected PII only at the state level
via its `after_model` / `before_model` hooks. Consumers reading the live
stream — `astream_events(version="v3")` or `run.messages` /
`run.tool_calls` / `run.values` — saw the raw model text, the raw
tool-call args, the raw tool outputs, and the raw state snapshots until
the run finished and the canonical conversation history was written.
This change registers a stream transformer ahead of
`MessagesTransformer` that redacts every wire surface of an agent run.
The transformer holds a sliding lookback buffer (default 128 characters)
per `(run_id, content-block index)` so PII patterns that straddle delta
boundaries are caught before the safe prefix is released downstream.
Anything older than the lookback is run through the configured detector
and emitted; the trailing tail stays buffered until a later delta
extends it past the cap or the block finishes. `_finalize_block` always
re-runs detection over the full block snapshot so the finalized content
lands fully redacted even when the in-flight buffer never released a
tail (short responses, or PII arriving in the final delta).
The `block` strategy is now supported on the streaming path via a
buffering mode that withholds every delta until the block resolves —
clean blocks release the full text at finalize, PII-bearing blocks zero
the wire and let `after_model` / `apply_to_tool_results` raise
`PIIDetectionError` on the original state message. Activation is gated
on `apply_to_output=True`, matching the existing post-hoc semantics. The
middleware's transformer factory is cloned by `StreamMux._make_child`
into every subgraph scope, so attaching `PIIMiddleware` at the outer
agent also redacts streamed deltas from sub-agents invoked inside tools.
## Tool-call and tools-channel coverage
The transformer covers every wire surface of an agent run, not just AI
message text:
- **Streamed AI text deltas** (`content-block-delta` of type
`text-delta`) — lookback machinery, redacted in place.
- **Streamed tool-call args** (`content-block-delta` with
`tool_call_chunk` / `server_tool_call_chunk` fields) — each delta
carries the full cumulative args string; detection runs on the field
directly and redacts in place. Verified empirically against
`_compat_bridge.py` and the consumer-side
`_merge_block_delta_into_store` snapshot-replace semantics.
- **Finalized tool-call blocks** (`content-block-finish` with
`tool_call` / `server_tool_call` / `invalid_tool_call`) — `args` dict
walked recursively and each string leaf redacted.
- **Tool execution events on the `tools` channel** —
`tool-started.input`, `tool-output-delta`, `tool-finished.output`,
`tool-error.message` all run through detection. String deltas use the
same lookback machinery as text-deltas keyed by `tool_call_id`;
structured payloads walk recursively.
- **State snapshots on the `values` channel** — message lists are walked
and each message's `.content` is redacted on a fresh copy. Graph state
itself stays intact for the state-level enforcer
(`apply_to_tool_results` via `before_model`) to act on independently.
- **Legacy `(BaseMessage, metadata)` payloads** on the `messages`
channel (Python 3.10 path, where `langgraph`'s `ASYNCIO_ACCEPTS_CONTEXT
= sys.version_info >= (3, 11)` falls back to a code path that doesn't
propagate the streaming callback into the chat model) — `.content` and
`AIMessage.tool_calls[*].args` are scrubbed. For `block`, the event's
`data` tuple is replaced with an empty-content copy so the original
message stays in state for `after_model` to raise on.
## Worth a careful look
- `_PIIStreamTransformer._mutate_text_delta` — lookback partition.
Anything older than `lookback` characters is released after redaction;
the tail stays buffered. Bulletproof against whitespace-permissive
detectors (notably `credit_card`, whose regex matches across spaces).
- `_PIIStreamTransformer._mutate_tool_call_chunk_delta` — direct
in-place redaction of the cumulative args string. No buffer; the wire
shape is cumulative-snapshot, the consumer-side merge is
replace-not-append.
- `_PIIStreamTransformer._mutate_legacy_payload` — the dual path:
mutate-in-place for non-`block` (idempotent with `after_model`),
replace-with-empty-copy for `block` (keeps original in graph state for
`after_model` to raise on).
- `_PIIStreamTransformer._redact_value` — the recursive walker.
`BaseMessage` branch returns a fresh `.content`-redacted copy via
`model_copy(update=...)` — never mutates in place — so tool-output
payloads that wrap a `ToolMessage` and message lists in state snapshots
flow through cleanly.
- The new `transformers` attribute on `PIIMiddleware`: this is what
makes `create_agent` pick the factory up. Multiple `PIIMiddleware`
instances each register one transformer; ordering is preserved within
the `before_builtins` lane.
## Compatibility
Bumps `langgraph` to `>=1.2.1` for the `before_builtins` opt-in on
`StreamTransformer`.
Bumps the `langchain-tests` minimum across the monorepo from `1.0.0` to
`1.1.9` and adds a partner-level `Makefile` so partner lockfiles can be
regenerated in one command, matching the existing convention under
`libs/`.
Adds a `transformers` attribute to `AgentMiddleware` so middleware can
declare scope-aware `StreamTransformer` factories alongside their
`tools` and lifecycle hooks. `create_agent` merges middleware-registered
factories with any caller-supplied ones at compile time.
## API
```python
class MyMiddleware(AgentMiddleware):
transformers = (MyTransformer,) # factory: (scope,) -> StreamTransformer
```
When the agent compiles, the final transformer order on the run mux is:
1. Built-in ``ToolCallTransformer``
2. Middleware-registered factories, in middleware order
3. Caller-supplied ``transformers=`` from ``create_agent``
This ordering keeps the built-in tool-call projection in front of any
consumer transformers and gives caller-supplied entries the final word.
Bumps [idna](https://github.com/kjd/idna) from 3.11 to 3.15.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/kjd/idna/blob/master/HISTORY.md">idna's
changelog</a>.</em></p>
<blockquote>
<h2>3.15 (2026-05-12)</h2>
<ul>
<li>Enforce DNS-length cap on individual labels early in
<code>check_label</code>,
short-circuiting contextual-rule processing for oversized input
while staying compatible with UTS 46 usage.</li>
<li>Tidy core helpers: hoist bidi category sets to module-level
frozensets (avoiding per-codepoint list construction), simplify
length checks, and reuse the shared <code>_unicode_dots_re</code> from
<code>idna.core</code> in the codec module.</li>
<li>Use <code>raise ... from err</code> for proper exception chaining
and
switch internal string formatting to f-strings.</li>
<li>Allow <code>flit_core</code> 4.x in the build backend.</li>
<li>Expand the ruff lint set (flake8-bugbear, flake8-simplify,
pyupgrade, perflint) and apply the surfaced fixes; pin lint CI
to Python 3.14.</li>
<li>Add Dependabot configuration for GitHub Actions.</li>
<li>Convert README and HISTORY from reStructuredText to Markdown.</li>
<li>Reference CVE-2026-45409 for the 3.14 advisory in place of the
initial GHSA identifier.</li>
</ul>
<p>Thanks to Felix Yan, Stan Ulbrych, and metsw24-max for
contributions to this release.</p>
<h2>3.14 (2026-05-10)</h2>
<ul>
<li>Removed opportunity to process long inputs into quadratic
time by rejecting oversize inputs up-front. Closes a bypass
of the CVE-2024-3651 mitigation. [CVE-2026-45409]</li>
</ul>
<p>Thanks to Stan Ulbrych for reporting the issue.</p>
<h2>3.13 (2026-04-22)</h2>
<ul>
<li>Correct classification error for codepoint U+A7F1</li>
</ul>
<h2>3.12 (2026-04-21)</h2>
<ul>
<li>Update to Unicode 17.0.0.</li>
<li>Issue a deprecation warning for the transitional argument.</li>
<li>Added lazy-loading to provide some performance improvements.</li>
<li>Removed vestiges of code related to Python 2 support, including
segmentation of data structures specific to Jython.</li>
</ul>
<p>Thanks to Rodrigo Nogueira for contributions to this release.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="af30a092e1"><code>af30a09</code></a>
Release 3.15</li>
<li><a
href="30314d4628"><code>30314d4</code></a>
Pre-release 3.15rc0</li>
<li><a
href="05d4b219aa"><code>05d4b21</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/237">#237</a> from
kjd/convert-docs-to-markdown</li>
<li><a
href="2987fdba19"><code>2987fdb</code></a>
Convert README and HISTORY from reStructuredText to Markdown</li>
<li><a
href="59fa8002d5"><code>59fa800</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/236">#236</a> from
kjd/dependabot/github_actions/actions-f3e34333ea</li>
<li><a
href="def69834ce"><code>def6983</code></a>
Merge branch 'master' into
dependabot/github_actions/actions-f3e34333ea</li>
<li><a
href="bbd8004a79"><code>bbd8004</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/234">#234</a> from
StanFromIreland/patch-1</li>
<li><a
href="edd07c0502"><code>edd07c0</code></a>
Bump github/codeql-action from 3.35.2 to 4.35.2 in the actions
group</li>
<li><a
href="5557db030c"><code>5557db0</code></a>
Merge branch 'master' into patch-1</li>
<li><a
href="f11746cf49"><code>f11746c</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/235">#235</a> from
StanFromIreland/patch-2</li>
<li>Additional commits viewable in <a
href="https://github.com/kjd/idna/compare/v3.11...v3.15">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langchain/network/alerts).
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Dependabot has been stripping upper/lower bounds from internal
`langchain-*` deps in partner `pyproject.toml` files (e.g. #37288
reduced `langchain-core>=1.3.2,<2.0.0` to bare `langchain-core`). Locks
down the config so bumps preserve existing specifiers, and restores the
bounds it already mangled across the monorepo.
## Changes
- Add `versioning-strategy: increase` to every `uv` ecosystem block in
`.github/dependabot.yml` so future bumps move the lower bound in place
instead of rewriting the constraint.
- Ignore workspace-internal packages (`langchain-core`, `langchain`,
`langchain-classic`, `langchain-text-splitters`, `langchain-tests`,
`langchain-model-profiles`) on every `uv` block — these are editable
installs from local paths and their published constraints are
hand-curated for release, not Dependabot's to bump.
- Restore stripped bounds across all `libs/` packages — runtime
`dependencies` and every dep group (`test`, `dev`, `test_integration`,
`typing`, `lint`) — to `>=1.4.0,<2.0.0` for `langchain-core` and
`>=1.0.0,<2.0.0` for the other internal packages.
Closes#37369
---
The `glob_search` tool in `FilesystemFileSearchMiddleware` documents
that
results are "sorted by modification time (most recently modified
first)",
but the implementation was returning files in the arbitrary order
provided
by `Path.glob()`.
This change adds a sort by modification timestamp (`modified_at`), in
descending order, immediately before extracting the file paths for the
return value. No public API changes.
---------
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/urllib3/urllib3/releases">urllib3's
releases</a>.</em></p>
<blockquote>
<h2>2.7.0</h2>
<h2>🚀 urllib3 is fundraising for HTTP/2 support</h2>
<p><a
href="https://sethmlarson.dev/urllib3-is-fundraising-for-http2-support">urllib3
is raising ~$40,000 USD</a> to release HTTP/2 support and ensure
long-term sustainable maintenance of the project after a sharp decline
in financial support. If your company or organization uses Python and
would benefit from HTTP/2 support in Requests, pip, cloud SDKs, and
thousands of other projects <a
href="https://opencollective.com/urllib3">please consider contributing
financially</a> to ensure HTTP/2 support is developed sustainably and
maintained for the long-haul.</p>
<p>Thank you for your support.</p>
<h2>Security</h2>
<p>Addressed high-severity security issues. Impact was limited to
specific use cases detailed in the accompanying advisories; overall user
exposure was estimated to be marginal.</p>
<ul>
<li>
<p>Decompression-bomb safeguards of the streaming API were bypassed:</p>
<ol>
<li>When <code>HTTPResponse.drain_conn()</code> was called after the
response had been read and decompressed partially. (Reported by <a
href="https://github.com/Cycloctane"><code>@Cycloctane</code></a>)</li>
<li>During the second <code>HTTPResponse.read(amt=N)</code> or
<code>HTTPResponse.stream(amt=N)</code> call when the response was
decompressed using the official <a
href="https://pypi.org/project/brotli/">Brotli</a> library. (Reported by
<a
href="https://github.com/kimkou2024"><code>@kimkou2024</code></a>)</li>
</ol>
<p>See GHSA-mf9v-mfxr-j63j for details.</p>
</li>
<li>
<p>HTTP pools created using
<code>ProxyManager.connection_from_url</code> did not strip sensitive
headers specified in <code>Retry.remove_headers_on_redirect</code> when
redirecting to a different host. (GHSA-qccp-gfcp-xxvc reported by <a
href="https://github.com/christos-spearbit"><code>@christos-spearbit</code></a>)</p>
</li>
</ul>
<h2>Deprecations and Removals</h2>
<ul>
<li>Used <code>FutureWarning</code> instead of
<code>DeprecationWarning</code> for better visibility of existing
deprecation notices. Rescheduled the removal of deprecated features to
version 3.0. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3763">urllib3/urllib3#3763</a>)</li>
<li>Removed support for end-of-life Python 3.9. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3720">urllib3/urllib3#3720</a>)</li>
<li>Removed support for end-of-life PyPy3.10. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4979">urllib3/urllib3#4979</a>)</li>
<li>Bumped the minimum supported pyOpenSSL version to 19.0.0. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3777">urllib3/urllib3#3777</a>)</li>
</ul>
<h2>Bugfixes</h2>
<ul>
<li>Fixed a bug where <code>HTTPResponse.read(amt=None)</code> was
ignoring decompressed data buffered from previous partial reads. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3636">urllib3/urllib3#3636</a>)</li>
<li>Fixed a bug where <code>HTTPResponse.read()</code> could cache only
part of the response after a partial read when
<code>cache_content=True</code>. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4967">urllib3/urllib3#4967</a>)</li>
<li>Fixed <code>HTTPResponse.stream()</code> and
<code>HTTPResponse.read_chunked()</code> to handle <code>amt=0</code>.
(<a
href="https://redirect.github.com/urllib3/urllib3/issues/3793">urllib3/urllib3#3793</a>)</li>
<li>Updated <code>_TYPE_BODY</code> type alias to include missing
<code>Iterable[str]</code>, matching the documented and runtime behavior
of chunked request bodies. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3798">urllib3/urllib3#3798</a>)</li>
<li>Fixed <code>LocationParseError</code> when paths resembling
schemeless URIs were passed to
<code>HTTPConnectionPool.urlopen()</code>. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3352">urllib3/urllib3#3352</a>)</li>
<li>Fixed <code>BaseHTTPResponse.readinto()</code> type annotation to
accept <code>memoryview</code> in addition to <code>bytearray</code>,
matching the <code>io.RawIOBase.readinto</code> contract and enabling
use with <code>io.BufferedReader</code> without type errors. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3764">urllib3/urllib3#3764</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/urllib3/urllib3/blob/main/CHANGES.rst">urllib3's
changelog</a>.</em></p>
<blockquote>
<h1>2.7.0 (2026-05-07)</h1>
<h2>Security</h2>
<p>Addressed high-severity security issues.
Impact was limited to specific use cases detailed in the accompanying
advisories; overall user exposure was estimated to be marginal.</p>
<ul>
<li>
<p>Decompression-bomb safeguards of the streaming API were bypassed:</p>
<ol>
<li>When <code>HTTPResponse.drain_conn()</code> was called after the
response had been
read and decompressed partially.</li>
<li>During the second <code>HTTPResponse.read(amt=N)</code> or
<code>HTTPResponse.stream(amt=N)</code> call when the response was
decompressed
using the official <code>Brotli
<https://pypi.org/project/brotli/></code>__ library.</li>
</ol>
<p>See <code>GHSA-mf9v-mfxr-j63j
<https://github.com/urllib3/urllib3/security/advisories/GHSA-mf9v-mfxr-j63j></code>__
for details.</p>
</li>
<li>
<p>HTTP pools created using
<code>ProxyManager.connection_from_url</code> did not strip
sensitive headers specified in
<code>Retry.remove_headers_on_redirect</code> when
redirecting to a different host.
(<code>GHSA-qccp-gfcp-xxvc
<https://github.com/urllib3/urllib3/security/advisories/GHSA-qccp-gfcp-xxvc></code>__)</p>
</li>
</ul>
<h2>Deprecations and Removals</h2>
<ul>
<li>Used <code>FutureWarning</code> instead of
<code>DeprecationWarning</code> for better
visibility of existing deprecation notices. Rescheduled the removal of
deprecated features to version 3.0.
(<code>[#3763](https://github.com/urllib3/urllib3/issues/3763)
<https://github.com/urllib3/urllib3/issues/3763></code>__)</li>
<li>Removed support for end-of-life Python 3.9.
(<code>[#3720](https://github.com/urllib3/urllib3/issues/3720)
<https://github.com/urllib3/urllib3/issues/3720></code>__)</li>
<li>Removed support for end-of-life PyPy3.10.
(<code>[#4979](https://github.com/urllib3/urllib3/issues/4979)
<https://github.com/urllib3/urllib3/issues/4979></code>__)</li>
<li>Bumped the minimum supported pyOpenSSL version to 19.0.0.
(<code>[#3777](https://github.com/urllib3/urllib3/issues/3777)
<https://github.com/urllib3/urllib3/issues/3777></code>__)</li>
</ul>
<h2>Bugfixes</h2>
<ul>
<li>Fixed a bug where <code>HTTPResponse.read(amt=None)</code> was
ignoring decompressed
data buffered from previous partial reads.
(<code>[#3636](https://github.com/urllib3/urllib3/issues/3636)
<https://github.com/urllib3/urllib3/issues/3636></code>__)</li>
<li>Fixed a bug where <code>HTTPResponse.read()</code> could cache only
part of the
response after a partial read when <code>cache_content=True</code>.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9a950b92d9"><code>9a950b9</code></a>
Release 2.7.0</li>
<li><a
href="5ec0de499b"><code>5ec0de4</code></a>
Merge commit from fork</li>
<li><a
href="2bdcc44d1e"><code>2bdcc44</code></a>
Merge commit from fork</li>
<li><a
href="f45b0df09d"><code>f45b0df</code></a>
Fix a misleading example for <code>ProxyManager</code> (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4970">#4970</a>)</li>
<li><a
href="577193ca02"><code>577193c</code></a>
Switch to nightly PyPy3.11 in CI for now (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4984">#4984</a>)</li>
<li><a
href="e90af45bb0"><code>e90af45</code></a>
Avoid infinite loop in <code>HTTPResponse.read_chunked</code> when
<code>amt=0</code> (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4974">#4974</a>)</li>
<li><a
href="67ed74fdae"><code>67ed74f</code></a>
Bump dev dependencies (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4972">#4972</a>)</li>
<li><a
href="3abd481097"><code>3abd481</code></a>
Upgrade mypy to version 1.20.2 (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4978">#4978</a>)</li>
<li><a
href="2b8725dfca"><code>2b8725d</code></a>
Drop support for EOL PyPy3.10 (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4979">#4979</a>)</li>
<li><a
href="2944b2a0a6"><code>2944b2a</code></a>
Upgrade <code>setup-chrome</code> and <code>setup-firefox</code> to fix
warnings (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4973">#4973</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langchain/network/alerts).
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
deprecate hub classic and hub runnable. This code path isn't expected to
be active for most users (it's dependent on having a very old version of
the langsmith sdk). harden usage of loads/dumps.
Sweep classic deprecations so every removal lands on `2.0.0`, runtime
warnings carry the auto-generated since/removal/alternative line, and
replacements steer at `langchain.agents.create_agent` and
`with_structured_output(...)` instead of pre-v1 LangGraph +
`python.langchain.com` links.
## Changes
- **Bump removal targets from `1.0` / `1.0.0` to `2.0.0`** across
agents, chains, memory, retrievers, structured-output, vectorstore
toolkits, and the `langchain_classic._api.module_import` shim — gives
users a real runway now that v1 has shipped.
- **Move bespoke `message=` strings onto `addendum=`** (or split into
`alternative=` + `addendum=`). `warn_deprecated` skips the
auto-generated since/removal/alternative line whenever `message=` is
set, so the prior pattern silently dropped that info from the runtime
`LangChainDeprecationWarning`. Matches the pattern already used in
`HTMLHeaderTextSplitter.split_text_from_url`, which is updated for
consistency.
- **Repoint `alternative=` at v1 replacements**: chains/memory/agent
toolkits → `langchain.agents.create_agent` (with checkpointer or
retrieval-tool guidance in the addendum); `openai_functions` and
`chains/structured_output` → `ChatModel.with_structured_output(...)`;
`openapi` chains → `ChatModel.bind_tools(...)` + HTTP client.
`ConversationChain` no longer points at `RunnableWithMessageHistory`.
- **Refresh `AGENT_DEPRECATION_WARNING`** in
`langchain_classic._api.deprecation` — drop stale LangGraph and
`python.langchain.com` links in favor of `langchain.agents.create_agent`
and the `docs.langchain.com/oss/python/migrate/langchain-v1` guide.
Propagates to all 13 caller sites in `agents/`.
- **Newly deprecate `langchain_classic.chat_models.init_chat_model` and
`langchain_classic.embeddings.init_embeddings`** with the framing
*"maintained in `langchain`; `langchain-classic` retains this entry
point for import-compatibility only"*. The classic docstring examples
and the warning admonition both point at `langchain.chat_models`.
- **Improve `init_chat_model` docstrings** in both `langchain_v1` and
the classic copy: clarify `provider:model` prefix vs. `model_provider=`,
recommend pinned IDs over moving aliases, add the `upstage` provider
row, and refresh examples to GA models (`gpt-5.5`, `claude-opus-4-7`).
- **Standardize partner Anthropic deprecations**: replace
`AnthropicLLM`'s `model_validator(raise_warning)` with
`@deprecated(since="0.1.0", removal="2.0.0",
alternative="ChatAnthropic")`, and pin the `ChatAnthropic`
`output_format` runtime warning at `langchain-anthropic 2.0.0` instead
of "a future version".
Re-enable the `[community]`, `[azure-ai]`, and `[cohere]` extras on
`langchain-classic`, and the `[cohere]` extra on `langchain` (v1). These
had been commented out as a temporary workaround during the `langchain`
-> `langchain-classic` rename so the renamed package could ship before
downstream partners were re-released against it. Now that
`langchain-community` 0.4.1, `langchain-cohere` 0.5.1, and
`langchain-azure-ai` 1.2.3 are published with the correct dependency
targets, the extras can be restored.
Bumps `langchain` from 1.2.16 → 1.2.17.
Picks up:
- `respond` decision added to HITL middleware (#37095)
> This PR was opened with AI-agent assistance.
Extends `HumanInTheLoopMiddleware` with a new `respond` decision type
for "ask user" style tools — tools whose real implementation is the
human's response. The interrupt is raised with the tool call as usual;
the resume payload becomes the body of a synthetic `ToolMessage` with
`status="success"`, and the tool itself is not executed.
This complements `reject` (which produces a synthetic `ToolMessage` with
`status="error"`) by enabling the symmetric success path: a reviewer can
answer on the tool's behalf without invoking it.
## Changes
- New `RespondDecision` `TypedDict` with a required `message: str`
field; added to the `Decision` union.
- `"respond"` added to the `DecisionType` literal.
- `_process_decision` handles `"respond"` by emitting a `ToolMessage`
with `status="success"` and preserving the original tool call on the
`AIMessage` so provider-required tool-call/tool-message pairing is
maintained.
- The `True` shortcut in `interrupt_on` now expands to `["approve",
"edit", "reject", "respond"]`, so existing callers that opted into "all
decisions" pick up the new capability without code changes. The `reject`
decision already permits a reviewer to inject arbitrary `ToolMessage`
content, so `respond` extends the same trust model — not a new
capability class.
## Example
```python
from langchain.agents.middleware import HumanInTheLoopMiddleware
middleware = HumanInTheLoopMiddleware(
interrupt_on={"ask_user": {"allowed_decisions": ["respond"]}}
)
# Resume payload: {"decisions": [{"type": "respond", "message": "blue"}]}
# → synthetic ToolMessage(content="blue", status="success") for `ask_user`.
```
---
*Implementation drafted with AI-agent assistance.*
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Description
Updates package metadata and README badges so LangChain social links
point to the new `@langchain_oss` X handle. This was completed with
AI-agent assistance.
## Test Plan
- [ ] Validate README badges and package metadata links point to
`https://x.com/langchain_oss`
_Opened collaboratively by Mason Daugherty and open-swe._
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
## Summary
Stop inlining the full agent state into every tool-dispatch `Send` in
`create_agent`. Dispatch with the bare list form `Send("tools",
[tool_call])` and let `ToolNode` hydrate `ToolRuntime.state` from graph
channels at tool-execution time.
**Depends on**
[langchain-ai/langgraph#7594](https://github.com/langchain-ai/langgraph/pull/7594)
which teaches `ToolNode` to read channel state via `CONFIG_KEY_READ`
when given a bare tool-call list. `uv.lock` pins that branch for CI
while the langgraph PR is in flight — this pin will be reverted to a
published `langgraph` version before merge.
## What was happening
Before this change, every pending tool call produced a `Send` whose
payload was:
```python
ToolCallWithContext(
__type="tool_call_with_context",
tool_call=tool_call,
state=state, # ← the FULL agent state dict, including messages list
)
```
For any agent that runs many turns, `state["messages"]` grows linearly
with the conversation. Every super-step that dispatches tools serializes
that whole list into every `Send`, and those Sends live forever in the
checkpointer's `__pregel_tasks` writes. The result is **O(N²)
`__pregel_tasks` storage** across a run.
## What changed
- `libs/langchain_v1/langchain/agents/factory.py`:
- `_make_model_to_tools_edge` now returns `Send("tools", [tool_call])` —
no inlined state.
- Drops the `ToolCallWithContext` import.
- `libs/langchain_v1/pyproject.toml` + `libs/langchain_v1/uv.lock`:
- Temporary `[tool.uv.sources]` pin on `langgraph`,
`langgraph-prebuilt`, `langgraph-checkpoint` to the companion PR branch
so CI exercises both changes end-to-end. Revert after langgraph release.
## Why it's safe
- Same snapshot semantics as before. `Send` is emitted at the end of the
model super-step and consumed at the start of the tools super-step;
channels by that point reflect every write from the model super-step
(including the new AIMessage). Parallel tool tasks all see the same
values since sibling writes don't land until end-of-super-step.
- Legacy `ToolCallWithContext` input path is preserved in `ToolNode` —
no-op for any external caller still constructing it by hand.
## Test plan
- [x] `tests/unit_tests/agents/` — **738 passed, 2 skipped, 1 xfailed**
- [x] `ruff check .` / `ruff format .` — clean
- [x] `mypy langchain/agents/factory.py` — clean
- [x] Before/after benchmark (below)
## Benchmark
Script runs `create_agent` with a mock `GenericFakeChatModel` and two
tools (`write_file`, `edit_file`). Each of the N turns dispatches 2 tool
calls. After the run, the `InMemorySaver` is inspected for bytes stored
under `__pregel_tasks` — the channel that carries the tool-dispatch
`Send` payloads.
| N | TASKS before | TASKS after | ratio |
|---:|---:|---:|---:|
| 5 | 87.6 KB | **4.7 KB** | **18.6× smaller** |
| 10 | 335 KB | **9.4 KB** | **35.7× smaller** |
| 25 | 2.05 MB | **23.7 KB** | **86.5× smaller** |
| 50 | 8.14 MB | **47.6 KB** | **171× smaller** |
| 100 | 32.5 MB | **95.3 KB** | **341× smaller** |
| 200 | 130 MB | **192 KB** | **677× smaller** |
| 500 | 815 MB | **482 KB** | **1,691× smaller** |
**Growth shape:**
- **Before:** per-Send bytes scale with current `messages` length (full
state is inlined), so total TASKS across N turns = Σ(2 × k) for k=1..N ≈
O(N²).
- **After:** per-Send bytes are constant — just the `tool_call` dict.
Total TASKS is O(#dispatches), completely independent of conversation
length. In this bench with ~2 dispatches/turn: **940–964 bytes per turn
across N=5..500, essentially flat.**
An agent that makes 100 tool calls in a single turn pays the same TASKS
cost as one that makes 100 across 50 turns — which is the semantically
correct behavior.
Note: the `messages` channel is unchanged by this PR — it's still the
dominant storage term (growing O(N²) via `add_messages`). TASKS was a
second, compounding cost sitting on top of it; at N=100 it added 40% on
top of `messages`, at N=500 it added 67%. After the fix, TASKS is a
rounding error regardless of N.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`ChatFireworks.max_retries` silently did nothing. The old code assigned
the value to a `ChatCompletionV2` sub-object rather than the base
client, and the pinned Fireworks SDK (0.13.0–0.19.20) never honors its
own `_max_retries` attribute on the base client either. Since the
Stainless-generated 1.x SDK that does implement retries is still
pre-release (1.0.1a63 at time of writing), retry responsibility is
ported to the LangChain side until the pin can be bumped.
## Summary
Updates the example in `ModelCallLimitMiddleware` docstring to use the
correct import path. The previous import referenced a non-existent
module, which could cause confusion for users following the
documentation.
CVE-2025-71176 (medium severity)
All are dev-only (test dependency group) — no impact on published
packages.
### Why syrupy was also bumped
syrupy 4.x (`<5.0.0`) constrains pytest to `<9.0.0`, blocking the CVE
fix. Widening to `<6.0.0` allows syrupy 5.x which supports pytest 9.x.