docs(roadmap): add workplan for extracting llm module as shared library
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
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
3-stage plan: decouple (RunConfig/LLMResponse move + app name parameterization) → extract to standalone package → adopt in first consumer. Registered as workstream in Custodian State Hub. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
214
roadmap/llm-shared-library/PLAN.md
Normal file
214
roadmap/llm-shared-library/PLAN.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# LLM Adapter Layer — Extract as Shared Library
|
||||
|
||||
## Vision
|
||||
|
||||
The `markitect.llm` module is a clean, stdlib-only adapter layer for calling
|
||||
LLMs via OpenRouter, Gemini, OpenAI, and the Claude Code CLI. It implements a
|
||||
uniform interface, a 7-layer TOML config chain, embedding support with caching,
|
||||
and typed exceptions. It should be usable by all projects in the Bernd Worsch
|
||||
ecosystem without pulling in all of markitect.
|
||||
|
||||
This roadmap tracks extracting it into a standalone installable library.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
The module lives at `markitect/llm/` (~16 files, ~1500 LOC, stdlib-only) and
|
||||
provides:
|
||||
- **4 text adapters**: OpenRouter, Gemini, OpenAI, Claude Code CLI
|
||||
- **2 embedding adapters**: OpenAI-compatible (OpenAI + OpenRouter)
|
||||
- **Embedding cache**: JSON-backed, content-digest validated
|
||||
- **Similarity utilities**: pure-Python cosine similarity, matrix, pair-finding
|
||||
- **7-layer TOML config chain**: CLI > env > user/dir preference/default > hardcoded
|
||||
- **Typed exceptions**: LLMError hierarchy
|
||||
- **HTTP wrapper**: urllib-only, typed exception translation
|
||||
|
||||
### Two Coupling Issues Blocking Clean Extraction
|
||||
|
||||
| Issue | Location | Severity |
|
||||
|-------|----------|----------|
|
||||
| `RunConfig` and `LLMResponse` are defined in `markitect.prompts.execution.models`, not in `markitect.llm` | `markitect/prompts/execution/models.py` | High — creates cross-module import for all consumers |
|
||||
| TOML config chain hardcodes `"markitect"` as app name (paths: `~/.config/markitect/`, env prefix `MARKITECT_`, files: `.markitect.toml`) | `markitect/llm/toml_config.py` | Medium — consumers either accept markitect config or can't use the chain |
|
||||
|
||||
---
|
||||
|
||||
## Terminology
|
||||
|
||||
- **adapter**: concrete implementation of `LLMAdapter` for a single provider
|
||||
- **factory**: `create_adapter()` / `create_embedding_adapter()` — provider-agnostic entry points
|
||||
- **config chain**: 7-layer resolution of provider + model (CLI → env → TOML → hardcoded)
|
||||
- **standalone library**: a Python package installable with `pip install` from a git URL or local path, without PyPI
|
||||
- **consumer**: any project that imports and uses the library (markitect itself, custodian, railiance, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Packaging Decision (Pending)
|
||||
|
||||
Before Phase 2 starts, one architectural decision must be resolved:
|
||||
|
||||
> **D1: Where does the extracted library live?**
|
||||
>
|
||||
> **Option A — Standalone repo** (`~/bw-llm` or similar):
|
||||
> - Clean separation, versioned independently, installable via `pip install git+file:///...` or git URL
|
||||
> - Adds a repo to maintain; changes require bumping version in dependents
|
||||
>
|
||||
> **Option B — Subfolder of markitect with own `pyproject.toml`** (monorepo-lite):
|
||||
> - Stays co-located with the main codebase that will use it most
|
||||
> - Less friction for iteration; single git history
|
||||
> - Slightly unorthodox but valid for personal infrastructure
|
||||
>
|
||||
> **Option C — Just `pip install markitect` in other projects**:
|
||||
> - Zero extraction work; reuse today
|
||||
> - Pulls all of markitect (prompts, infospace, CLI, etc.) as transitive deps
|
||||
> - Acceptable short-term if other projects are small
|
||||
|
||||
---
|
||||
|
||||
## Stages
|
||||
|
||||
### Stage 1 — Decouple (within markitect)
|
||||
|
||||
Prepare the module for extraction without changing its public API.
|
||||
|
||||
#### S1.1 — Move RunConfig + LLMResponse into markitect.llm
|
||||
|
||||
`RunConfig` and `LLMResponse` are currently in `markitect.prompts.execution.models`.
|
||||
The LLM adapters import from there, creating a hard dependency on the prompt system.
|
||||
|
||||
**Work:**
|
||||
- Move both dataclasses to `markitect/llm/models.py`
|
||||
- Update all imports in `markitect.llm` and `markitect.prompts`
|
||||
- Keep a re-export shim in `markitect.prompts.execution.models` for backwards compat
|
||||
|
||||
**Acceptance:** `markitect/llm/` has zero imports from `markitect.prompts.*`
|
||||
|
||||
#### S1.2 — Parameterize the TOML config chain
|
||||
|
||||
Replace the hardcoded `"markitect"` app name with a configurable `app_name` parameter.
|
||||
|
||||
**Work:**
|
||||
- Add `app_name: str = "markitect"` parameter to `resolve_llm()` and the config
|
||||
path helpers in `toml_config.py`
|
||||
- Derive config file path (`~/.config/{app_name}/config.toml`), env prefix
|
||||
(`{APP_NAME}_HELPER_MODEL`), and local config file (`.{app_name}.toml`) from it
|
||||
- All existing behaviour is preserved when `app_name="markitect"` (default)
|
||||
|
||||
**Acceptance:** A consumer can call `resolve_llm(app_name="railiance")` and get
|
||||
config from `~/.config/railiance/config.toml` and `RAILIANCE_HELPER_MODEL`.
|
||||
|
||||
#### S1.3 — Isolation tests
|
||||
|
||||
Write a test file that imports only from `markitect.llm.*` and verifies no
|
||||
accidental coupling remains.
|
||||
|
||||
**Acceptance:** `pytest tests/test_llm_isolation.py` passes; no import of
|
||||
`markitect.prompts` or `markitect.infospace` in the LLM module tree.
|
||||
|
||||
---
|
||||
|
||||
### Stage 2 — Extract
|
||||
|
||||
#### S2.1 — Resolve D1: packaging location
|
||||
|
||||
Record the decision and create the package scaffold.
|
||||
|
||||
**Acceptance:** D1 resolved, `pyproject.toml` for the library exists at the
|
||||
chosen location with name, version `0.1.0`, and declared dependencies.
|
||||
|
||||
#### S2.2 — Create standalone package
|
||||
|
||||
Move (or symlink) the llm module into the new package structure. Wire up
|
||||
the `pyproject.toml` entry points. Verify `pip install -e <path>` works.
|
||||
|
||||
**Files to carry over:**
|
||||
```
|
||||
llm/
|
||||
__init__.py # re-exports: create_adapter, create_embedding_adapter,
|
||||
# LLMAdapter, EmbeddingAdapter, LLMConfig, exceptions
|
||||
models.py # RunConfig, LLMResponse (moved from S1.1)
|
||||
config.py # load_config, resolve_api_key
|
||||
toml_config.py # resolve_llm (parameterized from S1.2)
|
||||
factory.py # create_adapter
|
||||
exceptions.py # LLM exception hierarchy
|
||||
openrouter.py
|
||||
claude_code.py
|
||||
gemini.py
|
||||
openai.py
|
||||
embedding_adapter.py
|
||||
embedding_openai.py
|
||||
embedding_factory.py # create_embedding_adapter
|
||||
embedding_cache.py
|
||||
similarity.py
|
||||
_http.py
|
||||
_token_estimator.py
|
||||
```
|
||||
|
||||
**Acceptance:** `python -c "from bw_llm import create_adapter; print('ok')"` works
|
||||
in a fresh venv with only the new package installed.
|
||||
|
||||
#### S2.3 — Update markitect to depend on extracted package
|
||||
|
||||
Replace `markitect/llm/` with an import alias pointing to the new package, or
|
||||
add the package as a path dependency in markitect's `pyproject.toml`.
|
||||
|
||||
**Acceptance:** All markitect tests pass; `markitect/llm/__init__.py` is either
|
||||
removed or becomes a thin re-export of `bw_llm`.
|
||||
|
||||
#### S2.4 — Integration smoke test
|
||||
|
||||
Run the full markitect infospace pipeline (entity extraction + evaluation) end-to-end
|
||||
against a small fixture to confirm nothing broke.
|
||||
|
||||
**Acceptance:** `markitect infospace evaluate --dry-run` succeeds on a 3-entity fixture.
|
||||
|
||||
---
|
||||
|
||||
### Stage 3 — Adopt in First Consumer
|
||||
|
||||
#### S3.1 — Integrate in one other project
|
||||
|
||||
Pick the first real consumer (likely the custodian state-hub, for LLM-assisted
|
||||
state summaries or decision rationale generation) and wire up the library.
|
||||
|
||||
**Work:**
|
||||
- Add `bw-llm` (or equivalent) as a dependency
|
||||
- Write a small usage example (e.g., `llm_helper.py`)
|
||||
- Confirm config chain works with the consumer's own app name
|
||||
|
||||
#### S3.2 — Usage guide
|
||||
|
||||
Write `README.md` for the library covering:
|
||||
- Installation (local path / git URL)
|
||||
- Supported providers and env vars
|
||||
- TOML config file locations and format
|
||||
- `create_adapter()` / `create_embedding_adapter()` quick-start
|
||||
- Error handling
|
||||
|
||||
**Acceptance:** Another developer (or agent) can follow the README to use the library
|
||||
in a new project without reading source code.
|
||||
|
||||
---
|
||||
|
||||
## Stage Summary
|
||||
|
||||
| Stage | Description | Key Deliverable | Blocks |
|
||||
|-------|-------------|-----------------|--------|
|
||||
| S1.1 | Move RunConfig/LLMResponse to llm | Zero cross-module deps | S2.2 |
|
||||
| S1.2 | Parameterize app name | Configurable config chain | S2.2 |
|
||||
| S1.3 | Isolation tests | Green test suite | S2.1 |
|
||||
| S2.1 | Resolve packaging decision (D1) | pyproject.toml scaffold | S2.2 |
|
||||
| S2.2 | Create standalone package | `pip install` works | S2.3 |
|
||||
| S2.3 | Update markitect | markitect uses extracted lib | S2.4 |
|
||||
| S2.4 | Integration smoke test | Full pipeline passes | S3.1 |
|
||||
| S3.1 | First consumer integration | Library used in real project | S3.2 |
|
||||
| S3.2 | Usage guide | README published | — |
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Publishing to PyPI (unnecessary for personal infrastructure; git/local installs suffice)
|
||||
- Adding new LLM providers (separate concern)
|
||||
- Porting the helper CLI to the library (the CLI is markitect-specific)
|
||||
- Async adapters (current sync interface is sufficient; can be added later)
|
||||
Reference in New Issue
Block a user