Anila-Ijaz/InsightRAG
GitHub: Anila-Ijaz/InsightRAG
面向 SEC 10-K 财务文件的生产级 RAG 系统,集混合检索、微调重排序、安全护栏与自动化评估于一体。
Stars: 1 | Forks: 0
# InsightRAG
**专为 SEC 10-K 财务文件打造的生产级检索增强生成系统。**
[](https://github.com/Anila-Ijaz/insightrag/actions)
[](https://www.python.org/)
[](https://fastapi.tiangolo.com/)
[](https://docs.pydantic.dev/)
[](https://qdrant.tech/)
[](https://platform.openai.com/)
[](https://streamlit.io/)
[](#是什么让它与典型的-rag-demo-不同)
[](https://www.docker.com/)
[](http://63.182.64.177:8501)
[](LICENSE)
### 🔗 在线演示:**[InsightRAG 聊天界面](http://63.182.64.177:8501)** · [API 文档](http://63.182.64.177:8000/docs)
InsightRAG 能够以自然语言回答有关 SEC EDGAR 10-K 文件语料库的问题,并提供带引用的有据可依的回答。本项目的构建旨在展示那些将入门教程与生产级 RAG 系统区分开来的工程决策。
它附带了一个 **Streamlit 聊天界面**,并支持在两种配置下运行:
| 配置 | 展示内容 | 资源占用 | 用途 |
|---|---|---|---|
| **完整版** (`docker-compose.yml`) | 本地 BGE embeddings + cross-encoder 重排序器,完整的可观测性技术栈 | ~5 GB 镜像,6–8 GB 内存 | 展示完整的检索工程,在本地运行 |
| **轻量版** (`docker-compose.lite.yml`) | OpenAI embeddings,关闭重排序器,仅包含 `api + qdrant + UI` | ~508 MB 镜像,<1 GB 内存 | 云平台免费层部署 + 在线演示 |
这两种配置共享一套代码库;embedding 提供商和重排序器通过环境变量 (`EMBEDDING_PROVIDER`, `ENABLE_RERANKER`) 进行切换。轻量版配置正是部署到 AWS 免费层的方式。
## 是什么让它与典型的 RAG demo 不同
大多数公开的 RAG 项目仅停留在“embedding → 存储 → 检索 → 生成”的阶段。而真正有价值的工程设计远不止于此。本仓库展示了:
| 关注点 | 朴素方案 | 本仓库 |
|---|---|---|
| **检索** | 仅依赖向量相似度 | 混合检索 (dense BGE + BM25) 结合 RRF 融合 |
| **重排序** | 无 | 在合成问答 + 困难负样本上微调的 cross-encoder (BGE-reranker) |
| **分块** | 固定大小的字符分割 | 递归、感知 token 且保留结构的分块 (10-K 章节元数据) |
| **Prompt 安全** | 盲目拼接并祈祷不出错 | 多层防护:长度限制、注入模式检测、PII 脱敏、输出引用验证 |
| **评估** | “看起来还不错” | 在夜间 CI 中运行 RAGAS + 针对 4 种配置的检索基准测试 (MRR/nDCG/Recall) |
| **可观测性** | print 语句 | 结构化 JSON 日志 (loguru)、Prometheus 指标、Langfuse 追踪 |
| **部署** | 单个 `python app.py` | 多阶段 Docker、compose 技术栈、GitHub Actions CI/CD、GHCR 镜像仓库 |
## 架构
```
┌────────────────────┐
│ SEC EDGAR (10-K) │
└─────────┬──────────┘
│ download
▼
┌─────────────────────────────────────────────────────────────────────┐
│ INGESTION PIPELINE │
│ Parser (SGML→HTML→sections) → Semantic Chunker (token-aware) → │
│ Embedder (BGE) │
└────────────────┬────────────────────────────────┬───────────────────┘
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Qdrant │ │ BM25 index │
│ (dense) │ │ (sparse) │
└───────┬───────┘ └────────┬────────┘
│ │
└────────────┬────────────────────┘
▼
┌──────────────────────┐
│ Hybrid Retriever │ ← Reciprocal Rank Fusion
│ (top-20 candidates) │
└──────────┬───────────┘
▼
┌──────────────────────┐
│ Cross-Encoder │ ← Fine-tuned on SEC corpus
│ Reranker (top-5) │
└──────────┬───────────┘
▼
┌──────────────────────┐
│ LLM Generation │ ← OpenAI/Anthropic, streamable
│ with citation guard │
└──────────┬───────────┘
▼
FastAPI (SSE)
```
## 技术栈
**核心:** Python 3.11、FastAPI (异步)、Pydantic v2
**前端:** Streamlit 聊天界面 (引用 + 实时延迟面板)
**Embeddings:** 本地 BGE (sentence-transformers) **或** OpenAI API — 提供商可切换
**检索:** sentence-transformers (BGE)、Qdrant、rank-bm25
**重排序器:** BGE-reranker-base (通过 `training/train_reranker.py` 微调)
**生成:** OpenAI / Anthropic SDK (对提供商进行抽象隔离)
**存储:** Postgres (元数据)、Redis (缓存)、S3 (原始文档)
**异步任务:** Celery
**护栏:** Presidio (PII)、正则表达式模式匹配 (注入)、输出引用验证
**可观测性:** loguru (结构化日志)、prometheus-client、OpenTelemetry、Langfuse
**评估:** RAGAS、自定义检索基准 (MRR/nDCG/Recall@k)
**基础设施:** Docker (多阶段)、docker-compose、GitHub Actions、GHCR
**测试:** pytest、pytest-asyncio、ruff、mypy
## 快速开始
### 轻量版配置 (推荐 — 聊天界面,无需 GPU,~1 GB 内存)
```
git clone https://github.com/Anila-Ijaz/insightrag
cd insightrag
cp .env.lite.example .env # then set OPENAI_API_KEY in .env
docker compose -f docker-compose.lite.yml up -d --build
```
然后打开 **聊天界面** 并开始提问:
- **聊天界面:** http://127.0.0.1:8501 *(如果你的 Docker 存在 IPv6 兼容问题,请使用 `127.0.0.1`,而不是 `localhost`)*
- **API 文档:** http://127.0.0.1:8000/docs
从界面侧边栏或通过 API 导入文件:
```
# 从 SEC EDGAR 索引 Apple 最新的 10-K
curl -X POST http://127.0.0.1:8000/v1/ingest \
-H "content-type: application/json" \
-d '{"ticker":"AAPL","limit":1}'
# 提出问题
curl -X POST http://127.0.0.1:8000/v1/query \
-H "content-type: application/json" \
-d '{"question":"What were Apple total net sales?","ticker":"AAPL","top_k":5}'
```
### 完整版配置 (本地 BGE + cross-encoder 重排序器 + 可观测性)
```
cp .env.example .env # set OPENAI_API_KEY
make up # api + qdrant + postgres + redis + prometheus + grafana
make ingest TICKER=AAPL
```
## 评估结果
### 观察到的延迟 — 轻量版配置,gpt-4o-mini
在轻量级技术栈 (OpenAI embeddings,关闭重排序器)、仅索引单个 AAPL 10-K 文件 (110 个 chunks) 的情况下,测得的端到端数据:
| 阶段 | 观察结果 |
|---|---:|
| 检索 (混合检索,OpenAI embed + Qdrant + BM25) | ~1.1–2.3 秒 |
| 重排序 | 0 毫秒 *(在轻量版中已禁用)* |
| 生成 (gpt-4o-mini) | ~1.5–3.3 秒 |
| **总计** | **~3.8–4.4 秒** |
### 检索基准测试
语料库:AAPL + MSFT + GOOGL 10-K 文件 (466 个 chunks)。测试集:由 `scripts/generate_retrieval_testset.py` 生成的 60 个合成 `(查询 → 相关 chunk)` 标签,通过 `evals/benchmark.py` 在 k=10 时进行评分。
轻量版配置,因此“Dense” = OpenAI `text-embedding-3-small` (非 BGE)。
| 配置 | MRR@10 | Recall@10 | nDCG@10 |
|---|---:|---:|---:|
| Dense (OpenAI text-embedding-3-small) | 0.689 | 0.883 | 0.735 |
| Sparse (BM25) | **0.762** | **0.967** | **0.811** |
| Hybrid (RRF) | 0.618 | 0.917 | 0.693 |
| Hybrid + 微调重排序器 | _不适用 (完整技术栈)_ | — | — |
### 端到端 RAG 质量 (RAGAS)
`evals/run_ragas.py` 针对 8 个问题 (按 ticker 过滤)、使用 gpt-4o-mini,并基于完整的检索上下文进行评判:
| 指标 | 得分 |
|---|---:|
| Faithfulness | 0.77 |
| Answer Relevancy | 0.72 |
| Context Precision | _需要标注的参考答案_ |
| Context Recall | _需要标注的参考答案_ |
## 设计决策 (及其原因)
### 为什么使用混合检索,而不是纯粹的向量搜索?
Dense embeddings 会漏掉财务文件中至关重要的精确匹配信号 —— 如特定金额、日期、ticker 代码和产品代号。BM25 则能捕捉到这些。单独使用任何一种都会产生明显且相互背离的失败模式;通过 RRF 结合起来,它们可以互补盲区。 (值得注意的是,在上面的合成基准测试中,BM25 的表现*优于*dense 和 hybrid —— 因为 LLM 生成的问题复用了源 chunk 的确切措辞。这很好地提醒了我们,“混合检索永远是赢家”只是一个神话:正确的融合方式取决于你的查询分布,这正是本仓库同时内置两种排序器和消融测试工具的原因。)
### 为什么选择 Reciprocal Rank Fusion 而不是加权分数融合?
RRF 无需参数 (无需针对每个语料库调整 `alpha`),对排序器之间分数分布的差异具有鲁棒性,并且在已发布的基准测试中始终表现强劲 (Cormack et al., 2009)。加权融合则主要用于消融研究而实现。
### 为什么要微调重排序器?
基础的 BGE-reranker 是在 MS-MARCO 上训练的。而财务文件拥有独特的术语 —— 基于你自己的语料库生成的合成问答对进行微调,可以显著提升领域内查询的 nDCG@10。从现有检索器中挖掘出的困难负样本则让整个流程闭环。
### 为什么要设置独立的输出护栏?
当仅提供了 5 个 chunks 时,LLM 偶尔会凭空捏造诸如 `[99]` 这样的引用。输出护栏会在响应离开 API 之前剔除无效的索引。这是一种低成本、高鲁棒性的深度防御手段。
### 为什么使用带 SSE 的流式传输,而不是 WebSockets?
SSE 原生兼容 HTTP (无需特殊配置即可穿透 CDN、负载均衡器和代理),其单向特性非常契合我们的用例,并且在任何客户端都能轻松调用 (`fetch` 结合 `EventSource`)。使用 WebSockets 反而会显得过于复杂。
### 为什么要对 LLM 进行提供商抽象?
生产系统常常因为供应商锁定和提供商宕机而蒙受损失。`LLMClient` ABC 允许你通过一个环境变量从 OpenAI 切换到 Anthropic,并使得未来添加自托管的 Llama 后备方案变得轻而易举。
## 项目结构
```
insightrag/
├── src/insightrag/
│ ├── ingestion/ # SEC parser, semantic chunker, embedder
│ ├── retrieval/ # Qdrant store, BM25 index, hybrid retriever, reranker
│ ├── generation/ # LLM client (OpenAI/Anthropic), prompts, RAG chain
│ ├── guardrails/ # Input (injection, PII) + output (citation validation)
│ ├── api/ # FastAPI app, schemas, dependencies
│ ├── observability/ # Logging, Prometheus metrics
│ └── config.py # pydantic-settings (provider switches)
├── frontend/ # Streamlit chat UI (app.py + Dockerfile)
├── training/ # Reranker fine-tuning (synthetic Q&A + hard negatives)
├── evals/ # RAGAS runner + retrieval benchmark + test set
├── tests/ # pytest suite (chunker, retrieval, guardrails, API)
├── infra/ # Prometheus config, k8s manifests, terraform
├── .github/workflows/ # ci.yml (lint/test/build), eval.yml (nightly RAGAS)
├── Dockerfile # Full image (local ML models)
├── Dockerfile.lite # Lite image (~508 MB, OpenAI embeddings, no torch)
├── docker-compose.yml # Full: api + qdrant + postgres + redis + prom + grafana
├── docker-compose.lite.yml # Lite: api + qdrant + Streamlit UI
├── requirements-lite.txt # Lite runtime deps (subset of pyproject)
├── pyproject.toml # Dependencies, ruff/mypy/pytest config
└── Makefile # install / dev / test / eval / up / down / ingest
```
## 状态
- ✅ **在 AWS 上运行** — EC2 (法兰克福)、Elastic IP、API 密钥认证
- ✅ **Streamlit 聊天界面** — 引用 + 实时延迟面板
- ✅ **两种运行配置** — 完整版 (本地 BGE + cross-encoder 重排序器) 和轻量版 (OpenAI,可部署在免费层)
- ✅ **已完成基准测试** — 检索 (MRR/nDCG/Recall) + RAGAS,以及上文展示的真实数据
- ✅ **CI** — ruff 代码检查/格式化,25 个通过的测试,容器构建 → GHCR
## 许可证
MIT — 详见 [LICENSE](LICENSE)。
标签:AV绕过, Docker, FastAPI, Kubernetes, RAG系统, 人工智能, 向量检索, 安全规则引擎, 安全防御评估, 搜索引擎查询, 测试用例, 用户模式Hook绕过, 自定义请求头, 请求拦截, 逆向工具, 金融数据