Quickstart¶
Installation¶
Source access
The source repository is private, so the git clone and pip install git+https://…
commands below work only for accounts that have been granted read access. To request
access, message Rounak Zoeb on
LinkedIn with your GitHub username. The rest of this site (concepts, recipes and the
generated API reference) is complete without it.
The toolkit requires Python 3.10 or newer — both openai 3.x and google-genai 2.x
do.
Working on the toolkit¶
Clone the repository and install in editable mode:
git clone https://github.com/rzoeb/doc-processing-toolkit.git
cd doc-processing-toolkit
pip install -e .
This installs the package from setup.py with all dependencies. You can then import:
from doc_processing_toolkit import Agent, AgentBuilder, tool, AgentResult
Pinning a release in an application¶
The toolkit is not published to PyPI, so a Git ref is the version. Pin an immutable one — an annotated tag, or a full 40-character commit SHA:
# By tag (preferred).
pip install "doc_processing_toolkit @ git+https://github.com/rzoeb/doc-processing-toolkit.git@v0.5.4"
# By SHA, when you need to pin something that has no tag.
pip install "doc_processing_toolkit @ git+https://github.com/rzoeb/doc-processing-toolkit.git@<40-char-sha>"
Tags are v<version> and match the __version__ literal in
src/doc_processing_toolkit/__init__.py, which is also what setup.py reports. A branch
name is not a pin: it moves under you between builds.
Never install from a branch in production, and never expect a tag to be re-pointed — releases are tagged once and a correction ships as a new version.
Install everything in one pip invocation¶
Resolve your application's requirements and the toolkit in a single command:
pip install -r requirements.txt \
"doc_processing_toolkit @ git+https://github.com/rzoeb/doc-processing-toolkit.git@v0.5.4"
Two sequential installs — application requirements first, toolkit second — let pip silently downgrade a shared dependency. Each invocation only sees its own requirement set, so a version the first step resolved can be replaced by the second without any conflict being reported; the build succeeds and the failure surfaces at runtime instead. A single resolve has both sets in front of it and fails the build on a genuine conflict, which is what you want.
This applies to multi-stage Dockerfiles in particular, where a RUN pip install -r
requirements.txt followed by a RUN pip install <toolkit> is the natural shape to write.
Tested versions versus the allowed range¶
These are two different statements and the repository keeps them in two different places:
| Question | Where the answer lives |
|---|---|
| Which versions are allowed to resolve? | install_requires in setup.py |
| Which versions was this release verified against? | tested_constraints.txt |
setup.py declares floors and, apart from mcp and httpx, no ceilings: a library that
caps a dependency imposes that cap on every application installing it, and a resolver is
free to satisfy the cap by downgrading the application's own SDK. Ceilings are kept only
where there is a tested constraint, a named incompatibility and a maintained line beneath
it — mcp>=1.25,<2 (2.0 removed mcp.server.fastmcp, which the toolkit's MCP servers
depend on) and httpx>=0.28.1,<1 (redundant, since google-genai and fireworks-ai cap it
themselves; openai 3.x and anthropic 1.x use httpx2). The anthropic<1 and tenacity<10 ceilings earlier builds
declared failed that test and are gone; an untested major of either now fails the toolkit's own
gate (tests/unit/test_sdk_canary.py) instead of capping your resolve.
An allowed range is not a promise that every version in it was exercised. mcp is
allowed anywhere in >=1.25,<2; the version this release was actually verified against is
1.27.0, which is what tested_constraints.txt records.
Two packages the toolkit imports directly are declared as floors in this release, with no
ceiling: aiohttp>=3.13.5 (imported when the package is imported, and previously present only
because fireworks-ai happened to pull it in) and requests>=2.33.1 (used by the PDF pipeline).
Your resolver now sees both, so an application that pins either below those floors gets a
resolution conflict at install time rather than an import error later.
To reproduce the verified set, pass it as a constraints file — constraints bound the packages you are already installing and never add one:
curl -O https://raw.githubusercontent.com/rzoeb/doc-processing-toolkit/v0.5.4/tested_constraints.txt
pip install -c tested_constraints.txt -r requirements.txt \
"doc_processing_toolkit @ git+https://github.com/rzoeb/doc-processing-toolkit.git@v0.5.4"
Because the toolkit installs from a Git ref rather than a wheel on PyPI, the constraints file lives in the repository at the tag rather than inside the installed package — fetch it at the same ref you pin.
Replacing the removed ceilings, tests/unit/test_sdk_canary.py runs in the offline unit
gate and fails when an untested SDK major is installed, naming the re-verification
procedure in the failure message. If you vendor or fork the toolkit, keep that test.
Rolling back¶
Because every release is an immutable tag, a rollback is a re-install at the previous ref. Tags published for this purpose:
| Tag | Commit | What it is |
|---|---|---|
v0.5.4 |
— | Public docs site, Gemini Nano Banana 2.1 with video and PDF references and the Files API route for references over 20 MB, Claude Haiku 5.5 and the tiered_100k pricing schema, thinking tokens on Gemini image calls billed |
v0.5.3 |
— | std-tools 0.6.0 compatibility: skills auto-injection on every route, env / cwd for subprocess servers, validated in-process FastMCP calls, per-run code-execution output folders, the per-request thinking flag, the hooks recipe for browser and desktop actions |
v0.5.2 |
— | Claude Sonnet 5.5, GPT-6.1 Sol, OpenAI Ultrafast and the opt-in WebSocket transport, thinking off on Claude, anthropic 1.x, Claude refusal billing, truncated OpenAI replies continued |
v0.5.1 |
— | Gemini 3.8 TTS and streamed speech, exact OpenAI TTS cost, tool output and spend for host applications, Tier 3 Google limits, Fireworks Ember 1, the Anthropic strict budget and Gemini tool-use billing |
v0.5.0 |
— | Session portability: envelope v3 with to_json / from_json, media policy and stores, cross-provider continuation and rebind, Agent.to_config(), agent-factory export / import, realtime export() and initial_items |
v0.4.0 |
— | September 2026 update: new models, provider-owned session history, the realtime session families, image sessions, cost corrections |
TLS trust store (openai 3.x, anthropic 1.x)¶
openai 3.0.0 and anthropic 1.0.0 replaced httpx with the separate httpx2
distribution, which verifies TLS certificates against the operating system trust store
(through truststore) rather than the certifi bundle inside the Python environment;
neither SDK installs certifi any more. Google and Fireworks still use httpx + certifi,
and so does Anthropic on an anthropic 0.x install.
The consequence is provider-split and therefore confusing: in a container image with no OS
CA bundle, OpenAI and Anthropic calls fail with APIConnectionError: Connection error.
(cause: [SSL: CERTIFICATE_VERIFY_FAILED] unable to get local issuer certificate) while
Google and Fireworks calls keep working. An image that installs the toolkit with no
anthropic pin picks up 1.x on its next build, so an image that was fine for Anthropic on
0.x can start failing there with no change of its own.
Official python:* images — slim and alpine alike — already install ca-certificates, so
most deployments are unaffected. Images at risk are hand-rolled minimal ones: a scratch
base, or a multi-stage build whose final stage copies only site-packages. Run this
inside the container before deploying (no network and no API keys needed):
python - <<'PY'
import os, ssl
d = ssl.get_default_verify_paths()
C = ["/etc/ssl/cert.pem", "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", "/etc/pki/tls/cert.pem", "/etc/ssl/certs/ca-certificates.crt", "/etc/ssl/ca-bundle.pem"]
print("cafile:", d.cafile, "| SSL_CERT_FILE:", os.environ.get("SSL_CERT_FILE"))
print("VERDICT:", "OK" if (d.cafile or any(os.path.isfile(c) for c in C)) else "NO OS CA BUNDLE - install ca-certificates or set SSL_CERT_FILE")
PY
If the verdict is negative, either install ca-certificates in the image or set
SSL_CERT_FILE=$(python -m certifi). httpx2 honours SSL_CERT_FILE and SSL_CERT_DIR.
The toolkit deliberately does not build a certifi context of its own for the OpenAI or
Anthropic client: doing so was measured to override a deploying application's SSL_CERT_FILE and
corporate CA, which would trade a loud, one-line-to-fix failure for a silent and
much harder one.
Windows: a call that never starts
On one Windows 11 development machine, a Claude call occasionally stalled inside the TLS
handshake, in the Windows certificate-chain API that truststore calls, and never timed out
(the client's connect timeout does not cover that call). It happened when several Python
processes started at once, and not on the next run. If you see a call hang before any
response on Windows, set SSL_CERT_FILE to the certifi bundle (python -m certifi prints
its path): httpx2 then verifies with OpenSSL and does not call that API. Linux and macOS do
not use it.
Environment variables¶
Create a .env file with your provider API keys:
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GOOGLE_GEMINI_API_KEY=...
FIREWORKS_API_KEY=...
See Configuration & Env Vars for the full list.
Minimal run¶
This quickstart shows a minimal Agent setup and one run.
from doc_processing_toolkit import Agent
agent = Agent(
model="claude-sonnet-5-5",
instructions="You are a concise assistant.",
)
result = agent.run_sync("What is 2 + 2?")
print(result.output)
print(result.input_tokens, result.output_tokens, result.cost)
Add tools¶
from doc_processing_toolkit import Agent, tool
@tool
def add(a: int, b: int) -> int:
return a + b
agent = Agent(
model="gpt-6-luna",
instructions="Use tools when needed.",
tools=[add],
)
result = agent.run_sync("Add 7 and 8")
print(result.output)
Streaming¶
For real-time output, use Agent.stream():
import asyncio
from doc_processing_toolkit import Agent
agent = Agent(
model="claude-sonnet-5-5",
instructions="You are a concise assistant.",
)
async def main():
async for event in agent.stream("Explain gravity in two sentences."):
if event.event_type == "text_delta":
print(event.content, end="", flush=True)
print()
asyncio.run(main())
See Streaming Output for advanced usage including tool call events and cost tracking during streams.