Skip to content

New: deep research, balance and auto-reload tools in the keyless MCP server. Install in one line

What is pruned context, and how many tokens does it save?

Pruned context is a web page cut down to the passages that matter for one query. ContextFetch splits each page into chunks, scores every chunk against your query with a cross-encoder, and keeps the chunks above a relevance threshold in their original order, always at least the best one. On our 29-case benchmark it cut tokens by 81.6% on average. You turn it on per search with pruned_context set to true.

Verified ConceptsPython/v1/search

1. The problem it solves

Web pages are long, and most of a page does not answer the question your agent asked. A documentation page about asyncio covers dozens of functions when your agent needs one. Passing whole pages to an LLM costs tokens on every call and gives the model more unrelated text to get wrong. Cutting pages to their first few thousand characters is no better: it keeps the introduction and drops the answer whenever the answer is further down.

2. How the chunks are chosen

ContextFetch converts each page to Markdown and splits it into chunks. A cross-encoder, a model that reads the query and a chunk together, scores how well each chunk answers the query. That is more precise than comparing embeddings, which score the query and the chunk separately. Every chunk at or above the relevance threshold is kept, in the order it appears on the page, and the best chunk is always kept even if it falls below the threshold.

The kept chunks are the page's own text. Nothing is summarised or rewritten, so a quote from pruned context can always be found word for word on the page.

3. What it saves

On our 29-case benchmark, pruned context used 81.6% fewer tokens than the full Markdown on average, with a median of 86.1%; the method and raw data are on the benchmarks page. Every search response also reports its own numbers in contextfetch.tokens: raw is the Markdown tokens of the returned results, pruned is the tokens of their pruned context, and savedPct is the difference. The program below prints both for a real query.

4. When to ask for the full text

Pruning is a trade-off. A fact mentioned only in passing, inside a chunk that is mostly about something else, can score below the threshold. When completeness matters more than tokens, for a contract, a full specification or a page you will quote at length, request text: true as well and use the full page. You can ask for both in one call, as the program does.

Highlights are the other option: the top few 400 to 700 character chunks with a score each, best first, for when you want a fixed small amount of context rather than everything relevant.

The complete code

Python, runnable as is with CONTEXTFETCH_API_KEY set. We ran it against the live API; the output below is what it printed.

