There are two ways to bring web page screenshots into LlamaIndex, and this page gives you a tested version of each:
capture_page, aFunctionTool. An agent calls it with a URL and gets back a public image URL and the image size. Use it when the agent needs a capture as evidence or as input for a later step.ScreenshotReader, a reader. You pass URLs and getImageDocumentobjects that hold the image bytes. Use it to feed pages to a multimodal LLM or a multimodal index.
Both call the RenderScreenshot API through our Python SDK, so no browser runs in your Python process. This page is part of our guide to giving AI agents web vision.
Last tested: 2026-10-15 with Python 3.12,
llama-index-core0.14.25,renderscreenshot1.0.0 (Python SDK from GitHub). The tool schema, tool calls, reader output and chat-message conversion ran against a stubbed API client; one real API call with an invalid key checked the error path. The agent example was constructed with LlamaIndex'sMockFunctionCallingLLMbut not run against a real LLM.
What you can build
- Visual QA over a sitemap. Read every page of a staging site as an image and ask a multimodal model which pages show a broken layout.
- A research agent with receipts. The agent cites a screenshot URL for every claim it makes about a web page.
- Dashboard digests. Capture internal status pages (with cookies set in your code) and summarize them each morning.
Prerequisites
pip install "llama-index-core==0.14.25" "git+https://github.com/Render-Screenshot/rs-python.git" export RS_API_KEY=rs_live_...
The Python SDK installs from GitHub until it is on PyPI (Python SDK docs). Get a key by creating a free account.
The module
Save as rs_llamaindex.py. This is the file we tested.
"""LlamaIndex helpers for the RenderScreenshot API: a FunctionTool and an image reader.""" import base64 import os from typing import Annotated, List, Optional from llama_index.core.readers.base import BaseReader from llama_index.core.schema import ImageDocument from llama_index.core.tools import FunctionTool from renderscreenshot import Client, RenderScreenshotError, TakeOptions _client: Optional[Client] = None def get_client() -> Client: global _client if _client is None: _client = Client(os.environ['RS_API_KEY'], timeout=60.0) return _client def capture_page( url: Annotated[str, 'Public http(s) URL to capture'], full_page: Annotated[bool, 'Capture the whole scrollable page'] = False, dark_mode: Annotated[bool, 'Render with prefers-color-scheme: dark'] = False, ) -> str: """Capture a web page and return a public image URL with its pixel size.""" options = TakeOptions.url(url).preset('full_page' if full_page else 'desktop_hd').format('webp') if dark_mode: options = options.dark_mode() try: shot = get_client().take_json(options) except RenderScreenshotError as e: return f'Capture failed ({e.http_status}): {e.message}' return f"{shot.get('url')} ({shot.get('width')}x{shot.get('height')})" screenshot_tool = FunctionTool.from_defaults( fn=capture_page, name='capture_page', description=( 'Capture a screenshot of a public web page. Returns an image URL and its size. ' 'Use it to check layout or visual state, not to read text.' ), ) class ScreenshotReader(BaseReader): """Load web pages as ImageDocuments for multimodal indexes and LLMs.""" def __init__(self, preset: str = 'desktop_hd', client: Optional[Client] = None): self.preset = preset self.client = client def load_data(self, urls: List[str]) -> List[ImageDocument]: client = self.client or get_client() docs = [] for url in urls: # take() returns the image bytes. Passing them in avoids a second download: # ImageDocument(image_url=...) fetches and verifies the URL when it is built. data = client.take(TakeOptions.url(url).preset(self.preset).format('webp')) docs.append(ImageDocument( image=base64.b64encode(data).decode('ascii'), image_mimetype='image/webp', metadata={'source_url': url, 'preset': self.preset}, )) return docs
Two things we learned while testing it:
- Do not add
from __future__ import annotations. With it,FunctionTool.from_defaultscould not resolve theAnnotatedhints and raisedPydanticUserError: capture_page is not fully definedwhen it built the schema. Without it, the parameter descriptions flow into the tool schema the model sees. ImageDocument(image_url=...)downloads the image. Our first reader passed the CDN URL. LlamaIndex 0.14.25 fetched and verified it during construction and raisedThe specified URL is not an accessible imagefor a URL that was not reachable. The bytes-based version above makes one request per page.
Use the tool in an agent
import asyncio from llama_index.core.agent.workflow import FunctionAgent from llama_index.llms.openai import OpenAI # pip install llama-index-llms-openai from rs_llamaindex import screenshot_tool agent = FunctionAgent( tools=[screenshot_tool], llm=OpenAI(model='gpt-4o-mini'), system_prompt='You review web pages. Cite the screenshot URL for every finding.', ) async def main(): print(await agent.run('Capture https://example.com and tell me the image size.')) asyncio.run(main())
This is the schema LlamaIndex built from the function, as printed in our test:
url string "Public http(s) URL to capture" full_page boolean default False "Capture the whole scrollable page" dark_mode boolean default False "Render with prefers-color-scheme: dark"
Send pages to a multimodal model
The reader's documents convert straight into an image block for a chat message:
from llama_index.core.llms import ChatMessage, ImageBlock, TextBlock from rs_llamaindex import ScreenshotReader docs = ScreenshotReader(preset='desktop_hd').load_data(['https://example.com']) msg = ChatMessage(role='user', blocks=[ ImageBlock(image=docs[0].image_resource.data, image_mimetype='image/webp'), TextBlock(text='Is the main call to action visible above the fold?'), ]) # response = your_multimodal_llm.chat([msg])
In our test, two stubbed pages produced two ImageDocument objects with image/webp data and source_url metadata, and the message held an ImageBlock and a TextBlock. A 1920x1080 capture costs a fixed number of visual tokens no matter what the page shows; our screenshot vs DOM vs accessibility tree study gives the numbers.
Parameter mapping
| LlamaIndex input | SDK call | API field | Notes |
|---|---|---|---|
url |
TakeOptions.url(url) |
url |
Public http(s) URL |
full_page=True |
.preset('full_page') |
preset |
Scrolls to trigger lazy loading (see presets) |
full_page=False |
.preset('desktop_hd') |
preset |
1920x1080 |
dark_mode=True |
.dark_mode() |
browser.dark_mode |
Only affects pages that use prefers-color-scheme |
Reader preset |
.preset(...) |
preset |
Any built-in preset, such as mobile |
| (fixed) | .format('webp') |
output.format |
Smaller than PNG |
Errors, retries and limits
- Tool errors return text. A real call with an invalid key returned
Capture failed (401): {...}to the agent instead of raising, so the agent can report it. - Reader errors raise. A batch load should fail loudly. Wrap
load_datain your own retry if you load many URLs; retry on HTTP 408, 429 and 5xx only. In SDK 1.0.0 we saw a 401 reported withretryable=True, so base the decision one.http_status. - Large batches: for more than a handful of URLs, use the API's batch endpoint through
client.batch(...)and build documents from the results. - Cost: each capture uses credits, reported in the
X-Credits-Usedheader. Cache hits on repeated URLs are faster; see caching.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
PydanticUserError: capture_page is not fully defined |
from __future__ import annotations in the tool module |
Remove it, or define the schema with fn_schema |
ValueError: The specified URL is not an accessible image |
ImageDocument(image_url=...) fetched an unreachable URL |
Pass bytes with image= as in the reader above |
KeyError: 'RS_API_KEY' |
Key not set | export RS_API_KEY=rs_live_... |
| Model ignores the screenshot | The tool returns a URL, not pixels | Use the reader and an ImageBlock for visual questions |
| Tall full-page images look blurry to the model | The model scales large images down | Use desktop_hd slices or element captures |
ModuleNotFoundError: llama_index.llms.openai |
LLM integrations are separate packages | pip install llama-index-llms-openai (or your provider's package) |
Related
- CrewAI integration: the same API as a CrewAI
BaseToolwith usage caps - LangChain integration: returning image blocks from a tool
- AI vision analysis use case: prompts and settings for vision models