一、项目介绍
1、概述
- 本项目基于 ChromaDB 构建向量数据库,实现一个完整的检索增强生成(RAG, Retrieval-Augmented Generation)系统,用于提升大语言模型在私有知识库问答场景中的准确性与可控性
- 项目核心流程包括:文档加载与清洗、文本分块(Chunking)、向量化嵌入(Embedding)、向量存储与索引构建,以及基于语义相似度的检索模块。在用户提问时,系统首先从 ChromaDB 中检索最相关的知识片段,再将其与问题一起输入大语言模型,从而生成基于上下文的高质量回答
- 在模型层面,模型选择千问模型,引入 RAG 架构,有效缓解大模型“幻觉”问题,提高回答准确性与可追溯性
- 项目最终实现了一个可用于知识库问答、企业文档检索与智能客服的轻量级 RAG 系统
2、技术栈
- Python 基础语法
- Pytorch 深度学习库
- Transformer 架构
- Langchain 框架
- ChromaDB 向量数据库
- FastAPI 服务器框架
- Vue-cli 脚手架
- MySQL 数据库
二、项目开发
1、知识库搭建
- 知识库的搭建非常重要,知识库的质量直接影响 RAG 系统检索效果
- 知识库的搭建属于离线行为,即在我们构建 RAG 系统的客户端和服务器之前,就应该把知识库构建好
- 以
法律数据集.csv数据集为案例演示: - 数据集的解析和存入:
import os
import pandas as pd
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
from langchain_huggingface import HuggingFaceEmbeddings
# 读取数据
df = pd.read_csv(r"./datasets/法律数据集.csv")
# 数据集提取
data = df['text'].tolist()
# 把分块后的文本转为Document对象
documents = [Document(page_content=item) for item in data]
# 向量模型
embedding_model = HuggingFaceEmbeddings(
# 模型名称
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
# 缓存目录
cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)
# 持久化目录
persist_directory = "./low_db"
if os.path.exists(persist_directory):
vectordb = Chroma(
# 持久化目录
persist_directory=persist_directory,
# 集合名称
collection_name="low",
# 向量模型
embedding_function=embedding_model
)
print("Chroma 向量数据库已加载")
else:
vectordb = Chroma.from_documents(
# 文档
documents=documents,
# 向量模型
embedding=embedding_model,
# 持久化目录
persist_directory=persist_directory,
# 集合名称
collection_name="low",
# 采用余弦相似度(cosine)作为向量距离度量方式来进行相似度搜索
collection_metadata={"hnsw:space": "cosine"}
)
print("Chroma 向量数据库已生成并保存")
- 测试:
import os
from langchain_chroma import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
embedding_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)
persist_directory = "./low_db"
vectordb = Chroma(
persist_directory=persist_directory,
collection_name="low",
embedding_function=embedding_model
)
# 检索器,检索3个最相关的文档
retriever = vectordb.as_retriever(search_kwargs={"k": 3})
# 提示模板
template = """
你是一个知识问答助手。
根据以下检索到的上下文回答用户问题,
如果答案不在上下文中,请说明“资料中未提及”。
如果答案在上线文中,不需要输出答案来自于上下文内容,只需要输出最终的结果。
上下文:
{context}
问题:
{question}
"""
prompt = PromptTemplate(input_variables=["context", "question"], template=template)
llm = ChatOpenAI(
model="qwen3.5-35b-a3b",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
rag_chain = (
RunnableParallel({
"context": retriever,
"question": RunnablePassthrough()
})
| prompt
| llm
| StrOutputParser()
)
query = "公安机关接到家庭暴力报案后应当做什么事情?"
answer = rag_chain.invoke(query)
print("\n最终回答:\n", answer)
2、服务器开发
2.1 搭建服务器项目
- 创建一个 FastAPI 项目
2.2 聊天接口配置
- 创建一个聊天接口子路由,功能代码先不写,先实现模板构建
- 在项目根目录下创建
api/controller文件夹,然后创建文件ChatController.py
from fastapi import APIRouter, Request
# 创建路由对象
chat_router = APIRouter()
# 聊天接口
@chat_router.get("/chat")
def chat(request: Request,question: str):
pass
main.py中配置跨域、注册子路由、配置启动
import uvicorn
from fastapi import FastAPI
from api.controller.ChatController import chat_router
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 跨域
app.add_middleware(
CORSMiddleware,
allow_origins=["http://127.0.0.1:8000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 注册子路由
app.include_router(chat_router)
# 启动服务
if __name__ == '__main__':
uvicorn.run(
app="main:app",
host="127.0.0.1",
port=8080,
reload=True,
)
2.3 工具封装
- 在项目根目录下创建
api/utils文件夹,然后创建文件ChromaDBUtil.py,封装一个获取 ChromaDB 连接的工具
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
def get_chromadb_conn(persist_directory: str):
# 向量模型
embedding_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)
return Chroma(
# 持久化目录
persist_directory=persist_directory,
# 集合名称
collection_name="low",
# 向量模型
embedding_function=embedding_model
)
- 在项目根目录下创建
api/utils文件夹,然后创建文件MyModel.py,封装一个加载模型的工具
import os
from langchain_openai import ChatOpenAI
def load_model():
return ChatOpenAI(
model="qwen3.5-35b-a3b",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
2.4 聊天接口实现
-
主要采取 MVC 分层思想,在业务逻辑层完成对应的聊天功能代码处理
-
在项目根目录下创建
api/service文件夹,然后创建文件ChatService.py
from api.utils.ChromaDBUtil import get_chromadb_conn
from api.utils.MyModel import load_model
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
def chat(question: str):
# 获取向量数据库连接
vectordb = get_chromadb_conn(r"F:\workspace\AI\chromadb_rag\feifan_rag_server\create_data\low_db")
# 检索器
retriever = vectordb.as_retriever(search_kwargs={"k": 10})
# 提示词模板
template = """
你是一个智能问答助手。
请先判断用户问题是否属于法律相关问题。
【处理规则】
1. 如果问题属于法律、法规、合同、劳动纠纷、侵权、婚姻、债务、刑事、行政等法律领域问题:
- 请优先结合提供的上下文内容进行回答
- 回答必须基于上下文信息
- 如果上下文中未提及相关内容,请明确回复:
“资料中未提及相关内容”
2. 如果问题不属于法律领域问题:
- 忽略上下文内容
- 直接基于你的自身知识正常回答用户问题
- 不要提及上下文或知识库
【上下文】
{context}
【用户问题】
{question}
请直接输出最终答案。
"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=template
)
# 加载模型
llm = load_model()
# RAG执行链
rag_chain = (
RunnableParallel({
"context": retriever,
"question": RunnablePassthrough()
})
| prompt
| llm
| StrOutputParser()
)
# 流式输出
for chunk in rag_chain.stream(question):
yield chunk
- 修改
api/controller/ChatController.py代码
from fastapi import APIRouter, Request
from starlette.responses import StreamingResponse
from api.service import ChatService
# 创建路由对象
chat_router = APIRouter()
# 聊天接口
@chat_router.get("/chat")
def chat(request: Request, question: str):
return StreamingResponse(content=ChatService.chat(question), media_type="text/event-stream")
2.5 测试

2.6 优化
- 在 RAG 检索后,加入 rerank,提高检索结果的质量
- 为了方便客户端解析,后端修改一下
- 修改
api/service/ChatService.py中的 chat 函数的返回值
return rag_chain.invoke(question)
- 修改
api/controller/ChatController.py代码
import json
from fastapi import APIRouter, Request
from starlette.responses import StreamingResponse
from api.service import ChatService
# 创建路由对象
chat_router = APIRouter()
# 聊天接口
@chat_router.get("/chat")
def chat(request: Request, question: str):
result = ChatService.chat(question)
def streaming_content():
for i in result:
yield f"data: {json.dumps({'content': i})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(content=streaming_content(), media_type="text/event-stream")
3、客户端开发
3.1 搭建客户端项目
- 创建以 vue-cli 项目
- 下载以下库
"axios": "^1.3.3",
"dompurify": "^3.3.3",
"element-ui": "^2.15.14",
"marked": "^3.0.8",
3.2 对话页面设计
- 套模板或者 AI 基于 element-ui 生成一个
<template>
<div id="chat-container">
<!-- 消息区 -->
<div id="messages">
<div
v-for="(msg, index) in messages"
:key="index"
class="message"
:class="msg.role"
>
<div class="avatar">
{{ msg.role === 'assistant' ? '🤖' : '👤' }}
</div>
<div class="content">
<div
class="text-message"
v-html="$renderMarkdown(msg.content)">
</div>
</div>
</div>
</div>
<!-- 输入区 -->
<div id="input-area">
<div class="input-wrapper">
<el-input
type="textarea"
v-model="input"
:rows="2"
placeholder="请输入你的问题..."
@keydown.enter.native.prevent="sendMessage"
>
</el-input>
<el-button
type="primary"
@click="sendMessage">
发送
</el-button>
</div>
</div>
</div>
</template>
<script>
import Vue from "vue";
export default {
name: "Index",
data() {
return {
serverUrl: "http://127.0.0.1:8080/",
input: "",
messages: [
{
role: "assistant",
content: "你好 👋,我是 AI 助手"
}
]
};
},
methods: {
// 滚动到底部
scrollToBottom() {
this.$nextTick(() => {
const box = document.getElementById("messages");
box.scrollTop = box.scrollHeight;
});
},
// 发送消息
sendMessage() {
if (this.input.trim() === "") return;
const userMsg = this.input.trim();
this.input = "";
// 用户消息
this.messages.push({
role: "user",
content: userMsg
});
// AI占位消息
this.messages.push({
role: "assistant",
content: "AI 正在思考..."
});
this.scrollToBottom();
const index = this.messages.length - 1;
let aiContent = "";
let finished = false;
// SSE请求
const params = new URLSearchParams({
question: userMsg
});
const es = new EventSource(
this.serverUrl + `chat?${params}`
);
let lastContent = "";
es.onmessage = e => {
if (e.data === "[DONE]") {
finished = true
es.close()
return
}
let data = JSON.parse(e.data)
if (data.content == "\n" && lastContent == "\n") {
return
}
aiContent += data.content;
Vue.set(this.messages, index, {
"role": "assistant",
"type": "text",
"content": aiContent
})
this.$nextTick(() => {
this.scrollToBottom()
})
lastContent = data.content;
}
es.onerror = () => {
if (finished) return;
es.close();
Vue.set(this.messages, index, {
role: "assistant",
content: aiContent || "连接中断,请重试"
});
};
}
}
};
</script>
<style scoped>
/* ===== 页面 ===== */
body {
margin: 0;
background: #f5f7fa;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto;
}
#chat-container {
width: 900px;
max-width: 95%;
height: 90vh;
margin: 5vh auto;
background: white;
border-radius: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
}
/* ===== 消息区 ===== */
#messages {
flex: 1;
overflow-y: auto;
padding: 24px;
background: #f7f8fa;
}
/* ===== 消息 ===== */
.message {
display: flex;
gap: 10px;
max-width: 75%;
margin-bottom: 18px;
padding: 12px 16px;
border-radius: 14px;
line-height: 1.6;
font-size: 14px;
}
.message.user {
margin-left: auto;
background: #409EFF;
color: white;
flex-direction: row-reverse;
}
.message.assistant {
background: white;
border: 1px solid #ebeef5;
}
/* ===== 头像 ===== */
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: white;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
/* ===== 内容 ===== */
.content {
flex: 1;
}
.text-message {
white-space: pre-wrap;
word-break: break-word;
}
/* ===== 输入区 ===== */
#input-area {
padding: 16px;
border-top: 1px solid #ebeef5;
background: white;
}
.input-wrapper {
display: flex;
align-items: flex-end;
}
.el-input {
flex: 1;
margin-right: 10px;
}
.el-button {
height: 52px;
}
</style>
3.3 测试

浙公网安备 33010602011771号