Skip to content

Day 4:加工具——让Agent"能干活"

昨天的问题

Agent已经能回答政策问题了——"退货要什么条件""退款多久到账"。但用户不会只问政策。他们问的最多的问题是什么?

"我的订单什么时候到?"

这个问题Agent目前处理不了。因为订单状态不在FAQ文档里——它在公司的订单系统里。Agent需要调用工具去查。

今天做什么

给Agent装上三件工具:

  1. 查订单:输入订单号,返回订单状态
  2. 查退货:输入订单号,判断能不能退
  3. 算退款:输入订单号,计算退多少钱

工具本质上就是带 @tool 装饰器的Python函数。 LangChain的 @tool 装饰器会提取函数名和docstring,告诉LLM"这个函数是干什么的、需要什么参数"。

当你用真实的LLM时,LLM会自动判断"用户问了这个问题,应该调用哪个工具"——不需要你写if-else。但因为今天还是Mock模式,我们用手动路由模拟这个过程。

📁 今天的代码目录

customer_service_agent/
├── day1.py
├── day2.py
├── day3.py
├── day4.py         ← 新增!加入三个工具
└── tools.py        ← 新增!工具函数独立成文件
python
# day4.py — 在Day3基础上增加工具调用

from langchain.prompts import ChatPromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import FakeEmbeddings

# ===== Day3的Prompt、MockLLM、RAG(保持不变)=====
SYSTEM_PROMPT = "你是电商客服'小智'。语气亲切。基于知识库回答政策问题。"
prompt = ChatPromptTemplate.from_messages([("system", SYSTEM_PROMPT), ("user", "{user_message}")])

class MockLLM:
    def invoke(self, msgs):
        msg = str(msgs[-1].content)
        if "你好" in msg: return "您好!我是小智 😊"
        if "谢谢" in msg: return "不客气!还有其他需要吗?"
        return "收到。如需人工请说'转人工'。"
llm = MockLLM()

FAQ = """## 退货政策\n顾客收到商品后7天内可申请退货。邮费公司承担。退款3个工作日内到账。\n## 退款流程\n在"我的订单"申请退款,24小时内审核,3-5个工作日到账。"""
vectorstore = Chroma.from_documents(
    RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=30).create_documents([FAQ]),
    FakeEmbeddings(size=128)
)
def search_kb(q):
    docs = vectorstore.similarity_search(q, k=2)
    return "\n".join([d.page_content for d in docs]) if docs else ""
# ===== Day3代码结束 =====

# ===== Day4新增:工具函数 =====
from langchain.tools import tool
from datetime import datetime

# Mock订单数据库(实际项目连真实数据库)
ORDERS = {
    "ORD001": {"status": "运输中", "item": "无线耳机", "price": 299, "date": "2025-01-18"},
    "ORD002": {"status": "已签收", "item": "手机壳", "price": 39, "date": "2025-01-10"},
    "ORD003": {"status": "待发货", "item": "机械键盘", "price": 599, "date": "2025-01-21"},
    "ORD004": {"status": "已签收", "item": "运动鞋", "price": 459, "date": "2025-01-19"},
}

@tool
def lookup_order(order_id: str) -> str:
    """查询指定订单的状态、商品名称和价格。输入订单号,如 ORD001。"""
    order = ORDERS.get(order_id.upper())
    if not order:
        return f"未找到订单 {order_id}。请核实订单号后重试(格式如 ORD001)。"
    return f"📦 订单{order_id} | 商品:{order['item']} | 金额:¥{order['price']} | 状态:{order['status']}"

