基于 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);
    }
    }

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>RAG 知识检索助手</title> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
    复制代码
          body {
              font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
              background: #f0f2f5;
              height: 100vh;
              display: flex;
              flex-direction: column;
          }
    
          /* 顶部栏 */
          .header {
              background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
              color: #fff;
              padding: 16px 24px;
              display: flex;
              align-items: center;
              justify-content: space-between;
              box-shadow: 0 2px 8px rgba(0,0,0,0.15);
          }
          .header h1 { font-size: 20px; font-weight: 600; }
          .header-actions { display: flex; gap: 8px; align-items: center; }
          .header-actions label {
              display: flex; align-items: center; gap: 6px;
              font-size: 13px; cursor: pointer;
              background: rgba(255,255,255,0.15);
              padding: 6px 12px; border-radius: 6px;
          }
          .header-actions label input { accent-color: #764ba2; }
          .header button {
              background: rgba(255,255,255,0.2);
              border: 1px solid rgba(255,255,255,0.4);
              color: #fff;
              padding: 6px 16px;
              border-radius: 6px;
              cursor: pointer;
              font-size: 14px;
              transition: background 0.2s;
          }
          .header button:hover { background: rgba(255,255,255,0.35); }
    
          /* 主体布局 */
          .main-layout {
              flex: 1;
              display: flex;
              overflow: hidden;
          }
    
          /* 聊天区域 */
          .chat-area {
              flex: 1;
              display: flex;
              flex-direction: column;
          }
          .chat-container {
              flex: 1;
              overflow-y: auto;
              padding: 20px;
              max-width: 800px;
              width: 100%;
              margin: 0 auto;
          }
    
          .message {
              display: flex;
              margin-bottom: 16px;
              animation: fadeIn 0.3s ease;
          }
          @keyframes fadeIn {
              from { opacity: 0; transform: translateY(8px); }
              to { opacity: 1; transform: translateY(0); }
          }
    
          .message.user { justify-content: flex-end; }
          .message.ai { justify-content: flex-start; }
    
          .avatar {
              width: 36px;
              height: 36px;
              border-radius: 50%;
              display: flex;
              align-items: center;
              justify-content: center;
              font-size: 16px;
              flex-shrink: 0;
          }
          .message.user .avatar { background: #667eea; color: #fff; margin-left: 10px; order: 2; }
          .message.ai .avatar { background: #e8e8e8; color: #555; margin-right: 10px; }
    
          .bubble {
              max-width: 70%;
              padding: 12px 16px;
              border-radius: 12px;
              line-height: 1.6;
              font-size: 15px;
              word-break: break-word;
          }
          .message.user .bubble {
              background: linear-gradient(135deg, #667eea, #764ba2);
              color: #fff;
              border-bottom-right-radius: 4px;
          }
          .message.ai .bubble {
              background: #fff;
              color: #333;
              border-bottom-left-radius: 4px;
              box-shadow: 0 1px 4px rgba(0,0,0,0.08);
          }
          .message.ai .bubble p { margin: 0.4em 0; }
          .message.ai .bubble pre {
              background: #1e1e1e;
              color: #d4d4d4;
              padding: 12px;
              border-radius: 6px;
              overflow-x: auto;
              margin: 8px 0;
              font-size: 13px;
          }
          .message.ai .bubble code {
              background: #f0f0f0;
              padding: 2px 6px;
              border-radius: 3px;
              font-size: 13px;
          }
          .message.ai .bubble pre code { background: none; padding: 0; }
    
          /* 加载动画 */
          .typing-indicator { display: flex; gap: 4px; padding: 4px 0; }
          .typing-indicator span {
              width: 8px; height: 8px;
              background: #999;
              border-radius: 50%;
              animation: bounce 1.4s infinite;
          }
          .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
          .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
          @keyframes bounce {
              0%, 60%, 100% { transform: translateY(0); }
              30% { transform: translateY(-6px); }
          }
    
          /* 输入区域 */
          .input-area {
              background: #fff;
              padding: 16px 20px;
              border-top: 1px solid #e0e0e0;
              box-shadow: 0 -2px 8px rgba(0,0,0,0.05);
          }
          .input-wrapper {
              max-width: 800px;
              margin: 0 auto;
              display: flex;
              gap: 12px;
          }
          .input-wrapper textarea {
              flex: 1;
              padding: 12px 16px;
              border: 1px solid #ddd;
              border-radius: 10px;
              font-size: 15px;
              resize: none;
              outline: none;
              font-family: inherit;
              transition: border-color 0.2s;
              min-height: 46px;
              max-height: 120px;
          }
          .input-wrapper textarea:focus { border-color: #667eea; }
          .input-wrapper button {
              background: linear-gradient(135deg, #667eea, #764ba2);
              color: #fff;
              border: none;
              padding: 12px 24px;
              border-radius: 10px;
              font-size: 15px;
              cursor: pointer;
              transition: opacity 0.2s;
              white-space: nowrap;
          }
          .input-wrapper button:hover { opacity: 0.9; }
          .input-wrapper button:disabled { opacity: 0.5; cursor: not-allowed; }
    
          /* ========== 知识库侧边栏 ========== */
          .sidebar-overlay {
              display: none;
              position: fixed;
              inset: 0;
              background: rgba(0,0,0,0.3);
              z-index: 100;
          }
          .sidebar-overlay.open { display: block; }
    
          .sidebar {
              position: fixed;
              top: 0; right: -480px;
              width: 460px;
              height: 100vh;
              background: #fff;
              box-shadow: -4px 0 20px rgba(0,0,0,0.1);
              z-index: 101;
              display: flex;
              flex-direction: column;
              transition: right 0.3s ease;
          }
          .sidebar.open { right: 0; }
    
          .sidebar-header {
              padding: 16px 20px;
              border-bottom: 1px solid #e0e0e0;
              display: flex;
              align-items: center;
              justify-content: space-between;
          }
          .sidebar-header h2 { font-size: 18px; }
          .sidebar-header button {
              background: none; border: none;
              font-size: 22px; cursor: pointer;
              color: #999; padding: 4px 8px;
          }
          .sidebar-header button:hover { color: #333; }
    
          .sidebar-body {
              flex: 1;
              overflow-y: auto;
              padding: 16px 20px;
          }
    
          /* 添加知识表单 */
          .add-form {
              background: #f8f9fa;
              border-radius: 10px;
              padding: 16px;
              margin-bottom: 20px;
          }
          .add-form h3 { font-size: 15px; margin-bottom: 12px; color: #555; }
          .add-form input, .add-form textarea, .add-form select {
              width: 100%;
              padding: 10px 12px;
              border: 1px solid #ddd;
              border-radius: 6px;
              font-size: 14px;
              font-family: inherit;
              margin-bottom: 10px;
              outline: none;
          }
          .add-form input:focus, .add-form textarea:focus { border-color: #667eea; }
          .add-form textarea { resize: vertical; min-height: 60px; }
          .add-form .form-actions { display: flex; gap: 8px; }
          .add-form .btn-add {
              background: linear-gradient(135deg, #667eea, #764ba2);
              color: #fff; border: none;
              padding: 8px 20px; border-radius: 6px;
              cursor: pointer; font-size: 14px;
          }
          .add-form .btn-add:hover { opacity: 0.9; }
    
          /* 知识列表 */
          .knowledge-item {
              background: #fff;
              border: 1px solid #eee;
              border-radius: 8px;
              padding: 12px;
              margin-bottom: 10px;
          }
          .knowledge-item .ki-header {
              display: flex; justify-content: space-between; align-items: center;
              margin-bottom: 6px;
          }
          .knowledge-item .ki-category {
              font-size: 12px;
              background: #667eea;
              color: #fff;
              padding: 2px 8px;
              border-radius: 10px;
          }
          .knowledge-item .ki-delete {
              background: none; border: none;
              color: #e74c3c; cursor: pointer;
              font-size: 13px;
          }
          .knowledge-item .ki-delete:hover { text-decoration: underline; }
          .knowledge-item .ki-question {
              font-size: 14px; font-weight: 600; color: #333;
              margin-bottom: 4px;
          }
          .knowledge-item .ki-answer {
              font-size: 13px; color: #666; line-height: 1.5;
              max-height: 60px; overflow: hidden;
          }
    
          .empty-tip {
              text-align: center; color: #999; padding: 40px 0; font-size: 14px;
          }
    
          /* 回答中的图片 */
          .bubble-images {
              display: flex;
              flex-wrap: wrap;
              gap: 8px;
              margin-top: 10px;
          }
          .bubble-images img {
              max-width: 280px;
              max-height: 200px;
              border-radius: 8px;
              object-fit: cover;
              cursor: pointer;
              transition: transform 0.2s;
          }
          .bubble-images img:hover {
              transform: scale(1.03);
          }
      </style>
    </head> <body>

    RAG 知识检索助手

    <label> 知识检索 </label> <button onclick="toggleSidebar()">知识库管理</button> <button onclick="newSession()">新对话</button>
    AI
    你好!我是RAG知识检索助手。你可以向我提问,我会从知识库中检索相关信息并回答。点击右上角「知识库管理」可以管理知识数据。
    复制代码
          <div class="input-area">
              <div class="input-wrapper">
                  <textarea id="userInput" placeholder="输入消息,按 Enter 发送..." rows="1"
                            oninput="autoResize(this)"></textarea>
                  <button id="sendBtn" onclick="sendMessage()">发送</button>
              </div>
          </div>
      </div>

    知识库管理

    <button onclick="toggleSidebar()">×</button>

    添加新知识

    <textarea id="addAnswer" placeholder="知识内容 / 答案" rows="3"></textarea>
    <button class="btn-add" onclick="submitKnowledge()">添加</button>
    复制代码
          <!-- 知识列表 -->
          <div id="knowledgeList"></div>
      </div>
    <script> let sessionId = null; const container = document.getElementById('chatContainer'); const userInput = document.getElementById('userInput'); const sendBtn = document.getElementById('sendBtn'); const ragToggle = document.getElementById('ragToggle');
    复制代码
      // Enter 发送
      userInput.addEventListener('keydown', (e) => {
          if (e.key === 'Enter' && !e.shiftKey) {
              e.preventDefault();
              sendMessage();
          }
      });
    
      function autoResize(el) {
          el.style.height = 'auto';
          el.style.height = Math.min(el.scrollHeight, 120) + 'px';
      }
    
      function scrollToBottom() {
          container.scrollTop = container.scrollHeight;
      }
    
      function addMessage(role, content, images = []) {
          const div = document.createElement('div');
          div.className = `message ${role}`;
    
          const avatar = document.createElement('div');
          avatar.className = 'avatar';
          avatar.textContent = role === 'user' ? '我' : 'AI';
    
          const bubble = document.createElement('div');
          bubble.className = 'bubble';
          bubble.innerHTML = role === 'ai' ? marked.parse(content) : content;
    
          // 如果有图片,追加到气泡下方
          if (images && images.length > 0) {
              const imgBox = document.createElement('div');
              imgBox.className = 'bubble-images';
              images.forEach(url => {
                  const img = document.createElement('img');
                  img.src = url;
                  img.alt = '知识配图';
                  img.onerror = function() { this.style.display = 'none'; };
                  imgBox.appendChild(img);
              });
              bubble.appendChild(imgBox);
          }
    
          div.appendChild(avatar);
          div.appendChild(bubble);
          container.appendChild(div);
          scrollToBottom();
          return bubble;
      }
    
      function addTypingIndicator() {
          const div = document.createElement('div');
          div.className = 'message ai';
          div.id = 'typing';
          div.innerHTML = `
              <div class="avatar">AI</div>
              <div class="bubble"><div class="typing-indicator"><span></span><span></span><span></span></div></div>
          `;
          container.appendChild(div);
          scrollToBottom();
      }
    
      function removeTypingIndicator() {
          const el = document.getElementById('typing');
          if (el) el.remove();
      }
    
      async function sendMessage() {
          const message = userInput.value.trim();
          if (!message) return;
    
          userInput.value = '';
          userInput.style.height = 'auto';
          sendBtn.disabled = true;
    
          const useRag = ragToggle.checked;
          addMessage('user', message);
          addTypingIndicator();
    
          try {
              const res = await axios.post('/api/chat', {
                  message,
                  session_id: sessionId,
                  use_rag: useRag
              });
              removeTypingIndicator();
              sessionId = res.data.session_id;
              addMessage('ai', res.data.reply, res.data.images || []);
          } catch (err) {
              removeTypingIndicator();
              const errMsg = err.response?.data?.error || '请求失败,请重试';
              addMessage('ai', `出错了: ${errMsg}`);
          } finally {
              sendBtn.disabled = false;
              userInput.focus();
          }
      }
    
      async function newSession() {
          try {
              const res = await axios.post('/api/new-session');
              sessionId = res.data.session_id;
              container.innerHTML = '';
              const mode = ragToggle.checked ? '知识检索' : '普通对话';
              addMessage('ai', `新对话已开始(当前模式:${mode})。有什么可以帮你的吗?`);
          } catch (err) {
              alert('创建新对话失败');
          }
      }
    
      // ========== 知识库管理 ==========
    
      function toggleSidebar() {
          document.getElementById('sidebar').classList.toggle('open');
          document.getElementById('sidebarOverlay').classList.toggle('open');
          loadKnowledgeList();
      }
    
      async function loadKnowledgeList() {
          try {
              const res = await axios.get('/api/knowledge');
              const list = res.data.data || [];
              const container = document.getElementById('knowledgeList');
    
              if (list.length === 0) {
                  container.innerHTML = '<div class="empty-tip">暂无知识数据</div>';
                  return;
              }
    
              container.innerHTML = list.map(item => `
                  <div class="knowledge-item">
                      <div class="ki-header">
                          <span class="ki-category">${item.category}</span>
                          <button class="ki-delete" onclick="deleteKnowledge(${item.id})">删除</button>
                      </div>
                      <div class="ki-question">${item.question}</div>
                      <div class="ki-answer">${item.answer}</div>
                  </div>
              `).join('');
          } catch (err) {
              console.error('加载知识列表失败', err);
          }
      }
    
      async function submitKnowledge() {
          const question = document.getElementById('addQuestion').value.trim();
          const answer = document.getElementById('addAnswer').value.trim();
          const category = document.getElementById('addCategory').value.trim();
    
          if (!question || !answer) {
              alert('问题和答案不能为空');
              return;
          }
    
          try {
              await axios.post('/api/knowledge', { question, answer, category });
              document.getElementById('addQuestion').value = '';
              document.getElementById('addAnswer').value = '';
              alert('添加成功,向量索引已更新');
              loadKnowledgeList();
          } catch (err) {
              alert('添加失败: ' + (err.response?.data?.msg || err.message));
          }
      }
    
      async function deleteKnowledge(id) {
          if (!confirm('确定删除该条知识吗?')) return;
          try {
              await axios.delete(`/api/knowledge/${id}`);
              alert('删除成功');
              loadKnowledgeList();
          } catch (err) {
              alert('删除失败');
          }
      }
    
      // 页面加载时创建会话
      newSession();
    </script> </body> </html>

九、完整数据流转示例

以"小鹏P7的排量是多少"为例:

复制代码
1. 用户输入 → "小鹏P7的排量是多少"
2. Embedding → 将问题转为 1536 维向量
3. FAISS 检索 → 找到最相似的文档:小鹏P7的基本参数和性能
4. 上下文拼接 → 将知识内容注入 Prompt
5. LLM 生成 → "小鹏P7为纯电动车型,无发动机排量。后驱版电机最大功率203kW..."
6. 图片返回 → 附带小鹏P7的配图
7. 前端渲染 → 文本 + 图片一起展示

数据库


实现效果

相关推荐
BJ-Giser1 小时前
Cesium 烟雾粒子特效
前端·可视化·cesium
空中海1 小时前
02 ArkTS 语言与工程规范
java·前端·spring
YJlio1 小时前
7.4.5 Windows 11 企业网络连接与网络重置实战:远程访问、本地策略与故障恢复
前端·chrome·windows·python·edge·机器人·django
Slow菜鸟2 小时前
Codex CLI 教程(五)| Skills 安装指南:面向 Java 全栈工程师打造个人 ECC(V1版)
大数据·前端·人工智能
Lee川2 小时前
打字机是怎么炼成的:Chat 流式输出深度解析
前端·后端·面试
前端若水2 小时前
过渡(transition)高级:贝塞尔曲线、硬件加速
前端·css·css3
Lee川2 小时前
Token 无感刷新与 Logout:前端安全会话管理实战
前端·后端·react.js
不会敲代码12 小时前
我写了一个 HTML 文件,把 JS 事件循环彻底搞懂了
前端·javascript·面试
写不来代码的草莓熊2 小时前
SVG 图标插件误读 PNG 图片 + Vite 重启缓存失效重新生成 + 浏览器严格渲染
前端