mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
14b1a243e57dc07d3b1e7b77e8f76c232533a2e4
218 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dfca7f4424 | feat(langchain): project subagent runs onto typed run.subagents channel (#37739) | ||
|
|
36be77b0f1 |
feat(langchain): add interrupt_mode and when predicate to HumanInTheLoopMiddleware (#37579)
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.
|
||
|
|
d08245f70d |
feat(langchain): redact streamed PII in flight on PIIMiddleware (#37616)
`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`. |
||
|
|
1aa4496fb4 |
feat(langchain): register stream transformers on middleware (#37591)
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.
|
||
|
|
d328d84b78 |
fix(langchain): sort glob_search results by mtime (newest first) (#37462)
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> |
||
|
|
36c381b149 | fix(langchain): alias Bedrock providers in summarization token check (#37453) | ||
|
|
da380bccf8 | chore(infra): merge v1.4 into master (#37350) | ||
|
|
9c48a120b9 | revert: feat(langchain): ls_agent_type tag on create_agent calls (#37249) | ||
|
|
ba56ac6f03 |
feat(langchain): add respond decision to HITL middleware (#37095)
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>
|
||
|
|
ee95ad6907 |
feat(langchain): ls_agent_type tag on create_agent calls (#36774)
|
||
|
|
c87cd04927 |
release(core): release 1.3.0 (#36851)
xRelease 1.3.0 |
||
|
|
8f5c800f41 |
test(langchain): test cache hit/miss on lru (#36659)
add quick test |
||
|
|
1ca47a5411 |
perf(langchain): add another init test with middleware (#36644)
add another init test with middleware |
||
|
|
af4d711a2f |
chore(core): reduce streaming metadata / perf (#36588)
- looking into reducing streaming metadata / perfm --------- Co-authored-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> |
||
|
|
f0c5a28fa0 |
perf(langchain): add benchmark command (#36641)
add benchmark in Makefile |
||
|
|
4e55c555ad |
test(langchain): cover runtime recursion limit override in create_agent (#36376)
Extends the existing unit test for to verify that a per-invoke override is visible inside the tool runtime config. This keeps the coverage in the existing fake-model end-to-end test and exercises both the default config path and the override path in one place. Created with [Deep Agents CLI](https://docs.langchain.com/oss/python/deepagents/cli/overview) using gpt-5.4 (provider: openai). |
||
|
|
c7a677bba5 |
chore(langchain): add async implementation to todolist and test (#36313)
add async func as well |
||
|
|
2f64d80cc6 |
fix(core,model-profiles): add missing ModelProfile fields, warn on schema drift (#36129)
PR #35788 added 7 new fields to the `langchain-profiles` CLI output (`name`, `status`, `release_date`, `last_updated`, `open_weights`, `attachment`, `temperature`) but didn't update `ModelProfile` in `langchain-core`. Partner packages like `langchain-aws` that set `extra="forbid"` on their Pydantic models hit `extra_forbidden` validation errors when Pydantic encountered undeclared TypedDict keys at construction time. This adds the missing fields, makes `ModelProfile` forward-compatible, provides a base-class hook so partners can stop duplicating model-profile validator boilerplate, migrates all in-repo partners to the new hook, and adds runtime + CI-time warnings for schema drift. ## Changes ### `langchain-core` - Add `__pydantic_config__ = ConfigDict(extra="allow")` to `ModelProfile` so unknown profile keys pass Pydantic validation even on models with `extra="forbid"` — forward-compatibility for when the CLI schema evolves ahead of core - Declare the 7 missing fields on `ModelProfile`: `name`, `status`, `release_date`, `last_updated`, `open_weights` (metadata) and `attachment`, `temperature` (capabilities) - Add `_warn_unknown_profile_keys()` in `model_profile.py` — emits a `UserWarning` when a profile dict contains keys not in `ModelProfile`, suggesting a core upgrade. Wrapped in a bare `except` so introspection failures never crash model construction - Add `BaseChatModel._resolve_model_profile()` hook that returns `None` by default. Partners can override this single method instead of redefining the full `_set_model_profile` validator — the base validator calls it automatically - Add `BaseChatModel._check_profile_keys` as a separate `model_validator` that calls `_warn_unknown_profile_keys`. Uses a distinct method name so partner overrides of `_set_model_profile` don't inadvertently suppress the check ### `langchain-profiles` CLI - Add `_warn_undeclared_profile_keys()` to the CLI (`cli.py`), called after merging augmentations in `refresh()` — warns at profile-generation time (not just runtime) when emitted keys aren't declared in `ModelProfile`. Gracefully skips if `langchain-core` isn't installed - Add guard test `test_model_data_to_profile_keys_subset_of_model_profile` in model-profiles — feeds a fully-populated model dict to `_model_data_to_profile()` and asserts every emitted key exists in `ModelProfile.__annotations__`. CI fails before any release if someone adds a CLI field without updating the TypedDict ### Partner packages - Migrate all 10 in-repo partners to the `_resolve_model_profile()` hook, replacing duplicated `@model_validator` / `_set_model_profile` overrides: anthropic, deepseek, fireworks, groq, huggingface, mistralai, openai (base + azure), openrouter, perplexity, xai - Anthropic retains custom logic (context-1m beta → `max_input_tokens` override); all others reduce to a one-liner - Add `pr_lint.yml` scope for the new `model-profiles` package |
||
|
|
9e4a6013be | fix(openai): add type: message to Responses API input items (#35693) | ||
|
|
46fdade7e6 | fix(langchain): normalize custom detector output to prevent KeyError in hash/mask strategies (#35651) | ||
|
|
bed4d2686a | chore(langchain): switch refs from gemini-3 to gemini-3.1 (#35535) | ||
|
|
83f81d65af |
fix(langchain): allow Gemini 3 models to use ProviderStrategy with tools (#34464)
|
||
|
|
8f1bc0d3ae | feat(openai): support automatic server-side compaction (#35212) | ||
|
|
65c224a3da |
chore(core): raise more descriptive model error in init_chat_model (#35167)
|
||
|
|
de05838fca |
chore(deps): bump the langchain-deps group across 3 directories with 40 updates (#35129)
Bumps the langchain-deps group with 10 updates in the /libs/core directory: | Package | From | To | | --- | --- | --- | | [langsmith](https://github.com/langchain-ai/langsmith-sdk) | `0.6.0` | `0.7.1` | | [tenacity](https://github.com/jd/tenacity) | `9.1.2` | `9.1.4` | | [packaging](https://github.com/pypa/packaging) | `25.0` | `26.0` | | [uuid-utils](https://github.com/aminalaee/uuid-utils) | `0.12.0` | `0.14.0` | | [ruff](https://github.com/astral-sh/ruff) | `0.14.11` | `0.15.0` | | [types-requests](https://github.com/typeshed-internal/stub_uploader) | `2.31.0.6` | `2.32.4.20260107` | | [setuptools](https://github.com/pypa/setuptools) | `78.1.1` | `82.0.0` | | [pytest](https://github.com/pytest-dev/pytest) | `8.4.2` | `9.0.2` | | [pytest-watcher](https://github.com/olzhasar/pytest-watcher) | `0.4.3` | `0.6.3` | | [pytest-codspeed](https://github.com/CodSpeedHQ/pytest-codspeed) | `4.2.0` | `4.3.0` | Bumps the langchain-deps group with 31 updates in the /libs/langchain directory: | Package | From | To | | --- | --- | --- | | [langsmith](https://github.com/langchain-ai/langsmith-sdk) | `0.4.31` | `0.7.1` | | [packaging](https://github.com/pypa/packaging) | `25.0` | `26.0` | | [pydantic](https://github.com/pydantic/pydantic) | `2.12.1` | `2.12.5` | | [ruff](https://github.com/astral-sh/ruff) | `0.14.11` | `0.15.0` | | [types-requests](https://github.com/typeshed-internal/stub_uploader) | `2.31.0.6` | `2.32.4.20260107` | | [setuptools](https://github.com/pypa/setuptools) | `80.9.0` | `82.0.0` | | [pytest](https://github.com/pytest-dev/pytest) | `8.4.2` | `9.0.2` | | [pytest-watcher](https://github.com/olzhasar/pytest-watcher) | `0.4.3` | `0.6.3` | | [numpy](https://github.com/numpy/numpy) | `1.26.4` | `2.2.6` | | [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) | `2.0.43` | `2.0.46` | | [langchain-anthropic](https://github.com/langchain-ai/langchain) | `0.0.1.post1` | `1.3.2` | | [langchain-google-vertexai](https://github.com/langchain-ai/langchain-google) | `2.1.2` | `3.2.2` | | [langchain-google-genai](https://github.com/langchain-ai/langchain-google) | `2.1.12` | `4.2.0` | | [langchain-fireworks](https://github.com/langchain-ai/langchain) | `1.0.0a1` | `1.1.0` | | [langchain-ollama](https://github.com/langchain-ai/langchain) | `1.0.0a1` | `1.0.1` | | [langchain-mistralai](https://github.com/langchain-ai/langchain) | `0.2.12` | `1.1.1` | | [langchain-huggingface](https://github.com/langchain-ai/langchain) | `1.0.0` | `1.2.0` | | [langchain-groq](https://github.com/langchain-ai/langchain) | `1.0.0a1` | `1.1.2` | | [langchain-aws](https://github.com/langchain-ai/langchain-aws) | `1.0.0a1` | `1.2.3` | | [langchain-deepseek](https://github.com/langchain-ai/langchain) | `1.0.0a1` | `1.0.1` | | [langchain-xai](https://github.com/langchain-ai/langchain) | `1.0.0a1` | `1.2.2` | | [langchain-perplexity](https://github.com/langchain-ai/langchain) | `1.0.0a1` | `1.1.0` | | [pytest-cov](https://github.com/pytest-dev/pytest-cov) | `4.1.0` | `7.0.0` | | [lark](https://github.com/lark-parser/lark) | `1.3.0` | `1.3.1` | | [wrapt](https://github.com/GrahamDumpleton/wrapt) | `1.17.3` | `2.1.1` | | [python-dotenv](https://github.com/theskumar/python-dotenv) | `1.1.1` | `1.2.1` | | langchainhub | `0.1.18` | `0.1.21` | | [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) | `3.6.0` | `5.0.0` | | [types-pytz](https://github.com/typeshed-internal/stub_uploader) | `2023.4.0.20240130` | `2025.2.0.20251108` | | [fastapi](https://github.com/fastapi/fastapi) | `0.128.0` | `0.128.6` | | [playwright](https://github.com/microsoft/playwright-python) | `1.55.0` | `1.58.0` | Bumps the langchain-deps group with 17 updates in the /libs/langchain_v1 directory: | Package | From | To | | --- | --- | --- | | [pydantic](https://github.com/pydantic/pydantic) | `2.12.4` | `2.12.5` | | [ruff](https://github.com/astral-sh/ruff) | `0.14.11` | `0.15.0` | | [pytest](https://github.com/pytest-dev/pytest) | `8.4.2` | `9.0.2` | | [pytest-watcher](https://github.com/olzhasar/pytest-watcher) | `0.4.3` | `0.6.3` | | [langchain-google-vertexai](https://github.com/langchain-ai/langchain-google) | `3.0.3` | `3.2.2` | | [langchain-google-genai](https://github.com/langchain-ai/langchain-google) | `3.0.3` | `4.2.0` | | [langchain-fireworks](https://github.com/langchain-ai/langchain) | `1.0.0` | `1.1.0` | | [langchain-ollama](https://github.com/langchain-ai/langchain) | `1.0.0` | `1.0.1` | | [langchain-mistralai](https://github.com/langchain-ai/langchain) | `1.0.1` | `1.1.1` | | [langchain-huggingface](https://github.com/langchain-ai/langchain) | `1.0.1` | `1.2.0` | | [langchain-groq](https://github.com/langchain-ai/langchain) | `1.0.1` | `1.1.2` | | [langchain-aws](https://github.com/langchain-ai/langchain-aws) | `1.0.0` | `1.2.3` | | [langchain-xai](https://github.com/langchain-ai/langchain) | `1.0.0` | `1.2.2` | | [langchain-perplexity](https://github.com/langchain-ai/langchain) | `1.0.0` | `1.1.0` | | [wrapt](https://github.com/GrahamDumpleton/wrapt) | `1.17.3` | `2.1.1` | | [langgraph](https://github.com/langchain-ai/langgraph) | `1.0.7` | `1.0.8` | | [langchain-azure-ai](https://github.com/langchain-ai/langchain-azure) | `1.0.3` | `1.0.4` | Updates `langsmith` from 0.6.0 to 0.7.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's releases</a>.</em></p> <blockquote> <h2>v0.7.1</h2> <h2>What's Changed</h2> <ul> <li>release(js): 0.5.0 by <a href="https://github.com/jacoblee93"><code>@jacoblee93</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2381">langchain-ai/langsmith-sdk#2381</a></li> <li>fix(ci): Convert JS release workflow to use OIDC by <a href="https://github.com/jacoblee93"><code>@jacoblee93</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2383">langchain-ai/langsmith-sdk#2383</a></li> <li>feat(js): run evaluations as examples complete by <a href="https://github.com/bees"><code>@bees</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2349">langchain-ai/langsmith-sdk#2349</a></li> <li>fix(js): Restore default concurrency behavior for JS eval runner by <a href="https://github.com/jacoblee93"><code>@jacoblee93</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2385">langchain-ai/langsmith-sdk#2385</a></li> <li>fix(js,py): Adds backcompat for old prompt cache API by <a href="https://github.com/jacoblee93"><code>@jacoblee93</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2386">langchain-ai/langsmith-sdk#2386</a></li> <li>fix(js): Add system message visibility to Anthropic wrapper traces by <a href="https://github.com/veeceey"><code>@veeceey</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2375">langchain-ai/langsmith-sdk#2375</a></li> <li>release(py): 0.7.1 by <a href="https://github.com/jacoblee93"><code>@jacoblee93</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2388">langchain-ai/langsmith-sdk#2388</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/veeceey"><code>@veeceey</code></a> made their first contribution in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2375">langchain-ai/langsmith-sdk#2375</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.0...v0.7.1">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.0...v0.7.1</a></p> <h2>v0.7.0</h2> <h2>What's Changed</h2> <ul> <li>chore(deps): bump form-data from 4.0.3 to 4.0.5 in /js by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2282">langchain-ai/langsmith-sdk#2282</a></li> <li>chore(deps-dev): bump langchain from 0.3.29 to 0.3.37 in /js by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2283">langchain-ai/langsmith-sdk#2283</a></li> <li>chore(deps): bump vite from 6.3.5 to 6.4.1 in /js by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2284">langchain-ai/langsmith-sdk#2284</a></li> <li>chore:deps by <a href="https://github.com/jkennedyvz"><code>@jkennedyvz</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2356">langchain-ai/langsmith-sdk#2356</a></li> <li>chore(deps): bump python-multipart from 0.0.21 to 0.0.22 in /python by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2351">langchain-ai/langsmith-sdk#2351</a></li> <li>chore(deps): bump protobuf from 6.33.2 to 6.33.5 in /python by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2352">langchain-ai/langsmith-sdk#2352</a></li> <li>chore(deps): bump pyasn1 from 0.6.1 to 0.6.2 in /python by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2353">langchain-ai/langsmith-sdk#2353</a></li> <li>chore(deps-dev): bump esbuild from 0.20.2 to 0.25.0 in /js/internal/environment_tests/test-exports-esbuild in the npm_and_yarn group across 1 directory by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2354">langchain-ai/langsmith-sdk#2354</a></li> <li>chore(deps): bump jws from 4.0.0 to 4.0.1 in /js by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2355">langchain-ai/langsmith-sdk#2355</a></li> <li>chore(deps): bump actions/setup-node from 3 to 6 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2357">langchain-ai/langsmith-sdk#2357</a></li> <li>chore(deps): bump actions/github-script from 6 to 8 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2358">langchain-ai/langsmith-sdk#2358</a></li> <li>chore(deps): bump actions/checkout from 2 to 6 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2359">langchain-ai/langsmith-sdk#2359</a></li> <li>chore(deps): bump astral-sh/setup-uv from 6 to 7 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2360">langchain-ai/langsmith-sdk#2360</a></li> <li>chore(deps): bump actions/cache from 4 to 5 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2361">langchain-ai/langsmith-sdk#2361</a></li> <li>chore(deps-dev): bump ty from 0.0.10 to 0.0.15 in /python in the py-minor-and-patch group by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2362">langchain-ai/langsmith-sdk#2362</a></li> <li>chore(deps): update httpx requirement from <0.28.0,>=0.23.0 to >=0.23.0,<0.29.0 in /python by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2363">langchain-ai/langsmith-sdk#2363</a></li> <li>chore(deps-dev): bump eslint-plugin-prettier from 4.2.1 to 4.2.5 in /js by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2366">langchain-ai/langsmith-sdk#2366</a></li> <li>chore(deps-dev): bump babel-jest from 29.7.0 to 30.2.0 in /js by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2365">langchain-ai/langsmith-sdk#2365</a></li> <li>chore: adding workflow perms by <a href="https://github.com/jkennedyvz"><code>@jkennedyvz</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2367">langchain-ai/langsmith-sdk#2367</a></li> <li>fix: esbuild CVE dependabot 78 by <a href="https://github.com/jkennedyvz"><code>@jkennedyvz</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2369">langchain-ai/langsmith-sdk#2369</a></li> <li>fix(deps): resolve glob CLI command injection (CVE-2025-64756) by <a href="https://github.com/jkennedyvz"><code>@jkennedyvz</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2368">langchain-ai/langsmith-sdk#2368</a></li> <li>fix(deps): resolve js-yaml prototype pollution vulnerability by <a href="https://github.com/jkennedyvz"><code>@jkennedyvz</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2370">langchain-ai/langsmith-sdk#2370</a></li> <li>chore(deps): bump the js-minor-and-patch group in /js with 20 updates by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2364">langchain-ai/langsmith-sdk#2364</a></li> <li>chore: rm unused fn by <a href="https://github.com/hinthornw"><code>@hinthornw</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2346">langchain-ai/langsmith-sdk#2346</a></li> <li>feat(py,js): Use global singleton for prompt cache, enable by default by <a href="https://github.com/jacoblee93"><code>@jacoblee93</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2371">langchain-ai/langsmith-sdk#2371</a></li> <li>chore: Support jwt auth in replicas by <a href="https://github.com/hinthornw"><code>@hinthornw</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2293">langchain-ai/langsmith-sdk#2293</a></li> <li>release(py): 0.7.0 by <a href="https://github.com/jacoblee93"><code>@jacoblee93</code></a> in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2382">langchain-ai/langsmith-sdk#2382</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/jkennedyvz"><code>@jkennedyvz</code></a> made their first contribution in <a href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2356">langchain-ai/langsmith-sdk#2356</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.6.9...v0.7.0">https://github.com/langchain-ai/langsmith-sdk/compare/v0.6.9...v0.7.0</a></p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href=" |
||
|
|
0040e1a8aa | fix(langchain): fix token counting on partial message sequences (#35101) | ||
|
|
ce5f73e07c |
refactor(langchain): rename _SUPPORTED_PROVIDERS -> _BUILTIN_PROVIDERS (#35100)
|
||
|
|
8767a462ca |
feat: support state updates from wrap_model_call with command(s) (#35033)
Alternative to https://github.com/langchain-ai/langchain/pull/35024. Paving the way for summarization in `wrap_model_call` (which requires state updates). --- Add `ExtendedModelResponse` dataclass that allows `wrap_model_call` middleware to return a `Command` alongside the model response for additional state updates. ```py @dataclass class ExtendedModelResponse(Generic[ResponseT]): model_response: ModelResponse[ResponseT] command: Command ``` ## Motivation Previously, `wrap_model_call` middleware could only return a `ModelResponse` or `AIMessage` — there was no way to inject additional state updates (e.g. custom state fields) from the model call middleware layer. `ExtendedModelResponse` fills this gap by accepting an optional `Command`. This feature is needed by the summarization middleware, which needs to track summarization trigger points calculated during `wrap_model_call`. ## Why `Command` instead of a plain `state_update` dict? We chose `Command` rather than the raw `state_update: dict` approach from the earlier iteration because `Command` is the established LangGraph primitive for state updates from nodes. Using `Command` means: - State updates flow through the graph's reducers (e.g. `add_messages`) rather than being merged as raw dicts. This makes messages updates additive alongside the model response instead of replacing them. - Consistency with `wrap_tool_call`, which already returns `Command`. - Future-proof: as `Command` gains new capabilities (e.g. `goto`, `send`), middleware can leverage them without API changes. ## Why keep `model_response` separate instead of using `Command` directly? The model node needs to distinguish the model's actual response (messages + structured output) from supplementary middleware state updates. If middleware returned only a `Command`, there would be no clean way to extract the `ModelResponse` for structured output handling, response validation, and the core model-to-tools routing logic. Keeping `model_response` explicit preserves a clear boundary between "what the model said" and "what middleware wants to update." Also, in order to avoid breaking, the `handler` passed to `wrap_tool_call` needs to always return a `ModelResponse`. There's no easy way to preserve this if we pump it into a `Command`. One nice thing about having this `ExtendedModelResponse` structure is that it's extensible if we want to add more metadata in the future. ## Composition When multiple middleware layers return `ExtendedModelResponse`, their commands compose naturally: - **Inner commands propagate outward:** At composition boundaries, `ExtendedModelResponse` is unwrapped to its underlying `ModelResponse` so outer middleware always sees a plain `ModelResponse` from `handler()`. The inner command is captured and accumulated. - **Commands are applied through reducers:** Each `Command` becomes a separate state update applied through the graph's reducers. For messages, this means they're additive (via `add_messages`), not replacing. - **Outer wins on conflicts:** For non-reducer state fields, commands are applied inner-first then outer, so the outermost middleware's value takes precedence on conflicting keys. - **Retry-safe:** When outer middleware retries by calling `handler()` again, accumulated inner commands are cleared and re-collected from the fresh call. ```python class Outer(AgentMiddleware): def wrap_model_call(self, request, handler): response = handler(request) # sees ModelResponse, not ExtendedModelResponse return ExtendedModelResponse( model_response=response, command=Command(update={"outer_key": "val"}), ) class Inner(AgentMiddleware): def wrap_model_call(self, request, handler): response = handler(request) return ExtendedModelResponse( model_response=response, command=Command(update={"inner_key": "val"}), ) # Final state merges both commands: {"inner_key": "val", "outer_key": "val"} ``` ## Backwards compatibility Fully backwards compatible. The `ModelCallResult` type alias is widened from `ModelResponse | AIMessage` to `ModelResponse | AIMessage | ExtendedModelResponse`, but existing middleware returning `ModelResponse` or `AIMessage` continues to work identically. ## Internals - `model_node` / `amodel_node` now return `list[Command]` instead of `dict[str, Any]` - `_build_commands` converts the model response + accumulated middleware commands into a list of `Command` objects for LangGraph - `_ComposedExtendedModelResponse` is the internal type that accumulates commands across layers during composition |
||
|
|
d181a59ebe |
test(langchain): types in test_tool_call_limit and test_model_retry (#34629)
Co-authored-by: Mason Daugherty <github@mdrxy.com> Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
dde2012b83 |
feat: threading context through create_agent flows + middleware (#34978)
Closes https://github.com/langchain-ai/langchain/issues/33956 * Making `ModelRequest` generic on `ContextT` and `ResponseT` so that we can thread type information through to `wrap_model_call` * Making builtin middlewares generic on `ContextT` and `ResponseT` so their context and response types can be inferred from the `create_agent` signature See new tests that verify backwards compatibility (for cases where folks use custom middleware that wasn't parametrized). This fixes: 1. Lack of access to context and response types in `wrap_model_call` 2. Lack of cohesion between middleware context + response types with those specified in `create_agent` See examples below: ### Type-safe context and response access ```python class MyMiddleware(AgentMiddleware[AgentState[AnalysisResult], UserContext, AnalysisResult]): def wrap_model_call( self, request: ModelRequest[UserContext], handler: Callable[[ModelRequest[UserContext]], ModelResponse[AnalysisResult]], ) -> ModelResponse[AnalysisResult]: # ✅ Now type-safe: IDE knows user_id exists and is str user_id: str = request.runtime.context["user_id"] # ❌ mypy error: "session_id" doesn't exist on UserContext request.runtime.context["session_id"] response = handler(request) if response.structured_response is not None: # ✅ Now type-safe: IDE knows sentiment exists and is str sentiment: str = response.structured_response.sentiment # ❌ mypy error: "summary" doesn't exist on AnalysisResult response.structured_response.summary return response ``` ### Mismatched middleware/schema caught at `create_agent` ```python class SessionMiddleware(AgentMiddleware[AgentState[Any], SessionContext, Any]): ... # ❌ mypy error: SessionMiddleware expects SessionContext, not UserContext create_agent( model=model, middleware=[SessionMiddleware()], context_schema=UserContext, # mismatch! ) class AnalysisMiddleware(AgentMiddleware[AgentState[AnalysisResult], ContextT, AnalysisResult]): ... # ❌ mypy error: AnalysisMiddleware expects AnalysisResult, not SummaryResult create_agent( model=model, middleware=[AnalysisMiddleware()], response_format=SummaryResult, # mismatch! ) ``` |
||
|
|
91bb474dbc | fix(langchain): avoid UnboundLocalError when no AIMessage exists (#34816) | ||
|
|
d189663b47 | fix: reuse ToolStrategy in agent factory to prevent name mismatch (#34871) | ||
|
|
72333ad644 |
fix(langchain): blocking unit test (#34866)
= |
||
|
|
ca9d2c0bdd | test(langchain): use blockbuster to detect blocking calls in the async event loop (#34777) | ||
|
|
bc8620189c |
feat: dynamic tool registration via middleware (#34842)
dependent upon https://github.com/langchain-ai/langgraph/pull/6711 1. relax constraint in `factory.py` to allow for tools not pre-registered in the `ModelRequest.tools` list 2. always add tool node if `wrap_tool_call` or `awrap_tool_call` is implemented 3. add tests confirming you can register new tools at runtime in `wrap_model_call` and execute them via `wrap_tool_call` allows for the following pattern ```py from langchain_core.messages import HumanMessage, ToolMessage from langchain_core.tools import tool from libs.langchain_v1.langchain.agents.factory import create_agent from libs.langchain_v1.langchain.agents.middleware.types import ( AgentMiddleware, ModelRequest, ToolCallRequest, ) @tool def get_weather(location: str) -> str: """Get the current weather for a location.""" return f"The weather in {location} is sunny and 72°F." @tool def calculate_tip(bill_amount: float, tip_percentage: float = 20.0) -> str: """Calculate the tip amount for a bill.""" tip = bill_amount * (tip_percentage / 100) return f"Tip: ${tip:.2f}, Total: ${bill_amount + tip:.2f}" class DynamicToolMiddleware(AgentMiddleware): """Middleware that adds and handles a dynamic tool.""" def wrap_model_call(self, request: ModelRequest, handler): updated = request.override(tools=[*request.tools, calculate_tip]) return handler(updated) def wrap_tool_call(self, request: ToolCallRequest, handler): if request.tool_call["name"] == "calculate_tip": return handler(request.override(tool=calculate_tip)) return handler(request) agent = create_agent(model="openai:gpt-4o-mini", tools=[get_weather], middleware=[DynamicToolMiddleware()]) result = agent.invoke({ "messages": [HumanMessage("What's the weather in NYC? Also calculate a 20% tip on a $85 bill")] }) for msg in result["messages"]: msg.pretty_print() ``` |
||
|
|
09c3c52fd0 |
fix(langchain): add metadata configuration to summarization model invocation (#34763)
We need to set `{"metadata": {"lc_source": "summarization"}}` on the
invocation so that consumers (e.g. `deepagents-cli`) can see that a
summarization LLM call is being made, and therefore take any necessary
actions (such as updating the status line to say `'Currently
summarizing...'`
See https://github.com/langchain-ai/deepagents/pull/742 for more
Related to #34693 (but for outbound)
|
||
|
|
73ebaddcf0 | chore: add tests for agent name metadata when streaming (#34764) | ||
|
|
ee6fce5586 |
Revert "metadata"
This reverts commit
|
||
|
|
13301a779e | metadata | ||
|
|
5799aa1045 |
chore: add tests for private state attr use (really, lack thereof) (#34725)
If a user injects a private state value, it should be ignored (and is)! |
||
|
|
725d204b95 | fix(langchain): tag messages generated from summarization (#34693) | ||
|
|
a7b943bbe3 |
fix(langchain): activate test_return_direct_spec tests, fix types (#34565)
Co-authored-by: Mason Daugherty <mason@langchain.dev> Co-authored-by: Mason Daugherty <github@mdrxy.com> |
||
|
|
5fbf270c9d |
chore(langchain): fix types in test_todo, test_tool_retry (#34503)
Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
e73b027686 |
chore(langchain): fix types in test_shell_tool (#34502)
Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
ecd19ff71f |
chore(langchain): activate mypy warn_return_any rule (#34549)
Co-authored-by: Mason Daugherty <github@mdrxy.com> Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
cb0d227d8a |
chore(langchain): fix types in test_tool_selection and test_tool_emulator (#34499)
Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
b688e36e38 |
chore(langchain): fix types in test_shell_execution_policies (#34498)
Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
36e590ca5f |
test(langchain): complete and activate test_responses tests (#34560)
Co-authored-by: Mason Daugherty <github@mdrxy.com> Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
fc417aaf17 |
fix(langchain): activate mypy warn-unreachable (#34553)
Co-authored-by: Mason Daugherty <mason@langchain.dev> |
||
|
|
5dc8ba3c99 |
chore(langchain): fix types in test_injected_runtime_create_agent, test_create_agent_tool_validation (#34568)
|