@tool
def check_return(order_id: str) -> str:
    """检查订单是否在7天退货期内。输入订单号。"""
    order = ORDERS.get(order_id.upper())
    if not order:
        return "订单不存在,请核实订单号。"
    
    if order["status"] != "已签收":
        return f"⏳ 订单当前状态为'{order['status']}'。请先确认收货后再申请退货。"
    
    # 计算签收了多少天
    delivered_date = datetime.strptime(order["date"], "%Y-%m-%d")
    days_passed = (datetime.now() - delivered_date).days
    
    if days_passed <= 7:
        return f"✅ 订单{order_id}签收了{days_passed}天,在7天退货期内,可以退货。退货邮费由公司承担。"
    else:
        return f"❌ 订单{order_id}已签收{days_passed}天,超过了7天退货期,无法退货。"

@tool
def calc_refund(order_id: str) -> str:
    """计算订单的退款金额。输入订单号。"""
    order = ORDERS.get(order_id.upper())
    if not order:
        return "订单不存在。"
    return f"💰 订单{order_id}退款金额:¥{order['price']}。退款将在3个工作日内到账。"

# ===== 组装Day4的Agent =====
def customer_service_agent(user_message: str) -> str:
    """Day4:带工具调用的客服Agent"""
    
    # 第一步:检测是否包含订单号 → 调工具
    for order_id in ORDERS:
        if order_id in user_message.upper():
            # 判断用户意图
            if any(w in user_message for w in ["退货", "退", "return"]):
                return check_return.invoke(order_id)
            elif any(w in user_message for w in ["退款", "退钱", "多少钱"]):
                return calc_refund.invoke(order_id)
            else:
                return lookup_order.invoke(order_id)
    
    # 第二步:政策相关问题 → 查RAG
    if any(w in user_message for w in ["退货", "退款", "换货", "政策", "流程", "条件"]):
        knowledge = search_kb(user_message)
        if knowledge:
            enhanced = f"用户问:{user_message}\n\n公司政策:\n{knowledge}\n\n请用亲切语气简洁回答。"
            return llm.invoke(prompt.invoke({"user_message": enhanced}).messages)
    
    # 第三步:一般对话
    return llm.invoke(prompt.invoke({"user_message": user_message}).messages)

# ===== 测试 =====
if __name__ == "__main__":
    test_cases = [
        "ORD001",                 # 查订单
        "ORD002 退货",            # 退货检查
        "ORD004 退款",            # 计算退款
        "退货需要什么条件?",      # 走RAG
        "你好",                   # 一般对话
    ]
    
    for msg in test_cases:
        print(f"👤 用户: {msg}")
        print(f"🤖 小智: {customer_service_agent(msg)}")
        print()

运行

bash
python day4.py

你会看到不同问题走了不同路径:

👤 用户: ORD001
🤖 小智: 📦 订单ORD001 | 商品:无线耳机 | 金额:¥299 | 状态:运输中

👤 用户: ORD002 退货
🤖 小智: ❌ 订单ORD002已签收15天,超过了7天退货期,无法退货。

👤 用户: ORD004 退款
🤖 小智: 💰 订单ORD004退款金额:¥459。退款将在3个工作日内到账。

👤 用户: 退货需要什么条件?
🤖 小智: 根据公司政策:顾客收到商品后7天内可申请退货。邮费由公司承担。

👤 用户: 你好
🤖 小智: 您好!我是小智 😊

不同问题走了不同路径——订单号走工具调用,政策问题走RAG,一般对话走MockLLM。

你学到了什么

工具调用是Agent从"参谋"升级到"助理"的关键一步。 前几天的Agent只会动嘴(回答政策问题),今天它能动手了(查订单、算退款)。

注意一个细节:@tool 装饰器会自动读取函数的docstring。当你接入真实LLM时,LLM就是靠读这些docstring来决定"这个函数是干什么的,该不该调用它"。所以工具函数的docstring要写得清楚——这是在跟LLM沟通,不是在写代码注释。

明天的预告

今天的Agent有一个致命缺陷:没有记忆。你说"ORD001",它回答了。然后你说"那这个能退货吗?"——它不知道"这个"指的是什么。明天用LangGraph给Agent装上"记忆"——让它记住对话历史。


Day 4完成。你的Agent现在能动嘴也能动手了——能回答问题,也能查订单、算退款。