Skip to content

Day 3:加RAG——让Agent"有知识"

昨天的问题

Day 2的MockLLM靠关键词匹配回答问题。但真实场景下,LLM不知道你们公司的退货政策、退款流程、售后条款。这些信息不在任何LLM的训练数据里。

而且你不可能把所有公司政策都写进MockLLM的if-else里——文档有几十页,产品每天都在更新,政策每个季度在变。

今天做什么:RAG(检索增强生成)

RAG的思路非常朴素——跟人一模一样:

  1. 把公司文档提前"读"进去(切成小段,存起来)
  2. 用户问问题的时候,从文档里找到最相关的几段
  3. 把找到的段落和用户问题一起交给LLM
  4. LLM基于这些"参考资料"生成回答

类比:开卷考试。你不是背答案,而是知道去哪本书的第几页找。

你需要安装

bash
pip install chromadb langchain-text-splitters langchain-community

📁 今天的代码目录

customer_service_agent/
├── day1.py
├── day2.py
└── day3.py         ← 新增!+50行,加入RAG
python
# 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现在有一个能检索的知识库了——就像给它配了一个随时可查的文件夹。