基于 Python + LangChain + RAG 的知识检索系统实战

一、项目背景

在大模型时代,如何让 AI 基于特定领域的知识库准确回答问题,而不是"胡编乱造"?RAG(Retrieval-Augmented Generation,检索增强生成)是目前最主流的解决方案。本文将带你从零实现一个完整的 RAG 知识检索系统,以汽车参数知识库为场景,涵盖后端、前端、向量数据库的完整链路。

二、什么是 RAG?

RAG 的核心思想很简单:先检索,再生成

复制代码
用户提问 → Embedding 向量化 → 向量数据库检索相关文档 → 检索结果 + 问题送入 LLM → 生成精准回答

传统 LLM 只能依赖训练时的知识,而 RAG 让它能"查阅资料"后再回答,大幅减少幻觉,回答更准确、更可控。

三、技术选型

组件 选型 说明
LLM 通义千问(qwen-plus) 阿里云大模型,兼容 OpenAI 接口
Embedding text-embedding-v2 通义千问向量模型,1536维
向量数据库 FAISS Facebook 开源,本地轻量,无需额外部署
关系数据库 MySQL 存储知识原文和元数据
后端框架 Flask Python 轻量 Web 框架
前端 HTML + JavaScript 简洁聊天界面 + 知识管理侧边栏
AI 框架 LangChain LLM 应用开发框架,串联整个 RAG 链路

四、系统架构

复制代码
┌─────────────┐     ┌──────────────────────────────────────────┐
│   前端页面   │────→│              Flask 后端                  │
│  聊天 + 管理 │←────│                                          │
└─────────────┘     │  /api/chat        → RAG 检索 + LLM 生成    │
                    │  /api/knowledge   → 知识 CRUD              │
                    │  /api/rebuild     → 重建向量索引            │
                    └──────┬──────────────┬──────────────────────┘
                           │              │
                    ┌──────▼──────┐ ┌─────▼──────┐
                    │   FAISS     │ │   MySQL    │
                    │  向量索引    │ │ knowledge  │
                    │  (本地文件)  │ │   表       │
                    └─────────────┘ └────────────┘

五、数据库设计

在 MySQL 中创建 knowledge 表,存储知识原文:

