Skip to main content

rspns

Reduce HTTP response bodies for LLMs in their original format: HTML → HTML, JavaScript → JavaScript, JSON → JSON, XML → XML.

The output is a string containing only the reduced body. No node schema, metadata envelope, synthetic reference objects or renamed fields are added.

pip install .
# Development:
uv sync --extra dev
uv run pytest -q

Python 3.11+; synchronous, local processing. No LLM, browser or HTTP client required.

Command line

pip install rspns
python -m rspns page.html
python -m rspns script.js
python -m rspns page.html > page.summarized.html

The command prints only the summarized body to stdout, without a report or extra newline. Warnings and errors go to stderr; input errors return a nonzero exit code. The input file is read without being modified. Use a different filename when redirecting output. HTML, JS (.js, .mjs, .cjs), JSON and XML extensions select the format; other extensions use automatic content detection.

JavaScript defaults to conservative reduction. To enable the lossy library omission described below, including for scripts embedded in HTML:

python -m rspns script.js --js-mode analysis > script.summarized.js

Quick start

from rspns import Summarizer

body = '<form action="/login"><input name="user"><button>Login</button></form>'
output = Summarizer().summarize(body)
print(output)
assert output == body  # Already minimal: nothing to remove.

Output:

<form action="/login"><input name="user"><button>Login</button></form>

With presentation and ordinary text:

body = """<html><head><style>.card { color: red; }</style></head><body>
<h1>Welcome</h1><p>A long introduction that the LLM does not need.</p>
<div class="card"><form action="/login"><input name="user"><button>Login</button></form></div>
</body></html>"""
assert Summarizer().summarize(body) == '<form action="/login"><input name="user"><button>Login</button></form>'

Per-format behavior

Input Output behavior
HTML Remove prose, page headings/titles, comments, CSS and presentation wrappers. Keep forms, controls, links, buttons, their labels and values, ARIA-associated text, event handlers, scripts and functional attributes. Preserve DOM targets identified by literal selectors and references.
JavaScript Keep functions and their complete bodies, validation, conditions, events, network calls and other functional logic. Remove recognized standalone cosmetic style assignments and DOM animations with literal arguments when no animation-event dependency is visible. Compact whitespace/comments without changing the parsed syntax or ASI-sensitive newlines.
JSON Compact whitespace and shorten long string values. Preserve keys, nesting, scalar types, duplicate object keys, exact numeric spellings, array order and all array entries, including duplicates.
XML Preserve elements, attributes, namespaces, order and mixed content. Remove indentation where appropriate and shorten long text/attribute values. Keep all repeated elements. Preserve xml:space and unresolved entity declarations/references.

A JSON array with 1,000 users remains an array with 1,000 users. Shortening a field changes only its string value, for example "abcdefghilmnopqrtsuvz" → "abcd...uvz" with suitably configured limits. There is no automatic sampling or global size cap.

These bodies are intended for LLM analysis. Runtime equivalence is not guaranteed: HTML presentation/text is deliberately removed and JSON/XML values may be shortened. JavaScript removal is deliberately conservative: unknown APIs, calls with side effects, animation completion dependencies, display/visibility changes, class toggles and control state are retained. Long JS literals are kept intact because they may be validation values, endpoints or other functional data. Third-party animation libraries are not guessed to be harmless. Scripts remain <script> elements containing code; they are never executed by the library, nor replaced with inert template elements.

JavaScript bundles for LLM analysis

Already minified bundles have almost no whitespace to remove. Beautifying first does not help the syntax-based reducer identify more removable code. For an explicitly lossy summary, enable ReductionConfig(javascript_mode="analysis"):

from rspns import ReductionConfig, SummaryConfig, Summarizer

config = SummaryConfig(reduction=ReductionConfig(javascript_mode="analysis"))
output = Summarizer(config).summarize(body, content_type="javascript")

This mode recognizes likely Vue, jQuery, Swiper, MobileDetect and flatpickr locale implementations inside numeric-keyed module tables using multiple syntax identifiers. It replaces each recognized factory body with a string statement identifying the omitted library and module. Module IDs, factory parameters, unknown modules and code outside the recognized factories remain. Network client libraries are not classified. Recognition is heuristic: customized library code can also be omitted. The output is for LLM analysis, not execution or a complete security review of dependencies. Unsupported bundle layouts and standalone libraries remain conservative; there is no guaranteed reduction ratio. Explicit JavaScript inclusions disable library omission, and protected_paths can protect individual factory bodies/subtrees. Diagnostics record original UTF-8 byte ranges and library names for every omission. The default javascript_mode="conservative" retains functional library code.

To measure a local bundle and save its reduced output and diagnostic report:

uv run python examples/reduce_javascript.py tests/testJS.js tests/testJS.summarized.js \
  --mode analysis --report /tmp/testJS.report.json

Keep the original response if you need to inspect discarded content later. A summary does not guarantee retention of every possible security clue. No content is fetched from external script URLs, XML DTDs/entities or XInclude references.

Detection and input

output = Summarizer().summarize(
    '{ "ok": true }',
    content_type="auto",  # default
    headers=[("Content-Type", "application/json")],
)
assert output == '{"ok":true}'

Accepts str or already decompressed bytes. Byte decoding uses BOMs, declared charsets, XML encoding declarations or UTF-8. Headers are pairs so duplicates survive. Optional url, status_code and source_id are recorded only in diagnostics.

auto treats Content-Type headers as hints and verifies the body: complete JSON first, then XML/HTML markup, then JavaScript syntax plus structural evidence. XML declarations/root namespaces distinguish XML; an HTML doctype or root identifies HTML. Namespaces inside CSS, scripts or nested SVG do not turn an HTML page into XML. Ambiguous markup fragments default to HTML with a diagnostic warning. Explicit MIME types and aliases (html, json, xml, js, javascript) select the strategy directly. MIME parameters and +json/+xml suffixes are supported.

HTML class-only layout wrappers are unwrapped unless a recognized inline DOM reference needs them. External scripts are retained but do not force every wrapper to survive: selectors constructed dynamically or hidden in external code cannot be inferred. Identifiers, control state and functional attributes remain available. Within links/buttons/labels, <small> and descriptive classes such as subtitle are removed only when another label survives; a sole label is retained. Explicit tag/selector inclusions can preserve this secondary content.

Malformed JSON/XML/JS is returned unchanged, with a warning available through summarize_result(). Filters on an unparseable body raise ParseError rather than silently failing to exclude content. Empty bodies return an empty string. Unsupported text receives local string shortening. Binary bodies raise ValueError from summarize(); summarize_result() provides their diagnostic metadata instead.

Filters and reduction settings

from rspns import ANY, FilterConfig, ReductionConfig, SummaryConfig, Summarizer

config = SummaryConfig(
    filters={
        "html": FilterConfig(exclude_categories={"buttons", "links"}),
        "json": FilterConfig(exclude_paths=[("users", ANY, "internal_notes")]),
        "xml": FilterConfig(
            exclude_xpath=["//api:debug"], namespaces={"api": "urn:api"},
        ),
        "javascript": FilterConfig(exclude_symbols={"debugLog"}),
    },
    reduction=ReductionConfig(
        string_threshold=512,
        string_prefix=320,
        string_suffix=128,
        protected_paths=[("users", ANY, "id")],
        remove_presentation=True,
    ),
)
output = Summarizer(config).summarize('<form><input name="x"><button>Go</button></form>')
assert output == '<form><input name="x"></form>'
  • HTML: include_categories / exclude_categories, include_tags / exclude_tags, include_selectors / exclude_selectors (CSS).
  • JSON: include_paths / exclude_paths, tuples of keys and integer array indices; ANY matches a single segment, whereas "*" is a literal key.
  • XML: include_xpath / exclude_xpath with a namespace map. Select elements, comments or processing instructions, not scalar XPath results or attributes.
  • JavaScript: include_categories / exclude_categories and exact include_symbols / exclude_symbols. Explicit exclusions can remove functional code; the renderer rejects combinations that would create invalid syntax.

Exclusions take precedence and remove subtrees. Inclusions retain enough ancestor context to interpret selected nodes. HTML categories include forms, controls, buttons, links, labels, scripts, styles, metadata, resources, comments, text and structure. JavaScript categories include functions, classes, variables, imports, exports, calls, assignments, strings, comments and logic. HTML inclusion of text, a particular presentation tag or selector can explicitly retain content otherwise removed by default. Explicit style inclusion can retain CSS. Embedded scripts/data use their corresponding language filters.

By default strings longer than 512 characters keep 320 leading characters, ..., and 128 trailing characters. Configure string_threshold=None to disable shortening. Keys, numbers, HTML attribute/control values and JS literals stay intact. Protected paths apply to subtrees. XML paths use child indices and "text" / "tail" segments.

Sampling is available only by explicit opt-in: sample_paths={("telemetry",): 10} keeps the first ten children at that path. The first matching configured path applies. Do not enable it for collections whose complete membership you need. XML mixed-content children include text/tails, so use it on collection containers carefully.

Optional diagnostics

result = Summarizer().summarize_result('{ "ok": true }')
assert result.content == result.to_text() == '{"ok":true}'
assert result.to_json() == '{"ok":true}'  # JSON response only
assert result.statistics["output_characters"] == len(result.content)
report = result.to_dict()  # Explicit report containing content and diagnostics

