thunderstornX/rag-threat-intel
GitHub: thunderstornX/rag-threat-intel
rag-threat-intel:一个主权 RAG 管道,用于漏洞和威胁情报问答。
Stars: 1 | Forks: 0
# rag威胁情报
[](https://doi.org/10.5281/zenodo.20480465)
[](LICENSE)
[](#tests)
[](#real-measured-eval)
[](#stack)

一个**主权 RAG 管道**,它将 NIST NVD CVE 源和公共安全 PDF 文件摄取到 pgvector 中,通过余弦相似度检索,+ MMR 重新排序,并使用 Ollama 提供的 LLM 生成——答案中的每个断言都必须引用检索集中或模型被告知拒绝的 `[doc_N]` 标签。
这个仓库的目的是一个“魔法问答箱”,而是在真实评估集上对三种分块策略进行的**测量比较**,报告了 MRR@5 / MRR@10 和三个轴的忠实度指标。下面的数字来自真实的本地运行,不是虚构的——请参阅 `results/README.md` 以获取复制配方。
## 真实测量评估 (v1.0.0)
语料库:7 个命名的历史 CVE(Log4Shell、Heartbleed、Apache Struts、BlueKeep、Zerologon、Spring4Shell、regreSSHion)+ 30 个最近 CVE 记录,嵌入 `all-minilm`(384-d)。
### 14 个 CVE 回忆查询的 MRR
| 策略 | MRR@5 | MRR@10 | 平均延迟 |
|----------------|-------:|-------:|-------------:|
| fixed_size | 0.768 | 0.780 | 39.9 毫秒 |
| **语义** | **0.821** | **0.833** | 42.2 毫秒 |
| sentence_window | 0.762 | 0.762 | 40.4 毫秒 |
**语义策略比句子窗口策略高约 5 个百分点**。句子窗口表现不佳,因为每个 CVE 都很短(每条记录一个描述);当父文档本身是一段时,±N 邻居上下文购买力很小。
### 忠实度,语义策略,llama3.2:1b,n=12
| 信号 | 值 |
|---------------------------|----------:|
| 平均引用密度 | 0.201 |
| **平均引用有效性**| **1.000** |
| 平均拒绝诚实度 | 0.333 |
| 平均时钟/查询 | 53.4 秒 |
诚实的阅读:1B 模型在我们的严格引用合同下**选择拒绝而不是冒险提出未引用的断言**。12 个查询中有 8 个收到了规范拒绝,即使检索器返回了相关片段。当它参与时,引用有效性是完美的(没有虚构的 `[doc_N]` 标签)。这正是三个轴评分器旨在揭示的权衡——一个数字会隐藏它。
## 堆栈
```
┌──────────────────────────────┐
│ FastAPI /query │
└────────────┬─────────────────┘
│
┌────────────────────────┼─────────────────────────┐
│ │ │
┌────▼────┐ ┌─────────▼──────────┐ ┌──────▼──────┐
│ Ollama │ │ pgvector (HNSW) │ │ Ollama │
│ embed │ │ chunks_fixed_size │ │ llama3.2:3b│
│ nomic-* │ │ chunks_semantic │ │ generate │
│ all-minilm │ chunks_sentence_* │ └─────────────┘
└─────────┘ └────────────────────┘
```
三个 pgvector 表——每个分块策略一个——所以运行时的查询只是“我在哪个表中查找?”HNSW 索引是 pgvector 随带提供的默认余弦索引;我们不调整 `m` / `ef_construction`,因为语料库足够小(几千个 NVD 记录和一些 PDF 文件),默认值占主导地位。
## 快速入门
```
git clone https://github.com/thunderstornX/rag-threat-intel.git
cd rag-threat-intel
cp .env.example .env
# 1. 启动 pgvector + Ollama + FastAPI
docker compose up -d
# 2. 拉取模型(仅第一次)
docker compose exec ollama ollama pull llama3.2:3b
docker compose exec ollama ollama pull nomic-embed-text
docker compose exec ollama ollama pull all-minilm
# 3. 将一些PDF文件放入 corpus/pdfs/ 中(NIST SP 800-53,ATT&CK 白皮书等)
# 然后导入:
docker compose exec api python -m ingest.bootstrap
# 4. 提出一个问题
curl -sS -X POST http://localhost:8000/query \
-H 'content-type: application/json' \
-d '{"question":"What is the CVSS score of CVE-2021-44228?",
"strategy":"semantic", "top_k":10, "top_n":4}'
```
响应是一个包含答案、引用的 `[doc_N]` 标签、检索来源(带相似度分数)和每阶段时间的 JSON 封装。
### 运行评估通过
```
docker compose exec api python -m eval.eval_mrr # MRR@5/10 across strategies
docker compose exec api python -m eval.eval_faithfulness # answer citation density + refusal honesty
```
评估脚本在 `results/` 下写入每个查询的 CSV 文件,并将摘要 JSON 打印到 stdout。50 个问题的测试集位于 `eval/test_queries.json`——它是有意混合的 **CVE 回忆查询**(答案应出现在语料库中)、**PDF 查找查询**(语料库取决于您摄取的内容)和 **硬负例**,包括 `expected_refusal: true` 行,以测试系统是否在应该时诚实地说“我无法回答这个问题”。
## 仓库布局
```
.
├── ingest/
│ ├── document.py # uniform Document shape (text + source + metadata + fingerprint)
│ ├── nvd_fetcher.py # NIST NVD 2.0 API client (paginated, polite-sleep)
│ ├── pdf_loader.py # pypdf-based per-page loader
│ ├── chunker.py # 3 chunking strategies — see below
│ └── bootstrap.py # one-shot: fetch + chunk + embed + write
├── embeddings/
│ ├── embed.py # Ollama /api/embeddings client
│ └── embed_compare.py # nomic-embed-text vs all-minilm side-by-side
├── retrieval/
│ ├── store.py # pgvector + HNSW + cosine; one table per strategy
│ └── reranker.py # Maximal Marginal Relevance (Carbonell-Goldstein)
├── generation/
│ ├── prompts.py # mandatory-citation system prompt + refusal contract
│ └── generator.py # Ollama /api/chat + citation-tag extractor
├── api/
│ └── main.py # FastAPI: /query, /health, /health/ready
├── eval/
│ ├── test_queries.json # 50 queries (CVE recall · PDF lookup · hard negatives · expected refusals)
│ ├── eval_mrr.py # MRR@5/10 per chunking strategy
│ └── eval_faithfulness.py # citation density · validity · refusal honesty
├── tests/ # 40 pytest cases (run in <2s, no Ollama required)
├── docker-compose.yml # pgvector/pgvector:pg16 + ollama + api
├── Dockerfile
├── corpus/pdfs/ # operator-populated PDF corpus
└── paper/ # 3-page IEEE methodology paper
```
## 三种分块策略
仓库的核心实验。所有三个都位于 `ingest/chunker.py` 中,并通过 `chunk_documents(docs, ChunkStrategy.X)` 暴露:
| 策略 | 它做什么 |
|----------------|--------------------------------------------------------------------------------------------------------------------------------|
| `fixed_size` | 字符预算为 ≈512 个标记,≈50 个标记重叠。退回到最近的空白处,这样我们就不会在单词中间分割。 |
| `semantic` | 在标题模式(markdown `#`、NIST 风格 `1.2.3 标题`、全大写短行)和段落之间分割。**块永远不会跨越标题**。短段落仅在**一个部分内**合并到 `max_chars`。 |
| `sentence_window` | 每个句子都是自己的块;块文本包括中心句子加上 ±N 邻居,以便嵌入器看到上下文。 |
目标不是宣布一个赢家——不同的语料库偏爱不同的策略。仓库的评估报告了每个策略的 MRR,论文讨论了 *为什么* 你会选择一个而不是另一个。
## 测试
40 个 pytest 用例。**完整套件运行在 ~1.3 秒**——它们都不需要 Ollama 或 pgvector 运行;它们模拟了线。
```
python -m pytest tests/ -v
```
覆盖率:
- **chunker 不变** — fixed-size 尊重预算 + 重叠,语义分割在 NIST 风格部分 ID 上,永远不会跨越标题,sentence-window 包括 ±N 邻居
- **文档指纹** — 在实例之间稳定,当文本更改时更改
- **NVD 捕获器** — 扁平化提取 CVSS + CWE + 引用,HTTP 错误不会中止摄取,跳过无 ID 的项
- **嵌入器** — 线格式,维度检测,**HTTP 错误永远不会回显响应体**
- **MMR 重新排序器** — 正交余弦 = 0,相同 = 1,当 lambda 偏好多样性时选择不同的对,长度不匹配引发
- **生成器** — 提取引用标签,删除虚构的 `[doc_42]` 标签,检测拒绝句子,**在错误路径中永远不会回显服务器体**
- **评估辅助工具** — 互反排名正确性,忠实度评分器,拒绝诚实度逻辑
## 方法论说明
### 为什么没有 LLM 作为检索的法官
`eval_mrr.py` 使用测试集中的真实 `expected_relevant_source_ids`,而不是 LLM 评分“这是否相关”。原因与记录在兄弟
[`llm-red-team-toolkit`](https://github.com/thunderstornX/llm-red-team-toolkit)
和 [`agentic-osint-agent`](https://github.com/thunderstornX/agentic-osint-agent)
中的相同:
LLM 判决者不可重复,在不同模型检查点之间漂移,并且在标准模糊时膨胀分数。作者标记的真实信息是可审计的。
### 为什么我们不将答案忠实度合并为一个数字
忠实度评估报告了三个正交信号:
1. **引用密度** — 以 `[doc_N]` 标签结束的句子比例
2. **引用有效性** — 指向真实检索文档的引用标签比例(不是虚构的 `[doc_42]`)
3. **拒绝诚实度**——对于标记为 `expected_refusal: true` 的查询,模型实际上拒绝了吗?对于非拒绝查询,它避免了虚假拒绝吗?
将这些合并为一个数字会隐藏权衡。拒绝每个问题的模型在有效性(没有无效标签,因为没有标签)和引用句子密度(同样)上得分为 100%,这显然不是“好的 RAG”。三个数字一起告诉了真实的故事。
## 道德使用
这是一个用于漏洞和威胁情报**阅读**的研究成果——不是用于制作漏洞利用的工具。系统提示明确拒绝有害请求;测试集包含一些那些行(`category: harmful`),评估验证模型拒绝。
如果您找到一种方法使此管道输出它不应该输出的内容,请提出问题——这正是测试集旨在捕获的发现。
## 引用此工作
```
@software{bhutto2026ragthreatintel,
author = {Bhutto, Ali Murtaza},
title = {rag-threat-intel: A sovereign RAG pipeline for vulnerability
and threat-intelligence Q\&A},
year = {2026},
doi = {10.5281/zenodo.20480465},
url = {https://github.com/thunderstornX/rag-threat-intel},
orcid = {0009-0007-2787-943X}
}
```
同一系列中的相关工作:
- [`agentic-osint-agent`](https://github.com/thunderstornX/agentic-osint-agent) — LangGraph ReAct OSINT 调查员(使用相同的评估纪律 / 无 LLM 判决者哲学)
- [`llm-red-team-toolkit`](https://github.com/thunderstornX/llm-red-team-toolkit) — 对 LLM 部署的对抗性探测(此项目的逆:测试模型,而不是使用它们)
- [`sovereign-llm-quickstart`](https://github.com/thunderstornX/sovereign-llm-quickstart) — 此仓库指向的本地 Ollama 堆栈
## 许可证
MIT © 2026 Ali Murtaza Bhutto
```
▓▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓
NVD · PDFs · OSINT · ...
>>>>> retrieve · rerank · generate · cite >>>>>
```
~ AMB · ORCID 0009-0007-2787-943X · v1.0 · 2026 ~
标签:AI风险缓解, Apex, AV绕过, BSD, Chaos, CVE, FastAPI, LLM评估, MRR评估, NVD, Ollama, pgvector, 信息检索, 反取证, 向量数据库, 威胁情报, 安全测试, 安全漏洞, 安全研究工具, 安全研究平台, 安全策略, 安全评估, 安全防护, 开发者工具, 性能比较, 提示词设计, 攻击性安全, 数字签名, 数据挖掘, 文本分析, 机器学习, 漏洞分析, 相似度搜索, 请求拦截, 路径探测, 问答系统