Responses PDF Input URL
Query a PDF with an explicit File Search index, bounded indexing checks, and cleanup of owned resources.
With file_search enabled, Agno automatically uploads the attached PDF, creates a vector store, and adds its ID to the tool. The source does not delete those resources, its indexing poll has no deadline, and a failed indexing status does not prevent the model request. Use the controlled example below for bounded indexing and cleanup.
"""
Openai Pdf Input Url
====================
Cookbook example for `openai/responses/pdf_input_url.py`.
"""
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.media import File
from agno.models.openai.responses import OpenAIResponses
# ---------------------------------------------------------------------------
# Create Agent
# ---------------------------------------------------------------------------
# Setup the database for the Agent Session to be stored
db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai"
db = PostgresDb(db_url=db_url)
agent = Agent(
model=OpenAIResponses(id="gpt-5.2"),
db=db,
tools=[{"type": "file_search"}, {"type": "web_search_preview"}],
markdown=True,
)
agent.print_response(
"Summarize the contents of the attached file and search the web for more information.",
files=[File(url="https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf")],
)
# Get the stored Agent session, to check the response citations
session = agent.get_session()
if session and session.runs and session.runs[-1].citations:
print("Citations:")
print(session.runs[-1].citations)
# ---------------------------------------------------------------------------
# Run Agent
# ---------------------------------------------------------------------------
if __name__ == "__main__":
passControlled File Search
Save this helper as openai_file_search.py. It creates one store for one PDF, waits up to 120 seconds for indexing, and yields the store ID only after indexing completes. SDK requests have a 30-second timeout and automatic retries are disabled; this polling budget is not a deadline for the entire agent run.
from contextlib import contextmanager
from time import monotonic, sleep
import sys
from openai import OpenAI
@contextmanager
def indexed_pdf(pdf_bytes: bytes, filename: str = "document.pdf"):
file_id = store_id = None
with OpenAI(timeout=30.0, max_retries=0) as client:
try:
uploaded = client.files.create(
file=(filename, pdf_bytes, "application/pdf"), purpose="assistants"
)
file_id = uploaded.id
store_id = client.vector_stores.create(name="Agno PDF example").id
indexed = client.vector_stores.files.create(
vector_store_id=store_id, file_id=file_id
)
deadline = monotonic() + 120.0
while indexed.status != "completed":
if indexed.status in {"failed", "cancelled"}:
raise RuntimeError(f"PDF indexing {indexed.status}: {indexed.last_error}")
remaining = deadline - monotonic()
if remaining <= 0:
raise TimeoutError("PDF indexing did not complete within 120 seconds")
sleep(min(1.0, remaining))
remaining = deadline - monotonic()
if remaining <= 0:
raise TimeoutError("PDF indexing did not complete within 120 seconds")
indexed = client.vector_stores.files.retrieve(
file_id, vector_store_id=store_id, timeout=min(30.0, remaining)
)
if monotonic() > deadline:
raise TimeoutError("PDF indexing did not complete within 120 seconds")
yield store_id
finally:
# Attempt both deletions, including after indexing or agent failures.
for resource_id, delete in (
(store_id, client.vector_stores.delete),
(file_id, client.files.delete),
):
if resource_id is not None:
try:
delete(resource_id)
except Exception as exc:
print(f"Cleanup failed for {resource_id}: {exc}", file=sys.stderr)The helper deletes only resources it creates. Cleanup failures report the IDs so you can retry deletion. A lost creation response or process termination can leave resources behind; inspect your OpenAI project if the script is interrupted. Deleting a vector store alone does not delete the uploaded File object, so both are handled separately. See OpenAI File Search.
Save the following as pdf_search_current.py beside the helper. It supplies an explicit store ID and does not pass files= to the agent, avoiding another automatic upload. Both turns share an in-memory session; the store remains available until the with block exits.
from agno.agent import Agent
from agno.db.in_memory import InMemoryDb
from agno.models.openai import OpenAIResponses
from openai_file_search import indexed_pdf
import httpx
download = httpx.get(
"https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf",
timeout=30.0,
follow_redirects=True,
)
download.raise_for_status()
pdf_bytes = download.content
with indexed_pdf(pdf_bytes, "ThaiRecipes.pdf") as store_id:
agent = Agent(
model=OpenAIResponses(id="gpt-5.6-luna"),
db=InMemoryDb(),
add_history_to_context=True,
tools=[{"type": "file_search", "vector_store_ids": [store_id]}],
markdown=True,
)
agent.print_response("Search the indexed recipe PDF and summarize its contents.")
agent.print_response("Suggest a recipe from the same PDF.")For small documents that do not need an index, see direct file input.
The current example focuses on PDF search and uses in-memory history. The original source also enables web search and stores a session in Postgres. File-only annotations can be absent from Agno's result.citations on this revision; the original conditional print is not a completeness check for file citations.
Run the Example
Set up your virtual environment
uv venv --python 3.12
source .venv/bin/activateInstall dependencies
uv pip install -U agno openai httpxExport your OpenAI API key
export OPENAI_API_KEY="your_openai_api_key_here"Run the example
Save the helper as openai_file_search.py and the controlled example as pdf_search_current.py in the same directory, then run:
python pdf_search_current.pyFull source: cookbook/90_models/openai/responses/pdf_input_url.py