Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FAQ KV Cache Agent

基于 LLM Prefix Caching 的智能 FAQ 问答系统。预缓存 FAQ 的 KV Cache,在用户提问时快速匹配相关条目并流式生成回答,兼顾速度与成本。

核心思路:将 FAQ 按类别分块,启动时通过 max_tokens=1 请求预建立 LLM 的 prefix cache;用户提问时先用 LLM 匹配类别,再从缓存命中块中检索相关 FAQ,最后生成回答。FAQ 检索和回答生成各只调一次 LLM。


特性

  • FAQ 管理 — 增删改查、批量导入(JSON/CSV)、按类别/关键词过滤
  • 问答对话 — 基于 SSE 的流式回答,展示引用来源,注入先验知识
  • 文档管理 — 上传 PDF/DOCX/TXT/MD,自动提取文本内容
  • KV Cache 加速 — FAQ 变更后自动重建缓存块并预热
  • 多会话 — 支持多用户多会话隔离,历史消息持久化

架构

User Question
    │
    ▼
┌─────────────────────────────────────────┐
│  Step 1: Catgeory Matching (LLM call)   │  ← LLM 匹配合适类别
│  "哪些类别与用户问题相关?"              │
└──────────────┬──────────────────────────┘
               │ matched categories
               ▼
┌─────────────────────────────────────────┐
│  Step 2: In-Block Retrieval (LLM call)  │  ← 对每个候选块并发搜索(KV Cache 命中)
│  "找出与当前对话相关的 FAQ"              │
└──────────────┬──────────────────────────┘
               │ matched FAQ items
               ▼
┌─────────────────────────────────────────┐
│  Step 3: Answer Generation (Streaming)  │  ← 拼接上下文,SSE 流式吐出
│  系统提示 + 历史消息 + FAQ + 问题        │
└─────────────────────────────────────────┘

缓存策略

启动 / FAQ变更
    │
    ▼
┌──────────────────────┐
│  rebuild_blocks()    │  ← 按类别分块,每块 ~300K tokens
│  类别A: block_001    │
│  类别A: block_002    │
│  类别B: block_001    │
└──────────┬───────────┘
           ▼
┌──────────────────────┐
│  warmup_all_blocks() │  ← 并发请求各块,max_tokens=1
│  prompt = prefix +   │    建立 prefix cache
│  content + warmup q  │
└──────────────────────┘

技术栈

技术
前端 React 18 + TypeScript + TailwindCSS + Vite + pnpm
后端 Python 3.10+ + FastAPI + Uvicorn
LLM SDK OpenAI Python SDK(兼容 DeepSeek / 任意 OpenAI 风格 API)
存储 本地 JSON 文件 + filelock 线程安全读写
部署 Docker + Docker Compose + Nginx

快速开始

前置要求

  • Python 3.10+
  • Node.js 24+ + pnpm
  • LLM API Key(DeepSeek / OpenAI / 兼容接口)

1. 后端

cd backend

# 安装依赖
python run.py install
# 或:pip install -r requirements.txt

# 配置环境变量
cp .env-example .env
# 编辑 .env,填入你的 API Key 和模型信息

# 启动 API 服务
python run.py api
# → http://localhost:8000

2. 前端

cd frontend
pnpm install
pnpm dev
# → http://localhost:5173

Vite 会自动将 /api/* 请求代理到 localhost:8000

3. Docker 部署

docker-compose up -d

项目结构

faq-kv-cache/
├── frontend/                     # React 前端
│   ├── src/
│   │   ├── pages/
│   │   │   ├── QaChat/           # 问答对话页
│   │   │   ├── FaqManage/        # FAQ 管理页
│   │   │   ├── DocumentManage/   # 文档管理页
│   │   │   └── Login.tsx         # 用户登录页
│   │   ├── components/
│   │   │   ├── Layout.tsx        # 全局布局 + 侧边栏
│   │   │   ├── ChatMessage.tsx   # 消息气泡(Markdown 渲染)
│   │   │   ├── FaqCard.tsx       # FAQ 卡片
│   │   │   ├── DocumentCard.tsx  # 文档卡片
│   │   │   └── PriorKnowledgeSelector.tsx  # 先验知识选择器
│   │   ├── services/             # API 客户端
│   │   │   ├── qa.ts             # 问答(含流式重连逻辑)
│   │   │   ├── faq.ts
│   │   │   ├── document.ts
│   │   │   └── session.ts
│   │   ├── types/index.ts        # TypeScript 类型定义
│   │   ├── App.tsx               # 路由入口
│   │   └── main.tsx              # 挂载点
│   ├── vite.config.ts
│   └── package.json
│
├── backend/
│   ├── app/
│   │   ├── api/                  # FastAPI 路由
│   │   │   ├── qa.py             # 问答接口(流式 + 非流式)
│   │   │   ├── faq.py            # FAQ CRUD
│   │   │   ├── document.py       # 文档上传/管理
│   │   │   ├── session.py        # 会话管理
│   │   │   └── auth.py           # 登录认证
│   │   ├── services/
│   │   │   ├── qa_service.py     # 问答核心逻辑
│   │   │   ├── block_manager.py  # KV Cache 块管理 + 类别匹配 + 块内检索
│   │   │   ├── faq_service.py    # FAQ 业务逻辑
│   │   │   └── document_service.py  # 文档提取
│   │   ├── core/
│   │   │   ├── config.py         # Pydantic 配置(.env 加载)
│   │   │   └── llm_client.py     # LLM 客户端(同步/异步/流式/缓存预热)
│   │   ├── storage/              # 存储层
│   │   │   ├── file_store.py     # JSON 文件 + filelock 读写
│   │   │   ├── session_store.py  # 会话持久化
│   │   │   └── user_store.py     # 用户存储
│   │   └── main.py               # FastAPI 应用入口 + 启动缓存预热
│   ├── data/
│   │   ├── faq_datasets/         # FAQ 数据集(按类别分文件)
│   │   │   ├── oncology/         # 肿瘤学 FAQ
│   │   │   └── amagasaki/        # 市政 FAQ(日语翻译)
│   │   └── sessions/             # 会话记录
│   ├── scripts/                  # 管理脚本
│   │   ├── faq_cli.py            # FAQ CLI(增删查 + 导入)
│   │   ├── import_oncology_csv.py
│   │   ├── translate_faqs.py     # FAQ 翻译
│   │   └── split_faqs.py         # 数据集拆分
│   ├── tests/                    # pytest 测试
│   ├── run.py                    # 一键启动脚本
│   ├── requirements.txt
│   └── pyproject.toml
│
├── docker-compose.yml
├── nginx.conf
└── Makefile

API 文档

问答

方法 路径 说明
POST /api/qa/ask 非流式问答,返回完整回答 + 引用
POST /api/qa/ask/stream 流式问答(SSE),逐 token 推送

流式请求体

{
  "question": "化疗期间需要注意什么?",
  "session_id": "session_xxx",
  "user_id": "user_xxx",
  "prior_knowledge_type": "text",
  "prior_knowledge_content": "患者为 65 岁女性...",
  "document_id": null,
  "previous_assistant_content": null
}

流式 SSE 事件

data: {"type":"status","content":"正在匹配相关类别..."}

data: {"type":"token","content":"化疗"}

data: {"type":"token","content":"期间"}

data: {"type":"done","references":[{"id":"...","question":"...","category":"..."}]}

data: {"type":"error","content":"错误信息"}
事件类型 说明
status 处理进度(搜索中、生成中)
token 逐 token 回答内容
done 回答完毕,携带引用来源
error 服务端错误

FAQ 管理

方法 路径 说明
GET /api/faqs 列表,支持 ?category=&keyword= 过滤
POST /api/faqs 创建 FAQ
POST /api/faqs/batch 批量创建
GET /api/faqs/{id} 获取单个
PUT /api/faqs/{id} 更新
DELETE /api/faqs/{id} 删除
POST /api/faqs/rebuild-cache 触发缓存重建 + 预热

文档管理

方法 路径 说明
POST /api/documents/upload 上传文件(PDF/DOCX/TXT/MD)
GET /api/documents 文档列表
GET /api/documents/{id}/content 获取提取的文本
DELETE /api/documents/{id} 删除文档

会话

方法 路径 说明
POST /api/sessions 创建会话
GET /api/sessions 用户会话列表
GET /api/sessions/{id} 获取会话消息
DELETE /api/sessions/{id} 删除会话

其他

方法 路径 说明
GET /api/health 健康检查,返回块数等信息
GET /api/cache/status 缓存预热状态

环境变量

通过 .env 或环境变量配置(参见 app/core/config.py):

变量 默认值 说明
LLM_API_KEY sk-your-api-key LLM API 密钥
LLM_BASE_URL https://api.deepseek.com/v1 API 地址(兼容 OpenAI 格式)
LLM_MODEL deepseek-chat 模型名称
LLM_MAX_TOKENS 4096 单次最大输出 token 数
LLM_TEMPERATURE 0.3 生成温度
FAQ_BLOCK_MIN_TOKENS 300000 每个缓存块的最小 token 数
FAQ_MAX_RESULTS 5 每次问答最多引用的 FAQ 条数
DATA_DIR data 数据存储目录
FAQ_DATASET_PATH data/faq_datasets/oncology FAQ 数据集路径
CORS_ORIGINS ["http://localhost:5173"] 允许的跨域来源

数据格式

FAQ 数据集按类别分文件存储,每个文件一个 JSON 数组:

[
  {
    "id": "a1b2c3d4e5f6",
    "category": "化疗",
    "question": "化疗期间可以吃中药吗?",
    "answer": "化疗期间使用中药需要在主治医生指导下进行...",
    "tags": ["化疗", "中药", "副作用"]
  }
]
字段 类型 必填 说明
id string 唯一 ID,不传则自动生成
category string 类别/科室
question string 问题
answer string 答案
tags string[] 可选标签

切换数据集只需修改配置 FAQ_DATASET_PATH


Cache Warmup 详解

为什么要预热?

LLM 的 prompt prefix caching 机制会缓存输入开头的 KV 计算状态。当新请求的前缀与已缓存内容匹配时,无需重新计算,显著降低首 token 延迟和成本。

预热流程

  1. 分块rebuild_blocks):按类别分组,每组累计 ~100K tokens 切一块,每块附带 [CAT:{category}:{seq}] 前缀标记
  2. 预热warmup_all_blocks):对每个块发送 max_tokens=1 的请求,让 LLM 服务端缓存该前缀的 KV 状态
  3. 触发:FAQ 创建/更新/删除后 5 秒防抖,自动触发重建 + 预热

查询流程

用户问题
  │
  ▼
类别匹配(LLM)──────→ 从所有类别中选出相关类别
  │
  ▼
块内搜索(LLM × N)──→ 对每个相关块的缓存进行并发检索(KV Cache 命中)
  │
  ▼
生成回答(SSE)──────→ 拼接完整上下文,流式输出

CLI 工具

cd backend

# 添加单条 FAQ
python scripts/faq_cli.py add \
  --category "化疗" \
  --question "化疗后白细胞低怎么办?" \
  --answer "化疗后白细胞减少是常见副作用..."

# 从 JSON 导入
python scripts/faq_cli.py import \
  --file scripts/sample_data/faqs.json

# 从 CSV 导入
python scripts/faq_cli.py import \
  --file scripts/sample_data/faqs.csv \
  --format csv

# 列出所有 FAQ
python scripts/faq_cli.py list

# 其他工具脚本
python scripts/translate_faqs.py   # 翻译 FAQ
python scripts/split_faqs.py       # 拆分数据集
python scripts/import_oncology_csv.py  # 导入肿瘤学 CSV

测试

cd backend

# 运行所有测试
python run.py test
# 或:pytest tests/ -v --tb=short

# 带覆盖率
python run.py test --coverage

# 监听模式(文件变化自动重跑)
python run.py test --watch

License

MIT

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages