第4章:实战项目一 打造你的第一个AI知识库问答机器人 (RAG)

各位老铁,欢迎来到我们专栏的第一个实战项目。

在过去的三个章节里,我们已经完成了所有的理论储备和环境搭建。我们理解了LLM的本质,掌握了Prompt Engineering的要领,洞悉了Embedding和向量数据库的魔力,并且熟悉了LangChain这个强大的“瑞士军刀”。

现在,是时候将这些散落的“龙珠”汇集起来,召唤出我们的第一条“神龙”了。

这个项目,是当前AI应用领域最经典、最实用、也是商业价值最高的场景之一:本地知识库问答。我们将从零开始,构建一个完整的系统,它可以:

  1. 读取并“学习”你提供的一份或多份本地文档(例如,一份PDF格式的产品说明书或公司规章制度)。
  2. 当用户用自然语言提问时,系统能够仅仅依据这些文档的内容,给出精准的回答。
  3. 如果文档中没有相关信息,它会明确地告知用户“根据已有信息无法回答”,而不是凭空捏造(产生幻觉)。

这个项目,是RAG(检索增强生成)模式的终极体现。完成它,你将不再是一个AI领域的旁观者,而是一个能够交付真正价值的AI应用工程师

本章内容详尽,代码完整,请务必保持专注,跟上我的节奏。让我们开始构建。

4.1 项目蓝图与技术栈规划

在动工之前,我们首先要像一个架构师一样,画出我们系统的蓝图。一个清晰的架构图,是我们后续所有工作的指导方针。

我们的系统,本质上包含两个核心的、相互独立的流程:

  1. 知识注入流程 (Ingestion Pipeline):这个流程是一次性的(或者定期执行的)。它的任务是将原始文档处理成向量化的知识,并存入向量数据库。
  2. 问答检索流程 (Query Pipeline):这个流程是实时响应用户请求的。它接收用户问题,从向量数据库中检索相关知识,并交由LLM生成最终答案。

下面这张图,就是我们本次项目的核心架构图:

流程二: 问答检索 (在线/实时)
流程一: 知识注入 (离线/一次性)
5. 查询向量化
用户提问
6. 检索相关文档
7. 构建增强Prompt
8. 调用LLM生成回答
返回精准答案
2. 文本分割
1. 加载文档 (PDF)
3. 文本向量化
4. 存入向量数据库

基于这个蓝图,我们确定本次项目的技术栈:

  • 核心框架: Python 3.10+
  • AI编排: LangChain - 我们整个工作流的粘合剂。
  • 大语言模型 (LLM): 通义千问 (qwen-turbo) - 由阿里云达摩院提供,负责最终的理解和生成。
  • Embedding模型: 通义千问文本向量模型 (text-embedding-v1) - 负责将文本翻译成向量。
  • 向量数据库: ChromaDB - 一个轻量级、开源、可本地运行的向量数据库。
  • 文档加载: PyPDFLoader - LangChain集成的一个用于加载PDF文档的工具。
  • 交互界面: Streamlit - 一个能用纯Python快速构建Web UI的库。

准备工作:

  1. 获取API Key:

    • 访问阿里云百炼平台。
    • 开通服务并进入控制台,在“API-KEY管理”中创建你的专属API Key。
    • 严格遵循我们的安全准则:在你的项目根目录创建.env文件,并写入:
      DASHSCOPE_API_KEY="sk-YourAlibabaCloudApiKey"
      
      同时,将.env加入.gitignore文件。
  2. 安装所有依赖:

    • 确保你已激活之前创建的venv虚拟环境。
    • 执行以下命令,一次性安装所有我们需要的库:
      pip install langchain langchain-community dashscope chromadb pypdf streamlit langchain-chroma
      
      • langchain-chroma: ChromaDB与LangChain集成的最新独立包,我们遵循最佳实践,使用这个版本。
  3. 准备一份测试文档:

    • 在你的项目根目录下,创建一个名为docs的文件夹。
    • 找一份你感兴趣的PDF文档放进去,比如一份产品说明书、一篇论文、或者一份公开的报告。

    在这里插入图片描述

一切就绪,让我们开始编写知识注入流程的代码。

4.2 流程一:构建知识注入管道 (ingest.py)

这个脚本的任务是读取docs文件夹下的所有PDF,将它们处理后存入本地的ChromaDB数据库。这个脚本只需要运行一次。

在你的项目根目录,创建一个名为ingest.py的文件,并写入以下代码:

import os
from dotenv import load_dotenvfrom langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_chroma import Chroma# 加载.env文件中的环境变量
load_dotenv()# 定义文档目录和Chroma持久化目录
DOCS_PATH = "docs"
CHROMA_PATH = "chroma_db"def main():"""主执行函数:1. 加载文档2. 分割文档3. 向量化并存入ChromaDB"""print("--- 开始知识注入流程 ---")# 1. 加载文档print(f"[步骤1] 从 '{DOCS_PATH}' 目录加载PDF文档...")if not os.path.exists(DOCS_PATH):print(f"错误:文档目录 '{DOCS_PATH}' 不存在。请创建该目录并放入PDF文件。")returnloader = DirectoryLoader(DOCS_PATH, glob="*.pdf", loader_cls=PyPDFLoader)documents = loader.load()if not documents:print("错误:在'docs'目录中未找到任何PDF文件。")returnprint(f"成功加载 {len(documents)} 个文档。")# 2. 分割文档print("\n[步骤2] 将加载的文档分割成小块(chunks)...")text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)chunks = text_splitter.split_documents(documents)print(f"文档被成功分割成 {len(chunks)} 个小块。")# 3. 向量化并存入ChromaDBprint("\n[步骤3] 初始化Embedding模型并创建Chroma数据库...")embeddings = DashScopeEmbeddings(model="text-embedding-v1")db = Chroma.from_documents(chunks, embeddings, persist_directory=CHROMA_PATH)print(f"\n--- 知识注入成功!---")print(f"向量化数据已成功存入 '{CHROMA_PATH}' 目录。")print("现在您可以运行查询脚本或Web UI来与您的知识库进行交互了。")if __name__ == "__main__":main()

代码解析:

  • DirectoryLoader: LangChain提供的便捷工具,自动遍历docs目录,使用PyPDFLoader加载所有PDF文件。
  • RecursiveCharacterTextSplitter: 智能文本分割器。chunk_size=500定义了每个文本块的目标大小,chunk_overlap=100确保块之间的上下文连续性。
  • DashScopeEmbeddings: LangChain对通义千问文本向量模型的封装。我们只需实例化,LangChain便会在需要时自动调用它。
  • langchain_chroma.Chroma: 我们使用最新的独立包来与ChromaDB交互。Chroma.from_documents方法是一个强大的“三合一”操作,它会自动完成文本块的向量化、存储,并通过persist_directory参数将数据库完整地写入本地磁盘。

如何运行?

在终端(已激活虚拟环境)中,直接运行此脚本:

python ingest.py

脚本执行完毕后,你的项目目录下会多出一个名为chroma_db的文件夹,这就是我们AI的“长期记忆体”!

4.3 流程二:构建问答检索管道 (app.py)

知识库已经建好,现在我们要构建一个能使用这个知识库的问答系统。我们直接使用Streamlit来构建一个简单的Web界面。

在你的项目根目录,创建一个名为app.py的文件,并写入以下代码:

