Three values of cfg.llm.provider (none / ollama / local), a curated GGUF catalog with host-probe / largest-fit helpers, and the cross-provider fallback chain.
← README
- Providers
- Cross-provider fallback
- Local provider — install + fetch-model
- Curated GGUF catalog
- Multi-part GGUFs + storage layout
- Minimal YAML + determinism
Codexa supports three values of cfg.llm.provider:
"none"— no LLM. RAG search returns retrieved passages only."ollama"— external Ollama server (cfg.llm.host+cfg.llm.model)."local"— embedded llama.cpp viallama-cpp-python. Fully offline, deterministic (greedy decoding withtemperature=0,top_k=1, fixedseed), no separate server.
Set cfg.llm.fallback to one of the other values to retry the call against the fallback when the primary is unreachable. Default is "local", so an Ollama setup keeps working when the server is down.
pip install -e ".[local-llm]" # installs llama-cpp-python
# Easiest: have Codexa fetch a curated GGUF for you and patch config.yml.
codexa-fetch-model --update-config config.yml # default: Qwen2.5-0.5B
codexa-fetch-model --list # full catalog with sizes/licenses
codexa-fetch-model --recommend # probe RAM/CPU/GPU, suggest best fit
codexa-fetch-model --largest --update-config config.yml # biggest entry that fits
codexa-fetch-model --url <hf-link> --out ~/m.gguf # custom mirror--recommend prints a host probe (RAM / CPU cores / GPU VRAM) and a ✓/✗ table of every catalog entry annotated with expected throughput on this host. --largest is the one-shot equivalent: it resolves to the biggest entry that fits the host's RAM budget (size × 1.2 ≤ RAM) and downloads it.
| Catalog entry | Size | License | Notes |
|---|---|---|---|
qwen2.5-0.5b (default) |
0.4 GB | Apache-2.0 | Smallest, fastest first run |
llama-3.2-1b |
0.8 GB | Llama 3.2 Community | 131 K context, runs anywhere |
qwen2.5-1.5b |
1.0 GB | Apache-2.0 | Balanced 32 K context |
qwen3-30b-a3b |
19 GB | Apache-2.0 | MoE (3 B active) — fastest of the big-class on CPU |
qwen3-32b |
20 GB | Apache-2.0 | Dense, hybrid thinking/non-thinking modes |
llama-3.3-70b |
42 GB | Llama 3.3 Community | Highest-quality dense for ≥64 GB RAM hosts |
deepseek-r1-distill-70b |
42 GB | MIT | Reasoning-tuned (chain-of-thought baked in) |
qwen2.5-72b |
47 GB | Apache-2.0 | Multi-part download (split + concatenated locally) |
mixtral-8x22b |
85 GB | Apache-2.0 | Multi-part; MoE (~39 B active); RAM-hungry |
Multi-part GGUFs (qwen2.5-72b, mixtral-8x22b) exceed Hugging Face's ~50 GB single-file cap; the downloader fetches each part in order and concatenates them into a single .gguf so model_path: stays one string.
Models land in {store.persist_dir}/models/ by default — co-located with the chroma index so a single backup root covers both. Override with $CODEXA_MODEL_DIR (env) or --out (per-call).
llm:
provider: "local"
local:
model_path: "/models/qwen2.5-0.5b-instruct.gguf"
n_ctx: 4096
seed: 1337
max_tokens: 512Greedy decoding with a fixed seed makes a given (model, prompt) pair reproduce the same answer turn after turn — useful when grounding answers in indexed corpus passages and you want results to be stable.
Where these prompts sit in the turn flow — and why the LLM is called only at fixed, named points instead of driving an agent loop — is recorded in ADR 0003.
Every LLM prompt the dispatcher constructs runs through a builder that pins five contracts per AGENTS.md prompt-template-integrity rule:
- SEC-1 fence — user-supplied text wraps in
<<<USER_INPUT>>>/<<<END_USER_INPUT>>>viautils.text.fence_user_segmentbefore interpolation. The system prompt names the fence so the model treats the block as inert. - NO_ANSWER_PHRASE — the constant lives in
search.prompt_builderand the answer template + detector reference the same string so a refusal phrase emitted by the LLM is detected programmatically and routed to the BOT-25 escalation packet. - Length rule — every template carries an explicit length rule (e.g. "2-3 short sentences" for the history-summary recap).
- Locale directive —
_language_directive(current_locale())+_language_header(...)prepend the prompt so a pt_BR session generates pt_BR sub-queries / rewrites / HyDE paragraphs / recaps / follow-ups / answers. Wired in HyDE, query_decomp, query_rewrite (PROMPT-10), history_summary (PROMPT-5), follow-ups (COH-6), and the answer template. [N]citation contract — each retrieved passage's row carries_citation_idmatching its[N]order in the prompt. Orphan markers whose index points past the surviving passage set get stripped post-generate viadrop_orphan_citations(FAITH-5) AND on saved-session reload (FAITH-7).
Each prompt builder ships a regression test under tests/test_phase87_prompt_integrity_* pinning the fence + locale + NO_ANSWER + length rule + [N] contract.
The process-local answer cache keys the SHA-256 digest of the exact rendered
RAG prompt together with canonical fingerprints of the full llm section and
the complete search.citations, search.regenerate_when_ungrounded, and
search.reranker policies. Mapping order is irrelevant; any value change that
can alter generation or grounding causes a miss. _ANSWER_CACHE_KEY_SCHEMA
names the recipe. Bump it whenever the key's field meaning or normalization
changes; prompt wording changes invalidate themselves through the prompt
digest.
cfg.search.regenerate_when_ungrounded.enabled: true opts the dispatcher into a second LLM pass when the first answer's per-sentence grounding score drops below cfg.search.regenerate_when_ungrounded.threshold (default 0.3). The revision prompt cites the unsupported sentences + instructs the model to drop or revise them.
The post-regen answer re-scores against the same passage pool + emits ungrounded_regenerate_outcome with (before_unsupported_n, after_unsupported_n, score_delta_mean, before_score_mean, after_score_mean, threshold) (FAITH-9) so operators can audit whether RAG-7 actually reduced unsupported claims across the eval set. eval_harness.run_case_with_regen_comparison (RETQ-8) runs each case twice (regen off / on) and stamps the delta on EvalResult.answer_faithfulness_after_regen.