Fix migration workflow: upload as artifact instead of scp to practice EC2
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# ADR 0009: No RAG framework — hand-written orchestration behind ports
|
||||
|
||||
## Status
|
||||
|
||||
Accepted. **Recorded retrospectively** during the 2026-08-12 documentation pass:
|
||||
the decision is unambiguous in the implementation, but no ADR existed for it.
|
||||
|
||||
## Context
|
||||
|
||||
The system performs retrieval-augmented generation with query understanding,
|
||||
multiple retrieval strategies, reranking, prompt construction, structured output
|
||||
parsing, and post-generation verification — the exact feature set LangChain and
|
||||
LlamaIndex exist to provide.
|
||||
|
||||
## Decision
|
||||
|
||||
Neither framework is used. There is no RAG or agent library of any kind.
|
||||
|
||||
Verifiable from the repository:
|
||||
|
||||
- `apps/ai-service/pyproject.toml` declares six runtime dependencies:
|
||||
`fastapi`, `httpx`, `psycopg`, `pydantic-settings`, `qdrant-client`,
|
||||
`uvicorn`. Optional extras add `prometheus-client`, `anthropic` and three
|
||||
OpenTelemetry packages.
|
||||
- `apps/ai-service/Dockerfile` installs that set plus `boto3`.
|
||||
- No file imports `langchain`, `llama_index`, `haystack` or any equivalent.
|
||||
|
||||
Instead:
|
||||
|
||||
- Orchestration is a plain class with an explicit branch table
|
||||
(`rag/agent.py::_route`).
|
||||
- Prompts are module-level constants with JSON schemas (`rag/prompt.py`).
|
||||
- Providers are injected through `typing.Protocol`s (`rag/ports.py`) and
|
||||
implemented in `adapters/`, which is the only package importing an SDK — and
|
||||
always lazily, inside a method.
|
||||
- `bootstrap.py` is the single composition root.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Enabled by this choice**
|
||||
|
||||
- `rag/` imports no SDK, so the entire domain — including every safety check —
|
||||
is unit-testable offline with stub objects. All 278 ai-service tests run in
|
||||
2.6 s with no network.
|
||||
- Behaviour is inspectable: the retrieval route for a given turn is a readable
|
||||
`if` chain, not framework dispatch.
|
||||
- Failure semantics are chosen per call site. The fail-closed/fail-open
|
||||
asymmetry in [02-system-architecture.md](../02-system-architecture.md#failure-boundaries)
|
||||
is deliberate and would be hard to express through a framework's uniform
|
||||
error handling.
|
||||
- Prompt text is reviewable as domain policy in one file, and swapping providers
|
||||
cannot silently change what the model was told.
|
||||
|
||||
**Costs**
|
||||
|
||||
- Retrieval strategies, rank fusion, context packing and evaluation harnesses
|
||||
are all hand-written. Two of them (`fusion.py`, `expansion.py`) were written
|
||||
and never wired ([27-technical-debt.md](../27-technical-debt.md#d-12--dead-code-three-tested-modules-with-no-runtime-caller)).
|
||||
- Optional retriever capabilities are discovered with `getattr` rather than
|
||||
declared, so the real interface is wider than `ports.py` documents (D-14).
|
||||
- No community tooling for tracing, caching or evaluation applies; the
|
||||
observability layer is bespoke.
|
||||
|
||||
## Rationale
|
||||
|
||||
Partially recoverable. The code does not state "we chose not to use a
|
||||
framework", but the ports-and-adapters discipline is documented repeatedly in
|
||||
module docstrings, and one of them makes the intent explicit —
|
||||
`rag/understanding.py`:
|
||||
|
||||
> `rag/` imports no SDK: the LLM is injected as a `JsonLlm` protocol … and a
|
||||
> deterministic stub runs the whole path offline in tests.
|
||||
|
||||
`rag/prompt.py` gives the parallel reason for prompts:
|
||||
|
||||
> This is domain policy, not infrastructure … it lives here so it can be read,
|
||||
> reviewed and tested without an SDK, and so swapping the provider cannot
|
||||
> silently change what the model was told.
|
||||
|
||||
The consistent theme is testability and reviewability of the safety layer.
|
||||
Whether cost, lock-in or framework maturity also weighed in the decision is not
|
||||
recoverable from the repository.
|
||||
Reference in New Issue
Block a user