Part 8 — Technology Stack, Decisions, and What Makes a Production System
What does the confirmed production stack look like, what was deliberately traded away, and what separates a production system from a demo?
13 min · Updated July 2026
8.1 The confirmed stack
The technology stack is not a list of tools. Every choice reflects a tradeoff, and every tradeoff was made explicitly rather than by default. Version numbers below were re-checked in July 2026; where a component has moved since the original June 2026 write-up, the current version is noted.
| Layer | Technology | Tradeoff accepted |
|---|---|---|
| Document parsing | Docling 2.x (MIT) | Best-in-class fidelity; slightly slower than lightweight alternatives |
| OCR escalation | Docling built-in OCR + OpenAI/Claude vision API | No GPU required; API cost on escalation |
| Audio transcription | faster-whisper / WhisperX (or Docling native ASR) | CPU-only; diarization available; slower than GPU-accelerated |
| LLM generation | OpenAI + Anthropic Claude via LiteLLM | Data transits third-party APIs; requires ZDR enterprise tiers |
| Embeddings | OpenAI text-embedding-3-large via LiteLLM | Dense only — no late chunking, no learned sparse; BM25 covers lexical channel |
| Sparse retrieval | BM25 (Qdrant native) | No learned sparse (SPLADE would need self-hosting); lexical recall is strong |
| Reranking | BGE-Reranker-v2-m3 (CPU container) | One self-hosted container; zero per-call token cost |
| Vector database | Qdrant 1.17+ (Apache-2) | Best-in-class hybrid + payload filtering; Milvus planned at >500M vectors |
| Knowledge graph | Kuzu (MIT) | Embedded, columnar, CPU-only; FalkorDB/Neo4j as alternatives |
| Chunking | Contextual retrieval (LLM-prepend) + semantic chunking | Late chunking unavailable with API embeddings; contextual retrieval is highest-accuracy-per-effort alternative |
| Agentic orchestration | LangGraph 1.2.x (MIT) | Deepest tracing, durable execution, most active development |
| Prompt optimization | DSPy (MIT) | Compiles prompts against metrics; requires golden sets |
| Ingestion pipeline | Dagster + Kafka/Redpanda | Asset-oriented model; strong observability hooks |
| Observability / telemetry | OpenTelemetry Collector + Langfuse | Full LLM-native tracing; Arize Phoenix for embedding drift |
| CI evaluation | RAGAS + DeepEval + pytest | RAGAS for RAG-specific metrics; DeepEval for agentic/multimodal metrics |
| Human review | Argilla (Apache-2) | Primary annotation platform; Label Studio for image-level annotation |
| Deployment | Kubernetes + Argo CD + KEDA | CPU-only cluster; GitOps; queue-driven autoscaling for ingestion |
What’s changed since June 2026 (checked July 2026)
LangGraph reached 1.0 and is now on the 1.2.x line. The one migration note that touches code: langgraph.prebuilt is deprecated — its functionality moved to langchain.agents. 1.x is backward-compatible and pins are recommended, since minor releases still change streaming/event-type behaviour. Pin langgraph==1.2.x and test upgrades in staging.
Docling now ships more than a parser. Recent Docling adds GraniteDocling (a document vision-language model) and native audio ASR, so the split between Docling for parsing and a separate Whisper for audio is now optional — Docling can cover both, which simplifies the audio row for some deployments.
The embedding field moved fast. The dense-only tradeoff below still holds, but the set of alternatives is larger now — see the updated note in §8.2.
8.2 Consequential tradeoffs
OpenAI embeddings vs. self-hosted BGE-M3
This is the most consequential technical tradeoff. BGE-M3 is a single model that produces dense vectors, sparse vectors (learned sparse), and multi-vector representations — enabling late chunking, SPLADE-based hybrid retrieval, and ColPali visual retrieval. OpenAI text-embedding-3 produces dense vectors only. Choosing OpenAI embeddings eliminates three accuracy techniques: late chunking, learned sparse (SPLADE), and ColPali.
The mitigation: contextual retrieval is promoted to the primary chunking strategy, BM25 covers the lexical channel, and ColPali is deferred but preserved via a reserved vector slot. The net accuracy difference is real but manageable for most domains; the operational simplicity gain is significant. If a domain turns out to be very visual or very dependent on rare technical terms, reintroducing self-hosted BGE-M3 is a container deployment and config change, not a rewrite.
Updated July 2026 — the alternatives have multiplied
When this was written, the choice read as essentially “OpenAI dense-only vs. self-hosted BGE-M3.” As of mid-2026 the field has compressed and widened: on the API side, Cohere embed-v4 and Voyage-3-large emit dense andsparse in a single call (removing the two-model requirement that motivated BGE-M3), Google Gemini Embedding 2 leads multimodal, and on the open-weight side Qwen3-Embedding-8B and Jina v5 now match commercial APIs on MTEB. Practical implication: if the reason you’d reach for self-hosted BGE-M3 is hybrid-in-one-model, an API like Cohere embed-v4 may now give you that without leaving the API-first posture. The durable advice is unchanged — MTEB rank is no longer the deciding factor; test on your own data.
API-first vs. self-hosted LLM generation
Commercial API generation means documents and queries transit third-party infrastructure. This requires contractual zero-data-retention enterprise tiers, PII redaction before API egress, and a per-tenant data-class allow-list. The compensating benefit: no GPU provisioning, no model serving infrastructure, no quantization management. The LiteLLM interface means self-hosted vLLM is always a config change away for tenants with stricter data residency requirements.
Contextual retrieval as the primary chunking strategy
Because late chunking is unavailable with dense-only API embeddings, contextual retrieval is the strongest accuracy lever remaining. It uses an LLM call to prepend a situating context to every chunk. With prompt caching enabled on both the OpenAI and Anthropic routes, this is cost-mitigated — the document’s structural context is cached, and only the chunk-specific content varies. Without prompt caching, contextual retrieval at scale would be prohibitively expensive.
8.3 What separates a production system from a demo

The list of technical components is not what separates them. A demo can use the same vector database, the same LLM API, the same chunking library. What separates them is architectural discipline in five specific areas.
1. The system knows its own uncertainty. A demo retrieves and generates. A production system measures, at every step, how confident it is in what it retrieved and what it generated. Docling’s confidence grades, reranker scores, CRAG grading, Self-RAG faithfulness checks — these are the signals that drive every quality gate, routing decision, and human-review trigger.
2. The system has explicit failure modes with explicit mitigations. Every pattern in this series exists to kill a named failure mode. A demo does not enumerate failure modes. A production system maps every known failure to a specific pattern, measures whether the pattern is working, and alerts when it is not.
3. The ingestion plane is treated as a first-class accuracy layer. In a demo, ingestion is one afternoon’s work. In a production system, ingestion is where most accuracy is actually determined — and it is instrumented, confidence-graded, routed, and human-reviewed with the same rigour as retrieval and generation.
4. Humans are structural components of the system, not an afterthought. A demo has no human-in-the-loop. A production system routes low-confidence documents, detected hallucinations, and failed retrievals to a structured human-review workflow and feeds corrections back as golden data that improves future performance. The human effort is targeted, not bulk — and it compounds, because each correction makes the confidence thresholds more accurate.
5. Accuracy is an enforced CI constraint, not a subjective judgment. A demo reports accuracy as a snapshot at demo time. A production system encodes the acceptable performance floor as a pytest/DeepEval gate that blocks every merge. Accuracy is maintained automatically rather than hoped for manually.
To make #5 concrete — this is the gate that turns “we think it’s accurate” into a build-breaking constraint:
# The CI gate that separates production from demo: accuracy blocks the merge.
import pytest
from ragas import evaluate
from ragas.metrics import context_recall, faithfulness, answer_relevancy
FLOORS = {"context_recall": 0.85, "faithfulness": 0.90, "answer_relevancy": 0.85}
def test_rag_quality_gate(golden_dataset):
scores = evaluate(golden_dataset,
metrics=[context_recall, faithfulness, answer_relevancy])
for metric, floor in FLOORS.items():
assert scores[metric] >= floor, (
f"{metric} {scores[metric]:.3f} below floor {floor} — merge blocked"
)The honest summary: anyone can assemble the components. What cannot be assembled in an afternoon is the routing logic for failure modes, the confidence-gated human-review loop, the compound accuracy flywheel, the CI enforcement, and the architectural discipline that keeps every component behind a stable interface so the stack can evolve without rebuilding. That is what a production-grade multimodal hybrid agentic RAG system actually is.
Appendix A: Open-source tool reference
| Tool | Category | License | Role |
|---|---|---|---|
| Docling 2.x | Parsing | MIT | Primary document parser; confidence scoring; now also GraniteDocling VLM + native ASR |
| faster-whisper / WhisperX | ASR | MIT / MIT | Audio/video transcription and diarization |
| tree-sitter | Code parsing | MIT | AST-aware code chunking |
| Trafilatura | HTML extraction | Apache-2 | Web content extraction |
| Chonkie | Chunking | Apache-2 | Modality-aware chunking |
| Qdrant 1.17+ | Vector DB | Apache-2 | Primary vector store; hybrid + named vectors |
| Kuzu | Knowledge graph | MIT | Embedded graph DB for GraphRAG |
| LangGraph 1.2.x | Agentic orchestration | MIT | Stateful multi-agent graph (prebuilt → langchain.agents) |
| LiteLLM | Model gateway | MIT | Unified API gateway; per-role routing |
| Dagster | Pipeline orchestration | Apache-2 | Asset-oriented ingestion pipeline |
| Kafka / Redpanda | Message bus | Apache-2 | Streaming ingestion intake |
| KEDA | Autoscaling | Apache-2 | Queue-driven worker autoscaling |
| Langfuse | Tracing / eval | MIT (self-hosted) | LLM-native trace, prompt, eval backend |
| Arize Phoenix | Embedding drift | Elastic-2 | Embedding-space cluster visualization |
| OpenTelemetry Collector | Telemetry | Apache-2 | Span collection and fan-out |
| RAGAS | RAG evaluation | Apache-2 | Context recall, faithfulness, relevancy |
| DeepEval | LLM evaluation | Apache-2 | CI gate; 50+ metrics; agentic eval |
| Argilla | Human review | Apache-2 | Annotation, golden dataset curation |
| Label Studio | Image annotation | Apache-2 | Visual/OCR-level document annotation |
| DSPy | Prompt optimization | MIT | Metric-driven prompt compilation |
| Presidio | PII detection | MIT | PII redaction before API egress |
| BGE-Reranker-v2-m3 | Reranking | MIT | Cross-encoder reranker (CPU container) |
| PostgreSQL 16+ | Metadata / audit | PostgreSQL | Tenants, ACLs, ingestion state, episodic log |
| Redis / Valkey | Cache / state | BSD / Apache-2 | Embedding cache, query cache, rate limiting |
| Argo CD / Argo Workflows | GitOps / batch | Apache-2 | Declarative deployment; batch job orchestration |
Alternatives worth evaluating (added July 2026), not replacements: embeddings — Cohere embed-v4 or Voyage-3-large (dense+sparse in one API call), Qwen3-Embedding-8B or Jina v5 (open-weight, MTEB-competitive), Gemini Embedding 2 (multimodal), Qwen3-VL-2B (open-weight cross-modal). These change the §8.2 tradeoff calculus; they don’t change the architecture.
Appendix B: Key empirical claims with attribution
These numbers appear throughout the series. They are sourced from the foundational research and should be treated as direction-of-effect estimates, not precise constants — results vary by domain and dataset.
- Hybrid retrieval beats dense-only or sparse-only: consistent across benchmark configurations; margin varies, ~10–25% context-recall improvement typical.
- Contextual retrieval: −67% retrieval errors (Anthropic, 2024) when combined with BM25 and a reranker.
- GraphRAG ~13% worse than vanilla RAG on simple factoid QA (Han et al., 2025).
- Edge intent classification: ~40% cost reduction, ~35% latency reduction from Adaptive RAG complexity-routing literature.
- Agentic multipliers: Adaptive ~1.5–2×, Self-RAG ~2–3×, CRAG ~3–5×, multi-hop ReAct ~4–10×. System-specific.
- Embedding fine-tuning on domain data: 10–30% retrieval improvement from domain-adaptation literature.
- Late chunking: Jina AI, 2024.
This article documents the design space of enterprise-grade multimodal hybrid agentic RAG as of July 2026. Framework versions, API capabilities, and benchmark results evolve — the design principles and failure-mode taxonomy are the durable parts.