复制代码
CREATE TABLE knowledge (
    id INT AUTO_INCREMENT PRIMARY KEY,
    question VARCHAR(500) NOT NULL COMMENT '问题或关键词描述',
    answer TEXT NOT NULL COMMENT '知识内容/答案',
    image_url VARCHAR(500) DEFAULT NULL COMMENT '图片链接',
    category VARCHAR(100) DEFAULT '通用' COMMENT '分类',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

为什么不直接用 MySQL 做检索? MySQL 是关系型数据库,擅长精确匹配和范围查询,但不支持向量相似度搜索。所以用 MySQL 存原文,FAISS 存向量,两者配合使用。

六、RAG 核心实现

6.1 Embedding 与 LLM 初始化

通过 LangChain 的 OpenAIEmbeddingsChatOpenAI,配合 DashScope 兼容接口:

复制代码
from langchain_openai import OpenAIEmbeddings, ChatOpenAI

def get_embeddings():
    return OpenAIEmbeddings(
        model="text-embedding-v2",
        openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
        openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
        check_embedding_ctx_length=False,  # 兼容 DashScope
    )

def get_llm():
    return ChatOpenAI(
        model="qwen-plus",
        openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
        openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )
6.2 从 MySQL 加载知识 → 构建向量索引
复制代码
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document

def load_knowledge_from_db():
    """从 MySQL 加载知识,转为 LangChain Document"""
    conn = pymysql.connect(**DB_CONFIG)
    cursor = conn.cursor()
    cursor.execute("SELECT id, question, answer, image_url, category FROM knowledge")
    documents = []
    for doc_id, question, answer, image_url, category in cursor.fetchall():
        content = f"问题: {question}\n答案: {answer}"
        documents.append(Document(
            page_content=content,
            metadata={"id": doc_id, "category": category, "image_url": image_url or ""}
        ))
    conn.close()
    return documents

def build_vector_store():
    """构建 FAISS 向量索引"""
    documents = load_knowledge_from_db()
    embeddings = get_embeddings()
    vectorstore = FAISS.from_documents(documents, embeddings)
    vectorstore.save_local("faiss_index")  # 持久化到本地
    return vectorstore

关键点: 每条知识的 page_content 包含问题和答案全文,metadata 存储图片等附加信息。FAISS 会将 page_content 转为向量,检索时基于向量相似度匹配。

6.3 RAG 检索 + 生成

这是整个系统的核心流程:

复制代码
def rag_query(user_question, top_k=3):
    # 1. 加载向量索引
    vectorstore = FAISS.load_local("faiss_index", get_embeddings(),
                                    allow_dangerous_deserialization=True)

    # 2. 检索最相关的 top_k 条知识
    retriever = vectorstore.as_retriever(search_kwargs={"k": top_k})
    docs = retriever.invoke(user_question)

    # 3. 收集图片
    images = [doc.metadata["image_url"] for doc in docs if doc.metadata.get("image_url")]

    # 4. 拼接上下文,送入 LLM
    context = "\n\n".join([doc.page_content for doc in docs])
    prompt = f"""你是知识问答助手。根据以下知识库内容回答用户问题。
知识库内容:
{context}

用户问题:{user_question}
请基于知识库内容回答:"""

    response = get_llm().invoke(prompt)
    return {"reply": response.content, "images": images}

七、Flask 后端接口

复制代码
@app.route("/api/chat", methods=["POST"])
def chat():
    data = request.json
    use_rag = data.get("use_rag", True)

    if use_rag:
        result = rag_query(data["message"])
        return jsonify({
            "reply": result["reply"],
            "images": result["images"],
            "session_id": session_id,
        })
    else:
        # 普通对话模式(不检索知识库)
        ...

# ==================== 知识库管理 API ====================

@app.route("/api/knowledge", methods=["GET"])
def get_knowledge():
    """获取知识库列表"""
    try:
        knowledge_list = list_knowledge()
        return jsonify({"code": 200, "data": knowledge_list})
    except Exception as e:
        return jsonify({"code": 500, "msg": str(e)})


@app.route("/api/knowledge", methods=["POST"])
def create_knowledge():
    """新增知识"""
    data = request.json
    question = data.get("question", "").strip()
    answer = data.get("answer", "").strip()
    category = data.get("category", "通用").strip()

    if not question or not answer:
        return jsonify({"code": 400, "msg": "问题和答案不能为空"})

    try:
        add_knowledge(question, answer, category)
        return jsonify({"code": 200, "msg": "添加成功"})
    except Exception as e:
        return jsonify({"code": 500, "msg": str(e)})


@app.route("/api/knowledge/<int:knowledge_id>", methods=["DELETE"])
def remove_knowledge(knowledge_id):
    """删除知识"""
    try:
        delete_knowledge(knowledge_id)
        return jsonify({"code": 200, "msg": "删除成功"})
    except Exception as e:
        return jsonify({"code": 500, "msg": str(e)})


@app.route("/api/rebuild-index", methods=["POST"])
def rebuild_index():
    """手动重建向量索引"""
    try:
        build_vector_store()
        return jsonify({"code": 200, "msg": "索引重建成功"})
    except Exception as e:
        return jsonify({"code": 500, "msg": str(e)})


if __name__ == "__main__":
    # 启动时自动构建向量索引
    print("正在构建向量索引...")
    build_vector_store()
    print("聊天机器人服务启动: http://127.0.0.1:5000")
    app.run(debug=True, port=5000)

同时提供知识管理接口:

接口 方法 功能
/api/knowledge GET 获取知识列表
/api/knowledge POST 新增知识(自动更新向量索引)
/api/knowledge/<id> DELETE 删除知识(自动重建索引)
/api/rebuild-index POST 手动重建向量索引

八、前端实现

前端采用纯 HTML + JavaScript,主要功能:

  • 聊天界面:支持 Markdown 渲染、加载动画、Enter 发送

  • 知识检索开关:顶部 checkbox 控制 RAG / 普通对话模式切换

  • 知识管理侧边栏:新增/查看/删除知识条目

  • 图片展示:AI 回答下方自动展示检索到的知识配图

    // 发送消息并处理含图片的响应
    async function sendMessage() {
    const res = await axios.post('/api/chat', {
    message, session_id: sessionId, use_rag: ragToggle.checked
    });
    addMessage('ai', res.data.reply, res.data.images || []);
    }

    function addMessage(role, content, images = []) {
    // 渲染文本 + 图片
    bubble.innerHTML = marked.parse(content);
    if (images.length > 0) {
    const imgBox = document.createElement('div');
    imgBox.className = 'bubble-images';
    images.forEach(url => {
    const img = document.createElement('img');
    img.src = url;
    imgBox.appendChild(img);
    });
    bubble.appendChild(imgBox);
    }
    }

    RAG 知识检索助手