Roadmap ======= This roadmap outlines planned features for positioning ``saia-python`` as the foundational Python building block for the GWDG/KISSKI AI ecosystem. Research Tooling (v0.5) ------------------------ **Batch processing**: ``client.chat.batch(prompts, model)`` — rate-limit-aware parallel inference with tqdm progress, automatic throttling, and checkpoint-based resume on failure. **Experiment logging**: ``client.chat.completions(..., log_to="experiment")`` — log prompts, responses, latency, and token counts to JSON Lines, CSV, or SQLite. Essential for reproducible research workflows. **Model comparison**: ``client.compare(models=["llama-3.3-70b", "qwen3-235b"], messages=[...])`` — same prompt to multiple models with side-by-side output, latency, and token count. Useful for systematic evaluation. **Usage tracking**: Cumulative quota tracking across a session or experiment. ``client.usage.summary()`` with alerts before quota exhaustion. **Response caching**: ``client.chat.completions(..., cache=True)`` — local cache keyed by (model, messages, parameters) to avoid redundant API calls during iterative prompt engineering. Ecosystem Integration (v0.6) ----------------------------- **LangChain integration** (``saia_python.langchain``): ``SaiaChatModel`` and ``SaiaEmbeddings`` classes compatible with LangChain chains, agents, and LCEL. Optional dependency via ``pip install saia-python[langchain]``. **LangChain ARCANA example** (``examples/langchain_arcana.ipynb``): Example-only notebook (no package dependency) covering the LangChain integration that SAIA's OpenAI-compatibility does *not* already provide for free: wrapping ARCANA RAG (``client.arcana.chat`` routed by ``arcana_id``) as a LangChain ``Runnable`` / retriever, then collapsing GWDG's verbose ``References:`` block into compact, numbered citations inside the chain via ``arcana_references.parse_arcana_references()``. Plain chat and tool calling are intentionally out of scope — those already work by pointing LangChain's ``ChatOpenAI`` at the SAIA ``base_url``. Serves as the low-commitment precursor to the native ``saia_python.langchain`` classes above, mirroring the example-only pattern of ``examples/openai_compatible_proxy.ipynb``. **Structured output**: Integration with ``instructor`` for Pydantic-validated model responses. Convenience method ``client.chat.completions_structured(model, messages, response_model=...)`` to eliminate patching boilerplate. **Native embeddings service** (``saia_python/embeddings.py``): Direct wrapper around ``POST /embeddings`` with typed return values, complementing the OpenAI-compatible access that already works via ``client.openai.embeddings.create()``. **Image generation service** (``saia_python/images.py``): Wrapper for ``POST /images/generations`` and ``POST /images/edits/``. Currently undocumented in the library. ARCANA incremental indexing (gated on backend) ----------------------------------------------- Client passthroughs that become useful once the ARCANA server adds the matching API support; documented here so the work is ready to wire up. Today the index trigger is whole-arcana and ``FileOutSchema`` exposes no content hash, so the library relies on the server skipping already-``INDEXED`` files — the "upload only the changed files, then index once" pattern. **Scoped and forced reindex**: ``generate_index(name, *, files=None, force=False)`` — once ``POST .../generate-index`` accepts ``{"files": [...]}`` / ``{"force": true}``, pass them through to (re)index only named files, or force a re-embed without re-uploading identical bytes. ``sync_directory`` would then hand its changed set (``uploaded`` + ``replaced``) to ``generate_index(files=...)`` for true per-file indexing instead of a whole-arcana trigger. **Server-side change detection**: Once ``FileOutSchema`` carries a ``content_sha256``, offer a built-in hash-based ``select`` default for ``sync_directory`` (local SHA-256 vs. the remote hash), removing the caller's own manifest. Valuable only paired with scoped indexing. **Priority (from a production consumer)**: Contract the skip-``INDEXED`` behavior and add ``force`` first; ship scoped ``files=`` and ``content_sha256`` together; de-prioritize per-file index-on-upload (it re-triggers once per file — the opposite of the batch-then-index pattern). Unified transport-error exception (deferred) -------------------------------------------- Now that control-plane calls carry a default timeout (a stalled call raises ``requests.exceptions.Timeout`` / ``ConnectionError`` instead of hanging), callers catch *two* exception families: ``SAIAError`` for HTTP-status failures and the raw ``requests.*`` transport errors. Wrapping the transport errors in a ``SAIAError`` subclass would collapse that to a single catch surface. **Status — deferred (low value for the current consumer)**: The production ingestion consumer is ``requests``-native: it imports only ``SAIAClient`` (catches no ``saia_python`` exceptions), and its transport-drop detection is built directly on ``requests.exceptions.*`` plus stdlib socket errors, walking the ``__cause__`` / ``__context__`` chain with a regex fallback explicitly "for SDK-specific exception classes that don't subclass ``requests.exceptions.*``." It already defends against wrapped exceptions, so a unified type adds nothing for it — and a careless version could regress it. The proposal's real audience is *simple* consumers that would rather not touch ``requests`` at all. **Requirements if revisited** (must be strictly backward-compatible): - **Dual-base the wrapper** — subclass *both* ``SAIAError`` and the underlying ``requests.exceptions.Timeout`` / ``ConnectionError``, so existing ``except requests.exceptions.*`` handlers keep matching. - **Preserve the cause chain** — raise via ``raise SAIA... from exc`` so consumers that walk ``__cause__`` / ``__context__`` still classify it. - **Leave ``generate_index``'s poll-deadline ``TimeoutError`` (stdlib) untouched** — consumers catch it directly; retyping it would silently break that branch. Adaptive rate-limit pacing (deferred) ---------------------------------------- Reactive 429 retry shipped in v0.6.0 (see ADR-0006); *proactive* pacing — a client-side throttle that spaces requests to stay under the limit so a 429 is rarely hit at all — is deferred. Reactive retry remains the safety net, so most workloads need nothing more. **Status — deferred (no implementation planned)**: Only sustained, high-throughput batch jobs that constantly bounce off the per-minute limit would benefit; ordinary use is well served by the shipped reactive retry. Parked until a workload actually needs it. **Constraint when revisited — the limit must be adaptable**: The account quota can change (a granted increase from, e.g., 30 to 60 per minute), so the pace target must **not** be hard-coded. It must be configurable *and* ideally derived from the server-reported ``x-ratelimit-limit-*`` headers — already parsed into ``RateLimitInfo`` on every response — so a quota increase is honored automatically, with no code or config change. Target a fraction (~90%) of the observed limit; an explicit ``target_rpm`` overrides it. Design detail lives in ``docs/proposals/rate-limit-handling.md`` (§8).