Skip to content

Repository files navigation

BioMed Knowledge API

一个本地可复现的 FastAPI 服务,用于生物医学文档检索、RAG 问答、结构化临床试验字段抽取,以及多步 Agent 报告生成。

基于 FastAPI、FAISS、LangGraph、Pydantic 构建。默认配置无需任何外部 API key——内置 HashEmbedding + FakeLLM,保证 CI 和离线开发可跑;生产环境可通过环境变量切换真实嵌入模型、重排器和 LLM。


特性

  • 混合检索(Hybrid RAG) — 稠密检索(FAISS)与稀疏检索(BM25)通过 RRF(Reciprocal Rank Fusion)融合,可选挂载 BGE Reranker 做精排。检索结果返回带 citation_idexcerptscoremetadata 的结构化证据。
  • 多策略查询改写 — standalone(规则,去代词)、step-back(LLM 抽象化)、HyDE(LLM 生成假设性答案文档再做稠密检索),按权重融合多查询的检索结果。
  • 三层记忆 — working(会话内 FIFO 滑窗)、episodic(项目级事件,按时间衰减 + 关键词重叠召回)、semantic(结构化事实,向量召回),基于 SQLite 持久化。
  • 结构化临床试验抽取 — 用 Pydantic v2 定义临床试验字段(phase、indication、endpoint、sample_size、criteria),通过「Prompt 约束 + model_validate_json 严格校验」两道防线将 LLM 输出收敛为强类型对象。
  • Agent 报告工作流 — 基于 LangGraph StateGraph 的多步流水线,将 retrieve、extract、summarize、return 拆分为可观测节点(success/skipped/failed 三态),含检索质量反馈环(top score 过低时改写 query 重检索)与可回放的执行轨迹树(trace_tree)。
  • 可插拔设计 — 嵌入、重排、LLM 均为 Protocol 抽象。默认零依赖(HashEmbedding + FakeLLM + NoopReranker),可切换 sentence-transformers(BGE-M3)/ OpenAI 兼容 API。
  • 文档摄入 — 支持同步与异步(FastAPI BackgroundTasks)两种摄入端点,样本文档按字符窗口切分入 FAISS 索引。
  • RAG 评估套件 — 独立的评估脚本(evals/),覆盖文档级 source 命中和关键词覆盖,退出码可作为 CI 门禁。

快速开始

uv sync
uv run uvicorn app.main:app --reload

打开 http://localhost:8000/docs 查看 Swagger UI。

跑测试:

uv run pytest

跑 RAG 评估(dense vs hybrid 对比):

uv run python evals/run_rag_eval.py        # 基线评估
uv run python evals/run_hybrid_eval.py     # dense vs hybrid 对比

用 Docker 运行:

docker compose up --build

API 参考

Health

curl http://localhost:8000/health
{"status":"ok","service":"biomed-agent-demo"}

文档摄入

将内置样本文档加载进内存 FAISS 索引。重复调用会重建索引而非追加重复 chunk。

curl -X POST http://localhost:8000/documents/ingest \
  -H "Content-Type: application/json" \
  -d '{"source":"samples"}'
{"document_count":3,"chunk_count":12,"vector_store_path":".local/faiss"}

异步摄入任务

通过 FastAPI BackgroundTasks 在后台执行的摄入请求。立即返回 job_id,可轮询状态。

curl -X POST http://localhost:8000/documents/ingest-jobs \
  -H "Content-Type: application/json" \
  -d '{"source":"samples"}'
{"job_id":"ingest_a1b2c3d4e5f6","status":"pending","source":"samples"}

轮询任务状态:

curl http://localhost:8000/documents/ingest-jobs/ingest_a1b2c3d4e5f6
{
  "job_id": "ingest_a1b2c3d4e5f6",
  "status": "succeeded",
  "source": "samples",
  "document_count": 3,
  "chunk_count": 12,
  "error": null
}

任务状态流转:pendingrunningsucceeded(或 failed)。不存在的 job_id 返回 404

注意: 任务存储仅在内存中——重启后丢失,跨 uvicorn worker 不可见。适用于开发与单 worker 部署。

查询(RAG 问答)

对已摄入的文档索引提问。响应包含带相似度分数和引用元数据的检索结果。

curl -X POST http://localhost:8000/query \
  -H "Content-Type: application/json" \
  -d '{"question":"What is the primary endpoint of the ADC trial?","top_k":3}'
{
  "answer": "...",
  "sources": [
    {
      "document_id": "trial_adc_001",
      "source": "trial_adc_001.md",
      "chunk_index": 2,
      "score": 0.1396,
      "text": "...",
      "citation_id": "trial_adc_001.md#2",
      "excerpt": "Primary Endpoint: Objective response rate ...",
      "metadata": {
        "citation_id": "trial_adc_001.md#2",
        "document_id": "trial_adc_001",
        "source": "trial_adc_001.md",
        "chunk_index": 2,
        "score": 0.1396
      }
    }
  ],
  "disclaimer": "This project is intended for research and development prototyping. It does not provide medical advice."
}

若未摄入文档,端点返回空 sources 列表,并说明无法从可用文档中确定答案。

结构化试验字段抽取

从样本文档或原始文本中抽取结构化的临床试验元数据。

curl -X POST http://localhost:8000/extract/trial \
  -H "Content-Type: application/json" \
  -d '{"document_id":"trial_adc_001"}'

直接传入文本而非 document_id:

curl -X POST http://localhost:8000/extract/trial \
  -H "Content-Type: application/json" \
  -d '{"text":"A Phase II trial of ADC-101 in HER2-positive advanced solid tumors with primary endpoint objective response rate."}'
{
  "result": {
    "trial_id": "trial_adc_001",
    "phase": "Phase II",
    "indication": "HER2-positive solid tumors",
    "intervention": "ADC-101",
    "primary_endpoint": "Objective response rate",
    "secondary_endpoints": ["Progression-free survival", "Safety"],
    "sample_size": 120,
    "inclusion_criteria": ["Adult patients", "ECOG performance status 0-1"],
    "exclusion_criteria": ["Uncontrolled infection"]
  },
  "validation_status": "valid"
}

文档不存在返回 404。所有错误遵循统一的响应结构:

{
  "error": "not_found",
  "message": "Document not found: samples/missing_trial.md",
  "request_id": null
}

Agent 报告

生成带可观测多步工作流的、有来源支撑的报告。每个步骤记录其名称、状态和摘要。

curl -X POST http://localhost:8000/agent/report \
  -H "Content-Type: application/json" \
  -d '{"topic":"ADC clinical trial"}'
{
  "report": "Summary for ADC clinical trial. ... Extracted trial design: phase Phase II; primary endpoint: Objective response rate; sample size: 120.",
  "steps": [
    {"name": "retrieve_documents", "status": "success", "summary": "Retrieved 3 source chunks."},
    {"name": "extract_trial_fields", "status": "success", "summary": "Extracted structured trial fields from trial_adc_001: Phase II, primary endpoint Objective response rate."},
    {"name": "summarize_findings", "status": "success", "summary": "Generated a source-grounded report summary."},
    {"name": "return_response", "status": "success", "summary": "Returned report, steps, and source references."}
  ],
  "sources": ["trial_adc_001.md#0", "pubmed_adc_summary.md#1"],
  "trace_tree": {
    "step": "retrieve_documents",
    "status": "success",
    "summary": "...",
    "children": [{"step": "extract_trial_fields", "status": "success", "...": "..."}]
  }
}

若检索质量不足,retrieve 节点会触发 query 改写 + 重新检索(replan);若检索内容不含临床试验数据,extract_trial_fields 步骤标记为 skipped


配置

默认配置纯本地,无需 API key。所有配置项通过环境变量(或 .env 文件)注入,完整样例见 .env.example

默认(零依赖,离线可跑)

LLM_PROVIDER=fake
EMBEDDING_PROVIDER=hash
RAG_MODE=dense
RERANKER_PROVIDER=none
QUERY_REWRITE_ENABLED=false

接入真实 LLM(DeepSeek / OpenAI / Qwen / Ollama / vLLM 等)

LLM_PROVIDER=openai-compatible
LLM_API_KEY=your-key
LLM_BASE_URL=https://api.deepseek.com/v1
LLM_MODEL=deepseek-chat

LLM_BASE_URL 可指向任何提供 OpenAI 兼容 /chat/completions 接口的服务。当前范围不含多模态、流式、原生 tool-calling。

启用混合检索 + 查询改写

RAG_MODE=hybrid                # dense + BM25 用 RRF 融合
RRF_K=60                       # RRF 常数
QUERY_REWRITE_ENABLED=true     # 需要 LLM_PROVIDER=openai-compatible

接入语义嵌入与重排(需装可选依赖)

uv sync --extra semantic       # 装 sentence-transformers / torch
EMBEDDING_PROVIDER=sentence    # BGE-M3 等
EMBEDDING_MODEL_NAME=BAAI/bge-m3
EMBEDDING_DIMENSION=1024       # 须与模型真实输出维度对齐
RERANKER_PROVIDER=bge          # BGE cross-encoder 精排
RERANKER_MODEL_NAME=BAAI/bge-reranker-base

架构

app/
├── api/routes/         # HTTP 端点(health / documents / query / extract / agent)
├── core/               # 配置(pydantic-settings)、依赖容器、错误处理
├── ingestion/          # 文件加载与文本切分
├── rag/                # 嵌入(可插拔)、FAISS 向量库、BM25 稀疏检索、RRF 融合、Reranker、Prompt
├── query_rewrite/      # 多策略查询改写(standalone / step-back / HyDE)
├── memory/             # 三层记忆(working / episodic / semantic)+ SQLite 持久化
├── extraction/         # Pydantic 校验 schema 与结构化字段抽取
├── llm/                # LLM 客户端 Protocol、fake 默认、OpenAI 兼容客户端
├── agent/              # LangGraph StateGraph 工作流、状态定义、工具函数、trace_tree
├── services/           # 业务编排(document / query / extraction / ingest job / agent)
└── schemas/            # 请求/响应 Pydantic 模型

service 层将 HTTP 路由与领域逻辑解耦。各模块单一职责,可独立替换——例如嵌入、重排、LLM 均为 Protocol 抽象,切换实现只需改 container.py 一处 + 环境变量,下游零改动。


技术栈

技术
API 框架 FastAPI、Uvicorn
校验 Pydantic v2
向量库 FAISS(内存,IndexFlatIP)
稀疏检索 rank-bm25(中英文混合分词)
融合 RRF(Reciprocal Rank Fusion)
嵌入 HashEmbedding(默认)/ sentence-transformers(BGE-M3,可选)
重排 BGE Reranker(可选)
LLM FakeLLM(默认)/ OpenAI 兼容(DeepSeek/Qwen/Ollama/vLLM 等)
记忆持久化 SQLite(标准库 sqlite3)
工作流编排 LangGraph(StateGraph)
测试 pytest、FastAPI TestClient
打包 uv
容器 Docker、Docker Compose

评估

evals/ 提供两个评估脚本:

  • run_rag_eval.py — 基线评估,对预定义 case 检查 top source 命中与关键词覆盖。退出码 0 = 全过,可作 CI 门禁。
  • run_hybrid_eval.py — 在同一批 case 上对比 dense 与 hybrid 两种模式的 source 命中数与 term 覆盖,判定规则:hybrid 不能在 source 命中上退步。

两个脚本都使用 FakeLLM + HashEmbedding,无外部 API key 依赖,完全可复现


样例数据

仓库内置三份合成的生物医学样本文档:

文件 类型 内容
samples/sop_cell_culture.md 标准操作流程 细胞培养复苏、传代、冻存
samples/pubmed_adc_summary.md 文献综述 ADC 肿瘤学综述,含临床试验结果
samples/trial_adc_001.md 临床试验摘要 Phase II ADC-101 试验设计与终点

这些是虚构样例,不含真实患者数据或专有信息。


局限

  • 本地向量库 — 内存 FAISS 索引不跨重启持久化,不支持多租户或分布式查询。生产部署应换远程向量库。
  • 内存任务存储 — 异步摄入任务仅在进程内存中,重启丢失、跨 worker 不可见、不支持取消或队列容量控制。
  • 无鉴权 — API 无内置 auth 层。非本地环境应部署在反向代理或 VPN 之后。
  • 仅研究原型 — 系统面向研发工作流原型设计,未经临床决策支持验证,不得用于医学诊断或治疗决策。
  • 合成样例数据 — 所有内置文档均为虚构。任何有意义的评估应替换为真实(去标识)数据。
  • 记忆模块未接入主流程 — 三层记忆模块已就绪(写入/召回接口 + 持久化),但尚未在 QueryService 中消费做多轮上下文。

许可证

MIT

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages