智能客服问答系统

#【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道!#

为一家大型电商企业构建一个智能客服系统,能够自动回答用户关于产品、订单、售后等常见问题,降低人工客服成本,提升响应效率

  • 用户问题理解:精准识别用户意图
  • 知识检索:从企业知识库中查找相关信息
  • 答案生成:生成准确、友好的回答
  • 多轮对话:支持上下文理解的多轮交互

技术架构图

用户输入 → 意图识别(BERT) → 知识检索(FAISS) → 答案生成(BART/LLaMA) → 响应输出
      ↑          ↓               ↓               ↓
  对话管理 ← 上下文管理 ← 实体抽取 ← 情感分析

技术栈与工具

模块核心技术工具/框架企业级考量
意图识别BERT Fine-tuningHugging Face Transformers, spaCy高准确率、低延迟
知识检索Dense RetrievalFAISS, Sentence-Transformers大规模向量检索、实时响应
答案生成RAG + LLMLangChain, LLaMA-2, GPT-3.5可控生成、避免幻觉
对话管理State MachineRasa, Custom Dialogue Manager业务流程集成
部署运维API服务化FastAPI, Docker, Kubernetes高可用、弹性伸缩
监控评估MLOpsMLflow, Prometheus, Grafana性能监控、持续优化

一、环境准备与依赖

requirements.txt

transformers==4.30.0
torch==2.0.0
langchain==0.0.200
faiss-cpu==1.7.4
sentence-transformers==2.2.2
fastapi==0.95.0
uvicorn==0.21.0
pydantic==1.10.7
redis==4.5.5
mlflow==2.4.2

二、知识库构建与向量检索

  • 语义搜索: 基于含义而非关键词匹配
  • 向量化表示: 使用深度学习模型将文本转换为向量
  • 近似最近邻搜索: FAISS提供高效的相似度搜索
  • 文档分块: 处理长文档,提高搜索精度
import faiss # Facebook的向量相似度搜索库
import numpy as np # 数值计算库
from sentence_transformers import SentenceTransformer # 文本嵌入模型
from langchain.text_splitter import RecursiveCharacterTextSplitter# 文本分割
from langchain.document_loaders import DirectoryLoader, TextLoader# 文档加载
import json

class KnowledgeBase:
    def __init__(self, model_name='all-MiniLM-L6-v2'): #使用 all-MiniLM-L6-v2 模型,这是一个轻量级的句子转换模型 生成384维的文本嵌入向量
        self.embedder = SentenceTransformer(model_name) # 文本嵌入模型
        self.index = None # FAISS索引
        self.documents = [] # 存储文档内容
        
    def build_from_directory(self, data_dir):
        """从文件构建知识库"""
        # 从指定目录加载所有.txt文件
        loader = DirectoryLoader(data_dir, glob="**/*.txt", #表示递归搜索所有子目录中的txt文件
                               loader_cls=TextLoader)
        documents = loader.load()
        
        # 文本分割
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=500, #将长文档分割成500字符的块
            chunk_overlap=50 #设置50字符的重叠,确保上下文连贯性
        )
        chunks = text_splitter.split_documents(documents)
        
        # 提取文本和元数据
        self.documents = [chunk.page_content for chunk in chunks] #提取文本内容到 self.documents
        metadata = [chunk.metadata for chunk in chunks] #保存元数据(可选使用)
        
        # 生成嵌入向量 将文本块转换为数值向量  show_progress_bar=True 显示进度条
        embeddings = self.embedder.encode(self.documents, show_progress_bar=True)
        
        # 构建FAISS索引
        dimension = embeddings.shape[1] #获取向量维度(384维)
        self.index = faiss.IndexFlatL2(dimension) #创建L2距离(欧几里得距离)索引
        self.index.add(embeddings.astype('float32')) #添加所有嵌入向量到索引
        
        # 保存知识库
        self._save_knowledge_base()
        
    def search(self, query, top_k=5):
        """语义搜索"""
        query_embedding = self.embedder.encode([query]) #将查询文本转换为向量
        #在FAISS索引中搜索最相似的top_k个向量  返回距离和索引
        distances, indices = self.index.search(query_embedding.astype('float32'), top_k)
        
        results = []
        for idx, distance in zip(indices[0], distances[0]):
            if idx != -1:  # FAISS可能返回-1
                results.append({
                    'text': self.documents[idx],
                    'score': float(distance),
                    'index': idx
                })
        return results

# 初始化知识库
kb = KnowledgeBase()
kb.build_from_directory("./knowledge_base/")

三、意图识别与分类

数据处理流程

原始文本 → Tokenizer → Token IDs → BERT模型 → Logits → Softmax → 概率分布
  • 客服系统: 自动路由用户问题到相应部门
  • 聊天机器人: 理解用户意图并提供相应服务
  • 语音助手: 识别用户请求类型
  • 数据分析: 对用户查询进行自动分类
from transformers import AutoTokenizer, AutoModelForSequenceClassification # Hugging Face Transformers库
from transformers import pipeline # 简化模型使用的管道
import torch # PyTorch深度学习框架

class IntentClassifier:
    def __init__(self, model_path="bert-base-uncased"):
        self.tokenizer = AutoTokenizer.from_pretrained(model_path) #将文本转换为模型可理解的token ID
        self.model = AutoModelForSequenceClassification.from_pretrained(
            model_path, num_labels=6 #BERT分类模型,输出6个类别的概率分布
        )
        #6种预定义的意图类别
        self.labels = ["产品咨询", "订单查询", "售后服务", "支付问题", "物流查询", "其他"]
        
    def predict_intent(self, text):
        """预测用户意图"""
        # return_tensors="pt": 返回PyTorch张量
        # truncation=True: 截断过长文本(BERT最大512token)
        # padding=True: 填充短文本到相同长度
        inputs = self.tokenizer(text, return_tensors="pt", truncation=True, padding=True)
        
        with torch.no_grad():#禁用梯度计算,节省内存和计算资源
            outputs = self.model(**inputs) #**inputs: 解包tokenizer输出的字典(包含input_ids, attention_mask等)
        
        probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)#softmax: 将logits转换为概率分布(总和为1)
        predicted_class_id = torch.argmax(probabilities, dim=-1).item()#argmax: 获取概率最大的类别ID
        
        return {
            "intent": self.labels[predicted_class_id], #预测的意图类别
            "confidence": float(probabilities[0][predicted_class_id]),#预测置信度(0-1)
            "all_probs": {label: float(prob) for label, prob in zip(self.labels, probabilities[0])}#所有类别的概率分布
        }

# 使用预训练模型进行意图分类
intent_classifier = IntentClassifier()

# 示例使用:
# result = intent_classifier.predict_intent("我的订单什么时候发货?")
# 输出: {"intent": "物流查询", "confidence": 0.95, ...}

四、 RAG(检索增强生成)答案生成

用户问题 → 知识库检索 → 构建提示词 → LLM生成 → 格式化输出
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM  # 序列到序列模型(未直接使用)
from langchain.llms import LlamaCpp # 本地LLaMA模型加载
from langchain.chains import RetrievalQA # 检索增强生成链(未直接使用)
from langchain.prompts import PromptTemplate # 提示词模板

class AnswerGenerator:
    def __init__(self, knowledge_base):
        self.kb = knowledge_base # 知识库实例
        self.llm = self._load_llm() # 加载语言模型
        self.prompt_template = self._create_prompt_template() # 创建提示词模板
        
    def _load_llm(self):
        """加载本地LLM(如LLaMA-2)"""
        return LlamaCpp(
            model_path="./models/llama-2-7b-chat.Q4_K_M.gguf",# 模型路径  4位量化版本的LLaMA-2 7B聊天模型
            temperature=0.1, # 温度参数(低值=更确定性)
            max_tokens=2000,# 最大生成token数 限制生成长度,防止过长响应
            top_p=1, # 核采样参数(1=禁用)
            verbose=False,# 不显示详细日志
        )
    
    def _create_prompt_template(self):
        """创建提示词模板"""
        template = """基于以下上下文信息,请以专业客服的身份回答用户问题。
        如果上下文信息不足以回答问题,请如实告知无法回答。

        上下文信息:
        {context}

        用户问题:{question}
        客服回答:"""
        
        return PromptTemplate(
            template=template,
            input_variables=["context", "question"]
        )
    
    def generate_answer(self, question, conversation_history=None):
        """生成答案"""
        # 检索相关知识 从知识库检索最相关的3个文档片段
        search_results = self.kb.search(question, top_k=3)
        context = "\n".join([result['text'] for result in search_results])#将检索结果合并为上下文字符串
        
        # 准备提示词 将检索到的上下文和用户问题填入模板
        prompt = self.prompt_template.format(
            context=context,
            question=question
        )
        
        # 生成回答 使用LLaMA模型生成回答
        response = self.llm(prompt)
        
        return {
            "answer": response.strip(), # 清理空白字符的回答
            "sources": search_results, # 检索到的源文档
            "context": context# 使用的上下文
        }

# 初始化答案生成器
answer_generator = AnswerGenerator(kb)

五、对话状态管理

用户请求 → 获取会话 → 更新历史 → 状态转换 → 持久化存储
  • 基于意图的状态转换: 根据分类结果切换状态
  • 上下文保持: 通过历史记录维持对话连贯性
  • 超时管理: 自动清理闲置会话
from datetime import datetime
import redis
import json

class DialogueManager:
    def __init__(self, redis_host='localhost', redis_port=6379):
        self.redis_client = redis.Redis(host=redis_host, port=redis_port, db=0)
        self.session_timeout = 1800  # 30分钟
        
    def create_session(self, user_id):
        """创建新的对话会话"""
        #会话ID生成策略,session_{用户ID}_{时间戳}   session_12345_1704067200.123456
        session_id = f"session_{user_id}_{datetime.now().timestamp()}"
        session_data = {
            "session_id": session_id,
            "user_id": user_id,
            "created_at": datetime.now().isoformat(),
            "history": [],
            "current_state": "greeting"
        }
        
        self.redis_client.setex( #设置键值对并指定过期时间
            session_id, 
            self.session_timeout, 
            json.dumps(session_data) #将Python字典转换为JSON字符串存储
        )
        return session_id
    
    def update_session(self, session_id, user_message, bot_response, intent):
        """更新会话状态"""
        session_data = self.get_session(session_id)
        if not session_data:
            return None
            
        # 更新对话历史,完整记录每次交互的时间、内容、意图,支持对话上下文理解
        session_data["history"].append({
            "timestamp": datetime.now().isoformat(),
            "user": user_message,
            "bot": bot_response,
            "intent": intent #来自意图分类器的结果
        })
        
        # 更新状态机(简化示例)
        if intent == "订单查询":
            session_data["current_state"] = "awaiting_order_id"
        elif intent == "支付问题":
            session_data["current_state"] = "resolving_payment"
        else:
            session_data["current_state"] = "general_consultation"
        
        self.redis_client.setex(
            session_id, 
            self.session_timeout, 
            json.dumps(session_data)
        )
        return session_data
    
    def get_session(self, session_id): #从Redis获取数据并反序列化为Python对象
        """获取会话数据"""
        data = self.redis_client.get(session_id)
        return json.loads(data) if data else None

# 初始化对话管理器
dialogue_manager = DialogueManager()

六、FastAPI服务部署

from fastapi import FastAPI, HTTPException, Depends # FastAPI 核心功能
from fastapi.middleware.cors import CORSMiddleware # 跨域中间件
from pydantic import BaseModel  # 数据验证模型
from typing import List, Optional # 类型注解
import uvicorn # ASGI 服务器

app = FastAPI(title="智能客服系统", version="1.0.0")#创建FaseAPI应用实例,设置API标题和版本


