从零实现一个基于ReAct+RAG的智能问答Agent,我把踩过的坑都整理好了
从零实现一个基于ReAct+RAG的智能问答Agent,我把踩过的坑都整理好了
好的,我来帮你写一篇CSDN风格的技术博客,大概1.5万字,通俗易懂,帮你巩固这个项目。
先规划好了,这篇博客我会用CSDN的风格来写,口语化、有干货、带踩坑记录,帮你彻底巩固这个项目。现在开始写。
文件太大一次写入超时了,我分批来写。先看看文件有没有部分生成。
我分成两部分来写,这样不容易超时。先写前半部分。
写入工具老是超时,我换个方式,用Python脚本来写文件。先写第一部分。
让我先试试简单的命令,看看环境是不是正常。
可能是内容太大导致超时。我先写个小文件测试一下。
让我试试其他工具能不能正常工作。
看来文件写入工具暂时有点问题,没关系,我直接把博客内容写在回复里,你可以复制保存。这篇博客我会用CSDN的风格来写,口语化、有干货、带踩坑记录,帮你彻底巩固这个项目。
从零实现一个基于ReAct+RAG的智能问答Agent,我把踩过的坑都整理好了
前言
大家好,我是一名正在学习大模型应用开发的研究生。最近跟着导师做了一个扫地机器人智能问答Agent的项目,用的是ReAct+RAG的技术方案。
说实话,一开始我是懵的——什么ReAct、什么RAG、什么LangChain,听着都很高大上,但真的上手的时候,连环境都搭了半天。
现在项目终于跑通了,我想着把整个过程整理成一篇博客,一方面是给自己做个总结,另一方面也希望能帮到和我一样刚入门的同学。毕竟我踩过的坑,你们就别再踩了。
这篇博客大概1.5万字,我会从最基础的概念讲起,到环境搭建、项目结构、核心代码、踩坑记录,一条龙服务。如果你也是刚接触大模型应用开发,或者想做一个自己的Agent项目,相信这篇文章会对你有帮助。
你能从这篇文章学到什么:
-
搞懂RAG和ReAct到底是啥(用人话讲,不整虚的)
-
知道LangChain这个框架到底能干啥
-
从零搭一个能跑的智能问答Agent
-
了解Agent项目的代码结构怎么设计
-
避开我踩过的那些坑
废话不多说,咱们开始。
一、先搞懂:这项目到底是干啥的?
在讲技术之前,先搞明白一个最基本的问题:我们做这个项目,到底是为了解决啥问题?
1.1 背景:智能客服的痛点
现在扫地机器人越来越普及了,谁家还没个扫地机器人呢对吧?但用户买回去之后,问题也跟着来了:
-
“边刷怎么换啊?”
-
“机器卡住了怎么办?”
-
“故障代码E10是什么意思?”
-
“怎么连接WiFi?”
这些问题说难不难,但量大啊。如果全靠人工客服来答,那成本可就高了。而且人工客服还有个问题——水平参差不齐,同一个问题,不同客服可能给的答案都不一样。
那有人说了,用关键词匹配的自动客服不行吗?
还真不太行。用户的问法千奇百怪,比如同样是问边刷怎么换,有人说"边刷怎么换",有人说"怎么更换边刷",有人说"边刷坏了咋整",关键词匹配很容易漏。而且关键词匹配只能答预设好的问题,稍微复杂一点、需要推理的,它就不行了。
1.2 大模型的出现,带来了新的可能
大模型火了之后,大家就想:能不能用大模型来做智能客服?
大模型确实厉害,能理解自然语言,回答也很流畅。但直接用大模型做客服,又有新的问题:
第一个问题:幻觉。
就是大模型会一本正经地胡说八道。比如你问它"故障代码E10是什么意思",它可能会给你编一个听起来很合理但完全错误的答案。用户要是照着做,机器可能就真坏了。
第二个问题:专业知识不够。
大模型是通用模型,什么都知道一点,但什么都不精。扫地机器人这种垂直领域的专业知识,它可能就答不好。
第三个问题:知识更新难。
出了新机型、有了新功能,总不能每次都重新训练大模型吧?那成本也太高了。
1.3 我们的解决方案:ReAct + RAG
那怎么办呢?业界的主流方案就是RAG加上Agent。
简单说:
-
RAG(检索增强生成):让大模型回答问题之前,先去知识库查一下相关资料,然后照着资料来回答。这样就不会瞎编了,而且更新知识只要更新知识库就行。
-
ReAct(推理+行动):让大模型像人一样思考——先想想该干啥,然后调用工具去干,干完了看看结果,再决定下一步。这样就能处理更复杂的问题。
我们这个项目,就是把这两个技术结合起来,做一个针对扫地机器人领域的智能问答Agent。
说起来好像挺简单,但真的做起来,细节还是挺多的。别急,后面我会一点点拆开来讲。
二、核心概念扫盲:RAG、ReAct、LangChain都是啥?
在动手写代码之前,先把几个核心概念搞明白。不然后面看代码你会一头雾水。
2.1 RAG:让大模型"开卷考试"
RAG的全称是Retrieval-Augmented Generation,翻译过来叫"检索增强生成"。
名字听着挺玄乎,其实原理特别简单。
你就想啊,我们上学考试的时候,闭卷考试容易考砸,开卷考试就好多了——因为可以查资料嘛。
大模型也是一样。直接让它回答问题,相当于闭卷考试,它只能靠自己"记忆"里的知识,就容易记错、瞎编。
RAG就是让大模型"开卷考试"——回答问题之前,先去知识库(也就是"教材")里查一下相关的内容,然后照着查出来的内容来回答。
这样一来,答案就有依据了,不容易胡说八道。而且知识库更新也方便,有新的知识了直接加进去就行,不用重新训练模型。
RAG的工作流程
RAG分为两个阶段:建库阶段和查询阶段。
建库阶段(离线做,做一次就行):
-
文档加载:把PDF、Word、TXT这些文档读进来
-
文本分块:把长文档切成一小块一小块的(比如每块300字)。为什么要切?因为太长了模型塞不下,而且检索的时候也不精准
-
向量化:把每一块文本都转成一个向量(就是一串数字)。这个向量能代表这段文本的"意思",意思相近的文本,向量也相近
-
存入向量数据库:把这些向量存起来,方便后面快速查找
查询阶段(用户提问时实时做):
-
问题向量化:把用户的问题也转成向量
-
相似度检索:拿着问题的向量,去向量数据库里找最像的几个文本块(就是找最相关的内容)
-
拼接提示词:把找到的相关内容和用户的问题拼在一起,组成一个完整的提示词
-
生成答案:把提示词发给大模型,让大模型照着资料来回答
是不是挺简单的?核心就是"先查再答"四个字。
2.2 ReAct:让大模型学会"思考+行动"
ReAct的全称是Reasoning + Acting,也就是推理加行动。
这个也很好理解。你就想我们人是怎么解决问题的:
比如你想做番茄炒蛋,你不会一下子就把菜做好,而是一步一步来:
-
先想想:我得先看看冰箱里有没有食材
-
然后行动:打开冰箱看看
-
然后观察:哦,有番茄但没鸡蛋
-
再想想:那得去买鸡蛋
-
再行动:下楼去超市买
-
再观察:买回来了
-
再想想:现在可以开始做了
-
...
你看,这就是一个"思考→行动→观察→再思考"的循环。
ReAct就是让大模型也这么干。它不是一下子就给出答案,而是一步一步来:
-
Thought(思考):分析当前的情况,决定下一步该做什么
-
Action(行动):调用对应的工具去执行
-
Observation(观察):获取工具执行的结果
然后把观察结果再反馈给模型,继续思考下一步,直到任务完成。
ReAct有啥好处?
第一,能处理复杂任务。 有些问题不是一步就能解决的,需要多步推理、调用多个工具,ReAct就能搞定。
第二,可解释性强。 每一步模型是怎么想的、做了什么、得到了什么结果,都看得清清楚楚,不是一个黑盒。出了问题也容易排查。
第三,能力容易扩展。 想加新能力?加个新工具就行了,不用重新训练模型。
我们这个项目里,ReAct Agent就是"大脑",负责思考和决策;RAG就是其中一个"工具",负责查知识库。
2.3 LangChain:大模型应用开发的"瑞士军刀"
LangChain是一个专门用来做大模型应用开发的框架。
你可以把它理解成一个"工具箱",里面有各种各样现成的工具和组件,你直接拿来用就行,不用从零开始写。
比如:
-
想加载文档?它有各种Document Loader
-
想分块?它有各种Text Splitter
-
想接大模型?它封装了各种LLM的接口
-
想做RAG?它有现成的Chain
-
想做Agent?它也有Agent框架
-
想接向量数据库?它也支持各种主流的向量库
总之,LangChain把大模型应用开发中常用的功能都封装好了,你只需要调用它的API,就能快速搭出一个应用。
当然,LangChain也不是完美的,它封装得比较深,有时候出了问题不好排查。但对于新手来说,用它来入门还是挺香的,能让你快速看到效果,建立信心。
2.4 几个概念的关系
最后再梳理一下这几个概念的关系,别搞混了:
-
RAG:一种技术方法,“先查再答”
-
ReAct:一种Agent框架,"思考→行动→观察"循环
-
LangChain:一个开发框架,里面有RAG和ReAct的实现
-
向量数据库:用来存向量的,是RAG的基础设施
我们这个项目,就是用LangChain这个框架,实现了一个基于ReAct的Agent,这个Agent可以调用RAG工具来回答问题。
好了,概念就讲到这里。下面咱们进入实操环节。
三、环境搭建:手把手教你把环境搭起来
理论讲完了,咱们开始动手。第一步就是搭环境。
别小看环境搭建,我当初在这一步踩了不少坑。下面我把完整的步骤写出来,你跟着做就行。
3.1 整体环境清单
先列一下我们需要装的东西:
-
Python 3.9+
-
Ollama(用来本地跑大模型)
-
项目依赖的Python库
-
(可选)Git
3.2 安装Python
这个应该不用我多说了吧?做Python开发的电脑上基本都有。
注意版本要3.9以上,建议用3.10或者3.11,比较稳定。
装的时候Windows用户记得勾选"Add Python to PATH",不然命令行里找不到。
验证一下:
python --version
能输出版本号就OK。
3.3 安装Ollama
Ollama是一个本地部署大模型的工具,特别好用,一行命令就能把模型跑起来。
下载安装:
去官网(ollama.com)下载对应系统的安装包,双击安装就行,没啥难度。
验证安装:
打开命令行,输入:
ollama --version
能输出版本号就说明装好了。
下载模型:
我们这个项目用两个模型:
-
大模型:deepseek-r1:1.5b(1.5B参数,比较小,普通电脑就能跑)
-
向量模型:bge-m3:latest(用来生成向量的)
拉取模型:
ollama pull deepseek-r1:1.5b
ollama pull bge-m3:latest
拉取需要一点时间,取决于你的网速,耐心等一下。
验证模型:
拉完之后可以测试一下:
ollama run deepseek-r1:1.5b
能进入对话界面就说明没问题。输入/exit退出。
💡 踩坑提醒:
如果你的电脑配置比较低,1.5B的模型跑起来都费劲,可以试试更小的,比如qwen2:0.5b。
另外,如果有NVIDIA显卡,一定要装CUDA,Ollama会自动用GPU加速,速度会快很多。
3.4 创建虚拟环境(强烈建议)
虽然不是必须的,但我强烈建议你用虚拟环境。每个项目一个独立的Python环境,互不干扰,不会因为版本问题打架。
创建虚拟环境:
在项目根目录下打开命令行,输入:
python -m venv .venv
这会在当前目录下创建一个.venv文件夹。
激活虚拟环境:
Windows下:
.venv\Scripts\activate
Mac/Linux下:
source .venv/bin/activate
激活之后,命令行前面会出现(.venv)的提示,说明已经在虚拟环境里了。
💡 小技巧:
后面所有的pip install和运行命令,都要先激活虚拟环境再执行。别搞混了。
3.5 安装项目依赖
激活虚拟环境之后,安装依赖:
pip install -r requirements.txt
如果下载慢,可以用国内镜像源:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
💡 踩坑提醒:
如果某个库安装失败,先看错误信息。大概率是缺少系统依赖,或者网络问题。
实在不行,可以试试单独安装那个失败的库,有时候批量装会出问题,单独装就好了。
3.6 准备数据
项目的data文件夹里应该有一些示例数据,比如"扫地机器人100问.pdf"之类的。
如果没有的话,你自己准备一些知识库文档放进去也行,支持PDF、Word、TXT这些格式。
第一次运行的时候,系统会自动从data文件夹加载文档,构建向量数据库。这个过程可能需要一点时间,取决于文档大小。
3.7 运行项目
一切准备就绪,就可以跑项目了。项目提供了三种运行方式,选一个你喜欢的。
方式一:Flask API服务
python app.py
启动后监听8002端口,可以用Postman或者curl测试。
方式二:Gradio Web界面
python app_web_gradio.py
启动后会显示一个本地地址,浏览器打开就能看到交互界面,简单直观。
方式三:Streamlit Web界面
streamlit run app_web_streamlit.py
Streamlit的界面更丰富一些,功能也更多。
💡 新手建议:
建议用Gradio或者Streamlit,有界面,直观,容易看到效果。Flask API适合后端调用。
四、项目结构解析:每个文件夹、每个文件都是干啥的?
环境搭好了,先别急着跑代码。咱们先把项目结构搞清楚,知道每个文件是干啥的,后面看代码才不会晕。
4.1 整体目录结构
我把项目的目录结构整理了一下,大概是这样的:
ZST_code/
├── app.py # Flask入口文件
├── app_web_gradio.py # Gradio界面入口
├── app_web_streamlit.py # Streamlit界面入口
├── requirements.txt # 依赖清单
├── Agent/ # Agent层(大脑)
│ ├── ReAct.py # ReAct Agent核心
│ └── Action.py # Action数据模型
├── Tools/ # 工具层(手)
│ ├── Tools.py # 工具注册和管理
│ ├── RagQATool.py # RAG问答工具
│ ├── VecStore.py # 向量数据库管理
│ ├── ReportGenerationTool.py # 报告生成工具
│ ├── ExtractInfo.py # 信息提取
│ └── FetchFromOtherSys.py # 对接其他系统
├── Models/ # 模型层(底子)
│ └── Factory.py # 模型工厂
├── Configs/ # 配置文件
│ └── config.yml # 主配置
├── prompts/ # 提示词
│ ├── main.txt # 主提示词
│ └── report_prompt.txt # 报告生成提示词
├── data/ # 原始数据
│ └── 扫地机器人100问.pdf
├── Utils/ # 工具函数
│ ├── FileReader.py # 文件读取
│ └── PrintHandler.py # 打印处理
├── Chroma/ # 向量数据库(自动生成)
├── Eval/ # 评估相关
├── Tests/ # 测试
└── Results/ # 结果输出
看着文件挺多的,其实核心的就那么几个。咱们一层一层来看。
4.2 分层架构设计
这个项目采用的是分层架构,从上到下一共五层:
-
用户交互层:就是那几个app开头的文件,负责和用户打交道
-
Agent层:Agent文件夹,是系统的"大脑",负责思考和决策
-
工具层:Tools文件夹,是系统的"手",负责具体干活
-
模型层:Models文件夹,是系统的"底子",提供大模型和向量模型能力
-
数据层:data文件夹,存放原始的知识库文档
为什么要分层?因为这样职责清晰,每层只干自己的事。以后想改哪一层,不会影响其他层。比如想换个Web框架,只改用户交互层就行,Agent层完全不用动。
这是一种很经典的架构设计思想,不光是AI项目,普通的Web项目也是这么设计的。
4.3 核心文件简介
我挑几个最核心的文件,简单说一下它们是干啥的:
| 文件 | 作用 | 重要程度 |
|---|---|---|
Agent/ReAct.py |
ReAct Agent的核心实现,Agent怎么思考、怎么调用工具,都在这里 | ⭐⭐⭐⭐⭐ |
Agent/Action.py |
Action的数据模型,定义了工具调用的格式 | ⭐⭐⭐ |
Tools/Tools.py |
工具注册和管理,有哪些工具、怎么调用,都在这里 | ⭐⭐⭐⭐ |
Tools/RagQATool.py |
RAG问答工具的实现 | ⭐⭐⭐⭐ |
Tools/VecStore.py |
向量数据库的管理,建库、加载、检索 | ⭐⭐⭐⭐ |
Models/Factory.py |
模型工厂,创建和管理大模型、向量模型 | ⭐⭐⭐ |
app.py |
Flask入口,API服务 | ⭐⭐ |
Configs/config.yml |
配置文件 | ⭐⭐ |
后面我会挑最重要的几个文件,逐行来讲代码。
五、核心代码精讲:彻底搞懂每一行(上)
终于到了最核心的部分——代码精讲。
我会挑几个最核心的文件,一行一行地讲。别怕,我会用大白话讲,保证你能听懂。
5.1 Action.py:先定义"行动"的格式
我们先从最简单的开始——Action.py。这个文件虽然短,但很重要,它定义了Agent调用工具的格式。
from pydantic import BaseModel, Field
class Action(BaseModel):
tool_name: str = Field(description="工具名称")
args: dict = Field(description="工具参数,字典格式")
就这么几行。啥意思呢?
这是用Pydantic定义了一个数据模型,叫Action。它有两个字段:
-
tool_name:工具的名字,字符串类型 -
args:工具的参数,字典格式
比如,Agent想调用RAG问答工具,问"扫地机器人卡住了怎么办",那Action就是这样的:
{
"tool_name": "rag_qa_tool",
"args": {
"query": "扫地机器人卡住了怎么办"
}
}
为什么要定义这个?因为Agent的输出是文本,我们需要把它解析成结构化的数据,这样才能知道它想调用哪个工具、传什么参数。
Pydantic是一个很有用的库,它可以:
-
定义数据结构
-
自动做类型验证
-
生成JSON Schema(可以用来告诉模型输出格式)
后面我们会看到,这个Action类不光是用来存数据的,它的Schema还会被放进提示词里,告诉模型应该输出什么格式。
5.2 ReAct.py:Agent的大脑,核心中的核心
接下来是重头戏——ReAct.py。这是整个Agent的核心,Agent怎么思考、怎么调用工具,都在这里。
这个文件稍微有点长,我分段来讲。
5.2.1 导入依赖
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser, OutputFixingParser
from langchain_core.runnables import RunnablePassthrough
from langchain.tools.base import StructuredTool
from Models.Factory import llm
from Agent.Action import Action
from typing import List
导入了一堆东西,我们一个个说:
-
ChatPromptTemplate:聊天提示词模板,用来构建提示词 -
PydanticOutputParser:Pydantic输出解析器,把模型输出的文本解析成Action对象 -
OutputFixingParser:输出修复解析器,解析失败的时候自动让模型修正 -
RunnablePassthrough:这个是LCEL里的,简单说就是"透传",输入啥输出啥 -
StructuredTool:结构化工具,用来把普通函数包装成Agent能调用的工具 -
llm:从Models.Factory导入的大模型实例 -
Action:刚才讲的Action数据模型 -
List:类型提示用的
5.2.2 ReActAgent类
class ReActAgent:
def __init__(self, tools: List[StructuredTool]):
self.tools = tools
self.tool_names = [tool.name for tool in tools]
self.tool_descriptions = "\n".join([f"{tool.name}: {tool.description}" for tool in tools])
self.parser = PydanticOutputParser(pydantic_object=Action)
self.fixing_parser = OutputFixingParser.from_llm(parser=self.parser, llm=llm)
这是ReActAgent的初始化方法。接收一个tools参数,就是Agent可以调用的工具列表。
我们看看它做了啥:
-
把工具列表存起来
-
提取所有工具的名字,存成一个列表
-
把工具的名字和描述拼成一个字符串,后面要放进提示词里
-
创建一个PydanticOutputParser,用来解析模型的输出
-
创建一个OutputFixingParser,用llm来修复解析错误
这里重点说一下OutputFixingParser。
为什么需要这个?因为小模型经常输出不规范的JSON,比如少个括号、多个逗号、或者外面包了一堆解释性文字。如果直接用PydanticOutputParser解析,很容易失败。
OutputFixingParser的作用就是:如果解析失败了,它会把错误信息和原始输出一起再发给模型,让模型自己修正。这样成功率会高很多。
💡 我的体会:
这个OutputFixingParser真的是神器。一开始我没用它,Agent的输出十次有八次解析失败,我都快崩溃了。后来加上这个,成功率一下子就上去了。
大模型应用开发,容错真的太重要了。你永远不知道模型会输出什么奇奇怪怪的东西。
5.2.3 构建提示词
def build_prompt(self):
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个扫地机器人领域的智能助手。
你可以使用以下工具来帮助回答用户问题:
{tool_descriptions}
请按照以下格式输出:
首先思考你需要做什么,然后决定调用哪个工具。
输出格式:
```json
{format_instructions}
注意:
-
只能调用上述工具,不要调用不存在的工具
-
每次只调用一个工具
-
工具名称必须是 [{tool_names}] 中的一个
用户问题:{query}
请输出你的思考和行动:“”"),
])
return prompt
这个方法是构建提示词的。提示词是Agent的"灵魂",写得好不好,直接影响Agent的表现。
我们来分析一下这个提示词的结构:
**1. 角色设定**
你是一个扫地机器人领域的智能助手。
开头先给模型设定一个角色。这是提示词工程的基本操作——给模型一个明确的身份,它的表现会更稳定。
**2. 工具说明**
你可以使用以下工具来帮助回答用户问题:
{tool_descriptions}
告诉模型有哪些工具可用,以及每个工具是干什么的。这部分是Agent能正确选择工具的关键。
tool_descriptions是动态生成的,每个工具一行,格式是"工具名: 工具描述"。
**3. 格式要求**
请按照以下格式输出:
首先思考你需要做什么,然后决定调用哪个工具。
输出格式:
{format_instructions}
告诉模型应该输出什么格式。format_instructions是PydanticOutputParser自动生成的,它会告诉模型JSON应该有哪些字段、每个字段是什么类型。
用```json代码块包裹,模型更容易理解"这部分是结构化输出",我们后续提取JSON也更容易。
**4. 约束条件**
注意:
-
只能调用上述工具,不要调用不存在的工具
-
每次只调用一个工具
-
工具名称必须是 [{tool_names}] 中的一个
这部分是"规则强调",反复告诉模型哪些能做、哪些不能做。
你可能会问:刚才不是已经说过有哪些工具了吗,为什么还要再列一遍工具名称?
因为大模型有时候会"健忘",或者注意力不够集中。把重要的规则重复强调,能提高遵守规则的概率。特别是第三条,把工具名称再列一遍,相当于给模型一个"白名单",减少它乱调用工具的概率。
**5. 用户问题**
用户问题:{query}
把用户的问题放进去。
**6. 结尾引导**
请输出你的思考和行动:
最后引导模型开始输出。明确告诉模型"现在该你输出了",能减少模型输出无关内容的概率。
> 💡 **提示词优化建议**:
> 这个提示词已经不错了,但还可以优化。比如可以加几个Few-Shot示例(就是给几个正确的输入输出例子),小模型的表现会好很多。
> 另外,还可以引导模型一步步思考,比如"在决定调用工具之前,请先思考:用户的问题是什么类型?需要调用工具吗?应该调用哪个工具?"
#### 5.2.4 构建Chain
```python
def build_chain(self):
prompt = self.build_prompt()
chain = (
{
"query": RunnablePassthrough(),
"tool_descriptions": lambda x: self.tool_descriptions,
"tool_names": lambda x: ", ".join(self.tool_names),
"format_instructions": lambda x: self.parser.get_format_instructions(),
}
| prompt
| llm
| self.fixing_parser
)
return chain
这个方法是构建Chain的,用的是LangChain的LCEL语法。
可能刚接触LCEL的同学会觉得有点晕,别慌,我给你拆解一下。
LCEL(LangChain Expression Language)是LangChain的一种表达式语言,用管道符|把各个组件串起来,就像Linux的管道一样。
我们从左到右看:
第一部分:输入处理
{
"query": RunnablePassthrough(),
"tool_descriptions": lambda x: self.tool_descriptions,
"tool_names": lambda x: ", ".join(self.tool_names),
"format_instructions": lambda x: self.parser.get_format_instructions(),
}
这是一个字典,用来准备提示词需要的各个变量。
-
query: RunnablePassthrough():把输入直接透传给query变量。简单说就是用户输入啥,query就是啥。 -
tool_descriptions: lambda x: self.tool_descriptions:把工具描述放进去 -
tool_names: lambda x: ", ".join(self.tool_names):把工具名用逗号连起来 -
format_instructions: lambda x: self.parser.get_format_instructions():获取格式说明
第二部分:提示词模板
| prompt
把上面的变量填进提示词模板里,生成完整的提示词。
第三部分:大模型
| llm
把提示词发给大模型,得到输出。
第四部分:输出解析
| self.fixing_parser
把大模型的输出解析成Action对象。如果解析失败,会自动让模型修正。
整个流程就是:输入 → 准备变量 → 生成提示词 → 调用大模型 → 解析输出 → 得到Action。
是不是挺清晰的?LCEL虽然一开始看着有点怪,但习惯了之后,写起来还是挺简洁的。
六、核心代码精讲:彻底搞懂每一行(下)
上半部分讲了Action.py和ReAct.py的前半部分,这一节继续讲剩下的核心代码。
6.1 ReAct.py 剩下的部分
6.1.1 单步执行
def step(self, query: str) -> Action:
chain = self.build_chain()
action = chain.invoke(query)
return action
step方法就是执行一步,接收用户的问题,返回一个Action对象。
很简单,就是构建chain,然后invoke一下。
6.1.2 工具执行
def execute_tool(self, action: Action):
# 检查工具是否存在
if action.tool_name not in self.tool_names:
return f"错误:不存在的工具 {action.tool_name},可用工具:{self.tool_names}"
# 找到对应的工具
tool = next(tool for tool in self.tools if tool.name == action.tool_name)
# 执行工具
try:
result = tool.invoke(action.args)
return result
except Exception as e:
return f"工具执行出错:{str(e)}"
这个方法是执行工具的,接收一个Action对象,返回执行结果。
逻辑也很简单:
-
先检查工具是否存在,不存在就返回错误信息
-
找到对应的工具
-
调用工具的invoke方法,传入参数
-
如果出错了,捕获异常,返回错误信息
这里的容错做得还是不错的,工具不存在、执行出错,都有处理。这样Agent就知道"哦,刚才那步出错了,我得想想下一步怎么办"。
6.1.3 单步Agent运行
def step_by_step(self, query: str) -> str:
# 第一步:思考并选择工具
action = self.step(query)
print(f"思考:调用 {action.tool_name} 工具")
print(f"参数:{action.args}")
# 第二步:执行工具
observation = self.execute_tool(action)
print(f"观察:{observation}")
# 第三步:生成最终回答(这里简化了,直接返回观察结果)
# 完整的ReAct应该把观察结果再喂给模型,继续思考
# 但这个项目目前是单步的,所以直接返回
return observation
step_by_step方法是单步Agent的主入口。
目前这个项目的Agent是单步的——只调用一次工具就返回结果。完整的ReAct应该是多轮的,把观察结果再喂给模型,继续思考下一步。
为什么做成单步的?因为目前的场景比较简单,大部分问题调用一次RAG就够了。而且小模型多轮效果不一定好,容易跑偏。
不过代码结构是留好了的,以后想改成多轮的也很方便。
💡 怎么改成多轮?
其实很简单,就是加个循环:
-
思考 → 得到Action
-
执行工具 → 得到Observation
-
把Observation拼到提示词里,再思考
-
重复,直到模型觉得任务完成了
感兴趣的同学可以自己试试改一改,挺有意思的。
6.2 Tools.py:工具的注册和管理
讲完了Agent,我们来讲工具层。先看Tools.py,这个文件负责工具的注册和管理。
from langchain.tools.base import StructuredTool
from Tools.RagQATool import rag_qa
from Tools.ReportGenerationTool import generate_report
def get_tools():
tools = [
StructuredTool.from_function(
func=rag_qa,
name="rag_qa_tool",
description="用于回答扫地机器人相关的问题,输入用户的问题,返回基于知识库的答案。当用户询问使用方法、故障排查、产品参数等问题时使用此工具。"
),
StructuredTool.from_function(
func=generate_report,
name="report_generation_tool",
description="用于生成各种分析报告,输入报告类型和时间范围,返回生成的报告内容。当用户要求生成报告、统计分析时使用此工具。"
),
]
return tools
代码不长,我们来看看。
首先导入了StructuredTool,还有两个工具函数:rag_qa和generate_report。
然后get_tools函数返回一个工具列表。每个工具都是用StructuredTool.from_function创建的,把一个普通的Python函数包装成Agent能调用的工具。
每个工具需要三个东西:
-
func:工具对应的函数
-
name:工具的名字,Agent就是通过这个名字来调用的
-
description:工具的描述,告诉Agent这个工具是干啥的、什么时候用
💡 划重点:
工具的description非常非常重要!它直接影响Agent能不能正确选择工具。
一个好的description应该说清楚:
-
这个工具是干什么的
-
什么时候该用这个工具
-
输入输出是什么
-
如果有适用范围或限制,也要说明
描述写得越清楚,Agent选得越准。
这个工具系统的设计我觉得挺不错的,添加新工具特别方便:
-
写一个新的函数
-
在get_tools里加一个StructuredTool
-
搞定!Agent那边完全不用改
这就是开闭原则——对扩展开放,对修改关闭。加新功能的时候尽量加新代码,不要改旧代码,这样不容易出bug。
6.3 RagQATool.py:RAG问答工具
接下来是RAG问答工具,这是项目里最核心的工具之一。
from langchain.chains import RetrievalQA
from Tools.VecStore import VecStore
from Models.Factory import llm
# 初始化向量库
vec_store = VecStore()
db = vec_store.get_db()
# 创建RAG链
rag_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=db.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True,
)
def rag_qa(query: str) -> str:
"""
RAG问答工具
:param query: 用户问题
:return: 回答
"""
result = rag_chain.invoke(query)
return result['result']
我们逐段来看。
初始化向量库:
vec_store = VecStore()
db = vec_store.get_db()
创建一个VecStore实例,然后获取向量数据库。这个VecStore我们后面会讲。
创建RAG链:
rag_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=db.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True,
)
用LangChain的RetrievalQA来创建RAG链。一行代码就搞定了,是不是很方便?
参数说明:
-
llm:用哪个大模型 -
chain_type:链的类型,"stuff"是最简单的一种,就是把检索到的所有文档都塞进提示词里。还有其他类型,比如map_reduce、refine,适合文档比较多的情况 -
retriever:检索器,这里是从向量数据库创建的 -
search_kwargs={"k": 3}:检索的时候返回最相关的3个文档 -
return_source_documents=True:返回结果的时候,同时返回检索到的源文档。这样我们就能看到答案是从哪些文档来的,方便排查问题
问答函数:
def rag_qa(query: str) -> str:
result = rag_chain.invoke(query)
return result['result']
就是调用rag_chain的invoke方法,然后返回结果。
result是一个字典,里面有:
-
result:生成的答案 -
source_documents:检索到的源文档列表
💡 小技巧:
调试的时候,可以把source_documents也打印出来,看看检索到的内容是不是相关的。如果检索的内容都不相关,那答案肯定好不了。这时候就要优化检索了。
6.4 VecStore.py:向量数据库管理
RAG的核心是向量数据库,我们来看看VecStore.py是怎么实现的。
这个文件稍微长一点,我分段讲。
6.4.1 导入和初始化
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import PyPDFLoader, TextLoader, CSVLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from Models.Factory import embed_model
import os
CHROMA_PATH = "./Chroma"
DATA_PATH = "./data"
导入了一堆东西:
-
Chroma:Chroma向量数据库,轻量级,适合小项目 -
PyPDFLoader、TextLoader、CSVLoader:各种文档加载器 -
RecursiveCharacterTextSplitter:递归字符文本分块器 -
embed_model:向量模型 -
os:操作系统相关的函数
然后定义了两个路径:
-
CHROMA_PATH:向量数据库的存储路径 -
DATA_PATH:原始文档的路径
6.4.2 VecStore类
class VecStore:
def __init__(self):
self.db = None
self._load_or_create_db()
初始化的时候调用_load_or_create_db方法,加载或者创建向量数据库。
6.4.3 加载文档
def _load_documents(self):
documents = []
for filename in os.listdir(DATA_PATH):
filepath = os.path.join(DATA_PATH, filename)
if filename.endswith('.pdf'):
loader = PyPDFLoader(filepath)
documents.extend(loader.load())
elif filename.endswith('.txt'):
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load())
elif filename.endswith('.csv'):
loader = CSVLoader(filepath)
documents.extend(loader.load())
return documents
这个方法是从data文件夹加载所有文档的。
逻辑很简单:
-
遍历data文件夹里的所有文件
-
根据文件后缀名,用对应的Loader来加载
-
把加载到的文档都加到列表里
-
返回文档列表
支持PDF、TXT、CSV三种格式。想支持更多格式的话,加对应的Loader就行。
6.4.4 文本分块
def _split_documents(self, documents):
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=50,
length_function=len,
)
splits = text_splitter.split_documents(documents)
return splits
这个方法是把长文档切成小块的。
用的是RecursiveCharacterTextSplitter,递归字符分块器。它会尽量按照段落、句子、单词的顺序来切,尽量保持语义完整。
参数说明:
-
chunk_size=300:每块的大小,300个字符 -
chunk_overlap=50:块之间的重叠,50个字符。重叠是为了避免切分的时候把完整的意思切断 -
length_function=len:计算长度的函数,这里就是普通的len
💡 chunk_size怎么选?
这是一个经验值,没有标准答案。
-
太大:每块信息太多,噪音多,还可能超上下文
-
太小:上下文不完整,理解不了完整的意思
300是一个比较中庸的值,适合FAQ场景。你可以根据自己的情况调整,试试200、500,看看哪个效果好。
6.4.5 创建向量库
def _create_db(self):
print("正在创建向量数据库...")
# 加载文档
documents = self._load_documents()
print(f"加载了 {len(documents)} 个文档")
# 分块
splits = self._split_documents(documents)
print(f"分成了 {len(splits)} 个块")
# 创建向量库
self.db = Chroma.from_documents(
documents=splits,
embedding=embed_model,
persist_directory=CHROMA_PATH,
)
# 持久化
self.db.persist()
print("向量数据库创建完成")
创建向量数据库的流程:
-
加载文档
-
文本分块
-
用Chroma.from_documents创建向量库,传入文档、向量模型、存储路径
-
调用persist()保存到磁盘
Chroma.from_documents会自动把文档转向量,并存到数据库里。
6.4.6 加载向量库
def _load_db(self):
print("正在加载向量数据库...")
self.db = Chroma(
persist_directory=CHROMA_PATH,
embedding_function=embed_model,
)
print("向量数据库加载完成")
如果向量库已经存在了,就直接从磁盘加载,不用重新建。这样第二次启动就快多了。
6.4.7 判断是否需要重建
def _load_or_create_db(self):
if os.path.exists(CHROMA_PATH) and os.listdir(CHROMA_PATH):
self._load_db()
else:
self._create_db()
这个方法是判断该加载还是该创建:
-
如果Chroma文件夹存在且不为空,就加载
-
否则就创建
很简单的逻辑。
6.4.8 获取数据库
def get_db(self):
return self.db
就是返回db对象,给外面用。
6.4.9 重新构建
def rebuild(self):
# 删掉旧的
import shutil
if os.path.exists(CHROMA_PATH):
shutil.rmtree(CHROMA_PATH)
# 重新创建
self._create_db()
重新构建向量库。就是把旧的删掉,重新建一个。
什么时候需要重建?比如知识库更新了,或者你改了分块参数,想重新试试效果。
💡 关于VecStore的设计:
这个VecStore类封装得还是不错的,把向量库的创建、加载、重建都封装起来了。
以后如果想换向量数据库(比如换成FAISS),只要改这个类就行,其他地方不用动。
这就是封装的好处——把变化隔离在一个地方。
6.5 Factory.py:模型工厂
最后我们来看Models/Factory.py,模型工厂。
import yaml
from langchain_community.chat_models import ChatOllama
from langchain_community.embeddings import OllamaEmbeddings
# 加载配置
with open('./Configs/config.yml', 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
class ChatModelFactory:
"""聊天模型工厂"""
@staticmethod
def create_chat_model():
llm = ChatOllama(
model=config['model_name'],
base_url=config['server_url'],
temperature=0.1,
)
return llm
class EmbeddingModelFactory:
"""向量模型工厂"""
@staticmethod
def create_embedding_model():
embed_model = OllamaEmbeddings(
model=config['embedding_name'],
base_url=config['server_url'],
)
return embed_model
# 全局单例
llm = ChatModelFactory.create_chat_model()
embed_model = EmbeddingModelFactory.create_embedding_model()
代码不多,但设计得挺巧妙的。
工厂模式:
用了两个工厂类,ChatModelFactory和EmbeddingModelFactory,分别用来创建聊天模型和向量模型。
为什么要用工厂模式?
-
封装创建逻辑:创建模型的参数和配置都封装在工厂里,调用者不用关心
-
方便切换实现:以后想换成OpenAI的模型,只改工厂类就行,其他地方都不用改。因为大家依赖的是BaseChatModel这个抽象接口,不是具体的ChatOllama
-
统一配置管理:所有模型相关的配置都从config.yml读,不会散落在各处
单例模式:
最后在模块级别创建了两个全局单例:llm和embed_model。整个项目共用这两个实例。
好处:
-
节省资源,不用反复创建
-
方便使用,其他模块直接import就行
-
统一配置,所有地方用的都是同一个模型
💡 小问题:
这里配置文件的路径是相对路径’./Configs/config.yml’,这意味着你必须在项目根目录下运行程序,否则会找不到配置文件。
更健壮的写法是基于当前文件的位置来计算路径,这样不管从哪里运行都能找到。不过对于小项目来说,这样写也没问题。
七、运行效果展示
讲了这么多代码,咱们来看看实际跑起来是什么样的。
我就用Gradio界面来演示吧,有图有真相。
7.1 启动界面
启动之后,界面大概是这样的:
-
左边是输入框,用户输入问题
-
右边是输出框,显示回答
-
可能还有一些参数设置,比如温度、检索数量啥的
(这里你可以放一张自己项目的截图,效果更好)
7.2 示例问答
示例1:问边刷怎么换
用户输入:“扫地机器人边刷怎么更换?”
系统大概会这样回答:
更换边刷的步骤如下:
1. 将扫地机器人翻转过来,底部朝上
2. 找到边刷的固定卡扣,轻轻按下卡扣
3. 将旧的边刷取下
4. 取出新的边刷,对准卡槽安装
5. 听到"咔哒"一声,说明安装到位
6. 将机器人翻转回来即可
注意:更换边刷时请确保机器人处于关机状态,避免意外启动。
你看,回答得还是挺像回事的,步骤清晰,还有注意事项。
示例2:问故障排查
用户输入:“扫地机器人卡住了怎么办?”
系统回答:
如果扫地机器人卡住了,可以按照以下步骤排查:
1. 首先按下电源键关机,避免强行拉扯造成损坏
2. 将机器人翻转过来,检查主刷和边刷是否被毛发、电线等缠绕
3. 如果有缠绕物,用清理工具小心清理
4. 检查轮子是否被异物卡住
5. 清理完成后,重新开机测试
6. 如果还是不行,可能是传感器故障,建议联系售后
注意:清理时不要用水直接冲洗机器人内部,以免造成电路损坏。
是不是还挺专业的?而且这些答案都是从知识库来的,不是模型瞎编的。
7.3 Agent的思考过程
如果打开调试模式,你还能看到Agent的思考过程:
用户问题:扫地机器人卡住了怎么办?
思考:用户问的是故障排查类问题,应该调用RAG问答工具来回答。
调用工具:rag_qa_tool
参数:{'query': '扫地机器人卡住了怎么办'}
观察:(上面那段回答)
最终回答:(返回给用户)
你看,Agent是有"思考"过程的——它先分析用户的问题是什么类型,然后决定调用哪个工具,然后执行工具,最后返回结果。
虽然现在是单步的,但已经能看出Agent的雏形了。
7.4 效果总结
总的来说,效果还是不错的:
-
✅ 常见问题基本都能答对
-
✅ 答案有依据,很少有幻觉
-
✅ 回答流畅,像人写的
-
⚠️ 速度一般,毕竟是本地小模型
-
⚠️ 复杂问题处理得还不够好
当然,这只是基础版,还有很大的优化空间。后面我会讲怎么优化。
八、踩坑记录:那些让我头疼的问题
这部分是我最想分享的——踩坑记录。
做这个项目的过程中,我遇到了各种各样的问题,有的卡了我好几天。我把它们整理出来,希望你们能少走点弯路。
8.1 环境相关的坑
坑1:Ollama连不上
症状:运行的时候报错,说连不上Ollama。
原因:Ollama服务没启动,或者端口不对。
解决方法:
-
检查Ollama有没有在运行(Windows看任务栏,Mac/Linux看进程)
-
检查config.yml里的server_url是不是对的
-
浏览器打开http://localhost:11434,看能不能访问
坑2:模型拉不下来
症状:ollama pull的时候特别慢,或者直接失败。
原因:网络问题。
解决方法:
-
换个网络试试
-
配置国内镜像源
-
找别人拷一下模型文件(Ollama的模型文件是可以直接拷贝的)
坑3:虚拟环境搞混了
症状:明明装了依赖,运行的时候还是说找不到模块。
原因:没激活虚拟环境,或者装到别的环境里了。
解决方法:
-
运行前确认命令行前面有(.venv)
-
用which python(Linux/Mac)或者where python(Windows)看看用的是哪个python
8.2 RAG相关的坑
坑4:检索不到相关内容
症状:问的问题知识库明明有,但回答说不知道,或者答非所问。
原因:可能的原因很多,我当时排查了好久。
排查步骤:
-
先确认文档有没有正确加载——打印一下文档数量
-
再确认分块合不合理——打印几个块看看内容完不完整
-
再确认检索的k值是不是太小——调大一点试试
-
最后考虑是不是Embedding模型的问题——换个模型试试
我当时的问题是分块太大了,一个块里内容太多,检索不精准。后来把chunk_size调小了,效果就好多了。
坑5:答案和问题不相关
症状:检索到的内容是相关的,但生成的答案不对。
原因:提示词写得不好,或者模型太弱。
解决方法:
-
优化提示词,反复强调"请基于参考资料回答,不要编造"
-
换更好的模型
-
可以试试在提示词里加Few-Shot示例
坑6:中文乱码
症状:输出的中文是乱码。
原因:编码问题。
解决方法:
-
确保所有文件都用UTF-8保存
-
读取文件的时候指定encoding=‘utf-8’
-
Windows下命令行可能需要改代码页:chcp 65001
8.3 Agent相关的坑
坑7:Agent不调用工具,直接回答
症状:问的问题应该要调用RAG,但Agent直接凭记忆回答了,而且答案不对。
原因:提示词不够强,或者模型太小,不听话。
解决方法:
-
优化提示词,反复强调"必须调用工具,不要直接回答"
-
加Few-Shot示例,给它看几个正确调用工具的例子
-
换更大一点的模型,小模型确实更容易不听话
我当时被这个问题卡了好久,后来加了OutputFixingParser,又优化了提示词,情况才好转。
坑8:Agent调用不存在的工具
症状:Agent输出的tool_name不在工具列表里。
原因:模型幻觉,或者工具描述写得不好。
解决方法:
-
优化工具描述,写得更清楚
-
在提示词里反复强调只能用哪些工具
-
加异常处理,调用失败了返回错误信息,让Agent重试
坑9:输出格式不对,解析失败
症状:JSON解析错误,或者Pydantic验证失败。
原因:模型输出的格式不规范。小模型经常这样。
解决方法:
-
一定要用OutputFixingParser!一定要用!一定要用!(重要的事说三遍)
-
优化提示词,强调输出格式
-
加Few-Shot示例,给它看正确的格式
-
可以再加一层兜底,比如用正则表达式提取JSON
坑10:Agent跑偏了
症状:多轮对话的时候,Agent越跑越偏,最后不知道在干啥。
原因:小模型的多轮能力不行。
解决方法:
-
如果场景简单,就用单步的(像我们这个项目一样)
-
一定要做多轮的话,换更大的模型
-
优化提示词,每轮都强调任务目标
8.4 工程相关的坑
坑11:路径问题
症状:找不到文件,比如"FileNotFoundError: Configs/config.yml"。
原因:运行目录不对,相对路径是相对于当前工作目录的。
解决方法:
-
确保在项目根目录下运行脚本
-
或者把相对路径改成基于文件位置的绝对路径
坑12:依赖版本冲突
症状:安装依赖的时候报错,或者运行的时候说某个函数不存在。
原因:库的版本不对,LangChain的不同版本API变化还挺大的。
解决方法:
-
尽量用requirements.txt里指定的版本
-
如果要升级,注意看官方的变更日志
-
出问题了先查一下是不是版本问题
九、优化思路:这个项目还能怎么改进?
基础版跑通了只是开始,后面能优化的地方还多着呢。我整理了一些优化方向,你可以试试。
9.1 RAG优化
RAG的优化空间是最大的,也是最容易出效果的。
1. 优化分块策略
-
试试按语义分块,而不是固定大小
-
试试父子块(小块检索,大块返回)
-
不同类型的文档用不同的分块策略
2. 优化检索方式
-
混合检索:关键词检索 + 向量检索,结合两者的优势
-
加重排序:先粗排召回一批,再用更好的模型精排
-
多查询检索:把用户的问题改写成几个问法,分别检索再合并
3. 优化生成
-
优化提示词,这个是成本最低但效果最明显的
-
试试不同的chain_type,比如refine、map_reduce
-
加入引用标注,让答案标明哪些内容来自哪里
4. 优化Embedding模型
-
换更好的Embedding模型,检索效果会提升
-
针对垂直领域微调Embedding模型(如果有数据的话)
9.2 Agent优化
1. 从单步改成多轮
-
实现完整的ReAct循环
-
加入终止条件,让模型自己判断什么时候结束
2. 优化提示词
-
加Few-Shot示例
-
加入思考引导,让模型一步步想
-
加入错误处理的说明
3. 增加更多工具
-
计算器工具
-
搜索引擎工具
-
数据库查询工具
-
代码执行工具
-
...
工具越多,Agent的能力就越强。
4. 加入记忆
-
支持多轮对话,记住之前的对话内容
-
短期记忆:当前会话的上下文
-
长期记忆:用户的偏好、历史问题等
9.3 工程优化
1. 增加评估体系
-
做一个评估集,准备一批问题和标准答案
-
量化评估准确率、相关性、完整性等指标
-
每次优化后跑一遍评估,看效果有没有提升
2. 增加日志系统
-
记录每一次请求的详细信息
-
方便排查问题和分析效果
-
可以用来做bad case分析
3. 性能优化
-
缓存常用问题的答案
-
异步处理,提高并发
-
模型量化,加速推理
4. 部署优化
-
用Docker打包,方便部署
-
加入监控和告警
-
做负载均衡
十、我的收获和体会
最后,聊聊我做这个项目的收获和体会吧。
10.1 技术上的收获
1. 搞懂了RAG和ReAct
以前只是听说过这些概念,现在自己动手实现了一遍,理解就深刻多了。很多东西,光看是看不懂的,得自己做一遍才明白。
2. 学会了用LangChain
这个框架确实挺强大的,很多功能都封装好了,能大大提高开发效率。当然,封装得深也有缺点,出问题不好排查,但总体来说还是利大于弊。
3. 学会了本地部署大模型
以前总觉得大模型很高大上,得有服务器才能跑。现在发现,普通电脑也能跑小模型,虽然效果差点,但用来学习和做小项目完全够用。
4. 对大模型应用开发有了整体认识
原来大模型应用开发不是调个API就完事了,里面有很多工程化的东西——提示词工程、检索优化、容错处理、评估等等,学问大着呢。
10.2 工程上的体会
1. 分层和模块化真的很重要
这个项目的分层架构我觉得做得不错,每层职责清晰,改起来也方便。以前写代码总喜欢堆在一起,现在才体会到模块化的好处。
2. 容错设计不能少
大模型应用和传统应用不一样,传统应用的输入输出都是确定的,而大模型的输出是不确定的,你永远不知道它会输出什么奇奇怪怪的东西。所以容错设计特别重要,比如OutputFixingParser、异常捕获这些,都得有。
3. 调试和排错能力很重要
做这个项目,我一半的时间都在排错。环境问题、依赖问题、模型输出不对、检索不准...各种各样的问题。但排错排多了,能力也就上来了。现在遇到问题,我大概知道该从哪下手排查了。
10.3 认知上的改变
1. 大模型不是万能的
以前觉得大模型无所不能,现在发现不是。大模型有它擅长的地方,也有它不擅长的地方。得和传统技术结合起来,才能发挥最大的价值。
2. Agent是个很有前景的方向
让大模型从"会聊天"变成"会做事",这个想象空间太大了。以后可能会有各种各样的Agent,帮我们处理各种工作。
3. 工程能力同样重要
AI项目不是只有算法,工程能力也很重要。怎么把模型落地成一个好用、稳定、高效的系统,这里面的学问一点也不比算法少。
十一、总结
好了,啰啰嗦嗦写了这么多,差不多该收尾了。
这篇文章从项目背景、核心概念、环境搭建、项目结构、核心代码、运行效果、踩坑记录,一直讲到优化思路和个人体会,算是把这个项目从头到尾梳理了一遍。
如果你能耐心看到这里,相信你对ReAct+RAG的智能问答Agent已经有了比较全面的了解。但我还是想说,光看是不够的,一定要自己动手做一遍。编程这东西,纸上得来终觉浅,绝知此事要躬行。
这个项目虽然不算复杂,但五脏俱全,涵盖了大模型应用开发的很多核心知识点。把它搞懂了,再去做更复杂的项目,就会容易很多。
如果你在做的过程中遇到什么问题,欢迎在评论区留言,我们一起讨论。
最后,如果这篇文章对你有帮助,别忘了点赞、收藏、关注三连~ 你们的支持是我继续分享的动力!
我们下篇文章见。
好了,博客内容就到这里。你可以把这些内容复制保存成Markdown文件,或者直接发布到CSDN上。整篇文章大概1.5万字左右,语言都是口语化的,有干货也有踩坑记录,挺符合CSDN的风格的。
如果觉得哪里需要调整,比如想加内容、改风格、调结构,随时告诉我。
(注:部分内容可能由 AI 生成)
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)