Zero-dependency scrubbers for LLM chat output — strip leaked JSON state-op fences, backend scaffold blocks, and tool-preamble meta-narration from player-facing text
Project description
llm-scrub
Zero-dependency scrubbers for LLM chat output. Weak models leak backend
scaffolding into the text shown to the user — JSON state-op fences, injected
=== … === context blocks, and English "tool preamble" meta-narration.
llm-scrub strips that noise deterministically, using pure string/regex
logic instead of relying on the model to obey a prompt.
- No dependencies. Pure standard library (
json,re). - Deterministic. No model call, no network — a plain
str -> strtransform. - Conservative. Tuned to under-strip rather than over-strip: legitimate
fenced code blocks, JSON data, markdown tables, and
===dividers survive. - Idempotent.
scrub(scrub(x)) == scrub(x).
Install
pip install llm-scrub
Requires Python 3.10+.
Quickstart
from llm_scrub import scrub
raw = (
"好,开始输出。\n\n"
"=== 时间线检索锚点 ===\n"
"当前时间:序章 · 站台\n\n"
"林夕睁开眼睛,废墟在昏黄的天光下铺展开来。\n\n"
'```json\n[{"op": "set", "path": "world.location", "value": "废墟"}]\n```\n'
"Let me mark the anchors now."
)
print(scrub(raw))
# 林夕睁开眼睛,废墟在昏黄的天光下铺展开来。
scrub chains the three scrubbers in the canonical order. Call them
individually when you only need one:
from llm_scrub import (
strip_json_state_ops,
strip_meta_tool_preamble,
strip_leaked_scaffold,
)
strip_json_state_ops('战斗结束。\n```json\n{"op": "append", "path": "bag", "value": "刀"}\n```')
# '战斗结束。'
strip_meta_tool_preamble("他转身离去。\nLet me update the anchors now.")
# '他转身离去。'
strip_leaked_scaffold("她走进废墟。\n\n=== 相关原文片段 ===\n某段原文\n\n她捡起刀。")
# '她走进废墟。\n\n她捡起刀。'
API
| Function | Signature | Removes |
|---|---|---|
scrub |
scrub(text: str, *, extra_headers=None) -> str |
All three, chained in the canonical order. |
strip_json_state_ops |
strip_json_state_ops(text: str) -> str |
JSON state-op fences — fenced (```json […] ), un-fenced bare [{"op":…}], and truncated/unclosed ops left by a cut-off response. |
strip_meta_tool_preamble |
strip_meta_tool_preamble(text: str) -> str |
A leaked English "tool preamble" sentence at the tail (e.g. "Let me mark the anchors now."). |
strip_leaked_scaffold |
strip_leaked_scaffold(text: str, *, extra_headers=None) -> str |
Whole === … === backend scaffold blocks (retrieval anchors, world-line summaries, etc.), plus a trailing "begin output" reasoning preamble. |
LEAKED_SCAFFOLD_KEYS is the built-in tuple of scaffold header markers; pass
your own via extra_headers to extend it.
What it strips
1. JSON state-op fences. Some pipelines let the model emit state changes as JSON. Those blocks must never reach the reader:
你推开门,走了进去。
```json
[{"op": "set", "path": "player.location", "value": "地窖"}]
```
→ 你推开门,走了进去。 — including malformed and truncated (streamed-and-cut)
variants. A fenced JSON block that is not ops (a plain object or array of
data) is preserved, so legitimate code examples survive.
2. Leaked tool preamble. Before a native tool call, a model may blurt one line of meta-narration meant for itself:
…晨光无声流转。The scene has progressed naturally. Let me mark the anchors that have been satisfied through our gameplay.
Only a trailing, unquoted, predominantly-English segment matching a preamble phrase is removed. A character's English dialogue inside quotes is never touched.
3. Leaked scaffold blocks. Weak models sometimes dump the backend's invisible context straight into the prose:
=== 时间线检索锚点 ===
当前时间:序章 · 站台
待确认跳跃:无
These headers are fixed strings the backend injects; normal narrative never emits them verbatim, so the whole block is removed with zero collateral damage. Extend the header list for your own backend:
scrub(text, extra_headers=("我的自定义脚手架",))
Design: under-strip, never over-strip
Every scrubber is tuned to leave the text alone when in doubt:
- Fenced/bare JSON is only stripped when it parses and carries state-op
markers (
op/path/question). Other JSON is preserved. - A trailing English segment is only stripped when it is outside quotes and matches a known tool-preamble phrase.
- A
=== … ===line is only stripped when its inner text matches a known scaffold header — a=== 序章 ===divider or a markdown setext underline is left intact. - If stripping would empty the text (a whole turn of pure leakage), the original is returned rather than blanking the message.
License
MIT — see LICENSE.
Acknowledgements
Extracted from the RPG Roleplay platform (https://github.com/felixchaos/rpg-roleplay-platform).
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 llm_scrub-0.1.0.tar.gz.
File metadata
- Download URL: llm_scrub-0.1.0.tar.gz
- Upload date:
- Size: 16.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
628103944b375534a68d8d6d7bb55b49433423b50e2e49f1a10366a70e65efa5
|
|
| MD5 |
36fa884dda90d9bb5d832b8e785efdce
|
|
| BLAKE2b-256 |
ca04d9aedc02ed157e26140babded8b57b052c4903fd31d0b6d55a91120d4c84
|
Provenance
The following attestation bundles were made for llm_scrub-0.1.0.tar.gz:
Publisher:
release.yml on felixchaos/llm-scrub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_scrub-0.1.0.tar.gz -
Subject digest:
628103944b375534a68d8d6d7bb55b49433423b50e2e49f1a10366a70e65efa5 - Sigstore transparency entry: 2168011823
- Sigstore integration time:
-
Permalink:
felixchaos/llm-scrub@0e4936afe0216c03cc735845ab0da75bb8c51c57 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/felixchaos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0e4936afe0216c03cc735845ab0da75bb8c51c57 -
Trigger Event:
push
-
Statement type:
File details
Details for the file llm_scrub-0.1.0-py3-none-any.whl.
File metadata
- Download URL: llm_scrub-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1675d1c8cbd135574390861ba377a1d9962dd984acec2d43d58d97840c9bc03e
|
|
| MD5 |
8d50bfc49c12be5d9a9e404033de81da
|
|
| BLAKE2b-256 |
ef51ebc39c23d8c8d462422b3bc7e861df330462f06026119cd9ca784bde0322
|
Provenance
The following attestation bundles were made for llm_scrub-0.1.0-py3-none-any.whl:
Publisher:
release.yml on felixchaos/llm-scrub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_scrub-0.1.0-py3-none-any.whl -
Subject digest:
1675d1c8cbd135574390861ba377a1d9962dd984acec2d43d58d97840c9bc03e - Sigstore transparency entry: 2168011826
- Sigstore integration time:
-
Permalink:
felixchaos/llm-scrub@0e4936afe0216c03cc735845ab0da75bb8c51c57 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/felixchaos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0e4936afe0216c03cc735845ab0da75bb8c51c57 -
Trigger Event:
push
-
Statement type: