This document captures the architectural gap analysis that drove the v2.0 upgrade, and maps each identified weakness to its resolution.
Gap: Raw text extraction flattened tables, split captions from their figures, and allowed multi-page tables to be fragmented into meaningless chunks.
Resolution (src/rag_system/components/layout_parser.py):
LayoutAwareParsergroups elements by semantic proximity before chunking- Tables are matched with their captions within a configurable line-proximity window
- Consecutive table elements on adjacent pages are merged into single
LayoutChunkobjects withis_continuation=True - Section headings are detected (Item 7, RISK FACTORS, NOTE 12, etc.) and propagated to all child chunks as
headingmetadata - All structured elements are wrapped in HTML (
<table>,<figure>,<section>) to give the LLM rich layout context to_document_elements()converts back toDocumentElementwith HTML embedded in thetextfield
Gap: LLMs computing multi-step financial formulas in natural language introduce errors. Direct arithmetic in generation is unreliable.
Resolution (src/rag_system/components/pot_executor.py):
ASTSandboxValidatorvalidates generated code against an AST node allowlist before execution — blocksimport,exec,eval,open,__builtins__access, and all network librariesPoTExecutor.execute_code()runs validated code in a restricted namespace with only safe builtins (round,abs,sum,min,max,float,int, etc.)asyncio.wait_for()enforces a hard 5-second timeout preventing infinite loop DoS- Named financial templates (
cagr,roi,gross_margin,percentage_change,ebitda_margin,debt_to_equity) are provided for direct use without LLM code generation - Code extraction supports fenced (
```python) and unfenced formats
Gap: Components (parser, vector store, LLM) were tightly coupled to specific implementations.
Resolution (src/rag_system/components/base.py):
BaseParser,BaseEmbedder,BaseVectorStore,BaseRetriever,BaseReranker,BaseGenerator,BaseEvaluatordefine strict contracts via@abc.abstractmethod- Shared data models (
DocumentElement,RetrievedChunk,GeneratedAnswer) are immutable Pydantic v2 models - All components accept base types — the pipeline orchestrator never imports concrete implementations
- Factory functions (
build_parser(),build_vector_store(),build_reranker(), etc.) map config values to concrete classes InMemoryVectorStoreprovides a zero-dependency alternative for testing and development
Gap: Dense-only retrieval missed exact financial figure matches (specific numbers, tickers, dates).
Resolution (src/rag_system/components/retriever/__init__.py):
HybridRetrievercombines dense vector search + in-memoryBM25Index- Fusion via
_reciprocal_rank_fusion()— no weight tuning required, robust to score scale differences CrossEncoderReranker(local,ms-marco-MiniLM-L-6-v2) andCohereReranker(cloud) provide final relevance scoring- Configurable via
RETRIEVER_CONFIG__STRATEGY=hybridandRERANKER_CONFIG__PROVIDER=cross_encoder
Gap: No automated quality measurement prevented regressions from reaching production.
Resolution (src/rag_system/components/evaluator/__init__.py, .github/workflows/ci.yml):
RagasEvaluatorwraps RAGAS (faithfulness, answer_relevancy, context_precision) with LLM-as-judge numeric accuracy scoringGoldenDatasetRunnerloads JSONL golden samples, runs pipeline, scores each, and producesEvalReport- History tracking in
evals/history.jsonenables regression detection (>5% faithfulness drop fails CI) - GitHub Actions workflow runs eval gate on every PR with
--fail-on-regression
Gap: No distributed tracing, no custom RAG metrics, no cost visibility.
Resolution (src/rag_system/utils/telemetry.py, grafana/dashboards/):
- OpenTelemetry spans:
ingest_pipeline,parse_document,vision_describe,retrieval,llm_generation - Prometheus custom metrics:
rag_query_latency_seconds,rag_hallucination_score,rag_citation_coverage_ratio,rag_query_cost_usd_total,rag_tokens_total,rag_cache_hits_total - Grafana dashboard JSON auto-provisioned with 10 panels covering latency percentiles, cost, token consumption, hallucination score, cache hit rate
Gap: No authentication, no PII protection on ingested content, no tamper-evident audit trail.
Resolution:
APIKeyMiddleware— constant-time SHA-256 key comparison, exempt paths for health probesPIIRedactor— Presidio integration with financial-domain extensions (CUSIP, ISIN, routing numbers)FinancialGuardrails— numeric grounding check (every answer number must appear in context), prompt injection detectionAuditLogger— append-only JSONL with SHA-256 content hash per event, GDPR/CCPA deletion logging
Gap: Single shared vector store with no tenant separation.
Resolution:
- Per-tenant dataset paths in DeepLake (
rag_financial_{tenant_id}) AsyncRateLimitermaintains per-tenant token buckets with global capCostTrackeraccumulates per-tenant, per-model USD costs with monthly quota enforcement- All log records, audit events, and Prometheus labels carry
tenant_id
Gap: System only usable via Jupyter notebook — no programmatic or operational interface.
Resolution:
rag-financialCLI (typer) —ingest,query,evaluate,serve,healthcommands withrichoutput- FastAPI application with
/api/v1/ingest,/api/v1/query,/api/v1/tenants,/healthz,/readyzendpoints - Python SDK (
src/rag_system/sdk) with bothasyncand sync wrappers, YAML config loader
Gap: No deployment artifacts — system only ran locally.
Resolution:
- Multi-stage Dockerfile (builder → non-root runtime, HEALTHCHECK, Prometheus port)
docker-compose.ymlwith Redis, OTel collector, Prometheus, Grafana, Jaeger- Kubernetes base manifests: Deployment, Service, HPA (2–10 replicas), ConfigMap, PVC, ServiceMonitor
- Helm chart with
values.yaml, HPA, PodDisruptionBudget, Prometheus ServiceMonitor - GitHub Actions CI: lint → test → security scan → eval gate → Docker build → Helm lint