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>
123 lines
4.0 KiB
Markdown
123 lines
4.0 KiB
Markdown
# Contract: Core — LLMAdapter Interface
|
|
|
|
**Layer:** Core
|
|
**Version:** 0.1.0
|
|
**Status:** Draft (stabilises at v1.0.0)
|
|
**Last updated:** 2026-04-01
|
|
|
|
---
|
|
|
|
## LLMAdapter ABC
|
|
|
|
`llm_connect.adapter.LLMAdapter`
|
|
|
|
### Interface
|
|
|
|
```python
|
|
class LLMAdapter(ABC):
|
|
@abstractmethod
|
|
def execute_prompt(self, prompt: str, config: RunConfig) -> LLMResponse: ...
|
|
|
|
@abstractmethod
|
|
def validate_config(self, config: RunConfig) -> bool: ...
|
|
```
|
|
|
|
**Planned addition (WP-0002 T07):**
|
|
```python
|
|
async def async_execute_prompt(self, prompt: str, config: RunConfig) -> LLMResponse:
|
|
# Default: runs execute_prompt in a thread executor
|
|
...
|
|
```
|
|
|
|
### Invariants
|
|
|
|
1. `execute_prompt` MUST return an `LLMResponse` with a non-empty `content` field on success.
|
|
2. `execute_prompt` MUST raise a subclass of `LLMError` on any failure — never a bare exception.
|
|
3. `validate_config` MUST be side-effect-free and return `bool` only.
|
|
4. `validate_config` returning `False` does not preclude calling `execute_prompt` — it is advisory.
|
|
5. Adapters MUST NOT mutate the `config` argument.
|
|
6. `execute_prompt` is allowed to be slow (network I/O) but MUST respect `config.timeout_seconds`.
|
|
|
|
### Failure modes
|
|
|
|
| Condition | Exception |
|
|
|-----------|-----------|
|
|
| Missing / invalid API key | `LLMConfigurationError` |
|
|
| HTTP 4xx (non-429) | `LLMAPIError` (with `.status_code`) |
|
|
| HTTP 429 | `LLMRateLimitError` |
|
|
| Request timeout | `LLMTimeoutError` |
|
|
| CLI subprocess failure | `LLMSubprocessError` (with `.return_code`, `.stderr`) |
|
|
| Token budget exceeded (WP-0002) | `LLMBudgetExceededError` |
|
|
|
|
### Compatibility rules
|
|
|
|
- Any code that accepts `LLMAdapter` MUST work with `MockLLMAdapter`.
|
|
- Adding new optional methods to the ABC is non-breaking (default implementations provided).
|
|
- Removing or changing the signature of `execute_prompt` or `validate_config` is a **breaking Core change** requiring a major version bump.
|
|
|
|
---
|
|
|
|
## RunConfig
|
|
|
|
`llm_connect.models.RunConfig`
|
|
|
|
### Fields and invariants
|
|
|
|
| Field | Type | Default | Invariant |
|
|
|-------|------|---------|-----------|
|
|
| `model_name` | `str` | `"gpt-4"` | Non-empty string; adapters MAY override |
|
|
| `temperature` | `float` | `0.7` | 0.0 ≤ temperature ≤ 2.0 |
|
|
| `max_tokens` | `int` | `2000` | > 0 |
|
|
| `model_params` | `dict` | `{}` | Provider-specific pass-through; no invariants |
|
|
| `max_depth` | `int` | `3` | ≥ 0 |
|
|
| `skip_if_exists` | `bool` | `True` | — |
|
|
| `timeout_seconds` | `int` | `300` | > 0 |
|
|
| `budget_tracker` | `BudgetTracker \| None` | `None` | Optional; added in WP-0002 |
|
|
|
|
Adapters MUST NOT mutate `RunConfig` fields.
|
|
|
|
---
|
|
|
|
## LLMResponse
|
|
|
|
`llm_connect.models.LLMResponse`
|
|
|
|
### Fields and invariants
|
|
|
|
| Field | Type | Invariant |
|
|
|-------|------|-----------|
|
|
| `content` | `str` | Non-empty on success; may be empty only if provider returned empty output |
|
|
| `model` | `str` | Non-empty; the model actually used (may differ from `RunConfig.model_name`) |
|
|
| `usage` | `dict` | Keys: `prompt_tokens`, `completion_tokens`, `total_tokens` (all int ≥ 0) |
|
|
| `finish_reason` | `str` | Provider-reported; `"stop"` is the normal value |
|
|
| `metadata` | `dict` | Arbitrary; always includes `"provider"` key |
|
|
|
|
---
|
|
|
|
## LLMError Hierarchy
|
|
|
|
```
|
|
LLMError
|
|
├── LLMConfigurationError bad key / unknown provider
|
|
├── LLMAPIError HTTP error (has .status_code, .response_body)
|
|
│ └── LLMRateLimitError 429
|
|
├── LLMTimeoutError request or subprocess timed out
|
|
├── LLMSubprocessError CLI failed (has .return_code, .stderr)
|
|
└── LLMBudgetExceededError token budget cap exceeded (WP-0002)
|
|
```
|
|
|
|
All exceptions carry optional `cause` (chained exception) and `context` (dict).
|
|
|
|
---
|
|
|
|
## Mock adapters
|
|
|
|
`MockLLMAdapter` and `ErrorLLMAdapter` are part of Core — they are test
|
|
primitives that any consumer may depend on without importing dev extras.
|
|
|
|
`MockLLMAdapter` invariants:
|
|
- Returns deterministic response without network I/O
|
|
- Increments `call_count` on each call
|
|
- Records `last_prompt` and `last_config`
|
|
- `reset()` clears all counters and recorded state
|