全部笔记All notes

基于大模型的知识库问答系统

阅读 5m 53s5m 53s read

知识库问答系统

基于 RAG 架构构建企业知识库问答系统的完整方案。


什么是知识库问答?

知识库问答系统让用户可以用自然语言查询企业内部文档,获得准确的答案。它解决了大模型的两个核心问题:

问题说明RAG 如何解决
知识截止模型不知道最新信息检索最新文档
幻觉模型可能编造信息基于真实文档回答
专业知识模型不了解企业内部信息检索企业文档

RAG(Retrieval-Augmented Generation)的核心思想:

用户问题 → 检索相关文档 → 将文档作为上下文 → LLM 生成答案

与微调的对比:

方面RAG微调
知识更新实时(更新文档即可)需要重新训练
成本低(无需训练)高(需要 GPU)
可解释性高(可追溯来源)低
适用场景知识密集型任务行为/风格调整

系统架构

一个完整的知识库问答系统包含离线处理和在线查询两个流程:

flowchart TD
    subgraph 离线处理
        A[文档] --> B[解析]
        B --> C[分块]
        C --> D[Embedding]
        D --> E[(向量数据库)]
    end
    
    subgraph 在线查询
        F[用户问题] --> G[Embedding]
        G --> H[向量检索]
        E --> H
        H --> I[重排序]
        I --> J[构建 Prompt]
        J --> K[LLM 生成]
        K --> L[返回答案]
    end

各环节说明:

环节作用关键技术
文档解析提取文本内容PDF 解析、OCR
分块切分为适当大小语义分块、重叠
Embedding转换为向量text-embedding-3
向量检索找到相关内容相似度搜索
重排序优化排序Cross-encoder
Prompt 构建组织上下文模板设计
LLM 生成生成答案GPT-4o、Claude

文档处理流程

1. 文档解析

支持多种文档格式的解析:

格式解析方式推荐工具注意事项
PDF文本提取PyPDF2, pdfplumber扫描件需 OCR
Word结构解析python-docx保留格式
Markdown直接读取-最简单
HTML标签清理BeautifulSoup去除样式
Excel表格转文本pandas保留表格结构

解析示例:

from langchain_community.document_loaders import (
    PyPDFLoader,
    Docx2txtLoader,
    UnstructuredMarkdownLoader
)

# PDF
loader = PyPDFLoader("document.pdf")
docs = loader.load()

# Word
loader = Docx2txtLoader("document.docx")
docs = loader.load()

2. 文本分块

分块是 RAG 系统最关键的环节之一。分块太大会引入噪音,太小会丢失上下文。

flowchart LR
    A[长文档] --> B{分块策略}
    B --> C[固定长度]
    B --> D[语义分块]
    B --> E[结构分块]
分块策略对比
策略优点缺点适用场景
固定长度简单、均匀可能切断语义通用
重叠分块保留上下文冗余存储推荐默认
按段落语义完整长度不均结构化文档
按标题层次清晰依赖格式技术文档
语义分块最佳语义计算复杂高质量需求
推荐参数
CHUNK_CONFIG = {
    "chunk_size": 500,      # 每块字符数(中文约 500 字)
    "chunk_overlap": 50,    # 重叠字符数(10% 左右)
    "separator": "\n\n"     # 优先分割符
}

分块大小选择:

场景建议大小原因
问答系统300-500精确匹配
文档摘要1000-2000更多上下文
代码库按函数/类语义完整

3. 元数据提取

为每个文档块添加元数据,便于过滤和溯源:

chunk_metadata = {
    "source": "产品手册.pdf",
    "page": 15,
    "section": "安装指南",
    "created_at": "2024-01-01",
    "category": "技术文档",
    "author": "技术部"
}

元数据的作用:

  • 过滤:只搜索特定类别的文档
  • 溯源:告诉用户答案来自哪里
  • 权重:新文档可以有更高权重
  • 权限:不同用户看到不同文档

检索优化

检索质量直接决定了最终答案的质量。“检索不到,就回答不了”。

1. 混合检索

单纯的向量检索可能遗漏关键词匹配的结果。混合检索结合两种方式:

flowchart TD
    A[查询] --> B[关键词检索]
    A --> C[向量检索]
    
    B --> D[BM25 结果]
    C --> E[语义结果]
    
    D --> F[结果融合]
    E --> F
    F --> G[最终结果]

两种检索的互补:

查询类型关键词检索向量检索
“API 文档”✅ 精确匹配⚠️ 可能不准
“如何调用接口”⚠️ 可能遗漏✅ 语义理解
混合查询部分匹配部分匹配
融合算法
def reciprocal_rank_fusion(results_list, k=60):
    """RRF 融合算法 - 简单有效的结果融合方法"""
    scores = {}
    for results in results_list:
        for rank, doc in enumerate(results):
            doc_id = doc.id
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

2. 查询改写

优化用户查询以提高检索效果:

flowchart LR
    A[原始查询] --> B[查询改写]
    B --> C[扩展查询]
    B --> D[同义词替换]
    B --> E[HyDE]
HyDE(假设文档嵌入)
def hyde_search(query, retriever, llm):
    # 1. 生成假设答案
    hypothetical = llm.generate(f"回答这个问题:{query}")
    
    # 2. 用假设答案检索
    results = retriever.search(hypothetical)
    
    return results

3. 重排序

对初步检索结果进行精排:

flowchart LR
    A[Top 20 结果] --> B[重排序模型]
    B --> C[Top 5 结果]
重排序方法效果成本
LLM 打分好高
Cross-Encoder很好中
规则过滤一般低

Prompt 构建

基础模板

RAG_PROMPT = """基于以下参考信息回答用户问题。

## 参考信息
{context}

## 规则
- 只基于参考信息回答
- 如果信息不足,诚实说明
- 引用来源时标注 [来源: xxx]

## 用户问题
{question}

## 回答
"""

带引用的模板

RAG_PROMPT_WITH_CITATION = """基于以下参考信息回答问题,并标注引用来源。

## 参考信息
{numbered_context}

## 用户问题
{question}

## 要求
- 回答后标注引用,格式:[1][2]
- 无相关信息时说明"未找到相关信息"
"""

答案生成

1. 基础问答

def answer_question(question, retriever, llm):
    # 检索
    docs = retriever.search(question, top_k=5)
    
    # 构建上下文
    context = "\n\n".join([d.content for d in docs])
    
    # 生成答案
    prompt = RAG_PROMPT.format(context=context, question=question)
    answer = llm.generate(prompt)
    
    return answer, docs

2. 带引用的问答

def answer_with_citation(question, retriever, llm):
    docs = retriever.search(question, top_k=5)
    
    # 编号上下文
    numbered = "\n\n".join([
        f"[{i+1}] {d.content}\n来源: {d.metadata['source']}"
        for i, d in enumerate(docs)
    ])
    
    prompt = RAG_PROMPT_WITH_CITATION.format(
        numbered_context=numbered,
        question=question
    )
    
    answer = llm.generate(prompt)
    return answer, docs

3. 多轮对话

flowchart TD
    A[用户问题] --> B{需要检索?}
    B -->|是| C[检索相关文档]
    B -->|否| D[直接对话]
    
    C --> E[结合历史生成]
    D --> E
    E --> F[返回答案]

评估指标

检索评估

指标说明计算方式
Recall@K召回率相关文档数 / 总相关数
MRR平均倒数排名1/首个相关文档排名
NDCG归一化折损累积增益考虑排序的相关性

生成评估

指标说明
准确性答案是否正确
相关性是否回答了问题
完整性信息是否充分
忠实度是否基于检索内容

自动评估

def evaluate_faithfulness(answer, context, llm):
    """评估答案是否忠实于上下文"""
    prompt = f"""判断答案是否完全基于给定的上下文。

上下文:{context}
答案:{answer}

评分(1-5):"""
    
    score = llm.generate(prompt)
    return int(score)

常见问题

Q: 检索不到相关内容?

原因解决方案
分块太大减小 chunk_size
查询太短查询扩展
语义差异使用 HyDE
数据不足补充知识库

Q: 答案不准确?

原因解决方案
检索结果差优化检索
上下文不足增加 top_k
Prompt 不好优化提示词
模型幻觉强调”基于上下文”

Q: 响应太慢?

优化点方法
检索使用 ANN 索引
重排序减少候选数量
生成使用更快的模型
缓存缓存热门问答

最佳实践

实践说明
定期更新知识库需要持续维护
质量监控收集用户反馈
分类管理按主题组织文档
权限控制敏感文档访问控制
溯源展示显示答案来源

相关文档