从零构建金融RAG问答机器人:基于LangChain与Qwen的实战指南
🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度
最近在后台收到不少私信,很多刚接触AI大模型的朋友都表示,面对海量的开源模型、复杂的部署流程和眼花缭乱的技术栈,感觉无从下手,学习曲线陡峭。确实,从零开始构建一个能跑起来、能对话、甚至能解决实际问题的AI应用,涉及环境、模型、框架、工程化等多个环节,任何一个环节卡住都可能让人放弃。
本文正是为了解决这个问题而生。我将以“金融大模型问答机器人”这个极具代表性的项目为蓝本,为你拆解一套从零到一的完整实战路径。无论你是想入门AI应用开发的在校学生,还是希望将大模型能力集成到现有业务中的开发者,都能从这篇教程中找到清晰的步骤、可运行的代码和避坑指南。学完后,你将掌握如何搭建一个具备私有知识问答能力的智能助手,并理解其背后的核心技术原理。
1. 背景与核心概念:为什么需要RAG问答机器人?
在开始敲代码之前,我们首先要理解我们要解决什么问题,以及为什么选择特定的技术方案。
1.1 大模型的能力与局限
当前的主流大语言模型(LLM),如 GPT、Qwen、LLaMA 等,在通用知识、逻辑推理和文本生成上表现惊人。它们经过了海量互联网数据的训练,堪称“万事通”。然而,它们也存在明显的短板:
- 知识滞后性 :模型的训练数据有截止日期,无法获取最新的信息(例如,今天的股价、刚发布的企业财报)。
- 缺乏领域深度 :对于金融、法律、医疗等专业领域,模型缺乏深度的、非公开的私有知识(例如,公司内部的业务规则、产品手册、机密报告)。
- “幻觉”问题 :模型可能会生成看似合理但完全错误的事实性内容。
- 成本与隐私 :直接调用商用API(如 OpenAI)处理大量私有文档,存在数据泄露风险和成本压力。
1.2 RAG:检索增强生成
为了解决上述问题, RAG(Retrieval-Augmented Generation,检索增强生成) 技术应运而生。它巧妙地将信息检索与大模型生成能力结合:
- 检索(Retrieval) :将你的私有知识库(如PDF、Word、数据库)进行切片、向量化,并存入向量数据库。
- 增强(Augmented) :当用户提问时,系统先从向量数据库中检索出与问题最相关的文档片段。
- 生成(Generation) :将这些检索到的片段作为“参考材料”,连同用户问题一起提交给大模型,让模型基于这些可靠材料生成答案。
简单比喻 :大模型就像一个聪明但记忆有限的学生,RAG 则为他配备了一个强大的“外部知识库”(向量数据库)和一位“图书管理员”(检索系统)。考试(回答用户问题)时,学生可以快速查阅图书管理员提供的精准资料,从而写出更准确、更有依据的答案。
1.3 项目全景图:金融问答机器人
我们的目标就是构建一个基于 RAG 架构的金融领域智能问答系统。它的核心工作流程如下:
用户提问 --> 向量化检索 --> 拼接上下文 --> 大模型生成 --> 返回答案
技术栈上,我们将采用当前业界最流行、最适合入门的组合:
- LLM 底座 :Qwen(通义千问)开源模型,性能优异,中文支持好,可本地部署。
- 应用框架 :LangChain,用于编排整个RAG流程(加载、切分、检索、提示)。
- 向量数据库 :Chroma,轻量级,易于上手,适合学习和原型开发。
- 后端API :FastAPI,快速构建高性能的Web服务接口。
- 前端(可选) :简单的Streamlit页面或Vue/React,用于演示。
接下来,我们从零开始,一步步实现它。
2. 环境准备与版本说明
一个稳定的环境是成功的第一步。为了避免后续出现各种依赖冲突,强烈建议使用 Conda 或 Venv 创建独立的 Python 虚拟环境。
2.1 基础环境配置
本文以 Ubuntu 20.04/22.04 或 Windows WSL2 环境为例,macOS 同样适用。
-
安装 Python :确保系统已安装 Python 3.8 - 3.11 版本。不建议使用 Python 3.12+,因为某些深度学习库的兼容性可能还不完善。
python3 --version # 输出应为 Python 3.8.x 或更高 -
创建并激活虚拟环境 :
# 使用 venv python3 -m venv rag_env source rag_env/bin/activate # Linux/macOS # 或 rag_env\Scripts\activate # Windows # 使用 conda conda create -n rag_env python=3.10 conda activate rag_env
2.2 核心依赖安装
我们将主要依赖 langchain 和 chromadb 。为了运行 Qwen 模型,还需要安装 transformers , torch 等深度学习库。请注意, torch 的安装命令需要根据你的CUDA版本进行调整。
# 升级pip
pip install --upgrade pip
# 安装 PyTorch (请访问 https://pytorch.org/get-started/locally/ 获取最适合你系统的命令)
# 例如,对于CUDA 11.8:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
# 或仅安装CPU版本:
# pip install torch torchvision torchaudio
# 安装LangChain及其社区工具包
pip install langchain langchain-community
# 安装文本嵌入模型和向量数据库
pip install sentence-transformers chromadb
# 安装用于加载各种文档的Loader
pip install pypdf python-docx markdown
# 安装FastAPI和前端依赖
pip install fastapi uvicorn
pip install streamlit # 可选,用于快速构建演示界面
# 安装Qwen模型所需的特定库
pip install transformers accelerate
2.3 项目结构初始化
创建一个清晰的项目文件夹,管理起来会更方便。
mkdir financial_rag_robot
cd financial_rag_robot
mkdir data docs core api static templates
touch requirements.txt README.md main.py
你的项目结构大致如下:
financial_rag_robot/
├── data/ # 存放原始金融文档(PDF、TXT等)
├── docs/ # 存放处理后的向量数据库
├── core/ # 核心业务逻辑
│ ├── __init__.py
│ ├── knowledge_base.py # 知识库构建与加载
│ └── qa_chain.py # 问答链构建
├── api/ # FastAPI 后端接口
│ └── main.py
├── static/ # 静态文件
├── templates/ # 前端模板(如果用的话)
├── requirements.txt # 依赖列表
├── README.md
└── main.py # 可能的统一启动入口
将之前安装的依赖导出到 requirements.txt :
pip freeze > requirements.txt
3. 核心技术原理拆解
在动手编码前,理解以下几个核心组件的原理至关重要。
3.1 文本嵌入与向量检索
计算机无法直接理解文本。我们需要将文本转换为数值向量(即嵌入向量)。语义相似的文本,其向量在空间中的距离也更近。
- 嵌入模型 :我们使用
sentence-transformers库中的paraphrase-multilingual-MiniLM-L12-v2模型。它是一个轻量级的多语言模型,能将中文句子映射到384维的向量空间,并且效果不错。 - 向量数据库 :Chroma 会存储这些向量。当用户提问时,问题也会被转换成向量,Chroma 通过计算余弦相似度,快速找出库中最相似的几个文本片段。
3.2 LangChain 框架的角色
LangChain 不是一个大模型,而是一个 编排框架 。它像乐高积木的说明书,将不同的组件(模型、提示模板、检索器、记忆等)连接成一个可执行的“链”(Chain)。
在我们的RAG流程中,LangChain 负责:
- Document Loader :加载
data/目录下的PDF、TXT文件。 - Text Splitter :将长文档切割成大小适中的片段(如500字符一段),并保留部分重叠以防止上下文断裂。
- Vectorstore :封装与 Chroma 的交互,实现文档的存储和检索。
- RetrievalQA Chain :这是核心链。它内部集成了检索器(从向量库找资料)和LLM(根据资料生成答案)。
3.3 Qwen 模型的本地加载与调用
我们将使用 Qwen-1.8B-Chat 这个相对较小的对话模型,它可以在消费级GPU(甚至CPU)上运行,适合学习和测试。
通过 transformers 库加载模型和分词器,并利用 pipeline 功能方便地调用。关键是要理解“对话模板”,Qwen 模型需要将用户提问和检索到的上下文按照特定格式组装,例如:
<|im_start|>system
你是一个专业的金融助手,请严格根据提供的背景资料回答问题。
<|im_end|>
<|im_start|>user
背景资料:[检索到的文本]
问题:用户的问题是什么?
<|im_end|>
<|im_start|>assistant
4. 完整实战案例:构建金融RAG问答机器人
现在,让我们把理论付诸实践。请跟随以下步骤,一步步完成系统搭建。
4.1 第一步:构建私有知识库
首先,在 data/ 文件夹中放入你的金融文档,例如 公司年报.pdf 、 理财产品说明书.txt 等。
然后,创建 core/knowledge_base.py 文件:
# core/knowledge_base.py
import os
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
class KnowledgeBase:
def __init__(self, data_path="./data", persist_path="./docs/chroma_db"):
self.data_path = data_path
self.persist_path = persist_path
# 初始化嵌入模型
self.embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
model_kwargs={'device': 'cpu'}, # 使用GPU可改为 'cuda'
encode_kwargs={'normalize_embeddings': True}
)
self.vectorstore = None
def load_and_split_documents(self):
"""加载并分割文档"""
documents = []
for filename in os.listdir(self.data_path):
file_path = os.path.join(self.data_path, filename)
if filename.endswith('.pdf'):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif filename.endswith('.txt') or filename.endswith('.md'):
loader = TextLoader(file_path, encoding='utf-8')
documents.extend(loader.load())
# 可以继续添加对docx, html等格式的支持
print(f"成功加载 {len(documents)} 个文档片段")
# 分割文档
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个片段约500字符
chunk_overlap=50, # 片段间重叠50字符,保持上下文
separators=["\n\n", "\n", "。", "!", "?", ";", ",", "、", " ", ""]
)
split_docs = text_splitter.split_documents(documents)
print(f"分割后得到 {len(split_docs)} 个文本块")
return split_docs
def create_vectorstore(self, docs):
"""创建并持久化向量数据库"""
self.vectorstore = Chroma.from_documents(
documents=docs,
embedding=self.embeddings,
persist_directory=self.persist_path
)
self.vectorstore.persist()
print(f"向量数据库已创建并保存至 {self.persist_path}")
return self.vectorstore
def load_existing_vectorstore(self):
"""加载已存在的向量数据库"""
if os.path.exists(self.persist_path):
self.vectorstore = Chroma(
persist_directory=self.persist_path,
embedding_function=self.embeddings
)
print(f"已从 {self.persist_path} 加载现有向量数据库")
return self.vectorstore
else:
print("未找到已有的向量数据库,请先创建。")
return None
if __name__ == "__main__":
# 首次运行,构建知识库
kb = KnowledgeBase()
docs = kb.load_and_split_documents()
kb.create_vectorstore(docs)
运行这个脚本,处理你的文档:
python core/knowledge_base.py
4.2 第二步:加载Qwen模型并创建问答链
创建 core/qa_chain.py 文件:
# core/qa_chain.py
from langchain.llms import HuggingFacePipeline
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
import torch
class QwenQAChain:
def __init__(self, vectorstore):
self.vectorstore = vectorstore
self.llm = self._load_qwen_model()
self.qa_chain = None
def _load_qwen_model(self):
"""加载Qwen-1.8B-Chat模型"""
model_name = "Qwen/Qwen-1.8B-Chat"
print(f"正在加载模型: {model_name},请耐心等待...")
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(
model_name,
trust_remote_code=True,
padding_side="left"
)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16, # 半精度减少内存占用
device_map="auto", # 自动分配GPU/CPU
trust_remote_code=True
)
model.eval() # 设置为评估模式
# 创建文本生成管道
text_generation_pipeline = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
max_new_tokens=512, # 生成答案的最大长度
temperature=0.1, # 较低的温度使输出更确定
do_sample=True,
top_p=0.9,
repetition_penalty=1.1,
pad_token_id=tokenizer.eos_token_id
)
# 包装成LangChain的LLM接口
llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
print("模型加载完成!")
return llm
def create_chain(self):
"""创建检索问答链"""
# 定义提示模板,指导模型根据上下文回答
prompt_template = """你是一个专业的金融问答助手。请严格根据以下提供的背景信息来回答问题。如果背景信息中没有相关答案,请直接说“根据现有资料,我无法回答这个问题”,不要编造信息。
背景信息:
{context}
问题:{question}
请根据背景信息提供专业、准确的回答:"""
PROMPT = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
# 构建检索器,从向量库中获取最相关的4个片段
retriever = self.vectorstore.as_retriever(search_kwargs={"k": 4})
# 创建 RetrievalQA 链
self.qa_chain = RetrievalQA.from_chain_type(
llm=self.llm,
chain_type="stuff", # 将所有检索到的上下文“塞”进提示词
retriever=retriever,
chain_type_kwargs={"prompt": PROMPT},
return_source_documents=True # 返回参考来源
)
print("问答链创建成功!")
return self.qa_chain
def ask(self, question):
"""提问并获取答案"""
if not self.qa_chain:
self.create_chain()
result = self.qa_chain({"query": question})
answer = result["result"]
source_docs = result["source_documents"]
return answer, source_docs
4.3 第三步:创建FastAPI后端服务
创建 api/main.py 文件:
# api/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from core.knowledge_base import KnowledgeBase
from core.qa_chain import QwenQAChain
import uvicorn
app = FastAPI(title="金融RAG问答机器人API", version="1.0")
# 全局变量,用于缓存知识库和问答链
kb = None
qa_system = None
class QuestionRequest(BaseModel):
question: str
class AnswerResponse(BaseModel):
answer: str
sources: list[str]
@app.on_event("startup")
async def startup_event():
"""服务启动时,加载知识库和模型"""
global kb, qa_system
try:
print("正在初始化知识库...")
kb = KnowledgeBase()
vectorstore = kb.load_existing_vectorstore()
if not vectorstore:
raise RuntimeError("向量数据库未找到,请先运行知识库构建脚本。")
print("正在加载问答系统...")
qa_system = QwenQAChain(vectorstore)
qa_system.create_chain()
print("系统启动完成,准备接收请求。")
except Exception as e:
print(f"启动失败: {e}")
raise
@app.get("/")
def read_root():
return {"message": "金融RAG问答机器人服务已启动"}
@app.post("/ask", response_model=AnswerResponse)
async def ask_question(req: QuestionRequest):
if qa_system is None:
raise HTTPException(status_code=503, detail="问答系统未就绪")
try:
answer, source_docs = qa_system.ask(req.question)
# 提取来源文档的前缀作为参考
source_list = [doc.metadata.get("source", "未知来源") for doc in source_docs]
return AnswerResponse(answer=answer, sources=source_list)
except Exception as e:
raise HTTPException(status_code=500, detail=f"处理问题时出错: {str(e)}")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
4.4 第四步:运行与测试
-
启动后端服务 :
cd financial_rag_robot python api/main.py看到“系统启动完成,准备接收请求”的日志后,服务就在
http://localhost:8000运行了。 -
使用curl测试API :
curl -X POST "http://localhost:8000/ask" \ -H "Content-Type: application/json" \ -d '{"question": "请总结一下本公司去年的主要财务表现?"}' -
(可选)创建简单的Streamlit前端 : 在项目根目录创建
app.py:# app.py import streamlit as st import requests st.title("💰 金融知识问答助手") st.markdown("基于RAG和Qwen大模型构建,可查询您的私有金融文档。") question = st.text_input("请输入您的问题:", placeholder="例如:什么是市盈率?") if st.button("提交") and question: with st.spinner("正在思考..."): try: response = requests.post( "http://localhost:8000/ask", json={"question": question}, timeout=30 ) if response.status_code == 200: result = response.json() st.success("回答:") st.write(result["answer"]) if result["sources"]: st.info("参考来源:") for src in result["sources"]: st.write(f"- {src}") else: st.error(f"请求失败: {response.status_code}") except Exception as e: st.error(f"连接出错: {e}")运行前端:
streamlit run app.py然后在浏览器中打开
http://localhost:8501即可体验交互界面。
5. 常见问题与排查思路
在实践过程中,你可能会遇到以下问题。这里提供一份排查清单。
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
运行 knowledge_base.py 时报错,提示缺少库 |
依赖未安装完整。 | 检查 requirements.txt ,确保已安装 pypdf , python-docx , sentence-transformers 等。 |
| 加载Qwen模型时内存/显存不足 | 模型太大,硬件资源不够。 | 1. 换用更小的模型,如 Qwen-1.8B-Chat 。 2. 使用 device_map=“cpu” 强制使用CPU(速度慢)。 3. 启用模型量化(如 bitsandbytes 库的8位量化)。 |
| 模型生成答案质量差,胡言乱语 | 1. 提示词(Prompt)没写好。 2. 检索到的上下文不相关。 3. 模型温度(temperature)参数过高。 |
1. 优化提示词,明确指令“严格根据上下文”。 2. 检查文本分割是否合理,调整 chunk_size 和 chunk_overlap 。 3. 尝试降低 temperature (如0.1)使输出更确定。 |
| API请求超时或无响应 | 1. 模型首次推理慢。 2. 后端服务崩溃。 3. 向量检索耗时过长。 |
1. 首次请求耐心等待。 2. 查看后端日志 api/main.py 的输出。 3. 确保Chroma数据库已正确创建并加载。 |
| 答案未基于提供的资料,仍是通用回答 | 检索器未找到相关文档,或提示词未生效。 | 1. 检查 ask 函数返回的 source_docs ,看检索到的内容是否相关。 2. 在提示词中更加强调背景信息,例如用“你必须依据以下信息回答:”开头。 |
| 中文支持不好,出现乱码或分割错误 | 1. 文本加载编码问题。 2. 嵌入模型不适合中文。 3. 文本分割器切分了中文字符。 |
1. 确保 TextLoader 指定 encoding=‘utf-8’ 。 2. 使用我们推荐的多语言MiniLM模型。 3. 在 RecursiveCharacterTextSplitter 的 separators 中加入中文标点。 |
6. 最佳实践与工程建议
当你成功运行起第一个Demo后,如果想将其升级为一个更健壮、可用于生产环境原型的系统,请关注以下方面:
6.1 知识库构建优化
- 文档预处理 :在加载前,清洗文档中的无关字符(如页眉页脚)、特殊格式。对于扫描版PDF,需要使用OCR工具(如
paddleocr,tesseract)先提取文字。 - 智能分块 :简单的按字符长度分割会切断句子完整性。可以尝试按语义分割(如
langchain的SemanticChunker)或利用文档结构(如按标题分割)。 - 元数据增强 :在分割文档时,为每个片段添加丰富的元数据,如
source(文件名)、page(页码)、category(文档类型)。这有助于后续检索和答案溯源。 - 增量更新 :实现知识库的增量更新功能,避免每次添加新文档都全量重建。Chroma 支持向已有集合中添加文档。
6.2 检索策略增强
- 混合检索 :结合 向量检索 (语义相似)和 关键词检索 (如BM25)。
langchain的EnsembleRetriever可以合并两者的结果,提高召回率。 - 重排序 :初步检索出10-20个片段后,使用一个更精细的交叉编码器模型对它们进行重排序,只将最相关的3-4个片段送给LLM,提升答案精度并节省上下文窗口。
- 元数据过滤 :允许用户在前端选择“仅在年报中搜索”或“仅搜索某章节”,通过向检索器传递元数据过滤器实现。
6.3 提示工程与模型优化
- 迭代提示词 :根据测试结果不断优化你的系统提示词。可以加入角色设定、输出格式要求、拒绝回答的规则等。
- 考虑流式输出 :对于长答案,使用FastAPI的
StreamingResponse或langchain的回调函数实现逐词输出,提升用户体验。 - 模型微调 :如果领域术语非常特殊或回答风格有固定要求,可以考虑使用少量高质量数据对Qwen模型进行 微调 ,例如使用LoRA等高效微调技术,让模型更“懂行”。
6.4 系统部署与监控
- 环境隔离 :使用
Docker容器化你的应用,确保环境一致性。编写Dockerfile和docker-compose.yml。 - 配置管理 :将模型路径、Chroma存储路径、API端口等配置项抽离到环境变量或配置文件中(如
.env)。 - 日志记录 :为关键步骤(文档加载、检索、模型调用)添加详细的日志,便于问题追踪。可以使用
logging模块。 - 基础监控 :记录每个问答请求的耗时、Token消耗、用户问题(注意脱敏),用于分析性能和优化成本。
- 备选方案 :对于核心服务,可以考虑将向量数据库升级为更成熟的
Milvus或Qdrant,将LLM替换为性能更好的Qwen-7B或通过API调用云端大模型(需注意数据安全)。
从本地文档处理到智能问答生成,我们完成了一个完整的RAG系统闭环。这个项目麻雀虽小,五脏俱全,涵盖了AI应用开发的核心流程:数据处理、模型集成、服务封装和前端展示。掌握它,你就拿到了进入大模型应用开发领域的钥匙。接下来,你可以尝试用更复杂的金融报告测试它,优化检索效果,或者将其改造成一个客服机器人、知识库助手。
🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度
更多推荐
所有评论(0)