大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于LangChain的带AI智能客服的微信小程序商城系统(RAG+FastAPI+Vue3)。

项目介绍
随着电子商务的快速发展和人工智能技术的不断成熟,传统电商平台的客户服务模式正面临着人力成本高、响应速度慢、服务质量参差不齐等问题。如何借助人工智能技术为用户提供高质量、低成本、全天候的智能客服服务,已成为电商系统建设的重要研究方向。大语言模型(LLM)虽然具备强大的自然语言理解与生成能力,但存在知识更新滞后、易产生“幻觉”、无法准确回答企业私有业务问题等局限。检索增强生成(Retrieval-Augmented Generation, RAG)技术通过将外部知识库检索与大模型生成相结合,能够有效缓解上述问题。
本文设计并实现了一个基于LangChain框架、融合RAG技术的带AI智能客服的微信小程序商城系统。系统采用前后端分离架构,后端使用Python的FastAPI框架构建高性能异步Web服务,通过SQLAlchemy操作MySQL数据库;管理后台采用Vue3 + Element Plus + Pinia + ECharts技术栈实现,提供商品、订单、用户、轮播图及知识库等管理功能;商城前台采用原生微信小程序实现,为用户提供浏览、购物、下单、支付及AI智能客服等服务。AI智能客服模块以LangChain为核心,结合Chroma向量数据库,对管理员上传的txt、doc、pdf、markdown等格式的知识库文件进行解析、分块、向量化存储;当用户提问时,系统先在向量库中进行相似度检索获取相关知识,再将检索结果作为上下文拼接到提示词中,调用大语言模型生成准确、友好的回答,从而实现知识增强的智能问答。
系统的测试结果表明,各功能模块运行稳定,AI智能客服能够基于私有知识库给出贴合业务的回答,有效提升了客服效率与用户体验。本文的研究对人工智能技术在电商领域的工程化落地具有一定的参考价值。
源码下载
链接: https://pan.baidu.com/s/1VFnbAvX50aGdIhhzmrfdhA?pwd=1234
提取码: 1234
系统展示









核心代码
"""
Chroma向量数据库服务
负责文档向量化存储和相似检索
"""
from typing import List, Optional
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
from config import (
OPENAI_API_KEY,
OPENAI_BASE_URL,
EMBEDDING_MODEL,
EMBEDDING_DIMENSIONS,
CHROMA_DIR,
CHROMA_COLLECTION,
CHUNK_SIZE,
CHUNK_OVERLAP,
RAG_TOP_K,
)
class VectorStoreService:
"""Chroma向量数据库服务类"""
def __init__(self):
"""初始化嵌入模型和向量存储"""
self.embeddings = OpenAIEmbeddings(
model=EMBEDDING_MODEL,
openai_api_key=OPENAI_API_KEY,
openai_api_base=OPENAI_BASE_URL,
dimensions=EMBEDDING_DIMENSIONS,
# 阿里云兼容接口只接受 str / list[str],LangChain 默认会发送 token id 数组导致 400
check_embedding_ctx_length=False,
)
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP,
length_function=len,
)
self._vectorstore: Optional[Chroma] = None
@property
def vectorstore(self) -> Chroma:
"""
获取或创建Chroma向量存储实例
:return: Chroma向量存储
"""
if self._vectorstore is None:
self._vectorstore = Chroma(
collection_name=CHROMA_COLLECTION,
embedding_function=self.embeddings,
persist_directory=str(CHROMA_DIR),
)
return self._vectorstore
def add_documents(self, text: str, file_id: int, file_name: str) -> int:
"""
将文本分块并向量化存入Chroma
:param text: 文档文本内容
:param file_id: 知识库文件ID
:param file_name: 文件名
:return: 分块数量
"""
doc = Document(
page_content=text,
metadata={"file_id": file_id, "file_name": file_name},
)
chunks = self.text_splitter.split_documents([doc])
if chunks:
self.vectorstore.add_documents(chunks)
return len(chunks)
def delete_by_file_id(self, file_id: int) -> None:
"""
根据文件ID删除向量数据
:param file_id: 知识库文件ID
"""
try:
self.vectorstore._collection.delete(where={"file_id": file_id})
except Exception:
pass
def similarity_search(self, query: str, top_k: int = RAG_TOP_K) -> List[Document]:
"""
相似度检索
:param query: 查询文本
:param top_k: 返回Top-K结果
:return: 相关文档列表
"""
try:
return self.vectorstore.similarity_search(query, k=top_k)
except Exception:
return []
# 全局单例
vector_store_service = VectorStoreService()
<template>
<div class="page-container">
<div class="search-bar">
<el-input v-model="query.keyword" placeholder="搜索订单号" clearable style="width:200px" @clear="loadData" />
<el-select v-model="query.status" placeholder="订单状态" clearable style="width:150px" @change="loadData">
<el-option v-for="(label, val) in orderStatusMap" :key="val" :label="label" :value="Number(val)" />
</el-select>
<el-button type="primary" @click="loadData">搜索</el-button>
</div>
<div class="table-wrapper">
<el-table :data="tableData" border stripe style="width:100%">
<el-table-column prop="order_no" label="订单号" min-width="160" />
<el-table-column prop="username" label="用户" min-width="100" />
<el-table-column prop="pay_amount" label="实付金额" min-width="100">
<template #default="{ row }">¥{{ row.pay_amount }}</template>
</el-table-column>
<el-table-column prop="status" label="订单状态" min-width="100">
<template #default="{ row }">
<el-tag :type="orderStatusTagType[row.status]" effect="light" round>
{{ orderStatusMap[row.status] }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="receiver_name" label="收货人" min-width="80" />
<el-table-column prop="receiver_phone" label="电话" min-width="120" />
<el-table-column prop="receiver_address" label="地址" min-width="180" show-overflow-tooltip />
<el-table-column prop="create_time" label="下单时间" min-width="160">
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
</el-table-column>
<el-table-column label="操作" min-width="240" fixed="right">
<template #default="{ row }">
<div v-if="getStatusActions(row.status).length" class="action-btns">
<el-button
v-for="action in getStatusActions(row.status)"
:key="action.status"
:type="action.type"
size="small"
@click="handleStatus(row, action.status)"
>{{ action.label }}</el-button>
</div>
<span v-else class="no-action">—</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-bar">
<el-pagination v-model:current-page="query.page" v-model:page-size="query.page_size"
:total="total" layout="total, sizes, prev, pager, next" @change="loadData" />
</div>
</div>
</template>
<script setup>
/** 订单管理页面 */
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { getOrderList, updateOrderStatus } from '@/api/order'
import { formatDateTime, orderStatusMap } from '@/utils/date'
const orderStatusTagType = {
0: 'warning',
1: 'primary',
2: 'info',
3: 'success',
4: 'danger',
}
const statusActionsMap = {
0: [
{ status: 1, label: '确认支付', type: 'success' },
{ status: 4, label: '取消订单', type: 'danger' },
],
1: [
{ status: 2, label: '发货', type: 'primary' },
{ status: 4, label: '取消订单', type: 'danger' },
],
2: [{ status: 3, label: '完成', type: 'success' }],
3: [],
4: [],
}
function getStatusActions(status) {
return statusActionsMap[status] || []
}
const tableData = ref([])
const total = ref(0)
const query = reactive({ page: 1, page_size: 10, keyword: '', status: null })
async function loadData() {
const res = await getOrderList(query)
tableData.value = res.data.list
total.value = res.data.total
}
async function handleStatus(row, status) {
await updateOrderStatus(row.id, status)
ElMessage.success('状态更新成功')
loadData()
}
onMounted(loadData)
</script>
<style scoped>
.action-btns {
display: flex;
flex-wrap: wrap;
gap: 4px 8px;
}
.no-action {
color: #c0c4cc;
}
</style>
<!-- 首页 - 轮播图+商品列表 -->
<view class="container">
<!-- 轮播图 -->
<swiper class="banner-swiper" indicator-dots autoplay circular>
<swiper-item wx:for="{{banners}}" wx:key="id">
<image class="banner-img" src="{{item.image}}" mode="aspectFill" />
</swiper-item>
</swiper>
<!-- 商品列表 -->
<view class="section-title">热门商品</view>
<view class="product-grid">
<view class="product-item" wx:for="{{products}}" wx:key="id" bindtap="goDetail" data-id="{{item.id}}">
<image class="product-img" src="{{item.image}}" mode="aspectFill" />
<view class="product-info">
<text class="product-name">{{item.name}}</text>
<view class="product-bottom">
<text class="product-price">¥{{item.price}}</text>
<text class="product-sales">已售{{item.sales}}</text>
</view>
</view>
</view>
</view>
<view wx:if="{{products.length === 0}}" class="empty-tip">暂无商品</view>
</view>

43万+

被折叠的 条评论
为什么被折叠?



