# luxinyue0910/speclens architecture

> Architecture diagram of the GitHub repository luxinyue0910/speclens, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-09-28). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/luxinyue0910/speclens
- Repository: https://github.com/luxinyue0910/speclens

## Overview

SpecLens is a local-first, evaluation-driven RAG service. Its main workflow ingests engineering documents and linked images into searchable chunks, routes questions to keyword, vector, or hybrid retrieval, then produces evidence-grounded answers and citations. Evaluation compares route accuracy, retrieval quality, latency, and cost. The README documents ingestion and evaluation capabilities beyond the sampled source; those branches are shown at their documented subsystem paths, with uncertain internal wiring omitted.

Main entry points: [`rag/pipeline.py`](https://github.com/luxinyue0910/speclens/blob/main/rag/pipeline.py) (RAG pipeline), [`rag/ingestion/build_indexes.py`](https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/build_indexes.py) (Index builder), [`rag/evaluation/run_experiment.py`](https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/run_experiment.py) (Experiment runner), [`apps/api/main.py`](https://github.com/luxinyue0910/speclens/blob/main/apps/api/main.py) (FastAPI app)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_api["API service"]
  node_api_service["FastAPI app<br/>[main.py]"]
  node_ask_route["Ask endpoint<br/>[ask.py]"]
  node_ingest_route["Ingest endpoint<br/>[ingest.py]"]
  node_eval_route["Eval endpoint<br/>[eval.py]"]
end

subgraph group_ingestion["Evidence ingestion"]
  node_loader["Document loader<br/>[loader.py]"]
  node_chunker["Text chunking<br/>[chunker.py]"]
  node_image_chunks["Image evidence<br/>[image_assets.py]"]
  node_ocr["OCR extraction<br/>[ocr.py]"]
  node_vision["Image summaries<br/>[vision_summary.py]"]
  node_index_builder["Index builder<br/>[build_indexes.py]"]
  node_chunk_store["Chunk data"]
end

subgraph group_retrieval["Retrieval and answers"]
  node_pipeline["RAG pipeline<br/>[pipeline.py]"]
  node_bm25["Keyword retrieval"]
  node_router["Query routing<br/>[query_router.py]"]
  node_vector["Vector retrieval"]
  node_hybrid["Hybrid fusion"]
  node_reranker["Optional reranking<br/>[reranker.py]"]
  node_generator["Answer generation"]
  node_citations["Grounded citations"]
end

subgraph group_evaluation["Evaluation"]
  node_eval_runner["Experiment runner<br/>[run_experiment.py]"]
  node_eval_metrics["Evaluation metrics<br/>[metrics.py]"]
  node_eval_judge["LLM judge<br/>[llm_judge.py]"]
  node_eval_report["Report generation"]
  node_questions["Labeled questions<br/>[questions.jsonl]"]
  node_reports["Experiment reports"]
end

node_user(("Questioner"))
node_corpus["Engineering corpus"]
node_assets["Linked image assets"]
node_qdrant[("Vector index")]
node_ollama["Ollama model"]

node_user -->|"asks"| node_api_service
node_api_service -->|"dispatches"| node_ask_route
node_api_service -->|"dispatches"| node_ingest_route
node_api_service -->|"dispatches"| node_eval_route
node_ask_route -->|"calls"| node_pipeline
node_ingest_route -->|"calls"| node_pipeline
node_eval_route -->|"runs"| node_eval_runner
node_pipeline -->|"routes"| node_router
node_pipeline -->|"selects"| node_bm25
node_pipeline -->|"selects"| node_vector
node_pipeline -->|"selects"| node_hybrid
node_pipeline -->|"reranks"| node_reranker
node_pipeline -->|"generates"| node_generator
node_generator -.->|"requests"| node_ollama
node_generator -->|"builds citations"| node_citations
node_hybrid -->|"retrieves"| node_bm25
node_hybrid -->|"retrieves"| node_vector
node_vector -->|"queries"| node_qdrant
node_bm25 -->|"loads chunks"| node_chunk_store
node_pipeline -->|"builds indexes"| node_index_builder
node_index_builder -->|"loads"| node_loader
node_loader -->|"reads"| node_corpus
node_index_builder -->|"chunks text"| node_chunker
node_index_builder -->|"extracts image evidence"| node_image_chunks
node_image_chunks -->|"resolves assets"| node_assets
node_image_chunks -->|"extracts text"| node_ocr
node_image_chunks -->|"summarizes"| node_vision
node_index_builder -->|"writes chunks"| node_chunk_store
node_index_builder -->|"writes vectors"| node_qdrant
node_eval_runner -->|"loads questions"| node_questions
node_eval_runner -->|"benchmarks"| node_pipeline
node_eval_runner -->|"measures"| node_eval_metrics
node_eval_runner -.->|"judges"| node_eval_judge
node_eval_runner -->|"generates"| node_eval_report
node_eval_report -->|"writes"| node_reports

click node_corpus "https://github.com/luxinyue0910/speclens/tree/main/data/docs"
click node_assets "https://github.com/luxinyue0910/speclens/tree/main/data/assets"
click node_api_service "https://github.com/luxinyue0910/speclens/blob/main/apps/api/main.py"
click node_ask_route "https://github.com/luxinyue0910/speclens/blob/main/apps/api/routes/ask.py"
click node_ingest_route "https://github.com/luxinyue0910/speclens/blob/main/apps/api/routes/ingest.py"
click node_eval_route "https://github.com/luxinyue0910/speclens/blob/main/apps/api/routes/eval.py"
click node_pipeline "https://github.com/luxinyue0910/speclens/blob/main/rag/pipeline.py"
click node_loader "https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/loader.py"
click node_chunker "https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/chunker.py"
click node_image_chunks "https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/image_assets.py"
click node_ocr "https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/ocr.py"
click node_vision "https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/vision_summary.py"
click node_index_builder "https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/build_indexes.py"
click node_chunk_store "https://github.com/luxinyue0910/speclens/tree/main/data/indexes"
click node_bm25 "https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/keyword_retriever.py"
click node_router "https://github.com/luxinyue0910/speclens/blob/main/rag/routing/query_router.py"
click node_vector "https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/vector_retriever.py"
click node_hybrid "https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/hybrid_retriever.py"
click node_reranker "https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/reranker.py"
click node_generator "https://github.com/luxinyue0910/speclens/blob/main/rag/generation/answer_generator.py"
click node_citations "https://github.com/luxinyue0910/speclens/blob/main/rag/generation/citation_builder.py"
click node_eval_runner "https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/run_experiment.py"
click node_eval_metrics "https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/metrics.py"
click node_eval_judge "https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/llm_judge.py"
click node_eval_report "https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/report_generator.py"
click node_questions "https://github.com/luxinyue0910/speclens/blob/main/data/eval/questions.jsonl"
click node_reports "https://github.com/luxinyue0910/speclens/tree/main/reports/experiments"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_api_service,node_ask_route,node_ingest_route,node_eval_route toneBlue
class node_loader,node_chunker,node_image_chunks,node_ocr,node_vision,node_index_builder,node_chunk_store,node_qdrant toneAmber
class node_pipeline,node_bm25,node_router,node_vector,node_hybrid,node_reranker,node_generator,node_citations toneMint
class node_eval_runner,node_eval_metrics,node_eval_judge,node_eval_report,node_questions,node_reports toneRose
class node_user,node_ollama toneIndigo
class node_corpus,node_assets toneTeal
```

## Components

### API service

- [FastAPI app](https://github.com/luxinyue0910/speclens/blob/main/apps/api/main.py): `apps/api/main.py`
- [Ask endpoint](https://github.com/luxinyue0910/speclens/blob/main/apps/api/routes/ask.py): `apps/api/routes/ask.py`
- [Ingest endpoint](https://github.com/luxinyue0910/speclens/blob/main/apps/api/routes/ingest.py): `apps/api/routes/ingest.py`
- [Eval endpoint](https://github.com/luxinyue0910/speclens/blob/main/apps/api/routes/eval.py): `apps/api/routes/eval.py`

### Evidence ingestion

- [Document loader](https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/loader.py): `rag/ingestion/loader.py`
- [Text chunking](https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/chunker.py): `rag/ingestion/chunker.py`
- [Image evidence](https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/image_assets.py): `rag/ingestion/image_assets.py`
- [OCR extraction](https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/ocr.py): `rag/ingestion/ocr.py`
- [Image summaries](https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/vision_summary.py): `rag/ingestion/vision_summary.py`
- [Index builder](https://github.com/luxinyue0910/speclens/blob/main/rag/ingestion/build_indexes.py): `rag/ingestion/build_indexes.py`
- [Chunk data](https://github.com/luxinyue0910/speclens/tree/main/data/indexes): `data/indexes`

### Retrieval and answers

- [RAG pipeline](https://github.com/luxinyue0910/speclens/blob/main/rag/pipeline.py): `rag/pipeline.py`
- [Keyword retrieval](https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/keyword_retriever.py): `rag/retrieval/keyword_retriever.py`
- [Query routing](https://github.com/luxinyue0910/speclens/blob/main/rag/routing/query_router.py): `rag/routing/query_router.py`
- [Vector retrieval](https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/vector_retriever.py): `rag/retrieval/vector_retriever.py`
- [Hybrid fusion](https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/hybrid_retriever.py): `rag/retrieval/hybrid_retriever.py`
- [Optional reranking](https://github.com/luxinyue0910/speclens/blob/main/rag/retrieval/reranker.py): `rag/retrieval/reranker.py`
- [Answer generation](https://github.com/luxinyue0910/speclens/blob/main/rag/generation/answer_generator.py): `rag/generation/answer_generator.py`
- [Grounded citations](https://github.com/luxinyue0910/speclens/blob/main/rag/generation/citation_builder.py): `rag/generation/citation_builder.py`

### Evaluation

- [Experiment runner](https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/run_experiment.py): `rag/evaluation/run_experiment.py`
- [Evaluation metrics](https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/metrics.py): `rag/evaluation/metrics.py`
- [LLM judge](https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/llm_judge.py): `rag/evaluation/llm_judge.py`
- [Report generation](https://github.com/luxinyue0910/speclens/blob/main/rag/evaluation/report_generator.py): `rag/evaluation/report_generator.py`
- [Labeled questions](https://github.com/luxinyue0910/speclens/blob/main/data/eval/questions.jsonl): `data/eval/questions.jsonl`
- [Experiment reports](https://github.com/luxinyue0910/speclens/tree/main/reports/experiments): `reports/experiments`

### Other components

- **Questioner**
- [Engineering corpus](https://github.com/luxinyue0910/speclens/tree/main/data/docs): `data/docs`
- [Linked image assets](https://github.com/luxinyue0910/speclens/tree/main/data/assets): `data/assets`
- **Vector index**
- **Ollama model**

## Connections

- Questioner → FastAPI app: asks
- FastAPI app → Ask endpoint: dispatches
- FastAPI app → Ingest endpoint: dispatches
- FastAPI app → Eval endpoint: dispatches
- Ask endpoint → RAG pipeline: calls
- Ingest endpoint → RAG pipeline: calls
- Eval endpoint → Experiment runner: runs
- RAG pipeline → Query routing: routes
- RAG pipeline → Keyword retrieval: selects
- RAG pipeline → Vector retrieval: selects
- RAG pipeline → Hybrid fusion: selects
- RAG pipeline → Optional reranking: reranks
- RAG pipeline → Answer generation: generates
- Answer generation → Ollama model: requests
- Answer generation → Grounded citations: builds citations
- Hybrid fusion → Keyword retrieval: retrieves
- Hybrid fusion → Vector retrieval: retrieves
- Vector retrieval → Vector index: queries
- Keyword retrieval → Chunk data: loads chunks
- RAG pipeline → Index builder: builds indexes
- Index builder → Document loader: loads
- Document loader → Engineering corpus: reads
- Index builder → Text chunking: chunks text
- Index builder → Image evidence: extracts image evidence
- Image evidence → Linked image assets: resolves assets
- Image evidence → OCR extraction: extracts text
- Image evidence → Image summaries: summarizes
- Index builder → Chunk data: writes chunks
- Index builder → Vector index: writes vectors
- Experiment runner → Labeled questions: loads questions
- Experiment runner → RAG pipeline: benchmarks
- Experiment runner → Evaluation metrics: measures
- Experiment runner → LLM judge: judges
- Experiment runner → Report generation: generates
- Report generation → Experiment reports: writes