summarize_result() returns a SummaryResult with content, detected format, warnings, reductions, source metadata and statistics. to_report_json() serializes this optional report; never use it when you want only the reduced body for an LLM. to_json() is reserved for JSON responses and raises on HTML/XML/JS.

The reduction log records dropped sections and original lengths outside the output. References address parsed DOM paths, ordered JSON member positions, XML paths, or decoded UTF-8 byte offsets for JavaScript. Embedded script offsets are script-local. The caller owns source storage. A preprocessor that changes the body marks its source as transformed in the report.

Python hooks and custom strategies

Pipeline: preprocessors → detection/extraction → document processors → filters → native rendering → postprocessors → result validation.

from dataclasses import replace

def remove_prefix(response, context):
    return replace(response, body=response.body.removeprefix("PREFIX:"))

def adjust_document(document, context):
    # Node values/attributes/children are available before rendering.
    return document

def adjust_output(result, context):
    result.content = result.content.replace('"internal_name":', '"name":')
    return result

summarizer = Summarizer(SummaryConfig(
    preprocessors=[remove_prefix],
    document_processors=[adjust_document],
    postprocessors=[adjust_output],
))
assert summarizer.summarize('PREFIX:{ "internal_name": "Ada" }') == '{"name":"Ada"}'

Hooks receive (value, context) and return the same public model: ResponseInput, Document, or SummaryResult. Empty lists disable a stage; callables run in order. Failures name the stage and callable in HookError. Hooks are trusted Python code. Document hooks preserve the parsed paths/selectors; JS nodes also have syntax layout information, so prefer input/output hooks for quick JavaScript transformations.

Register a strategy with StrategyRegistry.register(name, strategy, recognizer=callable, media_types=[...]). It supplies extract(input, context) -> Document and render(document, context) -> str. Custom recognizers run before built-in detection and receive decoded text. Existing format extractors can be replaced. Raise ParseError for malformed content; programming/configuration errors propagate.

Development and packaging

uv sync --extra dev
uv run pytest -q
uv run python examples/measure.py
uv build

The measurement script compares native input/output character counts. A small functional form stays identical; an HTML page dominated by CSS/prose can shrink substantially. A JS file dominated by functional string data can remain almost unchanged. Preservation takes precedence over an arbitrary compression ratio.

This revision changes the earlier experimental API: replace summarizer.summarize(body).to_json() with summarizer.summarize(body). Use summarize_result(body) only when you also want diagnostics. Removed graph-output options deduplicate and compact_tables have no equivalent: native collections retain their entries and original shape.

Publishing on GitHub and PyPI

CI tests Python 3.11–3.14 and builds distributions. The release workflow uses PyPI Trusted Publishing; no PyPI API token is stored in the repository.

Before publishing, create the GitHub repository with main as its default branch, verify the PyPI package name is available, and configure a PyPI trusted publisher for the repository and workflow publish.yml, with GitHub environment pypi. Create/protect that environment in GitHub. Add the final repository URL under [project.urls] in pyproject.toml. If the package name changes, update it in the metadata and workflow URL and refresh uv.lock.

For a release, update the version/changelog, run uv lock, the tests and uv build, then publish the matching GitHub Release v<version>. See CONTRIBUTING.md and SECURITY.md. License: MIT.

Metadata

Release files for rspns 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rspns 0.2.0
File Size Uploaded Provenance Actions
rspns-0.2.0.tar.gz 76.3 kB With provenance Details for rspns-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for rspns 0.2.0
File Interpreter ABI Platform Actions
rspns-0.2.0-py3-none-any.whl Python 3 none any Details for rspns-0.2.0-py3-none-any.whl

Total release size: 107.1 kB

Release files / rspns-0.2.0.tar.gz

Download URL rspns-0.2.0.tar.gz
Size 76.3 kB
Tags Source
SHA-256 checksum
How to use checksums
b79c62470f6c13f6178a5ffea53e66cc23b31a9af1653bc02274d5efa5170061
BLAKE2b-256 checksum
How to use checksums
ab071d726abdcabe480aea58497a227bec33eb401a9cab32a190c110e8096635
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / rspns-0.2.0-py3-none-any.whl

Download URL rspns-0.2.0-py3-none-any.whl
Size 30.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
aa95834c1f8b35c56bd91cb381aa5076e7c36f64e76ee2836c11671fbc3985d4
BLAKE2b-256 checksum
How to use checksums
e0f4b2c2a42be73329b20f01877c5b75c12c9188bc5e80665a2fb9719210840a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Security for rspns 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for rspns-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

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