import jsonimport osimport urllib.request API = "https://api.contextfetch.dev"KEY = os.environ["CONTEXTFETCH_API_KEY"]  def post(path, body):    req = urllib.request.Request(        API + path,        data=json.dumps(body).encode(),        headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"},    )    with urllib.request.urlopen(req, timeout=90) as resp:        return json.load(resp)  res = post("/v1/search", {    "query": "how to cancel an asyncio task after a timeout",    "numResults": 3,    "contents": {"text": True, "pruned_context": True},}) for r in res["results"]:    full, pruned = len(r["text"]), len(r["prunedContext"])    print(r["url"])    print(f"  full page {full:,} chars, pruned {pruned:,} chars ({100 - 100 * pruned // max(full, 1)}% smaller)") tokens = res["contextfetch"]["tokens"]print(f"\nAll results: {tokens['raw']:,} tokens of Markdown, {tokens['pruned']:,} pruned ({tokens['savedPct']:.1f}% saved)")print("\nPruned context of the first result, first 600 characters:\n")print(res["results"][0]["prunedContext"][:600]) 
OutputRecorded
https://www.pythontutorial.net/python-concurrency/python-asyncio-wait_for
  full page 3,642 chars, pruned 1,818 chars (51% smaller)
https://runebook.dev/en/docs/python/library/asyncio-task/timeouts
  full page 4,632 chars, pruned 1,940 chars (59% smaller)
https://async-concurrency.com/resilience-cancellation-error-handling/timeouts-and-deadlines/
  full page 20,658 chars, pruned 1,462 chars (93% smaller)

All results: 7,234 tokens of Markdown, 1,306 pruned (81.9% saved)

Pruned context of the first result, first 600 characters:

**Summary**: in this tutorial, you’ll learn how to use the `asyncio.wait_for()` function to wait for a coroutine to complete with a timeout.

## Introduction to the Python asyncio.wait\_for() function #

In the previous tutorial, you learned how to cancel a task that is in progress by using the `cancel()` method of the `Task` object.

To wait for a task to complete with a timeout, you can use the `asyncio.wait_for()` function. The `asyncio.wait_for()` function waits for a single task to be completed with a timeout.

When a timeout occurs, the `asyncio.wait_for()` function cancels the task and

The same call with curl

The raw HTTP request, and the response it returned (long text fields and lists shortened).

curl -s https://api.contextfetch.dev/v1/search \  -H "Authorization: Bearer $CONTEXTFETCH_API_KEY" \  -H "Content-Type: application/json" \  -d '{"query": "how to cancel an asyncio task after a timeout", "numResults": 2, "contents": {"pruned_context": true}}'
ResponseRecorded
{
  "requestId": "3f7132e797904f6189bbffd106552fc9",
  "results": [
    {
      "id": "e8780ab8-5bb2-5af9-b97d-3b281e35920e",
      "url": "https://www.pythontutorial.net/python-concurrency/python-asyncio-wait_for",
      "title": "Python asyncio.wait_for(): Cancel a Task with a Timeout",
      "score": 0.999885045033536,
      "publishedDate": "2023-06-03",
      "text": null,
      "highlights": [
        "```python\nMAX_TIMEOUT = 3\ntry:\n    await asyncio.wait_for(task, timeout=MAX_TIMEOUT)\nexcept TimeoutError:\n    print('The task was cancelled due to a timeout')Copy\n```\n\n## Shielding a task from cancellation #\n\nSometimes, you may want to inform users that a task is taking longer th…",
        "**Summary**: in this tutorial, you’ll learn how to use the `asyncio.wait_for()` function to wait for a coroutine to complete with a timeout.\n\n## Introduction to the Python asyncio.wait\\_for() function #\n\nIn the previous tutorial, you learned how to cancel a task that is in progre…",
        "```python\nimport asyncio\nfrom asyncio.exceptions import TimeoutError\n\n\nasync def call_api(message, result=1000, delay=3):\n    print(message)\n    await asyncio.sleep(delay)\n    return result\n\n\nasync def main():\n    task = asyncio.create_task(\n        call_api('Calling API...', res…"
      ],
      "highlightScores": [
        0.999885045033536,
        0.9997338496645116,
        0.9996247574963859
      ],
      "prunedContext": "**Summary**: in this tutorial, you’ll learn how to use the `asyncio.wait_for()` function to wait for a coroutine to complete with a timeout.\n\n## Introduction to the Python asyncio.wait\\_for() function #\n\nIn the previous tutorial, you learned how to cancel a task that is in progre…",
      "textSource": "page"
    },
    {
      "id": "6094a054-53de-5664-867b-d891e1bba609",
      "url": "https://runebook.dev/en/docs/python/library/asyncio-task/timeouts",
      "title": "python - The asyncio Timeout Trap: Why wait_for() Doesn't Stop Tasks Instantly and How to Fix It",
      "score": 0.9995142988625996,
      "publishedDate": "2026-10-12",
      "text": null,
      "highlights": [
        "Tip\n\nBy catching `asyncio.CancelledError`, you ensure that a timeout (which triggers a cancellation) allows your code to run cleanup logic before fully stopping.\n\n\\<hr>\n\nSometimes you need more control, such as running custom logic after a timeout without immediately raising an e…",
        "```python\nimport asyncio\n\nasync def work_to_be_timed():\n    print(\"Work task: Running...\")\n    await asyncio.sleep(4) # Takes 4 seconds\n    print(\"Work task: Finished.\")\n    return \"Work Done\"\n\nasync def main_guard():\n    task = asyncio.create_task(work_to_be_timed())\n    timeout…",
        "# The asyncio Timeout Trap: Why wait\\_for() Doesn't Stop Tasks Instantly and How to Fix It\n\nI'll break down the common issues and offer sample code for alternatives in a friendly way!\n\nIn Python's `asyncio`, timeouts are handled primarily using the `asyncio.wait_for()` utility. I…"
      ],
      "highlightScores": [
        0.9995142988625996,
        0.9818199533536138,
        0.9416564254911715
      ],
      "prunedContext": "In Python's `asyncio`, timeouts are handled primarily using the `asyncio.wait_for()` utility. It wraps a coroutine or task and raises an `asyncio.TimeoutError` if the specified duration is exceeded.\n\nA frequent misunderstanding is that `asyncio.wait_for()` magically stops the tas…",
      "textSource": "page"
    }
  ],
  "costDollars": {
    "total": 0.001
  },
  "searchTimeMs": 422,
  "searchTime": 422,
  "contextfetch": {
    "tokens": {
      "raw": 2069,
      "pruned": 940,
      "savedPct": 54.56742387626873
    },
    "sources": {
      "local": 2,
      "github": 0,
      "searxng": 15,
      "stackoverflow": 3,
      "google": 0
    },
    "cache": "miss",
    "timingsMs": {
      "recall": 294,
      "fetch": 117,
      "rerank": 103,
      "total": 422
    }
  }
}

Questions

Does pruned context cost extra?

No. Text, highlights and pruned context are included in the price of the search when you request them in contents. A search costs one credit either way.

Is pruned context rewritten or summarised?

No. It is the page's own Markdown with the low-scoring chunks removed. The kept chunks are verbatim and stay in their original order.

How is it different from highlights?

Highlights are the top few 400 to 700 character chunks, each with a score, best first. Pruned context keeps every chunk above the relevance threshold, in page order, so it reads as a shorter version of the page.

Can pruning drop something I needed?

Yes. A fact mentioned only in passing in an otherwise unrelated chunk can score below the threshold. When completeness matters more than tokens, request text: true and use the full page.

Run it with your own key

A free key has 2,500 searches and needs no signup. Agents can claim one themselves with the POST below.

curl -X POST https://api.contextfetch.dev/v1/keys/free
Get a free key