Official Python SDK for the Knowhere document parsing API.
pip install knowhere-python-sdk
Or with uv:
uv add knowhere-python-sdk
import knowhere client = knowhere.Knowhere(api_key="sk_...") result = client.parse( url="https://example.com/report.pdf", ) print(result.statistics.total_chunks) print(result.full_markdown[:200]) for chunk in result.text_chunks: print(chunk.content[:80]) for page in result.page_chunks: print(page.content_source) # "summary" print(page.content[:120]) # page-level summary print(page.metadata.page_nums) # [4, 5, 6]
New documents are published into a retrieval namespace. The server returns a
stable document_id on job create when it has a planned id, and on the
completed job_result after publication.
job = client.jobs.create( source_type="url", source_url="https://example.com/manual.pdf", namespace="support-center", document_metadata={"title": "Support manual"}, ) document_id = job.document_id job_result = client.jobs.wait(job.job_id) document_id = document_id or job_result.document_id if document_id is None: raise RuntimeError("Expected document_id after successful publication.")
After the job is done and published, query the canonical document content:
response = client.retrieval.query( namespace="support-center", query="How do I reset Bluetooth pairing?", chunk_types=["page"], top_k=5, channels=["path", "term"], filter_mode="keep", signal_paths=["Bluetooth", "Pairing"], ) print(response.router_used) print(response.answer_text) print(response.evidence) # grouped [E n] parts to consume print(response.evidence_text) # deprecated; always empty. Use evidence. print(response.stop_reason) print(response.failure_reason) for reference in response.referenced_chunks: print(reference.chunk_id, reference.chunk_type, reference.content_source) print(reference.metadata, reference.asset_url) for result in response.results: # raw path chunks for debug print(result.chunk_id, result.chunk_type, result.content_source) print(result.content) print(result.score) print(result.source.source_file_name, result.source.section_path)
Use document_id to update or archive a document:
update_job = client.jobs.create( source_type="url", source_url="https://example.com/manual-v2.pdf", document_id=document_id, ) document = client.documents.get(document_id) print(document.status) chunks = client.documents.list_chunks( document_id, page=1, page_size=50, chunk_type="page", include_asset_urls=True, ) print(chunks.pagination.total) if chunks.chunks: chunk = client.documents.get_chunk( document_id, chunks.chunks[0].id, include_asset_urls=True, ) print(chunk.chunk.content) print(chunk.chunk.metadata.get("page_nums")) # Page citations. print(chunk.chunk.asset_url) # Requested 7-day URL when available. page_assets = chunk.chunk.metadata.get("pageAssets") or [] print(page_assets) source = client.documents.get_page_citation_source(document_id) print(source.url) client.documents.archive(document_id)
You can also list documents in a namespace:
documents = client.documents.list( namespace="support-center", page=1, page_size=50, ) for document in documents.documents: print(document.document_id, document.status) print(documents.pagination.total_pages)
Retrieval can limit documents for one request and exclude documents or sections.
Omitting include_document_ids leaves documents unrestricted by inclusion;
passing [] matches no documents. Exclusions take precedence over inclusions.
response = client.retrieval.query( namespace="support-center", query="battery charging", include_document_ids=["doc_123", "doc_old"], exclude_document_ids=["doc_old"], exclude_sections=[ {"document_id": "doc_123", "section_path": "Appendix / Legal"} ], )
While you can provide an api_key keyword argument, we recommend using python-dotenv to add KNOWHERE_API_KEY="sk_..." to your .env file so that your API key is not stored in source control.
Short-lived dashboard tokens can use auth_token_provider instead of a static key. If api_key is also set, the static key wins.
client = knowhere.Knowhere(auth_token_provider=lambda: current_access_token())
from pathlib import Path result = client.parse( file=Path("report.pdf"), parsing_params={"model": "advanced", "ocr_enabled": True}, ) print(result.manifest.source_file_name) # "report.pdf" print(len(result.chunks)) # 152 print(result.namespace) # "default" or your explicit namespace print(result.document_id) # Published canonical document id
Pass OpenAI-compatible credentials for parsing or agentic retrieval.
Flat root applies to both channels (one multimodal model). Use models for
different model ids on the same endpoint, or text / vision for different
provider endpoints:
# Multimodal shorthand — one model for text + vision llm_config = { "api_key": "sk-...", "model": "gpt-4o", "base_url": "https://api.openai.com/v1", } # Same endpoint, different models per channel llm_config = { "api_key": "sk-...", "base_url": "https://api.openai.com/v1", "models": {"text": "gpt-4o-mini", "vision": "gpt-4o"}, } # Or two different endpoints llm_config = { "text": { "api_key": "sk-...", "model": "gpt-4o-mini", "base_url": "https://api.openai.com/v1", }, "vision": { "api_key": "sk-ali-...", "model": "qwen-vl-max", "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", }, } result = client.parse(file=Path("report.pdf"), llm_config=llm_config) response = client.retrieval.query( namespace="support-center", query="refund policy", use_agentic=True, llm_config=llm_config, )
result = client.parse(url="https://example.com/report.pdf") # Text chunks for chunk in result.text_chunks: print(chunk.metadata.keywords) print(chunk.metadata.summary) # Page chunks (v2 page-memory results) for chunk in result.page_chunks: print(chunk.content_source) # "summary" print(chunk.content[:120]) print(chunk.metadata.page_nums) # citation pages print(chunk.metadata.entities) # Image chunks (raw bytes loaded from ZIP) for chunk in result.image_chunks: print(chunk.file_path) print(len(chunk.data)) # bytes chunk.save("./output/") # writes image to disk # Table chunks (HTML loaded from ZIP) for chunk in result.table_chunks: print(chunk.file_path) print(chunk.html[:100])
result = client.parse(file=Path("report.pdf")) result.save("./output/report/")
import asyncio import knowhere async def main(): async with knowhere.AsyncKnowhere(api_key="sk_...") as client: result = await client.parse(url="https://example.com/report.pdf") print(result.statistics.total_chunks) for chunk in result.text_chunks: print(chunk.summary) asyncio.run(main())
For granular control over the parsing workflow, use the jobs resource directly:
from pathlib import Path # Step 1: Create a parsing job job = client.jobs.create( source_type="file", file_name="report.pdf", namespace="support-center", parsing_params={"model": "advanced", "ocr_enabled": True}, ) # Step 2: Upload file to presigned URL client.jobs.upload(job, file=Path("report.pdf")) # Step 3: Poll until done (adaptive backoff) job_result = client.jobs.wait(job.job_id, poll_interval=10.0, poll_timeout=1800.0) print(job_result.document_id) # Persist this to update/archive the document later. # Step 4: Download and parse results result = client.jobs.load(job_result) print(result.statistics)
All errors inherit from knowhere.KnowhereError.
import knowhere try: result = client.parse(url="https://example.com/report.pdf") except knowhere.AuthenticationError: print("Invalid API key") except knowhere.APIStatusError as e: print(f"{e.status_code}: {e.message}")
The SDK reads configuration from constructor arguments, environment variables, or defaults (in that priority order):
| Variable | Description | Default |
|---|---|---|
KNOWHERE_API_KEY |
API key (required) | — |
KNOWHERE_BASE_URL |
API base URL | https://api.knowhereto.ai |
KNOWHERE_LOG_LEVEL |
Log level | WARNING |
# Uses environment variables automatically client = knowhere.Knowhere() # Or configure explicitly client = knowhere.Knowhere( api_key="sk_...", base_url="https://api.knowhereto.ai", timeout=30.0, # HTTP request timeout (default: 60s) upload_timeout=300.0, # File upload timeout (default: 600s) max_retries=3, # Max retry attempts (default: 5) )
Connection errors, 429 Rate Limit, and >=500 Internal errors are automatically retried with exponential backoff.
client = knowhere.Knowhere( api_key="sk_...", max_retries=3, # default is 5 )
import knowhere print(knowhere.__version__)
This package follows Semantic Versioning.
We publish stable releases to PyPI. To install the latest unreleased changes directly from the repository: https://github.com/Ontos-AI/knowhere-python-sdk
- Python 3.9+
- httpx
>=0.25.0,<1.0 - pydantic
>=2.0.0,<3.0 - typing-extensions
>=4.7.0
- Contributing guide: CONTRIBUTING.md
- Security policy: SECURITY.md
- Code of conduct: CODE_OF_CONDUCT.md
MIT