Đồ án 2 — Chương trình Kỹ sư AI
Multimodal Fine-grained RAG-based AI Agent cho domain y tế: nhận query text + ảnh X-quang/MRI/pathology, thực hiện retrieval đa phương thức, fuse evidence, sinh câu trả lời có citation.
| Phase | Mô tả | Trạng thái |
|---|---|---|
| Baseline RAG | BM25 + TF-IDF + extractive generator | ✅ Done |
| Data pipeline | 6 datasets, 69K docs, 49K images, canonical JSONL | ✅ Done |
| Evaluation | Recall@k, MRR, routing accuracy, ablation A/B/C/D | ✅ Done |
| Model wrappers | BioCLIP, BioMedBERT, Qwen2.5-VL, BGE Reranker | ✅ Done |
| Qdrant indexing | Multi-vector indexer, 5-patch image chunking | ✅ Done |
| LangGraph Agent | Decomposer → Retriever → Decision → Generator | ✅ Done |
| Advanced eval | EM/F1, per-dataset metrics, RAGAS fallback, free-form quality proxies | ✅ Done |
| Gradio Demo | Interactive UI với image upload, local validation pending | 🔄 Ready to test |
| Full benchmark | 200 VQA-RAD + 100 MedQA + 50 MIMIC-CXR / cloud larger run | ⏳ Optional next step |
Query (text + image)
│
▼
┌─────────────────────────────────────────────────────┐
│ LAYER 1 — Data & Knowledge Base │
│ MedQA | BioASQ | VQA-RAD | ROCO | MIMIC-CXR | │
│ PathVQA → Canonical JSONL → data/<dataset>/ │
└──────────────────────┬──────────────────────────────┘
│ ingest
┌──────────────────────▼──────────────────────────────┐
│ LAYER 2 — Multimodal Indexing │
│ BioMedBERT (1024-dim) → Qdrant text_chunks │
│ BioCLIP (512-dim) + 5-patch → Qdrant image_patches │
│ Fallback: BM25 + TF-IDF → joblib index │
└──────────────────────┬──────────────────────────────┘
│ query
┌──────────────────────▼──────────────────────────────┐
│ LAYER 3 — LangGraph Agent │
│ decompose_query (VLM / rule-based router) │
│ ├── retrieve_text: Dense + BM25 + BGE rerank │
│ └── retrieve_visual: BioCLIP → ROI crop → score │
│ fuse_and_verify + generate_answer (Qwen2.5-VL) │
└──────────────────────┬──────────────────────────────┘
│ evaluate
┌──────────────────────▼──────────────────────────────┐
│ LAYER 4 — Evaluation │
│ Recall@k | MRR | Routing accuracy | RAGAS │
│ Ablation A/B/C/D | Error analysis │
└─────────────────────────────────────────────────────┘
# Clone và cài package
git clone <repo-url>
cd KS_Project_2
python -m pip install -e .
# Build index từ data đã có
python -m medical_rag build-index --data-dir data --index-dir data/processed/indexes
# Query thử
python -m medical_rag query "pneumonia treatment" --index-dir data/processed/indexes --top-k 5
# Cài full dependencies
python -m pip install -e ".[gpu,qdrant,agent,eval]"
# Start Qdrant
docker run -d -p 6333:6333 -v ${PWD}/qdrant_storage:/qdrant/storage qdrant/qdrant
# Build Qdrant index (cần model downloads lần đầu)
python -m medical_rag build-qdrant-index --data-dir data --limit 500
For the target laptop profile, do not self-host Qwen-VL or build full embeddings locally. Use the split workflow below:
| Runtime | Responsibility |
|---|---|
| Kaggle / Colab | BioMedBERT/BioCLIP embedding, BGE reranking, Qdrant index build, benchmark/ablation |
| Laptop | Gradio/CLI QA app, local smoke tests, baseline experiments |
| OpenRouter | Gemini 2.5 Flash VLM/LLM generation and visual grounding |
| Qdrant | Local/in-memory debug or Qdrant Cloud persistent vector store |
Key commands:
# local diagnostics
python -m medical_rag test-encoders --mock
python -m medical_rag test-qdrant --qdrant-url :memory:
python -m medical_rag test-openrouter
# Kaggle/Colab real indexing
python scripts/colab_workflow.py build-index-resumable --data-dir data --qdrant-url $QDRANT_URL --datasets all --modality both --image-mode full_only --max-minutes 100 --use-cloud-auth
# app on laptop
python demo/app.py --use-agent --use-qdrant
Full runbooks:
# Audit datasets
python -m medical_rag audit-data --data-dir data
# Build index
python -m medical_rag build-index --data-dir data --index-dir data/processed/indexes --limit 500
# Query
python -m medical_rag query "chest xray pneumonia" --index-dir data/processed/indexes --top-k 5
# Evaluate
python -m medical_rag evaluate --eval-file data/eval_cases.json --index-dir data/processed/indexes
# Ablation study
python -m medical_rag ablate --eval-file data/eval_cases.json --output-dir outputs/ablation
# Export static demo
python -m medical_rag export-demo --output-dir demo
from medical_rag.pipeline import MedicalRAGPipeline
from medical_rag.config import RAGConfig
from medical_rag.indexing import load_indexes
config = RAGConfig(index_dir="data/processed/indexes").resolved()
pipeline = MedicalRAGPipeline(config)
answer = pipeline.run("What treats pneumonia?")
print(answer.answer)
from medical_rag.agents.graph import AgenticRAGPipeline
pipeline = AgenticRAGPipeline({
"use_qdrant": True, # requires Qdrant running
"use_vlm_generation": True, # requires GPU or API key
"use_fine_grained_visual": True,
"index_dir": "data/processed/indexes",
})
result = pipeline.run("What does this chest X-ray show?", image_path="xray.jpg")
print(result["answer"])
print(result["citations"])
KS_Project_2/
├── data/ # Datasets (không commit lên git)
│ ├── medqa/ # 12,723 docs — text-only QA
│ ├── bioasq/ # 8,216 docs — biomedical QA
│ ├── vqa_rad/ # 2,244 — visual QA radiology
│ ├── roco/ # 12,415 — medical image captions
│ ├── mimic_cxr/ # 30,633 — chest X-ray reports
│ ├── pathvqa/ # 3,600 — pathology VQA
│ └── processed/ # Legacy processed JSONL + joblib index
│ └── indexes/ # rag_indexes.joblib (baseline index)
├── src/medical_rag/ # Main Python package
│ ├── agents/ # LangGraph agent nodes (Phase 3)
│ ├── models/ # BioCLIP, BioMedBERT, Qwen2.5-VL (Phase 1)
│ ├── ingestion/ # Chunker + Qdrant indexer (Phase 2)
│ ├── retrieval/ # Baseline BM25+TF-IDF retrievers
│ ├── data_tools/ # Dataset canonicalization
│ ├── pipeline.py # Baseline MedicalRAGPipeline
│ ├── config.py # RAGConfig (all settings)
│ ├── schema.py # Pydantic data models
│ └── __main__.py # CLI entrypoint
├── docs/ # Documentation
├── tests/ # Pytest smoke tests
├── scripts/ # Dataset download utilities
├── outputs/ # Evaluation results, ablation JSONs
├── demo/ # Static HTML demo export
└── pyproject.toml # Package config + dependencies
Xem thêm: docs/02-development/folder-structure.md
| Dataset | Records | Modality | Source |
|---|---|---|---|
| MedQA | 12,723 | Text | Local JSONL |
| BioASQ | 8,216 | Text | HuggingFace |
| VQA-RAD | 2,244 | Text + Image | HuggingFace |
| ROCO | 12,415 | Image (caption) | Local 2.5GB subset |
| MIMIC-CXR | 30,633 | Report + X-ray | HuggingFace (credentialed) |
| PathVQA | 3,600 | Pathology image | HuggingFace (1GB subset) |
| Component | Library | Version |
|---|---|---|
| Agent framework | LangGraph | ≥0.2.28 |
| Vector DB | Qdrant | latest |
| Image embedding | BioCLIP | open_clip_torch |
| Text embedding | BioMedBERT-large | transformers |
| VLM | Qwen2.5-VL-7B-Instruct | 4-bit quant |
| Reranker | BGE-reranker-v2-m3 | transformers |
| Evaluation | RAGAS | ≥0.2 |
| Baseline retrieval | rank-bm25 + scikit-learn | — |
Kết quả tổng hợp từ TASK.md, outputs/ablation_full/, và outputs/benchmark/:
| Profile | Mô tả | Recall@5 | MRR@5 | Routing | Image Recall |
|---|---|---|---|---|---|
| A | Text-only TF-IDF | 0.4291 | 0.4054 | 0.5891 | 0.0000 |
| B | Text-only + rerank | 0.4291 | 0.4054 | 0.5891 | 0.0000 |
| C | Image branch + Weighted RRF | 0.8145 | 0.6812 | 0.7127 | 0.9204 |
| D | Full baseline | 0.8073 | 0.6781 | 0.7127 | 0.9204 |
Advanced baseline:
| Metric | Value |
|---|---|
| Recall@5 | 0.8073 |
| MRR@5 | 0.6781 |
| Routing accuracy | 0.7127 |
| Exact Match | 0.0000 |
| Token F1 | 0.0181 |
[!NOTE] Exact Match và Token F1 thấp vì generator hiện trả lời dạng evidence draft/citation-rich, không phải short-form answer. Project đã bổ sung các quality proxy nhẹ như answer non-empty, citation coverage, evidence overlap và groundedness proxy để báo cáo công bằng hơn.
Điểm cần cải thiện tiếp:
Xem CONTRIBUTING.md và docs/02-development/coding-standards.md.
MIT — xem LICENSE.
Papers được tham khảo trong src/support_repo/:
Đồ án 2 — Chương trình Kỹ sư AI
Multimodal Fine-grained RAG-based AI Agent cho domain y tế: nhận query text + ảnh X-quang/MRI/pathology, thực hiện retrieval đa phương thức, fuse evidence, sinh câu trả lời có citation.
| Phase | Mô tả | Trạng thái |
|---|---|---|
| Baseline RAG | BM25 + TF-IDF + extractive generator | ✅ Done |
| Data pipeline | 6 datasets, 69K docs, 49K images, canonical JSONL | ✅ Done |
| Evaluation | Recall@k, MRR, routing accuracy, ablation A/B/C/D | ✅ Done |
| Model wrappers | BioCLIP, BioMedBERT, Qwen2.5-VL, BGE Reranker | ✅ Done |
| Qdrant indexing | Multi-vector indexer, 5-patch image chunking | ✅ Done |
| LangGraph Agent | Decomposer → Retriever → Decision → Generator | ✅ Done |
| Advanced eval | EM/F1, per-dataset metrics, RAGAS fallback, free-form quality proxies | ✅ Done |
| Gradio Demo | Interactive UI với image upload, local validation pending | 🔄 Ready to test |
| Full benchmark | 200 VQA-RAD + 100 MedQA + 50 MIMIC-CXR / cloud larger run | ⏳ Optional next step |
Query (text + image)
│
▼
┌─────────────────────────────────────────────────────┐
│ LAYER 1 — Data & Knowledge Base │
│ MedQA | BioASQ | VQA-RAD | ROCO | MIMIC-CXR | │
│ PathVQA → Canonical JSONL → data/<dataset>/ │
└──────────────────────┬──────────────────────────────┘
│ ingest
┌──────────────────────▼──────────────────────────────┐
│ LAYER 2 — Multimodal Indexing │
│ BioMedBERT (1024-dim) → Qdrant text_chunks │
│ BioCLIP (512-dim) + 5-patch → Qdrant image_patches │
│ Fallback: BM25 + TF-IDF → joblib index │
└──────────────────────┬──────────────────────────────┘
│ query
┌──────────────────────▼──────────────────────────────┐
│ LAYER 3 — LangGraph Agent │
│ decompose_query (VLM / rule-based router) │
│ ├── retrieve_text: Dense + BM25 + BGE rerank │
│ └── retrieve_visual: BioCLIP → ROI crop → score │
│ fuse_and_verify + generate_answer (Qwen2.5-VL) │
└──────────────────────┬──────────────────────────────┘
│ evaluate
┌──────────────────────▼──────────────────────────────┐
│ LAYER 4 — Evaluation │
│ Recall@k | MRR | Routing accuracy | RAGAS │
│ Ablation A/B/C/D | Error analysis │
└─────────────────────────────────────────────────────┘
# Clone và cài package
git clone <repo-url>
cd KS_Project_2
python -m pip install -e .
# Build index từ data đã có
python -m medical_rag build-index --data-dir data --index-dir data/processed/indexes
# Query thử
python -m medical_rag query "pneumonia treatment" --index-dir data/processed/indexes --top-k 5
# Cài full dependencies
python -m pip install -e ".[gpu,qdrant,agent,eval]"
# Start Qdrant
docker run -d -p 6333:6333 -v ${PWD}/qdrant_storage:/qdrant/storage qdrant/qdrant
# Build Qdrant index (cần model downloads lần đầu)
python -m medical_rag build-qdrant-index --data-dir data --limit 500
For the target laptop profile, do not self-host Qwen-VL or build full embeddings locally. Use the split workflow below:
| Runtime | Responsibility |
|---|---|
| Kaggle / Colab | BioMedBERT/BioCLIP embedding, BGE reranking, Qdrant index build, benchmark/ablation |
| Laptop | Gradio/CLI QA app, local smoke tests, baseline experiments |
| OpenRouter | Gemini 2.5 Flash VLM/LLM generation and visual grounding |
| Qdrant | Local/in-memory debug or Qdrant Cloud persistent vector store |
Key commands:
# local diagnostics
python -m medical_rag test-encoders --mock
python -m medical_rag test-qdrant --qdrant-url :memory:
python -m medical_rag test-openrouter
# Kaggle/Colab real indexing
python scripts/colab_workflow.py build-index-resumable --data-dir data --qdrant-url $QDRANT_URL --datasets all --modality both --image-mode full_only --max-minutes 100 --use-cloud-auth
# app on laptop
python demo/app.py --use-agent --use-qdrant
Full runbooks:
# Audit datasets
python -m medical_rag audit-data --data-dir data
# Build index
python -m medical_rag build-index --data-dir data --index-dir data/processed/indexes --limit 500
# Query
python -m medical_rag query "chest xray pneumonia" --index-dir data/processed/indexes --top-k 5
# Evaluate
python -m medical_rag evaluate --eval-file data/eval_cases.json --index-dir data/processed/indexes
# Ablation study
python -m medical_rag ablate --eval-file data/eval_cases.json --output-dir outputs/ablation
# Export static demo
python -m medical_rag export-demo --output-dir demo
from medical_rag.pipeline import MedicalRAGPipeline
from medical_rag.config import RAGConfig
from medical_rag.indexing import load_indexes
config = RAGConfig(index_dir="data/processed/indexes").resolved()
pipeline = MedicalRAGPipeline(config)
answer = pipeline.run("What treats pneumonia?")
print(answer.answer)
from medical_rag.agents.graph import AgenticRAGPipeline
pipeline = AgenticRAGPipeline({
"use_qdrant": True, # requires Qdrant running
"use_vlm_generation": True, # requires GPU or API key
"use_fine_grained_visual": True,
"index_dir": "data/processed/indexes",
})
result = pipeline.run("What does this chest X-ray show?", image_path="xray.jpg")
print(result["answer"])
print(result["citations"])
KS_Project_2/
├── data/ # Datasets (không commit lên git)
│ ├── medqa/ # 12,723 docs — text-only QA
│ ├── bioasq/ # 8,216 docs — biomedical QA
│ ├── vqa_rad/ # 2,244 — visual QA radiology
│ ├── roco/ # 12,415 — medical image captions
│ ├── mimic_cxr/ # 30,633 — chest X-ray reports
│ ├── pathvqa/ # 3,600 — pathology VQA
│ └── processed/ # Legacy processed JSONL + joblib index
│ └── indexes/ # rag_indexes.joblib (baseline index)
├── src/medical_rag/ # Main Python package
│ ├── agents/ # LangGraph agent nodes (Phase 3)
│ ├── models/ # BioCLIP, BioMedBERT, Qwen2.5-VL (Phase 1)
│ ├── ingestion/ # Chunker + Qdrant indexer (Phase 2)
│ ├── retrieval/ # Baseline BM25+TF-IDF retrievers
│ ├── data_tools/ # Dataset canonicalization
│ ├── pipeline.py # Baseline MedicalRAGPipeline
│ ├── config.py # RAGConfig (all settings)
│ ├── schema.py # Pydantic data models
│ └── __main__.py # CLI entrypoint
├── docs/ # Documentation
├── tests/ # Pytest smoke tests
├── scripts/ # Dataset download utilities
├── outputs/ # Evaluation results, ablation JSONs
├── demo/ # Static HTML demo export
└── pyproject.toml # Package config + dependencies
Xem thêm: docs/02-development/folder-structure.md
| Dataset | Records | Modality | Source |
|---|---|---|---|
| MedQA | 12,723 | Text | Local JSONL |
| BioASQ | 8,216 | Text | HuggingFace |
| VQA-RAD | 2,244 | Text + Image | HuggingFace |
| ROCO | 12,415 | Image (caption) | Local 2.5GB subset |
| MIMIC-CXR | 30,633 | Report + X-ray | HuggingFace (credentialed) |
| PathVQA | 3,600 | Pathology image | HuggingFace (1GB subset) |
| Component | Library | Version |
|---|---|---|
| Agent framework | LangGraph | ≥0.2.28 |
| Vector DB | Qdrant | latest |
| Image embedding | BioCLIP | open_clip_torch |
| Text embedding | BioMedBERT-large | transformers |
| VLM | Qwen2.5-VL-7B-Instruct | 4-bit quant |
| Reranker | BGE-reranker-v2-m3 | transformers |
| Evaluation | RAGAS | ≥0.2 |
| Baseline retrieval | rank-bm25 + scikit-learn | — |
Kết quả tổng hợp từ TASK.md, outputs/ablation_full/, và outputs/benchmark/:
| Profile | Mô tả | Recall@5 | MRR@5 | Routing | Image Recall |
|---|---|---|---|---|---|
| A | Text-only TF-IDF | 0.4291 | 0.4054 | 0.5891 | 0.0000 |
| B | Text-only + rerank | 0.4291 | 0.4054 | 0.5891 | 0.0000 |
| C | Image branch + Weighted RRF | 0.8145 | 0.6812 | 0.7127 | 0.9204 |
| D | Full baseline | 0.8073 | 0.6781 | 0.7127 | 0.9204 |
Advanced baseline:
| Metric | Value |
|---|---|
| Recall@5 | 0.8073 |
| MRR@5 | 0.6781 |
| Routing accuracy | 0.7127 |
| Exact Match | 0.0000 |
| Token F1 | 0.0181 |
[!NOTE] Exact Match và Token F1 thấp vì generator hiện trả lời dạng evidence draft/citation-rich, không phải short-form answer. Project đã bổ sung các quality proxy nhẹ như answer non-empty, citation coverage, evidence overlap và groundedness proxy để báo cáo công bằng hơn.
Điểm cần cải thiện tiếp:
Xem CONTRIBUTING.md và docs/02-development/coding-standards.md.
MIT — xem LICENSE.
Papers được tham khảo trong src/support_repo/: