Files
markitect-main/markitect/llm/factory.py
tegwick 36c20f37d0
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
feat(llm): extract adapter layer for standalone llm-connect package (S1+S2)
Stage 1 — Decouple:
- Move RunConfig + LLMResponse to markitect/llm/models.py (canonical)
- Move LLMAdapter + Mock/ErrorLLMAdapter to markitect/llm/adapter.py
- markitect/prompts/execution/models.py and llm_adapter.py become re-export shims
- All 4 adapters + factory.py updated to import from markitect.llm.*
- Parameterize app_name in toml_config.py (resolve_llm, get_default_layers,
  get_preference_layers): paths and env var now derived from app_name arg
- Add tests/test_llm_isolation.py: 7 isolation + backward-compat tests

Stage 2 — Extract:
- Standalone llm-connect package created at ~/llm-connect/
- All 18 llm files copied; markitect.* imports replaced with llm_connect.*
- LLMError base inlined in llm_connect/exceptions.py (no markitect dep)
- llm-connect installed into markitect-venv; declared in pyproject.toml

Smoke test: markitect llm-check succeeds (live Gemini API call).
Backward compat: markitect.prompts.execution.{models,llm_adapter} still work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:04:50 +01:00

61 lines
2.1 KiB
Python

"""
Factory for creating LLM adapters by provider name.
"""
from typing import Optional, Dict, Any
from markitect.llm.adapter import LLMAdapter
from markitect.llm.exceptions import LLMConfigurationError
# Lazy imports to avoid pulling in every adapter at module load time.
_PROVIDERS: Dict[str, str] = {
"openrouter": "markitect.llm.openrouter.OpenRouterAdapter",
"claude-code": "markitect.llm.claude_code.ClaudeCodeAdapter",
"gemini": "markitect.llm.gemini.GeminiAdapter",
"openai": "markitect.llm.openai.OpenAIAdapter",
}
def create_adapter(
provider: str = "openrouter",
model: Optional[str] = None,
api_key: Optional[str] = None,
system_prompt: Optional[str] = None,
**kwargs: Any,
) -> LLMAdapter:
"""Instantiate an :class:`LLMAdapter` for the given *provider*.
Args:
provider: ``"openrouter"``, ``"claude-code"``, ``"gemini"``, or ``"openai"``.
model: Model name (passed to the adapter constructor).
api_key: Explicit API key (OpenRouter / Gemini / OpenAI).
system_prompt: Optional system prompt (OpenRouter / Gemini / OpenAI).
**kwargs: Extra keyword arguments forwarded to the adapter.
Returns:
A ready-to-use :class:`LLMAdapter` instance.
Raises:
LLMConfigurationError: If *provider* is not recognised.
"""
if provider not in _PROVIDERS:
known = ", ".join(sorted(_PROVIDERS))
raise LLMConfigurationError(
f"Unknown LLM provider {provider!r}. Choose from: {known}",
context={"provider": provider},
)
# Lazy import
fqn = _PROVIDERS[provider]
module_path, class_name = fqn.rsplit(".", 1)
import importlib
mod = importlib.import_module(module_path)
cls = getattr(mod, class_name)
if provider in ("openrouter", "gemini", "openai"):
return cls(model=model, api_key=api_key, system_prompt=system_prompt, **kwargs)
elif provider == "claude-code":
return cls(model=model, **kwargs)
else:
return cls(**kwargs) # pragma: no cover