# CORS中间件
app.add_middleware(
    CORSMiddleware, #跨域资源共享,允许前端应用访问API
    allow_origins=["*"],#生产环境应限制为具体域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 请求数据模型
class ChatRequest(BaseModel):
    message: str  # 必需:用户消息
    session_id: Optional[str] = None # 可选:会话ID
    user_id: str # 必需:用户ID

#响应模型
class ChatResponse(BaseModel):
    response: str # 机器人回复
    session_id: str # 会话ID
    intent: str # 识别出的意图
    confidence: float # 意图置信度
    sources: List[dict] # 知识库来源

# 依赖注入
def get_services():
    return {
        "intent_classifier": intent_classifier,
        "answer_generator": answer_generator,
        "dialogue_manager": dialogue_manager
    }

@app.post("/chat", response_model=ChatResponse) #处理聊天请求
async def chat_endpoint(request: ChatRequest, services: dict = Depends(get_services)):
    try:
        # 获取或创建会话
        if not request.session_id:
            session_id = services["dialogue_manager"].create_session(request.user_id)
        else:
            session_id = request.session_id
        
        # 意图识别,调用意图分类器分析用户意图
        intent_result = services["intent_classifier"].predict_intent(request.message)
        
        # 生成回答(基于知识库生成)
        answer_result = services["answer_generator"].generate_answer(
            request.message, 
            conversation_history=None  # 可传入历史记录
        )
        
        # 更新会话状态(记录对话历史和状态变更)
        services["dialogue_manager"].update_session(
            session_id, 
            request.message, 
            answer_result["answer"], 
            intent_result["intent"]
        )
        
        return ChatResponse(
            response=answer_result["answer"],
            session_id=session_id,
            intent=intent_result["intent"],
            confidence=intent_result["confidence"],
            sources=answer_result["sources"]
        )
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health_check():
    return {"status": "healthy", "timestamp": datetime.now().isoformat()}

#服务器启动 uvicorn: 高性能 ASGI 服务器  host="0.0.0.0": 监听所有网络接口
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

请求示例:

POST /chat HTTP/1.1
Content-Type: application/json

{
    "message": "我的订单状态怎么样?",
    "user_id": "user_12345"
}

响应示例:

HTTP/1.1 200 OK
Content-Type: application/json

{
    "response": "您的订单正在配送中...",
    "session_id": "session_12345_1704067200",
    "intent": "订单查询", 
    "confidence": 0.95,
    "sources": [...]
}

七、 Docker容器化部署

# Dockerfile
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# 下载模型(可在构建时或运行时下载)
RUN python -c "
from sentence_transformers import SentenceTransformer
SentenceTransformer('all-MiniLM-L6-v2')
"

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

八、性能监控与评估

import mlflow # 机器学习实验追踪和管理
from prometheus_client import Counter, Histogram, generate_latest # Prometheus指标客户端
from fastapi import Response  # FastAPI响应处理

# Prometheus指标(计数器指标   直方图指标)
# 名称: request_count
# 描述: 应用请求计数
# method(HTTP方法), endpoint(端点路径), http_status(状态码)
# 统计各端点的请求次数和成功率
REQUEST_COUNT = Counter('request_count', 'App Request Count', ['method', 'endpoint', 'http_status'])
# 名称: request_latency_seconds
# 描述: 请求延迟(秒)
# 标签: endpoint(端点路径)
# 用途: 测量API响应时间分布
REQUEST_LATENCY = Histogram('request_latency_seconds', 'Request latency', ['endpoint'])

@app.middleware("http")
async def monitor_requests(request, call_next):
    start_time = time.time() #记录开始时间
    response = await call_next(request) #执行请求处理
    process_time = time.time() - start_time #计算处理时间
    
    REQUEST_LATENCY.labels(endpoint=request.url.path).observe(process_time)#记录延迟指标  .observe(): 记录时间值到直方图
    
    REQUEST_COUNT.labels( #设置标签值(端点路径),设置多个标签
        method=request.method, 
        endpoint=request.url.path, 
        http_status=response.status_code
    ).inc() #计数器增加1
    
    return response

@app.get("/metrics")
async def metrics():
    return Response(generate_latest(), media_type="text/plain") #text/plain (Prometheus标准格式)

# MLflow实验跟踪
def log_experiment(params, metrics, artifacts):
    with mlflow.start_run():
        mlflow.log_params(params)
        mlflow.log_metrics(metrics)
        for artifact in artifacts:
            mlflow.log_artifact(artifact)
        mlflow.set_tag("project", "customer_service_ai")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值