Skip to content

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.

Next steps

  • Learn the run loop and routing: Agent
  • Use the builder pattern: Agent
  • Learn tool formats: Tools
  • Learn multi-turn state: Sessions
  • Learn provider-specific knobs: Providers
  • Try recipes sourced from tests: Recipes