import os
import streamlit as st
from dotenv import load_dotenvfrom langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_chroma import Chroma
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA# 加载.env文件中的环境变量
load_dotenv()# --- 全局配置 ---
CHROMA_PATH = "chroma_db"
PROMPT_TEMPLATE = """
请注意:你是一个专业的AI助手,你的任务是根据下面提供的“背景信息”来回答问题。
请严格依据“背景信息”的内容进行回答,不要自行发挥或使用你的内部知识。
如果“背景信息”中没有足够的内容来回答问题,请直接说“根据已有信息,我无法回答该问题。”。
不允许在答案中添加任何非“背景信息”中的内容。背景信息:
{context}问题:
{question}请给出你的回答:
"""def main():"""主函数,构建Streamlit Web应用"""st.set_page_config(page_title="阿威的AI知识库", page_icon="🤖")st.title("🤖 阿威的AI知识库")st.markdown("你好!我是你的AI助手。请在下方输入框中提出你关于已上传文档的问题。")@st.cache_resourcedef initialize_components():if not os.path.exists(CHROMA_PATH):st.error(f"错误:向量数据库目录 '{CHROMA_PATH}' 不存在。请先运行 'ingest.py' 来创建数据库。")return None, Noneembeddings = DashScopeEmbeddings(model="text-embedding-v1")db = Chroma(persist_directory=CHROMA_PATH, embedding_function=embeddings)llm = ChatTongyi(model_name="qwen-turbo")return db, llmdb, llm = initialize_components()if db is None or llm is None:returnretriever = db.as_retriever()prompt = PromptTemplate(template=PROMPT_TEMPLATE, input_variables=["context", "question"])qa_chain = RetrievalQA.from_chain_type(llm=llm,chain_type="stuff",retriever=retriever,return_source_documents=True,chain_type_kwargs={"prompt": prompt})query = st.text_input("请输入你的问题:", placeholder="例如:阿里巴巴开发手册里藏着什么宝贝?")if st.button("提问"):if not query:st.warning("请输入问题后再提问。")else:with st.spinner("AI正在思考中,请稍候..."):try:result = qa_chain.invoke({"query": query})st.subheader("💡 AI的回答:")st.write(result["result"])with st.expander("查看答案来源"):for doc in result["source_documents"]:st.info(f"来源文件: {os.path.basename(doc.metadata.get('source', '未知'))}")st.write(doc.page_content)st.divider()except Exception as e:st.error(f"在回答问题时发生错误: {e}")if __name__ == "__main__":main()

代码解析:

  • st.cache_resource: Streamlit的缓存装饰器,用于缓存模型加载、数据库连接等耗时操作,避免重复执行,提升应用响应速度。
  • Chroma(...): 这里我们直接实例化Chroma类,通过persist_directory从本地加载数据库。embedding_function是必须提供的,因为Chroma需要用同一个Embedding模型来向量化用户的查询。
  • ChatTongyi: LangChain对通义千问聊天模型的封装,我们选择qwen-turbo模型以平衡性能和成本。
  • PROMPT_TEMPLATE: 这是我们RAG系统的灵魂!我们通过严格的指令为LLM设定了行为边界,包括角色扮演、核心指令、兜底策略和幻觉抑制,这直接决定了系统的可靠性。
  • db.as_retriever(): 将ChromaDB实例转换为一个标准的LangChain Retriever对象,它封装了所有检索逻辑。
  • RetrievalQA.from_chain_type: LangChain提供的构建RAG应用的便捷Chain。chain_type="stuff"表示将检索到的所有文档片段全部“塞”进Prompt的{context}部分,这是最直接的策略。

如何运行?

注意! Streamlit应用有其专属的启动方式。你不能使用python app.py来运行它。

请在终端(已激活虚拟环境)中,使用以下正确的命令来启动Web应用:

streamlit run app.py

执行该命令后,你的浏览器会自动打开一个新标签页,显示出我们应用的交互界面。现在,你可以像使用聊天机器人一样,输入你关于文档内容的问题,然后点击“提问”按钮,见证AI的强大能力。
示例:
在这里插入图片描述

4.4 总结、挑战与展望

恭喜你!你已经成功地从零开始,构建并运行了一个功能完备、逻辑严谨的AI知识库问答系统。

让我们回顾一下我们所取得的成就:

  1. 我们设计并实现了分离的知识注入和问答检索两大流程,这是工程上的最佳实践。
  2. 我们掌握了使用LangChain整合文档加载、文本分割、Embedding、向量存储的全过程。
  3. 我们学会了如何构建一个高质量的RAG Prompt,来约束LLM的行为,抑制幻觉。
  4. 我们成功地将**检索器(Retriever)大语言模型(LLM)**通过RetrievalQA链无缝地结合在一起。
  5. 我们还用Streamlit为我们的应用穿上了一件漂亮的“外衣”,让它变得可用、可交互。

然而,作为一个严谨的工程师,我们也要清醒地认识到,我们这个V1版本的系统还存在一些挑战和可优化的空间

  • 检索质量: RAG系统的天花板取决于检索器能否找对相关的文档。如何优化检索效果是一个重要的课题。
  • 分块策略 (Chunking): chunk_sizechunk_overlap的设置对结果影响很大,需要根据文档类型进行调整。
  • 成本与延迟: 在生产环境中,需要对API调用和数据库查询的延迟与成本进行监控和优化。
  • 评估体系: 如何科学地评估问答系统的好坏?这需要一套评估标准和数据集,是AI应用工程化中的重要一环。

这些挑战,也正是我们未来继续深入学习和探索的方向。

在本章之后,你已经掌握了构建“知识型”AI应用的核心技能。在下一章,我们将探索一个更令人兴奋的领域——AI智能体(Agent)。我们将赋予AI“手”和“脚”,让它不再仅仅是“回答”问题,而是能够“执行”任务,成为一个真正能为你干活的“数字员工”。

实战之旅,精彩继续。我们下期见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/913723.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913723.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

身份证识别api-便捷生活与安全社会的双重保障

身份证识别技术是人工智能和图像处理领域的杰出产物之一,正逐步渗透到我们生活的方方面面。而最直观的作用就是简化身份证验证流程。现如今,无论是银行开户、酒店入住还是政务办理、线上支付,都需要输入 身份证信息进行身份验证,传…

跨国企业进入中国市场:如何利用亚马逊云科技文档 MCP 服务器解决区域差异问题

业务场景 想象一下,您是一家美国科技公司的 IT 架构师,公司刚刚决定将业务扩展到中国市场。作为技术负责人,您需要规划如何将现有的基于亚马逊云科技的应用迁移到中国区域。然而,您很快发现中国区的云服务环境与您熟悉的全球区域…

WPF使用WebBrowser 解决href标签target=_blank在浏览器窗口打开新链接而非窗体内部打开的问题

前言 最近在WPF中使用WebBrowser控件显示网页的时候遇到一个问题,由于网页里面有大规模的连接标签使用了target=_blank的属性,导致打开的网页不是在我们的程序内部,而是调用系统浏览器打开了我们的网页内容,这种情况非常的影响用户体验。于是就有了这篇文章内容。本文将详细…

制作MikTex本地包可用于离线安装包

MikTex安装包版本是basic-miktex-24.1-x64.exe。注:basic版本表示只安装MikTex基本包,不安装全部包。在能够联网的电脑上安装MikTex软件后,可以按以下步骤制作本地包库。一、制作本地包库1、新建一个文件夹,比如在D盘新建miktex-l…

Redis基础的介绍与使用(一)(Redis简介以及Redis下载和安装)

0 引言 本系列用于和大伙儿一起入门Redis,主要包括Redis的下载,分别在终端,图形显示界面以及JAVA代码中进行使用,适合给需要快速了解Redis是什么以及上手使用的朋友们,希望我用最简单的语言来讲清楚相关内容&#xff…

七牛云C++开发面试题及参考答案

智能指针的原理及应用场景是什么? 智能指针是 C 中用于管理动态分配内存的工具,其核心原理是通过 RAII(资源获取即初始化)技术,将堆内存的生命周期与对象的生命周期绑定,从而避免手动管理内存带来的内存泄…

【Python办公】Excel横板表头转竖版通用工具(GUI版本)横向到纵向的数据重构

目录 专栏导读前言项目概述功能特性技术栈核心代码解析1. 类结构设计2. 界面布局设计3. 滚动列表实现4. 数据转换核心逻辑5. 预览功能实现设计亮点1. 用户体验优化2. 技术实现优势3. 代码结构优势使用场景扩展建议总结完整代码结尾专栏导读 🌸 欢迎来到Python办公自动化专栏—…

C#项目 在Vue/React前端项目中 使用使用wkeWebBrowser引用并且内部使用iframe网页外链 页面部分白屏

