Python client for Page Search API by PageIndex. Upload a PDF, ask up to 10 questions at once, and get back the pages that answer each one, ranked by relevance. Every page is read.
pip install pageindex-searchCreate an API key in the console and set it:
export PAGEINDEX_SEARCH_API_KEY=pis_live_...from pageindex_search import PageSearch
client = PageSearch()
doc = client.documents.upload("3M_2018_10K.pdf")
result = client.search(["What was capital expenditure in 2018?", "Who is the CEO?"], document=doc)
for answer in result.results:
print(answer.question)
for page in answer.pages:
print(f" page {page.page}: {page.relevance}")
print(f"cost ${result.usage.cost_usd}, credit left ${result.usage.balance_usd}")Upload a document once and search it as often as you like; uploading the same file again returns the same document. Documents are kept until you delete them:
for doc in client.documents.list(): # every document, newest first, a page at a time
print(doc.id, doc.filename, doc.pages)
client.search(["What was revenue in 2018?"], document="doc_60a07bcef3094b68806070f2") # search one again by its id
client.documents.delete("doc_60a07bcef3094b68806070f2")With many documents, search across them: the documents likely to answer are found first, then their pages.
result = client.search("What was capital expenditure in 2018?") # every document of yours
for doc in result.results[0].documents:
print(doc.filename, [(p.page, p.relevance) for p in doc.pages])To find only which documents answer, use client.documents.search.
Group documents into collections, and search within one:
reports = client.collections.create("Annual reports")
client.documents.upload("3M_2018_10K.pdf", collection=reports) # or later: client.collections.add(reports, [doc])
result = client.search("What was capital expenditure in 2018?", collection=reports)A document may be in many collections; deleting a collection leaves its documents.
Give documents your own metadata (strings, numbers and booleans) and filter by it:
doc = client.documents.upload("3M_2018_10K.pdf", metadata={"ticker": "MMM", "year": 2018})
client.documents.update(doc, metadata={"audited": True}) # None removes a key
recent = client.documents.list(filter={"year": {"$gte": 2020}})
result = client.search("Who is the CEO?", filter={"ticker": {"$in": ["MMM", "AAPL"]}})Filters take $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and and $or.
Read pages: their text, or a page as a picture. Each page a search lists says how it is best read (page.read:
"text", or "image" for a scan or a chart, whose text may be incomplete); include_text=True lists it with its
text.
texts = client.documents.pages(doc, [38, 39]) # each with .text and .read
png = client.documents.page_image(doc, 38) # PNG bytesclient.tools() gives an LLM agent four tools over your documents: search_documents (with your metadata's keys
and values in its description, so the agent can write filters), search_pages (with each page's text),
read_pages (text, with a picture of any page that is one) and view_pages (pages as pictures).
tools = client.tools(collection=reports) # or filter=..., or neither for all your documents
response = anthropic.messages.create(model=..., tools=tools.definitions, messages=messages)
for block in response.content:
if block.type == "tool_use":
content = tools.run(block.name, block.input).anthropic() # the tool_result contentFor OpenAI, pass tools.openai; tools.run() gives .text and .images, each image with .data_url. With the
OpenAI Agents SDK (pip install "pageindex-search[agents]"), tools.openai_agents() gives the tools ready to use:
from agents import Agent, Runner
agent = Agent(name="Reports", instructions="Answer from the documents.", tools=tools.openai_agents())
print(Runner.run_sync(agent, "What was capital expenditure in 2018?").final_output)PageSearch(api_key=None, base_url=None, timeout=120, max_retries=2) |
The key defaults to PAGEINDEX_SEARCH_API_KEY. |
documents.upload(file, collection=None, metadata=None, wait=True) |
A path, bytes or an open file. Returns a Document, ready to search unless wait=False. |
search(questions, document=None, documents=None, collection=None, filter=None, max_documents=None, document_top_k=None, top_k=None, include_text=False, idempotency_key=None) |
Up to 10 questions. For each, the pages that help, under the documents they are in: of one document, or across documents (those named, a collection's, those meeting filter, or all), whose most relevant are opened: those likely to answer, at most max_documents (default 3), or exactly document_top_k. Each document lists the pages likely to help, or exactly top_k, each with how it is best read, and with include_text, its text; without top_k, more_pages lists the next most relevant, without text. A search retried after a timeout is not charged twice. |
documents.search(questions, document_ids=None, collection=None, filter=None, top_k=None, idempotency_key=None) |
Up to 10 questions of your documents (all of them, up to 1,000, those named, or a collection's). Each answer lists the documents likely to hold it, or exactly top_k. Each document is priced as a page. |
documents.list(limit=20, after=None, collection=None, filter=None) |
Your documents, newest first, or a collection's, or those whose metadata meets filter. Loop over it for every document; .data and .has_more are one page. |
documents.get(id), documents.delete(document) |
One document; delete one. |
documents.pages(document, pages), documents.page_image(document, page) |
The text of up to 20 pages, each with how it is best read; a page as PNG bytes. |
tools(collection=None, filter=None) |
Tools for an LLM agent: .definitions (Anthropic), .openai, .openai_agents() (OpenAI Agents SDK), and .run(name, input). |
documents.update(document, metadata=) |
Set metadata keys; None removes one. |
collections.schema(collection), documents.schema(collection=None, filter=None) |
The metadata keys the documents have: each key's types, how many documents have it, its least and greatest numbers and commonest strings. Counted as they are now; nothing is enforced. |
collections.create(name), .list(), .get(id), .rename(collection, name), .delete(collection) |
Named groups of your documents. Deleting one leaves its documents. |
collections.add(collection, documents), .remove(collection, document) |
Put documents in a collection, or take one out. documents.upload(..., collection=) puts a new one in. |
usage(start=None, end=None) |
Uploads, searches and cost by day, your credit and stored pages. |
In 0.4, search(document, questions) became search(questions, document=document), and each answer lists
documents, each with its pages (answer.pages is the first document's). Before 0.2 these were upload(), document(), documents(), iter_documents() and delete() on the client;
they still work, with a warning, for now.
Errors raise PageSearchError, with the API's status, code, message and request_id.
See the API reference for every field and the pricing.