n4nAI

Multimodal agents for PDF and chart understanding

Practical guide to building multimodal agents for PDF and chart understanding: extraction, vision models, agent loops, and production tradeoffs.

n4n Team4 min read825 words

Audio narration

Coming soon — every post will get a voice note here.

Building a multimodal agent PDF chart understanding system means combining layout extraction, vision-language inference, and tool use so a model can answer questions over scanned reports and dashboard exports. This guide lays out an ordered path from raw bytes to a working agent, with the tradeoffs you’ll hit when traffic grows.

1. Extract layout before you extract text

A multimodal agent PDF chart understanding pipeline fails when it treats a PDF as a flat string. Financial reports put numbers in tables, sidebars, and embedded charts. Pulling page.extract_text() from a library like PyPDF2 discards spatial relationships and drops images entirely.

Use a layout-aware parser. PyMuPDF (fitz) gives you text blocks with bounding boxes and renders page images so you can crop figures.

import fitz

doc = fitz.open("report.pdf")
page = doc[0]
blocks = page.get_text("blocks")  # (x0, y0, x1, y1, text, block_no, block_type)
for b in blocks:
    if b[6] == 1:  # image block
        rect = fitz.Rect(b[0], b[1], b[2], b[3])
        pix = page.get_pixmap(clip=rect)
        pix.save(f"fig_{b[5]}.png")

For scanned PDFs with no text layer, run OCR on the rendered page first. Tesseract is free but slows below 2 pages/sec; a cloud OCR API costs per page but keeps latency predictable. Decide based on volume.

Pitfall: coordinate systems differ between PDF units and image pixels. Scale clips by page.rect.width / pix.width when re-cropping at higher resolution.

2. Treat charts as independent vision inputs

Charts are not decorations. A bar chart with a broken axis or a log-scale line plot carries meaning a text summary loses. Crop each figure to its own PNG and keep the page number and surrounding caption text as metadata.

# Continue from previous snippet
captions = [b[4] for b in blocks if b[6] == 0 and "figure" in b[4].lower()]

Feed the image and its caption to a vision model separately from the body text. This isolates hallucination risk: if the model misreads a y-axis, you can retry the single figure instead of re-processing the whole document.

Tradeoff: storing many small images increases object storage calls. Batch them into a single sprite sheet only if your vision endpoint charges per request, not per token.

3. Prompt the vision model for structured chart semantics

A multimodal agent PDF chart understanding loop needs machine-readable chart data, not a prose description. Ask the VLM for JSON: chart type, axis labels, series, and approximate values. Use an OpenAI-compatible client so you can swap models.

from openai import OpenAI

client = OpenAI(base_url="https://api.openai.com/v1", api_key="KEY")

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Return JSON only: {type, x_axis, y_axis, series: [{name, points: [[x,y],...]}]}"},
        {"role": "user", "content": [
            {"type": "text", "text": "Extract structured data from this chart."},
            {"type": "image_url", "image_url": {"url": "fig_3.png"}}
        ]}
    ],
    response_format={"type": "json_object"}
)
chart_json = resp.choices[0].message.content

If you route through an OpenAI-compatible gateway such as n4n.ai, you get automatic fallback when a provider is rate-limited and can forward cache-control hints to avoid re-paying for the same figure on retry.

Pitfall: VLMs invent precise numbers. Force the model to output "approx": true or bind confidence scores. For regulatory docs, verify extracted points against the source image with a second pass at higher resolution.

4. Wire the agent loop with tools

The agent combines extracted text, chart JSON, and user questions. Give it tools: lookup_table, compute_trend, fetch_external_rate. Use function calling so the model decides when to calculate rather than guess.

tools = [{
    "type": "function",
    "function": {
        "name": "compute_cagr",
        "description": "Compound annual growth rate from series",
        "parameters": {"type": "object", "properties": {
            "start": {"type": "number"}, "end": {"type": "number"},
            "years": {"type": "number"}}}
    }
}]

messages = [
    {"role": "system", "content": "You answer questions using provided PDF text and chart JSON."},
    {"role": "user", "content": f"Text: {body_text}\nCharts: {chart_json}\nWhat is the revenue CAGR?"}
]

# loop: call model, if tool_call, execute, append result, repeat

Keep the first call constrained to a single page’s context. Cross-page queries break the context window fast.

Tradeoff: agentic retries multiply token cost. Set max_tool_rounds=3 and cache the chart JSON in a Redis key hashed by pdf_sha + page.

5. Scale to multi-page and cross-chart reasoning

Real questions span the whole document: “Compare the Q2 chart on page 3 to the table on page 9.” You need a state store that maps page numbers to extracted artifacts.

Build a simple index:

doc_index = {
    "pages": [
        {"page": 1, "text": "...", "figs": ["fig_1.png", "fig_2.png"]},
        {"page": 3, "text": "...", "figs": ["fig_7.png"]}
    ]
}

Retrieve only the relevant pages with a lightweight embedding search over the text blocks. Don’t stuff 50 pages into the prompt; you’ll truncate charts.

Pitfall: vision models degrade on small text in dense dashboards. Render figures at 2x and downscale in the prompt if the endpoint resizes anyway.

6. Evaluate against a frozen set of PDFs

Ship an eval harness before production. Take 20 real documents, write expected answers for 50 questions, and run the agent offline.

for q in eval_set:
    got = agent.ask(q["pdf"], q["question"])
    assert q["check"](got)  # custom validator

Track two metrics: answer correctness and token spend per question. If correctness drops when you switch vision models, the extraction layer is too tightly coupled to one model’s quirks.

Common failure: agents that sound confident on garbled OCR. Add a guardrail that flags low OCR confidence and routes to human review instead of answering.

7. Production concerns: metering, caching, routing

Per-token usage metering is non-negotiable. Log usage.prompt_tokens and usage.completion_tokens on every call, partitioned by model and route. When a client sends cache_control headers, forward them so the provider caches the large PDF text prefix and you pay less on follow-up questions.

If you honor client routing directives, let callers pin a specific vision model for tricky charts while defaulting to a cheaper one for text pages. Automatic fallback covers the case where that model is degraded—your agent should catch the 429 and retry on the secondary without bubbling an error to the user.

Tradeoff: fallback adds latency. Set a timeout budget of 800 ms per vision call; beyond that, return the text-only answer with a “chart unavailable” note.

Where to draw the line

A multimodal agent PDF chart understanding stack does not need a vector database on day one. Start with deterministic extraction, one vision model, and a hardcoded agent loop. Add retrieval and eval only after you have 100 real documents and a cost problem. The bottleneck is almost always chart hallucination, not orchestration. Fix the input quality first.

Tagsmultimodal-agentsdocument-aichart-understandingpdf-parsing

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All multi-modal agents: vision + action posts →