基于 LangChain 0.3.27 + Milvus 2.6.x + LangSmith + langgraph 0.6.6 的专业医疗领域RAG(检索增强生成)系统,支持多向量混合检索和智能问答。 使用约定数据格式,可无缝迁移到其他领域,例如:法学、金融
- 专业医疗领域优化:支持领域稀疏向量计算,可直接通过配置完成领域词表管理;也可以使用原生的Milvus进行稀疏向量管理
- 多向量混合检索:稠密向量 + 稀疏向量(BM25) 的混合检索策略
- 灵活的架构设计:支持一键配置多种LLM提供商(OpenAI、Ollama)和嵌入模型
- 完整的数据流水线:从数据预处理、入库到检索问答、以及评估的端到端解决方案
- RAG智能体:自动确定检索内容、检索参数、自动确定是否符合文档事实、自动确定是否开启网络检索,将检索功能全部交由智能体托管,一站式定义查询即可得到你想要的答案!
- 更丰富的工程细节:自带token估计、摘要提取、检索文档动态整合等重要特性,在多轮对话性能强悍。
medical-rag/
├── src/MedicalRag/
│ ├── config/ # 配置管理系统
│ │ ├── models.py # Pydantic配置模型
│ │ ├── loader.py # 配置加载器
│ │ └── app_config.yaml # 默认配置文件
│ ├── core/ # 核心组件
│ │ ├── utils.py # LLM/嵌入模型创建工具
│ │ ├── KnowledgeBase.py # 多向量知识库
│ │ ├── HybridRetriever.py # 混合检索器
│ │ ├── insert.py # Milvus入库工具类
│ │ ├── DBFactory.py # 知识库工厂,并行检索时保证单例客户端的线程安全
│ │ └── IngestionPipeline.py # 数据入库流水线
│ ├── embed/ # 嵌入相关
│ │ ├── vocab/ # 领域词表默认保存目录
│ │ ├── sparse.py # BM25稀疏向量实现
│ │ └── bm25.py # BM25适配器
│ ├── data/ # 数据处理
│ │ └── annotation.py # 自动标注系统
│ ├── rag/ # RAG核心
│ │ ├── RagBase.py # RAG实现的基类
│ │ ├── RagEvaluate.py # RAG评测的基类与实现类
│ │ ├── utils.py # 工具类
│ │ ├── MultiDialogueRag.py # 多轮对话实现类
│ │ └── SimpleRag.py # 基础RAG实现
│ ├── prompts/ # 提示词管理
│ │ └── templates.py # 提示词模板
│ └── agent/ # 智能体实现
│ │ ├── tools/ # 工具包
│ │ │ ├── AgentTools.py # 工具类
│ │ │ └── TencentSearch.py # 腾讯云网络检索器
│ │ ├── utils.py # 工具函数
│ │ ├── AgentBase.py # 智能体基类
│ │ ├── MedicalAgent.py # 多轮问讯智能体
│ └── └── SearchGraph.py # 单轮对话智能体
├── scripts/ # 使用脚本
├── Milvus/ # Milvus客户端启动相关
└── .vscode/ # vscode快捷运行配置
huatuo-qa 数据集
也可以使用本项目提供的由 huatuo-qa 采样而来的数据集用作示例,详见 data 目录
git clone https://github.com/yolo-hyl/medical-rag
cd medical-rag
conda env create -f environment.ymlcd src
pip install -e .启动 Milvus 向量数据库
由于本项目默认可以采用稀疏向量管理,所以需要使用客户端Milvus。
# 使用项目提供的脚本
cd Milvus
bash standalone_embed.sh start启动 Ollama(如果使用本地模型)
# 安装并启动 Ollama
ollama serve
# 拉取所需模型
ollama pull bge-m3:latest # 嵌入模型
ollama pull qwen3:32b # 对话模型更多配置详见 Ollama
编辑 src/MedicalRag/config/app_config.yaml可修改默认配置,也可在引入config时动态修改部分配置:
# Milvus向量数据库配置
milvus:
uri: http://localhost:19530
token: null
collection_name: medical_knowledge
drop_old: true # 第一次建库时,是否删除同名 collection,调试用,生产环境严禁使用
auto_id: false # 可选是否自动生成id,否则采用hash值作为id自动去重
# 嵌入模型配置(支持多向量字段)
embedding:
summary_dense: # 问题向量(稠密)
provider: ollama
model: bge-m3:latest
base_url: http://localhost:11434
dimension: 1024
text_dense: # 文本向量(稠密)
provider: ollama
model: bge-m3:latest
base_url: http://localhost:11434
dimension: 1024
text_sparse: # BM25稀疏向量
provider: self # 或 "Milvus" 使用内置BM25
vocab_path_or_name: vocab.pkl.gz
algorithm: BM25
domain_model: medicine # 医疗领域分词
k1: 1.5
b: 0.75
# 大语言模型配置
llm:
provider: ollama
model: qwen3:32b
base_url: http://localhost:11434
temperature: 0.1
# 数据字段映射
data:
summary_field: question # 问题字段
document_field: answer # 答案字段
default_source: qa
default_source_name: huatuo_qa
# 多轮对话配置
multi_dialogue_rag:
estimate_token_fun: avg # 默认token估计方式
llm_max_token: 1024 # 大模型最长token数量
cut_dialogue_scale: 2 # 预估达到最长token时的裁切比例,2表时裁切一半的历史对话生成摘要
max_token_threshold: 1.01 # 最长token的缓冲值 > 1 表示宽松策略,<1 表示严格策略
smith_debug: false # 是否使用 Langsmith 进行debug查看
console_debug: true # 是否启用控制台日志查看
thinking_in_context: false # 是否将思考内容加入上下文历史对话
agent: # 智能体会沿用上述多轮对话rag的配置
mode: analysis
max_attempts: 2 # 每一个子目标查询的最大重试次数,否则进行联网搜索
network_search_enabled: True # 是否启用联网搜索
network_search_cnt: 10 # 开启联网搜索时,返回的数量
auto_search_param: True # 是否开启确定搜索参数当配置 embedding.text_sparse.provider: "self" 时需要先构建词表:
conda activate rag
python scripts/01_build_vocab.py领域分词依赖 pkuseg 库,更多领域可详见其项目主页。
数据配置
data:
summary_field: question
document_field: answer
default_source: qa
default_source_name: huatuo_qa
default_lt_doc_id: ''
default_chunk_id: -1支持医疗QA数据的批量入库,自动处理多向量字段:
conda activate rag
python scripts/02_ingest_data.py数据格式示例:
{
"question": "高血压的症状有哪些?",
"answer": "高血压的主要症状包括头痛、头晕、心悸..."
}source和source_name可不指定,但需要配置默认的数据源和数据源名称。
入库后,Milvus中存储的字段如下:
| 字段名 | 字段类型 | 说明 |
|---|---|---|
| pk | INT64 or VARCHAR | 主键。当自动生成id时,使用INT64,否则使用varchar |
| text | VARCHAR | 核心知识文本。qa数据=summary+document;文献数据=document |
| summary | VARCHAR | 当前知识摘要。qa数据=question;文献数据=采样或者生成的摘要示例文本 |
| document | VARCHAR | 原始文本。qa数据=answer;文献数据=原始文本 |
| source | VARCHAR | 数据源。暂只支持:qa和literature |
| source_name | VARCHAR | 数据源名称。例如:huatuo、neikebook |
| lt_doc_id | VARCHAR | 文档id。用于寻找同一个切片的文档 |
| chunk_id | INT64 | 切片id。同一个切片的数据切片id相同,用于反查相关文档 |
| summary_dense | FLOAT_VECTOR | 摘要的稠密向量 |
| text_dense | FLOAT_VECTOR | 知识的稠密向量 |
| text_sparse | SPARSE_FLOAT_VECTOR | 知识的稀疏向量,用于关键词匹配 |
测试多向量混合检索效果:
python scripts/03_search_data.py 基于检索结果生成专业医疗回答:
python scripts/04_basic_rag.py将会生成以下数据的知识库检索回答:我有点肚子痛,该怎么办?
从插入的示例数据中,采样200条回答,改写Q-A对,以便进行RAG评测:
cd data/eval
python change_data.py使用改写后的Q-A对,进行RAG的评测,也可以使用自己的数据集,指定对应的列名即可
python 05_eval_rag.pypython 06_muti_dialogue_rag然后输入你的问题即可
同时多轮对话支持自定义token估计,估计越准确,上下文内容越准确,已支持 avg历史token平均估计 和 tiktoken库估计 。
# 支持自定义token估计方法
from MedicalRag.rag.utils import register_estimate_function
# 1) 注册自己的函数
@register_estimate_function("self_fun")
def estimate_tokens(text: str) -> int:
""" 示例:简单的线性关系 你需要自己实现根据传入的自然语言来估计可能会被模型编码的token数量"""
tokens = len(text) * 0.8 #
return tokens
# 2) 修改配置文件(已有默认实现:avg、tiktoken)
config_manager.change({"multi_dialogue_rag.estimate_token_fun": "self_fun"})
# 3) 传入配置,开始问答
rag = MultiDialogueRag(config_manager.config)使用这个示例时,需要有一个能力较强的大模型,充当智能体调用工具的角色,所以需要修改这个脚本,传入ChatModel
推荐使用 qwen-plus 在这个智能体中:检索参数、检索内容、是否符合文档事实、是否需要进行网络检索 全部由智能体自己确定,用户只需要定义想要问讯的问题即可回答。
python 07_single_dialogue_agent.py问答智能体依赖单轮RAG问答智能体,检索时使用的是其子图。推荐传入更强大的模型作为调度器。
python 08_medical_agent.pyOpenAI配置(支持代理):
llm:
provider: openai
model: gpt-4o-mini
api_key: "your-api-key"
base_url: "https://api.openai.com/v1"
proxy: "http://localhost:10809" # 可选代理设置
temperature: 0.1
max_tokens: 2000混合配置(不同组件使用不同提供商):
embedding:
summary_dense:
provider: openai
model: text-embedding-3-small
api_key: "your-key"
text_dense:
provider: ollama
model: bge-m3:latest
base_url: http://localhost:11434
llm:
provider: openai
model: gpt-4o-mini自管理BM25(推荐用于生产):
embedding:
text_sparse:
provider: self
vocab_path_or_name: vocab.pkl.gz
domain_model: medicine # 使用医疗分词模型,完美迁移其他领域
k1: 1.5 # BM25参数调优
b: 0.75
build:
workers: 8 # 并行分词线程数
chunksize: 64Milvus内置BM25(简化版):
embedding:
text_sparse:
provider: Milvus # Milvus 2.5+支持
k1: 1.5
b: 0.75RRF融合:
fuse = FusionSpec(
method="rrf",
k=60 # RRF参数,通常60-100效果较好
)加权融合:
fuse = FusionSpec(
method="weighted",
weights=[0.6, 0.3, 0.1] # 对应各向量字段权重
)from MedicalRag.prompts.templates import register_prompt_template
# 注册自定义医疗提示词
register_prompt_template("professional_medical", {
"system": "你是一名资深的医学专家,拥有丰富的临床经验...",
"user": """
基于以下医学资料回答患者问题,要求:
1. 专业准确,同时通俗易懂
2. 如涉及诊疗,提醒就医
3. 不要编造信息
参考资料: {context}
患者问题: {input}
专业回答:
"""
})from fastapi import FastAPI
from MedicalRag.rag.basic_rag import BasicRAG
from MedicalRag.config.loader import ConfigLoader
app = FastAPI(title="Medical RAG API")
config = ConfigLoader().config
rag_system = BasicRAG(config)
@app.post("/ask")
async def ask_medical_question(question: str):
"""医疗问答API"""
result = rag_system.answer(question, return_context=True)
return {
"question": question,
"answer": result["answer"],
"sources": [ctx["metadata"]["source"] for ctx in result["context"]],
"confidence": len(result["context"])
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)本项目还定义了 .vscode 快捷启动配置,可使用vscode打开一键运行。
| 特性 | 说明 |
|---|---|
| 医疗领域优化 | 使用pkuseg医疗分词、医疗停用词库 |
| 混合检索 | 稠密向量+稀疏向量,召回率更高 |
| 多向量架构 | 问题向量、文本向量、BM25向量独立优化 |
| 灵活配置 | 支持多种LLM/嵌入模型提供商 |
| 生产就绪 | 完整的数据流水线和错误处理 |
| 检索方式 | 召回率 | 精确率 | 适用场景 |
|---|---|---|---|
| 仅稠密向量 | 70.12% | 85.64% | 语义相似问题 |
| 仅BM25 | 61.08% | 70.90% | 关键词匹配 |
| 混合检索 | 91.32% | 92.15% | 综合最佳 |
- 文档数量: 支持百万级医疗文档
- 并发查询: 支持高并发检索请求
- 响应时间: < 500ms(混合检索)
- 准确率: 医疗领域问答准确率 > 85%
- 确保医疗数据符合相关法规(HIPAA、GDPR等)
- 建议在私有环境部署
- 定期备份向量数据库
- 硬件配置: 推荐16GB内存
- 批处理: 大量数据入库时使用批处理模式
- 索引优化: 根据数据量调整HNSW参数
- 缓存策略: 高频查询可增加缓存层
欢迎提交Issue和Pull Request!
- 遵循PEP 8编码规范
- 添加类型注解和文档字符串
- 提交前运行测试用例
本项目采用 MIT 许可证。详见 LICENSE 文件。
如有问题,欢迎提交Issue或联系项目维护者!