Raw HTML Tool Output is an AI code smell, a sign of trouble in LLM systems, in the Couplers group. The tool returns the whole page, menus and footer included, and the model gets lost.
To read one number, the agent paid for more than 30,000 tokens. By the second page, the context was above 60,000. By the fourth, the model quoted the price of the “related product” in the footer.
On a Brazilian marketplace the same pattern repeats: the listing page carries a menu, recommendations, other buyers’ questions, tracking scripts, and the price in three places (cash, installments, “was / now”). The model picks one. Sometimes the wrong one.
Signs of Raw HTML Tool Output:
Tools return HTML, API JSON, or logs in full, with no filtering.
Tool results run to tens of thousands of tokens.
The agent gets information wrong that was in the result, because there was too much noise.
The context fills up after a few calls.
The content includes scripts, menus, footers, and ads.
flowchart LR
A(["1. Agent asks for the page"]):::llm --> T["2. fetch_page"]:::tool
T --> H["3. Full HTML: menu, scripts, footer"]:::untrusted
H --> A
A --> X(["4. Wrong price, full context"]):::io
The agent needs a small piece of data.
The tool fetches the source.
It returns everything, as it came.
The model hunts for the data amid the noise and sometimes finds the wrong one.
Reasons
The tool was written as a thin wrapper.requests.get(url).text is the fastest implementation. In ten minutes the agent “reads the web”. The cost of not filtering shows up in the trace, not in the first commit.
Trusting the model to filter. “It finds what it needs” is often true, at a high cost and with errors when the number appears several times.
Nobody looked at result sizes. Without traces that show tokens per tool, the weight is invisible. The bill arrives at month end, and the wrong price looks like a hallucination.
Converting HTML to markdown looks like enough.html2text strips the tags and leaves the menu, the footer, and the recommendations. Eight thousand tokens of prose still hide the price.
The paper “Lost in the Middle” (Liu et al., 2023) showed that models retrieve worse the information that sits in the middle of a long context, and better what sits at the start or the end. A 30,000-token page puts the product price exactly where the model misses most, in the middle, between <nav> and <footer>.
Cost of ignoring
Money. One tool call can cost more than the rest of the task. Four pages at 30,000 input tokens are already more than 100,000 tokens before the agent answers.
Quality. Too much noise leads to wrong data. Listing price, struck-through price, “customers also viewed” price.
Context. A few calls fill the window and degrade the decisions that follow. The agent forgets the original question because the HTML took the space.
Security. More outside text in the context means more room for hidden instructions. Greshake and colleagues (2023) showed indirect injection in content the agent fetches. A huge page is a larger billboard for the attack. See Instructions from Untrusted Data.
A bill for one price
The numbers below are illustrative. Suppose the agent compares 10 listings and each raw page is about 30,000 tokens.
Tool return
Tokens per page
For 10 listings (input only)
Raw HTML
30,000
300,000
Clean text, no menu or script
800
8,000
Fields title, price, installment, url
about 40
400
Extraction in code (a selector, JSON-LD, an API field) turns the task from “find a number in a novel” into “read a record”. The model compares ten prices. It does not hunt for ten needles.
Always. The tool returns only the useful fields, in a stable format: clean main text, or extracted fields. Also a target: MCP Adapter.
Three levels, from cheapest to most general:
Deterministic filter. Strip script, style, nav, footer, ads. Convert the rest to text. That already cuts most of the tokens.
Fields extracted in code. If you always want price, title, and availability, the tool returns those. CSS selector, JSON-LD, API endpoint. Failure becomes a tool error, not a model hallucination.
Summary inside the tool. When the layout varies too much for a selector, a small model (no side-effect tools) reads the page and returns the record. The main agent never sees the HTML.
The trap: normalizing only the HTML and leaving the log tool returning 5,000 lines, the API tool returning nested JSON with 400 keys, the email tool returning the whole MIME. The smell is raw output, not the .html extension.
Payoff
Tool results of hundreds of tokens, not tens of thousands.
The model finds the right data.
Longer tasks fit in the context.
Less surface for malicious content.
When to ignore
The raw content is the task. An agent that inspects a page’s HTML for accessibility problems needs the HTML. Even then, strip scripts and styles, which do not enter the analysis and bloat the context.
Low volume and small pages. If the result is already short (an API that returns 80 tokens with the price), normalizing brings little. Measure size in the trace before you build a parser.
Is it this smell?
Quick answers
What is Raw HTML Tool Output?
Raw HTML Tool Output is an AI code smell, a sign of trouble in LLM systems, in the Couplers group. The tool returns the whole page, menus and footer included, and the model gets lost.
How do you fix Raw HTML Tool Output?
With Normalize Tool Output. Each refactoring has safe step-by-step mechanics with an eval before and after.
Where does Raw HTML Tool Output show up in practice?