|
| 1 | +# LLM backends for extraction |
| 2 | + |
| 3 | +`ctxgraph`'s LLM extraction path (`LlmExtractor`, used by the v0.3 universal |
| 4 | +pipeline) is a plain **OpenAI-compatible HTTP client**. It does not bind to any |
| 5 | +particular runtime — it POSTs to `/v1/chat/completions` and parses the JSON |
| 6 | +back. That means any of the following are drop-in backends, selected entirely by |
| 7 | +environment variables. |
| 8 | + |
| 9 | +## Configuration |
| 10 | + |
| 11 | +`LlmExtractor::from_env()` resolves a backend in this order: |
| 12 | + |
| 13 | +1. **Explicit (Tier 0)** — `CTXGRAPH_LLM_KEY` set → use `CTXGRAPH_LLM_URL` / |
| 14 | + `CTXGRAPH_LLM_MODEL` verbatim. Highest priority; this is how you force a |
| 15 | + specific local model. |
| 16 | +2. **Ollama auto-detect (Tier 1)** — probes `localhost:11434`, picks a preferred |
| 17 | + small model. Free, private, zero config. |
| 18 | +3. **Cloud (Tier 2)** — `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `OPENROUTER_API_KEY`. |
| 19 | + |
| 20 | +| Variable | Purpose | Default | |
| 21 | +|---|---|---| |
| 22 | +| `CTXGRAPH_LLM_KEY` | API key (any non-empty string for local servers) | — | |
| 23 | +| `CTXGRAPH_LLM_URL` | Full `/v1/chat/completions` URL | provider-specific | |
| 24 | +| `CTXGRAPH_LLM_MODEL` | Model name sent in the request | `gpt-4o-mini` | |
| 25 | +| `CTXGRAPH_LLM_TIMEOUT` | Per-request timeout, seconds | 60 (Ollama 120) | |
| 26 | +| `CTXGRAPH_NO_LLM=1` | Disable the LLM path entirely | — | |
| 27 | +| `CTXGRAPH_NO_OLLAMA=1` | Skip Ollama auto-detect | — | |
| 28 | + |
| 29 | +> Large local models partly offloaded to CPU can take **minutes** per episode. |
| 30 | +> Set `CTXGRAPH_LLM_TIMEOUT=600` so the client doesn't give up early. |
| 31 | +
|
| 32 | +## Backend A — Ollama (default, easiest) |
| 33 | + |
| 34 | +```bash |
| 35 | +ollama pull hf.co/yuxinlu1/gemma-4-12B-coder-fable5-composer2.5-v1-GGUF:Q4_K_M |
| 36 | +env CTXGRAPH_LLM_KEY=ollama \ |
| 37 | + CTXGRAPH_LLM_URL=http://localhost:11434/v1/chat/completions \ |
| 38 | + CTXGRAPH_LLM_MODEL='hf.co/yuxinlu1/gemma-4-12B-coder-fable5-composer2.5-v1-GGUF:Q4_K_M' \ |
| 39 | + CTXGRAPH_LLM_TIMEOUT=600 \ |
| 40 | + cargo test -p ctxgraph-extract --test llm_cross_domain_test -- --ignored --nocapture |
| 41 | +``` |
| 42 | + |
| 43 | +## Backend B — llama.cpp `llama-server` (more control) |
| 44 | + |
| 45 | +Use this when you want direct control over GPU offload (`-ngl`), context size, |
| 46 | +flash attention, or grammar-constrained output. `scripts/run-llama-server.sh` |
| 47 | +serves a GGUF — by default **reusing the blob Ollama already downloaded**, so |
| 48 | +there's no second copy on disk: |
| 49 | + |
| 50 | +```bash |
| 51 | +# terminal 1 — serve (resolves the GGUF from the ollama model tag) |
| 52 | +scripts/run-llama-server.sh # default coder model, port 8080 |
| 53 | +# or: GGUF=/path/to/model.gguf NGL=24 PORT=8080 scripts/run-llama-server.sh |
| 54 | + |
| 55 | +# terminal 2 — point ctxgraph at it |
| 56 | +env CTXGRAPH_LLM_KEY=llamacpp \ |
| 57 | + CTXGRAPH_LLM_URL=http://127.0.0.1:8080/v1/chat/completions \ |
| 58 | + CTXGRAPH_LLM_MODEL=local \ |
| 59 | + CTXGRAPH_LLM_TIMEOUT=600 \ |
| 60 | + cargo test -p ctxgraph-extract --test llm_cross_domain_test -- --ignored --nocapture |
| 61 | +``` |
| 62 | + |
| 63 | +The script's header documents how to build a CUDA `llama-server` into |
| 64 | +`~/.local/bin` (no sudo needed if the CUDA toolkit is installed). |
| 65 | + |
| 66 | +## Testing a model: the harness |
| 67 | + |
| 68 | +`tests/llm_cross_domain_test.rs` exercises `LlmExtractor` directly (not the |
| 69 | +GLiNER ONNX pipeline) so you can score any backend: |
| 70 | + |
| 71 | +- `llm_smoke_test` — 3 off-fixture real-world snippets; asserts the model |
| 72 | + returns usable extraction JSON at all. |
| 73 | +- `llm_cross_domain_hard_test` — per-domain entity + relation F1 across the 6 |
| 74 | + `cross_domain_episodes.json` domains. Exploratory (no threshold asserted); |
| 75 | + never panics on bad model output — extraction errors are counted and reported. |
| 76 | + |
| 77 | +## Reference: local-model comparison |
| 78 | + |
| 79 | +Same harness, fixtures, and fuzzy-F1 scorer across all 10 cross-domain episodes |
| 80 | +on a 6 GB-VRAM laptop (RTX 4050, partial CPU offload via Ollama): |
| 81 | + |
| 82 | +| Model | Entity F1 (name) | Entity F1 (strict) | Relation F1 | Combined | Errors | Wall time | |
| 83 | +|---|---|---|---|---|---|---| |
| 84 | +| gemma-4-12B-coder (Q4_K_M) | 0.816 | 0.492 | 0.398 | 0.607 | 0/10 | ~20 min | |
| 85 | +| qwen2.5:7b-instruct (Q4_K_M) | 0.850 | 0.383 | 0.263 | 0.557 | 0/10 | ~2.4 min | |
| 86 | +| Gemma-4-12B-OBLITERATED (Q4_K_M) | 0.083 | 0.067 | 0.100 | 0.092 | 9/10 | ~105 min | |
| 87 | + |
| 88 | +Takeaways: |
| 89 | + |
| 90 | +- **Entity recall is the strong suit** of every working model; **relation F1 and |
| 91 | + strict (name+type) F1 are the weak spots** — partly real model error, partly |
| 92 | + the scorer requiring exact head/tail entity-name matches. |
| 93 | +- **The coder fine-tune did not beat a stock 7B instruct** in a way that justifies |
| 94 | + 8× the latency: qwen2.5:7b matches its entity recall and fits entirely in VRAM. |
| 95 | + The coder's code bias even leaks `snake_case` entity names that break relation |
| 96 | + matching (worst on the finance domain). |
| 97 | +- **Abliterated models are unusable here** — `Gemma-4-12B-OBLITERATED` returned |
| 98 | + empty/unparseable output on 9/10 episodes. Abliteration strips the |
| 99 | + instruction-following that structured-JSON extraction relies on. |
0 commit comments