如果是使用wkeWebBrowser的引用方式 非常有可能是版本问题导致的 问题分析 1. wkeWebBrowser 的局限性 不支持或不完全支持 ES6 语法(如 let, const, Promise, async/await) 缺少对现代 Web API 的支持(如 Intl, fetch, WebSocket&#xff0…

系统架构设计师论文分享-论微服务架构

我的软考历程 摘要 2023年2月,我所在的公司通过了研发纱线MES系统的立项,该系统为国内纱线工厂提供SAAS服务,旨在提高纱线工厂的数字化和智能化水平。我在该项目中担任系统架构设计师一职,负责该项目的架构设计工作。本文结合我…

The History of Big Data

数据洪流悄然重塑世界的进程中,大数据的历史是技术迭代与需求驱动的交响。从 2003 年分布式系统雏形初现,到 Hadoop 掀起开源浪潮,再到 Spark、容器化技术与深度学习的接力革新,以及 Hadoop 生态的兴衰起落,大数据发展…

【JS逆向基础】数据分析之正则表达式

前言:前面介绍了关于JS逆向所需的基本知识,比如前端三件套等,从这里开始就要进入到数据分析的范围内了,当然对于一些小白而言一些基本的知识还是需要知道的,比如正则,XPATNY与BS4;三个内容用三篇…

Mac mini 高性价比扩容 + Crossover 游戏实测 全流程手册

Mac mini 高性价比扩容 Crossover 游戏实测 全流程手册 本文将图文并茂地指导你如何: 为 M4 Mac mini 外置扩容(绿联 USB4 硬盘盒 致态 TiPlus7100)安装并配置 Crossover/Whisky 运行 Windows 应用实测游戏运行性能、诊断常见异常一、准备工…

【PyTorch】PyTorch中torch.nn模块的卷积层

PyTorch深度学习总结 第七章 PyTorch中torch.nn模块的卷积层 文章目录PyTorch深度学习总结前言一、torch.nn模块1. 模块的基本组成部分1.1 层(Layers)1.2 损失函数(Loss Functions)1.3 激活函数(Activation Functions…

Rust简洁控制流:if let与let else高效编程指南

文章目录Rust简洁控制流:if let与let else高效编程指南🎯 if let:专注单一匹配场景💡 if let核心优势:🔄 if let与else搭配使用🚀 let else:错误处理与提前返回💎 let el…

upload-labs靶场通关详解:第19关 条件竞争(二)

一、分析源代码//index.php // 初始化变量:标记上传状态和错误消息 $is_upload false; $msg null;// 检查是否通过POST方式提交了表单 if (isset($_POST[submit])) {// 引入自定义上传类require_once("./myupload.php");// 生成基于时间戳的文件名&…

一天两道力扣(3)

解法一:class Solution(object):def invertTree(self, root):if not root:return Noneroot.left, root.right root.right, root.leftself.invertTree(root.right)self.invertTree(root.left)return root解析:递归解法二:class Solution(obje…

jenkins2025安装、插件、邮箱发送使用

Tips:卸载从新安装(需要在C盘线先删除.jenkins文件),然后换个默认浏览器从新安装推荐的插件(不然安装插件这一步会报错,连接不到jenkins) 一、jenkins安装 访问jenkins官网:https://www.jenkins.io/download/ 双击war包开始下载…

vue中通过tabs 切换 时 显示不同的echarts 特殊处理

需要进行特殊处理 比如强制 进行resize 的方法 不然 大小显示会出现问题我先把全部的代码弄上<script setup lang"ts"> import { ref, onMounted, onBeforeUnmount, nextTick } from vue import { useRoute } from vue-router import { message } from ant-des…

浅度解读-(未完成版)浅层神经网络-深层神经网络

文章目录浅层神经网络的前向传播计算流程矩阵在运算时形状的变化激活函数的作用为什么要有激活函数反向传播深层神经网络参数超参数参数初始化初始化权重的值选择浅层神经网络的前向传播 计算流程 #mermaid-svg-tMPs4IUCtqxvhJ24 {font-family:"trebuchet ms",verda…

【vben3源码解读】【useEcharts】【VueUse】详解useEcharts这个hooks的作用与相关库的使用(VueUse)

源代码 import type { EChartsOption } from echarts;import type { Ref } from vue;import type { Nullable } from vben/types;import type EchartsUI from ./echarts-ui.vue;import { computed, nextTick, watch } from vue;import { usePreferences } from vben/preference…