摘要:理论听了千百遍,不如亲手敲行代码。本文将彻底告别空谈,基于Python和LangChain框架,从0到1带你构建一个有“职业操守”的AI客服。它只根据你提供的知识库回答问题,当知识库没有答案时,它会干脆利落地承认“我不知道”,而不是一本正经地胡说八道。
前言:为什么你的AI客服总在“自由发挥”?
如果你只是简单地将用户问题丢给大模型,那么AI的回答将是不可控的。因为它会动用它庞大的、但可能过时或错误的内部知识。我们的目标,是为AI带上“缰绳”,强制它成为一个只从我们私有知识库中寻找答案的“检索员”+“总结员”。
这套方法论就是RAG(Retrieval-Augmented Generation)。接下来,我们将用代码实现它。
Step 1: 环境准备与项目设定
首先,确保你的Python环境中安装了必要的库。
Bash
pip install langchain openai faiss-cpu tiktoken
-
langchain
: 我们的核心编排框架。 -
openai
: 用于调用大模型API(本文以此为例,你也可以换成其他模型)。 -
faiss-cpu
: 一个轻量级的本地向量数据库,用于存储和搜索我们的知识。 -
tiktoken
: 用于文本分词。
然后,配置你的API密钥(例如,在环境变量中设置OPENAI_API_KEY
)。
最后,我们创建项目的核心——知识库。新建一个名为knowledge_base.txt
的文件,这就是我们AI客服的“唯一事实来源”。
knowledge_base.txt
# 2025年夏季大促活动政策
## 关于退货政策
所有在2025年7月1日至7月31日期间购买的“夏日限定”系列商品,享受30天无理由退货服务。退货商品需保持包装完好,不影响二次销售。常规商品维持原有的14天退货政策。
## 关于优惠券使用
活动期间,用户可领取一张“满300减50”的优惠券,每个账户限领一次。该优惠券不可与会员折扣同时使用。优惠券有效期至2025年8月15日。
## 关于发货时间
由于订单量激增,活动期间的订单将在支付成功后的72小时内发货。我们将优先处理顺丰速运可达的地区。
Step 2: 知识库的“数字化”——加载、切分与向量化
现在,我们用代码将这份文档加载进来,并将其转化为AI能够理解的格式。
Python
import os
from langchain_community.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
# 请确保你已设置 OPENAI_API_KEY 环境变量
# os.environ["OPENAI_API_KEY"] = "sk-..."
# 1. 加载文档
loader = TextLoader("knowledge_base.txt")
documents = loader.load()
# 2. 切分文档
# RecursiveCharacterTextSplitter 是一个很好的选择,它会按段落、句子等语义单位切分
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
docs = text_splitter.split_documents(documents)
print(f"文档被切分成了 {len(docs)} 个块")
# 3. 向量化与存入数据库
# embeddings 模型负责将文本块转换为数学向量
embeddings = OpenAIEmbeddings()
# FAISS 是一个高效的本地向量数据库
db = FAISS.from_documents(docs, embeddings)
# 将数据库保存到本地,方便后续直接加载使用
db.save_local("faiss_index")
运行以上代码后,你就拥有了一个名为faiss_index
的本地向量数据库。这是我们AI的“长期记忆”,它已经“学习”了知识库里的全部内容。
Step 3: 构建“绝不撒谎”的问答链(RAG Chain)
这是整个项目的核心。我们将构建一个工作流,它会先根据用户问题去向量数据库中检索最相关的知识,然后将这些知识连同问题一起交给大模型,并用一个“铁律”般的Prompt来约束它。
Python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
# 加载之前创建的数据库
embeddings = OpenAIEmbeddings()
db = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
# 创建一个检索器
retriever = db.as_retriever()
# 1. 定义核心Prompt模板
# 这是防止AI幻觉的关键!我们给了它非常严格的指令。
template = """
你是一个专业的客服机器人。请严格根据下面提供的【背景知识】来回答问题。
如果【背景知识】中没有相关信息,就直接回答:“抱歉,关于这个问题我没有查到相关信息,您可以咨询人工客服。”
禁止编造或使用你自己的知识。
【背景知识】
{context}
【用户问题】
{question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 2. 初始化大语言模型
# 我们将 temperature 设置为 0,最大程度减少随机性/创造性
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
# 3. 构建RAG链 (使用LangChain表达式语言 LCEL)
# 这是一个非常清晰的声明式工作流
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# --- 测试我们的问答链 ---
# 测试1:问题在知识库中有明确答案
question1 = "夏日限定商品可以退货吗?"
print(f"提问: {question1}")
response1 = rag_chain.invoke(question1)
print(f"回答: {response1}\n")
# 测试2:问题在知识库中没有答案
question2 = "你们支持货到付款吗?"
print(f"提问: {question2}")
response2 = rag_chain.invoke(question2)
print(f"回答: {response2}\n")
# 测试3:一个更复杂的问题
question3 = "我既是会员,也想用那个满减券,可以一起用吗?"
print(f"提问: {question3}")
response3 = rag_chain.invoke(question3)
print(f"回答: {response3}\n")
预期输出:
提问: 夏日限定商品可以退货吗?
回答: 可以的,在2025年7月1日至7月31日期间购买的“夏日限定”系列商品,享受30天无理由退货服务,但需要保持包装完好。
提问: 你们支持货到付款吗?
回答: 抱歉,关于这个问题我没有查到相关信息,您可以咨询人工客服。
提问: 我既是会员,也想用那个满减券,可以一起用吗?
回答: 根据背景知识,活动期间的“满300减50”优惠券不可与会员折扣同时使用。
看到结果了吗?我们的AI客服做到了:
-
有据可依:对于知识库里的问题,它能准确回答。
-
知之为知之,不知为不知:当知识库没有相关信息时,它会诚实地承认,而不是去编造一个答案。这正是我们对抗AI幻觉的核心目标。
结论:从代码原型到生产系统的思考
我们刚刚用不到100行代码,就构建了一个具备基础幻觉防御能力的AI客服原型。它虽然简单,但五脏俱全,完美诠释了RAG的核心思想。
从这个原型走向生产环境,你还需要考虑:
-
更强大的向量数据库:比如Milvus或Weaviate,以应对海量知识。
-
混合搜索:结合关键词搜索与向量搜索,提高检索精度。
-
评估与监控:建立一套评估体系(如RAGAS框架)和日志监控,持续追踪AI的表现,并用bad case来迭代优化。
-
更精细的兜底策略:设计更复杂的规则,在特定场景(如用户情绪激动时)自动转接人工。
但无论系统多么复杂,其底层逻辑都与我们今天所构建的一致。掌握了这套从数据到链条的构建方法,你就拥有了驾驭大模型、构建可靠AI应用的核心能力。