Metadata-Version: 2.4
Name: jsmithpkp-llm-client-kit
Version: 0.1.3
Summary: Cached LLM client with Ollama + Anthropic provider dispatch, fixture mode, and JSON-schema-strict completions.
Author: Jonathan Smith
License: MIT
Project-URL: Homepage, https://github.com/jsmithpkp21/llm-client-kit
Project-URL: Issues, https://github.com/jsmithpkp21/llm-client-kit/issues
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40; extra == "anthropic"

# llm-client-kit

Cached, fixture-aware LLM client with **Ollama** (default) and **Anthropic** provider dispatch. Carved out of `resume-builder/src/resume_builder/llm_client.py` so it can be shared across internal Python apps without each app rolling its own dispatch + caching layer.

PyPI: [`jsmithpkp-llm-client-kit`](https://pypi.org/project/jsmithpkp-llm-client-kit/). Import path stays `llm_client_kit` (PyPI distribution name and Python import name are allowed to differ).

## Install

```bash
pip install jsmithpkp-llm-client-kit
```

For the Anthropic provider path, install the extra:

```bash
pip install 'jsmithpkp-llm-client-kit[anthropic]'
```

## Public API

```python
from llm_client_kit import (
    LLMClient,
    LLMResponse,
    LLMEndpointUnreachableError,
    AnthropicProviderError,
    PROVIDER_OLLAMA,
    PROVIDER_ANTHROPIC,
)
```

### Construct from env (resume-builder-compatible)

```python
client = LLMClient.from_env()  # reads RESUME_BUILDER_LLM_* env vars
result = client.complete_json(
    namespace="my_stage",
    system_prompt="You are a JSON-only classifier...",
    user_payload={"subject": "...", "body": "..."},
)
```

Env vars honored:

| Env var | Default | Notes |
|---|---|---|
| `RESUME_BUILDER_LLM_PROVIDER` | `ollama` | `ollama` or `anthropic` |
| `RESUME_BUILDER_LLM_API_URL` | provider-default | OpenAI-compat for Ollama; ignored by Anthropic adapter |
| `RESUME_BUILDER_LLM_MODEL` | `llama3.1:8b` / `claude-sonnet-4-6` | per-provider default |
| `RESUME_BUILDER_LLM_API_KEY` / `ANTHROPIC_API_KEY` | unset | required for Anthropic |
| `RESUME_BUILDER_LLM_TIMEOUT_SECONDS` | `90` | per-request timeout |
| `RESUME_BUILDER_LLM_TIMEOUT_<NAMESPACE>` | unset | per-stage timeout override |
| `RESUME_BUILDER_LLM_MAX_TOKENS` | `2048` | Anthropic only |
| `RESUME_BUILDER_LLM_FIXTURE` | `0` | fixture mode (no network calls; cache-only) |
| `RESUME_BUILDER_LLM_CACHE_DIR` | `.llm_cache` / `tests/fixtures/llm_cache` (fixture mode) | response cache location |

> The env var prefix `RESUME_BUILDER_LLM_*` is preserved from the carve-out source for v0.1.0 backward compat. A future release will parameterize the prefix so consumers can use their own namespace.

### Construct directly (no env)

```python
from pathlib import Path
from llm_client_kit import LLMClient, PROVIDER_OLLAMA

client = LLMClient(
    endpoint="http://localhost:11434/v1/chat/completions",
    model="llama3.2:3b",
    cache_dir=Path("./.cache"),
    fixture_mode=False,
    timeout_seconds=30.0,
    provider=PROVIDER_OLLAMA,
)
```

## Error contract

`complete_json` documents three failure modes:

- `ValueError` — invalid / non-object JSON response, malformed cache payload.
- `RuntimeError` — provider-agnostic failures (cache I/O, fixture-mode miss) AND Ollama-path transport/parse failures.
- `LLMEndpointUnreachableError` (`RuntimeError` subclass) — connection refused / DNS failure / host unreachable, on the Ollama path.
- `AnthropicProviderError` (NOT a `RuntimeError` subclass) — any failure on the Anthropic path. **Intentionally** outside the `(RuntimeError, ValueError)` fallback chain: a paid API call halting loudly is better than silently degrading.

See class docstrings for the full rationale.

## Releasing

Releases are automated. Tag-triggered workflow at `.github/workflows/release.yml`:

1. Bump `version` in `pyproject.toml` and `__version__` in `src/llm_client_kit/__init__.py` on a PR.
2. After the PR merges, tag `vX.Y.Z` on `main` and push the tag.
3. The `release` workflow runs `gitleaks` against the working tree AND full git history — any finding aborts the workflow before the build. Then `python -m build` produces sdist + wheel, and `pypa/gh-action-pypi-publish` uploads via PyPI Trusted Publisher OIDC (no API token stored in repo secrets).

```bash
# example bump-and-release flow
git switch main && git pull
# (PR merged that bumps version to 0.1.4)
git tag v0.1.4
git push origin v0.1.4
# watch: gh run watch
```

The secret-scan step is **mandatory** and is the only thing standing between an accidentally-committed credential and a published wheel. Do not edit the workflow to skip it without first understanding what it catches.
