Files
llm-connect/contracts/functional/adapters.md
Bernd Worsch d71f4114d1 feat: WP-0001 foundation + WP-0002 core extensions
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>
2026-04-01 22:24:14 +00:00

3.0 KiB

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

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.