Providers¶
Every LLM and embedding call in the pipeline is served by one of three interchangeable backends. Two config keys select who does what; the code calling them never knows which backend answered.
llm_provider: bedrock # chart reading, review, classify, chunk boundaries
embedding_provider: bedrock # chunk + query embeddings
They're independent — chat on one provider, embeddings on another is a supported (and sometimes ideal) setup.
The three backends¶
| Bedrock (default) | OpenAI | Ollama | |
|---|---|---|---|
| Chat + vision | Nova 2 Lite | GPT-5 (gpt-5-mini) |
Qwen3.5 9B (any local model) |
| Structured output | ✓ schema-enforced | ✓ schema-enforced | ✓ schema-enforced (GGUF builds) |
| Text embeddings | Nova multimodal, 1024-dim | text-embedding-3, 1024-dim | qwen3-embedding, 1024-dim |
| Image embeddings | ✓ | — | — |
| Needs | AWS account, Bedrock model access | OPENAI_API_KEY |
a local Ollama server — no key |
| Data leaves your infra | AWS | OpenAI | never |
Model ids are overridable per backend (bedrock:, openai:, ollama:
sections). The ollama backend speaks the OpenAI-compatible API, so any
server that implements it — vLLM, LM Studio — works by pointing
ollama.base_url at it.
The vision requirement¶
Parse's enrichment (charts → data tables, figure descriptions) and image-file classification send images to the LLM. All three reference models are vision-capable; if you swap in your own model, it must be too.
Ollama: use GGUF builds, not -mlx
Ollama's MLX engine silently delivers no images to the model and
ignores schema enforcement — charts get hallucinated instead of read.
The standard GGUF builds (qwen3.5:9b) handle both correctly. This is
verified behavior, not superstition.
Switching the LLM provider¶
Free. The next call routes to the new backend — no migration, nothing to rebuild. Quality differs (a local 9B won't match cloud models on dense chart pages), but nothing breaks.
Switching the embedding provider¶
Not free. Embeddings from different models live in different vector
spaces — a query embedded by model B finds nothing meaningful among
vectors from model A. After changing embedding_provider:
- Re-ingest your corpus (
skip_existing=False), or - Keep separate stores/namespaces per embedding model
The stored ingest manifest records which dimension and store each document was ingested with, so a mismatch is diagnosable after the fact.
Reranking is chosen separately¶
reranker: jina | aws | none picks retrieve()'s second stage — a
cross-encoder that reads full text and re-orders candidates:
- jina (default) —
jina-reranker-v3via API, free tier available - aws — Amazon Rerank via Bedrock, same AWS credentials
- none — vector order as-is; also the fully-local choice
A reranker failure never kills a query: retrieve logs a warning and returns vector order.
Next: Storage model — artifacts, vectors, and hybrid search.