Skip to main content

langchain-scrubkit

Clean tool outputs in LangChain, before your agent ever reads them. Fixes broken encoding, leftover HTML and invisible characters — and tells the agent when a tool came back with a bot wall instead of content.

PyPI Python License

Your scraping tool returns Café <b>Noir</b>&nbsp;. Your model reads it, and reasons about the damage as though it were content. There is no later stage that undoes that.

pip install langchain-scrubkit
from langchain.agents import create_agent
from langchain_scrubkit import ScrubkitMiddleware

agent = create_agent(model, tools=[scrape], middleware=[ScrubkitMiddleware()])

That is the whole setup. Every tool result is now repaired at the moment it is produced — the conversation never contains the broken version.


What your agent stops seeing

Tool returns Model reads
Café Café
<p>Warm light.</p><p>Fits any room.</p> Warm light. Fits any room.
Solid oak &mdash; seats&nbsp;two. Solid oak — seats two.
Tokyo​ Ltd­ Tokyo Ltd

Deterministic, no LLM, no network call. The same input always produces the same output, so a cached run stays a cached run.

The part nobody else does

A scraper that hits a bot wall returns HTTP 200 and a page saying Access denied. Every agent framework hands that to the model as a normal tool result. The model has no way to know the extraction failed, so it treats the error page as data and answers confidently from nothing.

Without the middleware, this is what reaches the model:

Please wait, verifying you are human

With it, the same result carries its diagnosis:

Please wait, verifying you are human

[scrubkit] This tool result looks like what a bot wall or error page returns
(matched 'please wait'), not the page's content. The extraction probably
failed: re-run the tool or try another source rather than treating the text
above as data. If the text is genuinely about that phrase, ignore this note.

The agent can now retry, switch source, or tell the user it failed — instead of inventing an answer. The original text is kept, never replaced.

Three modes:

ScrubkitMiddleware(on_flags="note")    # default: annotate, let the agent decide
ScrubkitMiddleware(on_flags="ignore")  # repair silently
ScrubkitMiddleware(on_flags="raise")   # stop the run (ExtractionFailedError)

What this saves you writing

Without it, cleaning tool output means writing the middleware yourself:

@wrap_tool_call
def clean_tool_output(request, handler):
    result = handler(request)
    result.content = ftfy.fix_text(result.content)   # and then?
    return result

That one line is the easy part. What is left:

  • HTML — ftfy does not strip tags. <p>A</p><p>B</p> must not become AB.
  • content is not always a string. It is typed str | list[str | dict]; the list form carries content blocks, and a text repairer must not touch an image payload.
  • JSON tool results. Cleaning a whole JSON document as one opaque string works badly — and parsing it means you now own re-serialisation, and the decision not to reformat a document that was already fine.
  • CSV tool results must not come back as JSON.
  • Knowing what not to touch. "None" is a surname, "NA" is Namibia, is a required letter-shaping character in Persian, and the joiner inside 👨‍👩‍👧 holds a family emoji together.
  • Never breaking the agent. A repair that throws must not end someone's run.
  • The bot-wall case above, which no text repairer addresses at all.

This package is those decisions, tested. If your only problem is broken Unicode, ftfy is excellent and you may not need anything else.

What it does not do

By default it runs only scrubkit's AUTO tier: seven rules that provably cannot change a value's meaning, its type, or the shape of your data. It cannot drop a row, retype a field, or rename a key unless you ask by name.

It does not touch user input or model output. Those did not come from a scraper; repairing them would be overreach.

And it never destroys a legitimate value:

Input Kept as-is because
"None" it is a common surname
"NA" it is Namibia's ISO country code
می‌تواند the zero-width non-joiner is required Persian orthography
👨‍👩‍👧 removing the joiner splits one family into three people
AT&T, 5 < 7 a real HTML entity ends in ; — these are not markup
3 m², ½ cm Unicode NFKC would rewrite these to 3 m2 and 1/2

A known limitation, stated rather than hidden. Bot-wall detection matches phrases, with no notion of context. A product genuinely called CAPTCHA Solver Pro, or an article titled A guide to CAPTCHA design, trips the same rule as a real captcha page. That is why the note quotes what triggered it and hedges rather than asserts: the model sees both the text and the reason, and can disagree with us. Nothing is deleted either way.

Usage

Clean only the tools that reach the open web. A calculator or a SQL tool has nothing to repair:

ScrubkitMiddleware(tools=["scrape_products", "fetch_page"])

Opt into the riskier repairs, each off by default because each changes something you may depend on:

ScrubkitMiddleware(
    placeholder_policy="null_high_confidence",  # "N/A" -> None; "None" stays
    drop_exact_duplicates=True,                 # changes your row count
    coerce_numeric_text=True,                   # changes a value's type
    repair_keys=True,                           # changes your schema
)

For chains rather than agents, the same cleaning as a Runnable:

from langchain_scrubkit import scrub

chain = scraper | scrub() | prompt | model

scrub() preserves shape — a string stays a string, a row stays a row, a list keeps every element. It defaults to on_flags="ignore", since a chain has no conversation to annotate; on_flags="raise" is the useful one there, to stop rather than write a captcha page into your vector store.

Verify it yourself

pip install "langchain-scrubkit[test]"
python -m pytest --pyargs langchain_scrubkit

101 tests ship inside the package. They cover shape safety, the control set of legitimate data above, both content forms, sync and async, and — through a real create_agent — that a broken tool result reaches the model repaired.

The engine

The cleaning itself is scrubkit: a standalone, dependency-free Python library under Apache-2.0. Use it directly if you are cleaning scraped data outside LangChain.

The same engine also runs as a hosted service at aidatatools.dev, which adds dataset-level quality scoring that neither library includes.

Requirements

Python 3.10+, langchain>=1.0. The middleware hooks wrap_tool_call, checked against langchain 1.0.0 and 1.3.x.

License

Apache-2.0. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langchain_scrubkit-0.1.0.tar.gz (30.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langchain_scrubkit-0.1.0-py3-none-any.whl (33.7 kB view details)

Uploaded Python 3

File details

Details for the file langchain_scrubkit-0.1.0.tar.gz.

File metadata

  • Download URL: langchain_scrubkit-0.1.0.tar.gz
  • Upload date:
  • Size: 30.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.3

File hashes

Hashes for langchain_scrubkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 59df44abaec3068721e8f0d16adec17da4a5f5ab0f1a4e762294f629d1f57aa0
MD5 d831f6f39e6233a7685811b7c38f8dc8
BLAKE2b-256 05caecb98e181769344a7931fcfa51d8284c3a7cb51b005d00b3ed6cde2b98a7

See more details on using hashes here.

File details

Details for the file langchain_scrubkit-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for langchain_scrubkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5594b119e0f822f2f55b1dda0865b4071866b6205041f33c226c8383609e9256
MD5 541b9abe45dc3e0b8a617e15e6a65dc5
BLAKE2b-256 88e19111d7b8c24b905d833492a30be4a45273422cea38812ebb0548ead6a5d3

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page