Day 3:加RAG——让Agent"有知识"
昨天的问题
Day 2的MockLLM靠关键词匹配回答问题。但真实场景下,LLM不知道你们公司的退货政策、退款流程、售后条款。这些信息不在任何LLM的训练数据里。
而且你不可能把所有公司政策都写进MockLLM的if-else里——文档有几十页,产品每天都在更新,政策每个季度在变。
今天做什么:RAG(检索增强生成)
RAG的思路非常朴素——跟人一模一样:
- 把公司文档提前"读"进去(切成小段,存起来)
- 用户问问题的时候,从文档里找到最相关的几段
- 把找到的段落和用户问题一起交给LLM
- LLM基于这些"参考资料"生成回答
类比:开卷考试。你不是背答案,而是知道去哪本书的第几页找。
你需要安装
bash
pip install chromadb langchain-text-splitters langchain-community📁 今天的代码目录
customer_service_agent/
├── day1.py
├── day2.py
└── day3.py ← 新增!+50行,加入RAGpython
# day3.py — 在Day2基础上追加RAG
from langchain.prompts import ChatPromptTemplate
# ===== Day2的Prompt和MockLLM(保持不变)=====
SYSTEM_PROMPT = """你是电商客服"小智"。语气亲切友好。
如果用户问政策相关问题,请基于知识库内容回答,不要编造。"""
prompt = ChatPromptTemplate.from_messages([("system", SYSTEM_PROMPT), ("user", "{user_message}")])
class MockLLM:
def invoke(self, messages):
last_msg = str(messages[-1].content)
if "你好" in last_msg: return "您好!我是小智,有什么可以帮您?😊"
if "订单" in last_msg: return "请问您的订单号是多少?我帮您查。"
if "谢谢" in last_msg: return "不客气!"
return "收到您的消息。如需人工客服,请说'转人工'。"
llm = MockLLM()
# ===== Day2代码结束 =====
# ===== Day3新增:RAG知识库 =====
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import FakeEmbeddings
# 1. 公司的FAQ文档(实际项目中从文件读取)
FAQ_DOCUMENT = """
## 退货政策
顾客在收到商品后的7天内可以申请退货。
退回的商品必须保持原包装,不影响二次销售。
退货的邮费由公司全额承担。
退款将在收到退货商品后的3个工作日内,原路退回到顾客的支付账户。
## 换货政策
如果商品存在质量问题,顾客可以在收到商品后的15天内申请换货。
换货产生的来回邮费均由公司承担。
换货商品将在收到退回商品后的2个工作日内发出。
## 退款流程
顾客可在"我的订单"页面点击"申请退款"按钮。
选择退款原因并提交申请。
客服团队将在24小时内完成审核。
审核通过后,退款将在3-5个工作日内到账。
如果审核不通过,客服会说明具体原因。
## 修改订单
未发货的订单,顾客可以自行修改收货地址和联系方式。
已发货的订单无法修改地址,但可以联系快递公司改派。
如需修改购买的商品规格,需要先取消原订单,再重新下单。
"""
# 2. 文档切分——为什么不能整篇存?
# 如果把整篇FAQ(400字)当成一段,用户搜"退款"搜出来的也包含退货、换货的无关内容。
# 切成小段(每段200字),检索更精准。重叠30字防止关键信息刚好卡在两段边界。
splitter = RecursiveCharacterTextSplitter(
chunk_size=200, # 每段最多200字
chunk_overlap=30, # 段与段之间重叠30字
separators=["\n\n", "\n", "。", ","] # 优先按自然段落切
)
chunks = splitter.create_documents([FAQ_DOCUMENT])
print(f"📄 文档被切成了 {len(chunks)} 段")
for i, chunk in enumerate(chunks):
print(f" 段{i+1}: {chunk.page_content[:50]}...")
# 3. 向量化存储
# 把每段文字转成"向量"(一串代表语义的数字),存入ChromaDB。
# FakeEmbeddings是Mock版的嵌入模型——不花钱,不联网。
embeddings = FakeEmbeddings(size=128)
vectorstore = Chroma.from_documents(
chunks,
embeddings,
collection_name="customer_service_faq" # 给知识库起个名字
)
def search_knowledge_base(query: str, top_k: int = 2) -> str:
"""
从知识库中检索和用户问题最相关的文档段。
top_k=2 表示返回最相关的2段。
"""
results = vectorstore.similarity_search(query, k=top_k)
if results:
return "\n\n".join([doc.page_content for doc in results])
return ""
# ===== 组装Day3的Agent =====
def customer_service_agent(user_message: str) -> str:
"""Day3:带RAG的客服Agent"""
# 第一步:判断是否需要查知识库
policy_keywords = ["退货", "退款", "换货", "政策", "流程", "修改订单", "多久", "条件"]
needs_knowledge = any(kw in user_message for kw in policy_keywords)
if needs_knowledge:
# 第二步:检索相关知识
knowledge = search_knowledge_base(user_message)
if knowledge:
# 第三步:把知识拼接进Prompt,让LLM基于它回答
enhanced_message = f"""请根据以下公司政策回答用户问题。
用户问题:{user_message}
公司政策(请严格基于以下内容回答,不要编造):
{knowledge}
请用亲切的语气回答。"""
else:
enhanced_message = user_message
else:
enhanced_message = user_message
formatted = prompt.invoke({"user_message": enhanced_message})
return llm.invoke(formatted.messages)
# ===== 测试 =====
if __name__ == "__main__":
test_cases = [
"你好",
"退货需要什么条件?",
"退款多久能到账?",
"我在哪申请退款?",
"谢谢"
]
for msg in test_cases:
print(f"👤 用户: {msg}")
print(f"🤖 小智: {customer_service_agent(msg)}")
print()运行
bash
python day3.py你会看到:
📄 文档被切成了 3 段
段1: ## 退货政策
顾客在收到商品后的7天内可以申请退货。...
段2: 如果商品存在质量问题,顾客可以在收到商品后的15天内申请换货。...
段3: 顾客可在"我的订单"页面点击"申请退款"按钮。...
👤 用户: 你好
🤖 小智: 您好!我是小智,有什么可以帮您?😊
👤 用户: 退货需要什么条件?
🤖 小智: 根据公司政策:顾客在收到商品后的7天内可以申请退货。退回商品需保持原包装。退货邮费由公司承担。
👤 用户: 退款多久能到账?
🤖 小智: 退款在收到退货后3个工作日内到账。
👤 用户: 我在哪申请退款?
🤖 小智: 在"我的订单"页面点击"申请退款"按钮,客服24小时内审核。Agent能回答退货政策、退款流程了——这些内容不在MockLLM里,是从FAQ文档里检索出来的!
你学到了什么
RAG让Agent拥有了"外部知识"。LLM只知道自己训练时见过的数据,但通过RAG,Agent可以回答任何你喂给它的文档内容。
今天你理解了文档切分的两个关键参数:chunk_size(每段多大)和 chunk_overlap(段间重叠多少)。太大检索不准,太小信息太碎——200-500字是经验值。
还有一点很重要:你今天用的是 FakeEmbeddings,这是Mock版的嵌入模型。换成真实嵌入模型(比如OpenAI的 text-embedding-3-small),只需要换一行代码。架构完全一样。
明天的预告
今天的Agent已经能查政策了,但它还不会"干活"。明天我们给它装上工具(Tools)——查订单状态、判断能不能退货、计算退款金额。让它从"动嘴"升级到"动手"。
Day 3完成。你的Agent现在有一个能检索的知识库了——就像给它配了一个随时可查的文件夹。

