Avatar for the langchain-ai user
langchain-ai
langchain
BlogDocsChangelog

Performance History

Latest Results

release(langchain): 1.4.6 (#41247)
master
9 hours ago
release(langchain): 1.4.6
byhow/release-langchain
9 hours ago
feat(langchain): add opt-in per-call interrupts to HITL middleware (#41062) Closes #41007 `HumanInTheLoopMiddleware` can now pause once per gated tool call instead of once per model turn. Pass `interrupt_mode="per_call"` to opt in. The default stays `"batched"`, so existing agents behave exactly as before. Per-call is planned to become the default in the next major release. Today one interrupt carries every gated call of a turn, with the allowed decisions in a separate list matched by position, so approval UIs have to infer what they're looking at, and a bad edit is only caught when the tool runs. In per-call mode, each interrupt has type `tool_approval` and carries a `response_schema` listing that tool's allowed decisions. Reviewers answer by interrupt ID, so each call can be answered on its own. This suits approval UIs that show one card or message per tool call, and clients that build a typed form from the schema instead of hardcoding the decisions. LangGraph checks every answer against the schema before saving it: - an edit can't switch tools, and its args must match the tool's Pydantic schema. Args the tool doesn't declare are ignored, as they are when the model sends them. Edits to tools defined by a raw JSON schema are shown but not checked - a bad answer raises a `ValidationError` pointing at the problem, and nothing is saved, so the call can be answered again - if one of several answers sent in one resume is invalid, the others' tools may already have run even if they still show as pending (a LangGraph checkpointing race), so answering them again would run them twice: check answers against `response_schema` first, or send them one at a time A gated call with no tool call ID raises before pausing. In per-call mode, list this middleware before tool retry or error-handling middleware so it wraps them. On resume, middleware listed before it runs again up to the pause, so it must not have side effects before calling `handler`. The config's `args_schema` isn't sent in either mode; in per-call mode `response_schema` shows the tool's own argument schema instead. As in batched mode, running the agent asynchronously needs Python 3.11 or later. Also raise the `langgraph` floor to 1.2.12, the first release with interrupt `response_schema`. Drop the `# type: ignore[attr-defined]` comments the newer `langgraph` made redundant. ### Before: `batched` (default) ```mermaid sequenceDiagram autonumber participant App as Client app participant HITL as Agent + HITL<br/>after_model participant Tools App->>HITL: invoke(user message) Note over HITL,Tools: model asks for send_email, delete_file, lookup<br/>ONE pause for the whole turn, no tool has run yet HITL-->>App: 1 interrupt: action_requests [send_email, delete_file]<br/>review_configs [...], response_schema = None App->>HITL: resume {decisions: [edit to=5, reject]}<br/>matched by position Note over HITL,Tools: nothing checks the answer, it is saved as-is HITL->>Tools: delete_file: skipped (rejected) HITL->>Tools: lookup(weather) HITL->>Tools: send_email(to=5) Tools-->>HITL: error: the model must fix a call it never made Note over App,Tools: Wrong order [reject, edit]: the edit lands on delete_file, ValueError.<br/>The bad answer is already saved, so the corrected one fails too. Thread stuck. ``` ### After: `per_call` (opt-in) ```mermaid sequenceDiagram autonumber participant App as Client app participant Agent as Agent<br/>after_model: no-op participant T1 as task c1<br/>send_email participant T2 as task c2<br/>delete_file participant T3 as task c3<br/>lookup App->>Agent: invoke(user message) par one task per tool call Agent->>T1: run and Agent->>T2: run and Agent->>T3: run end T3-->>Agent: found (not gated, runs right away) T1-->>App: interrupt 68fc: tool_approval send_email(to=alice)<br/>schema: approve | edit(to: string) | reject | respond T2-->>App: interrupt 834e: tool_approval delete_file(report.txt)<br/>schema: approve | reject App->>Agent: resume {68fc: edit to=5} Note over Agent,T2: LangGraph checks 68fc's schema first<br/>ValidationError at edit.edited_action.args.to, nothing saved, both still paused App->>Agent: resume {68fc: edit to=bob, 834e: reject "keep it"} Agent->>T1: resume with its answer T1-->>Agent: sent to bob (+ edit notice for the model) Agent->>T2: resume with its answer T2-->>Agent: rejected: keep it (tool not run) Note over Agent,T3: model runs once with all 3 results, lookup did not run again ``` ## Release note `HumanInTheLoopMiddleware` accepts `interrupt_mode="per_call"`, which pauses once per gated tool call instead of once per model turn. Each interrupt has type `tool_approval` and a `response_schema` listing the decisions that tool allows, is answered with a single decision keyed by interrupt ID, and LangGraph checks the answer against the schema before saving it. The default stays `"batched"`. https://github.com/langchain-ai/langgraph/pull/9265
master
10 hours ago
Wizard run
10 hours ago
Wizard run
11 hours ago
Wizard run
11 hours ago
Wizard run
11 hours ago

Latest Branches

CodSpeed Performance Gauge
0%
release(langchain): 1.4.6#41247
10 hours ago
1d9d72a
byhow/release-langchain
CodSpeed Performance Gauge
0%
feat(core,langchain,standard-tests): read `tool-call-delta` in stream consumers#41246
11 hours ago
6022808
richardscarrott/core/tool-call-delta-consumers
CodSpeed Performance Gauge
+57%
1 day ago
71ef70f
fix/hitl-per-call-interrupts
© 2026 CodSpeed Technology
Home Terms Privacy Docs