LangChain agent middleware that validates LLM-generated tool-call arguments against each tool's schema before tool execution / HITL.
Project description
langchain-tool-args-validation-middleware
A LangChain agent middleware that validates LLM-generated tool-call arguments
against each tool's schema before the tool runs (and before any
human-in-the-loop approval step). When arguments are invalid it appends error
ToolMessages and re-invokes the model so it can self-correct — all inside the
model node, so only the final valid AIMessage ever enters the graph state.
pip install langchain-tool-args-validation-middleware # Pydantic tools only
pip install "langchain-tool-args-validation-middleware[jsonschema]" # + MCP / dict-schema tools
Why
LLMs frequently emit malformed tool calls: missing required fields, wrong types, hallucinated empty values, or extra keys. Without validation those reach the tool node and cause runtime errors or silent corruption — and in human-in-the-loop workflows, a human is asked to approve obviously-broken arguments. Catching this at the model boundary lets the agent fix itself in one extra model call instead of a full agent-loop iteration.
It complements, rather than replaces, ToolRetryMiddleware (retries on tool
exceptions) and ModelRetryMiddleware (retries on model exceptions): this
one retries on schema violations, before execution.
A trace of create_oos_alert: the model emitted arguments that violate the
schema, the middleware rejected them with a precise error and a corrective hint,
and the model retried — all inside the model node, before the tool ran.
Usage
from langchain.agents import create_agent
from langchain_tool_args_validation_middleware import ToolArgsValidationMiddleware
agent = create_agent(
model,
tools=tools,
middleware=[ToolArgsValidationMiddleware()], # resolves schemas from the agent's tools
)
Both validation paths are supported automatically:
- Pydantic tools (
@tool, or any tool with aBaseModelargs_schema) → validated withBaseModel.model_validate. - MCP / dict-schema tools (
args_schemais a raw JSON Schemadict) → validated withjsonschema(soft dependency,Draft7Validatorby default).
Unknown tools (no resolvable schema) pass through unvalidated.
Configuration
| Parameter | Default | Description |
|---|---|---|
tools |
None |
Explicit tool list. If omitted, schemas are resolved lazily from request.tools and cached by tool-name set (handles dynamic toolsets). |
max_retries |
2 |
Validation-retry cycles per model invocation (up to max_retries + 1 model calls). |
strip_empty_values |
True |
Recursively drop None / {} / [] before validation. |
strip_placeholder_strings |
False |
Also drop placeholder strings like "null". Off by default — see below. |
placeholder_strings |
conservative set | Set used when string stripping is enabled. |
json_schema_validator_class |
None |
Override the JSON Schema validator class. None → lazy Draft7Validator. |
extra_validators |
None |
Extra (name, args) -> list[str] checks for domain rules. |
on_failure |
"pass" |
After retries are exhausted: "pass" (fail open) or "raise". |
Design decisions for the two thorniest cases
Batch (partial) failure
Providers (Anthropic, Gemini, OpenAI) require that every tool_call in an
assistant message receive a matching ToolMessage before the next turn. So when
a multi-call turn has any invalid call, the middleware emits:
- an error
ToolMessagefor each invalid call, and - a "not executed" notice for each valid sibling call (it hasn't run yet — we're still inside the model node — so it can't have a real result), asking the model to re-issue the whole batch with corrected arguments.
The failed AIMessage is placed before these ToolMessages, and failed turns
accumulate across retries so the model sees its repeated mistakes.
strip_empty_values and the write-back contract
LLMs (Gemini especially) emit explicit null/{}/[] for optional fields
instead of omitting them, causing needless validation failures. When stripping
is on, the cleaned arguments replace the originals on the tool call, so what
we validate is exactly what executes — no soundness gap between validation and
execution.
The trade-off: stripping a value that is meaningfully empty (e.g. tags: []
meaning "clear all tags", or null meaning "explicitly unset") changes
behaviour. Container stripping (None/{}/[]) is on by default because it's
usually safe. String-placeholder stripping is opt-in only — tokens like
"NA" (Namibia's ISO code) are legitimate values and must never be dropped
silently. Enable it deliberately with strip_placeholder_strings=True and a set
you control.
Fail-open
After max_retries, the default on_failure="pass" returns the last response
unchanged — the (still-invalid) args reach the tool node, where normal tool
error handling takes over. This makes the middleware best-effort
self-correction, not a hard guarantee. Use on_failure="raise" if you'd rather
surface a ToolArgsValidationError.
Extra validators
Plug in domain rules without touching core behaviour. A bundled example flags
LangChain internal message IDs (lc_<uuid>) that LLMs sometimes mistake for
real data identifiers:
from langchain_tool_args_validation_middleware import detect_langchain_internal_ids
ToolArgsValidationMiddleware(extra_validators=[detect_langchain_internal_ids])
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langchain_tool_args_validation_middleware-0.1.0.tar.gz.
File metadata
- Download URL: langchain_tool_args_validation_middleware-0.1.0.tar.gz
- Upload date:
- Size: 453.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8df6a6597a30c840e5c09d15aa5d203bee9746c767a4bd59b29b9cbae292fa8
|
|
| MD5 |
d74354107dc517427b97d18c38c8466a
|
|
| BLAKE2b-256 |
bdef64c9ae52cbacf4dac17e92cf134ac02b342c9b2841cc7272354a92e90e29
|
File details
Details for the file langchain_tool_args_validation_middleware-0.1.0-py3-none-any.whl.
File metadata
- Download URL: langchain_tool_args_validation_middleware-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3b9be8fc49fed4e7fe2279c72346dca6544f8d86bb377321293dfe4c535952f
|
|
| MD5 |
d694780f551bc7d1f2f36a829de652c0
|
|
| BLAKE2b-256 |
14b88dff4394b8cbe48956fde3d2f69f902dd3609f179c8990e7e9b011b156de
|