generated from coulomb/repo-seed
WP-0001 — Foundation & GAAF Baseline - SCOPE.md, ARCHITECTURE-LAYERS.md, contracts/ tree - .claude/rules/ stubs filled (architecture, stack, boundary) - 57 tests (pytest), pyproject.toml with ruff+mypy, CI workflow WP-0002 — Core Extensions (FR-4 + FR-3) - FR-4: BudgetTracker (thread-safe) + LLMBudgetExceededError + optional RunConfig.budget_tracker + enforcement in all adapters - FR-3: async_execute_prompt on LLMAdapter ABC (asyncio.to_thread fallback) + native asyncio.create_subprocess_exec in ClaudeCodeAdapter 81 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
95 lines
3.0 KiB
Markdown
95 lines
3.0 KiB
Markdown
# Contract: Functional — Provider Adapters
|
|
|
|
**Layer:** Functional
|
|
**Version:** 0.1.0
|
|
**Maturity:** Beta (all adapters)
|
|
**Last updated:** 2026-04-01
|
|
|
|
---
|
|
|
|
## Common adapter contract
|
|
|
|
All provider adapters implement `LLMAdapter` (see `contracts/core/llm-adapter.md`).
|
|
|
|
Additional shared guarantees:
|
|
|
|
- Constructors resolve API keys at instantiation and raise `LLMConfigurationError`
|
|
immediately if no key is found (fail-fast).
|
|
- HTTP-based adapters (`OpenAIAdapter`, `GeminiAdapter`, `OpenRouterAdapter`)
|
|
use `_http.post_json` and do not add runtime dependencies beyond stdlib.
|
|
- `metadata` in the returned `LLMResponse` always contains `"provider"` and
|
|
`"latency_seconds"` keys.
|
|
- HTTP adapters that retry (`OpenAIAdapter`, `OpenRouterAdapter`) use
|
|
exponential backoff: `sleep(2 ** attempt)` on 429 and 5xx.
|
|
|
|
---
|
|
|
|
## OpenAIAdapter
|
|
|
|
**Provider key:** `"openai"`
|
|
**Default model:** `gpt-4.1-mini`
|
|
**API:** `https://api.openai.com/v1/chat/completions`
|
|
**Auth:** `OPENAI_API_KEY` env var or `apikey-chatgpt.txt` in project root
|
|
**Retries:** 3 (exponential backoff on 429 and 5xx)
|
|
|
|
---
|
|
|
|
## GeminiAdapter
|
|
|
|
**Provider key:** `"gemini"`
|
|
**Default model:** `gemini-2.5-flash`
|
|
**API:** `https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent`
|
|
**Auth:** `GEMINI_API_KEY` env var or `apikey-geminifree.txt` in project root
|
|
**Retries:** 0 (no retry logic; rate-limit handling deferred)
|
|
**Note:** System prompt is simulated via a user/model turn pair (Gemini has no native system role).
|
|
|
|
---
|
|
|
|
## OpenRouterAdapter
|
|
|
|
**Provider key:** `"openrouter"`
|
|
**Default model:** `anthropic/claude-sonnet-4`
|
|
**API:** `https://openrouter.ai/api/v1/chat/completions` (configurable via `LLMConfig.api_base`)
|
|
**Auth:** `OPENROUTER_API_KEY` env var or `apikey-openrouter.txt` in project root
|
|
**Retries:** 3 (exponential backoff on 429 and 5xx)
|
|
**Note:** OpenRouter is an OpenAI-compatible endpoint; `RunConfig.model_params` are merged into the payload.
|
|
|
|
---
|
|
|
|
## ClaudeCodeAdapter
|
|
|
|
**Provider key:** `"claude-code"`
|
|
**Default model:** n/a (uses the CLI's configured default)
|
|
**Auth:** none (delegates to locally installed `claude` CLI)
|
|
**Subprocess:** `claude --print [--model M]` with prompt on stdin
|
|
**Token counts:** estimated via `_token_estimator` (not provider-reported)
|
|
**validate_config:** runs `claude --version`; returns `False` if CLI not found
|
|
|
|
---
|
|
|
|
## EmbeddingAdapter ABC
|
|
|
|
`llm_connect.embedding_adapter.EmbeddingAdapter`
|
|
|
|
```python
|
|
class EmbeddingAdapter(ABC):
|
|
@abstractmethod
|
|
def embed(self, texts: list[str]) -> list[list[float]]: ...
|
|
```
|
|
|
|
Invariant: returns a list of the same length as `texts`.
|
|
|
|
### OpenAICompatibleEmbeddingAdapter
|
|
|
|
Compatible with any OpenAI-format embedding endpoint (`/v1/embeddings`).
|
|
Default model: `text-embedding-3-small`.
|
|
|
|
---
|
|
|
|
## EmbeddingCache
|
|
|
|
`llm_connect.embedding_cache.EmbeddingCache`
|
|
|
|
Disk-backed cache keyed by text content (SHA-256 hash).
|
|
`get_or_compute(text, compute_fn)` returns cached vector or calls `compute_fn`.
|