Day 4:加工具——让Agent"能干活"
昨天的问题
Agent已经能回答政策问题了——"退货要什么条件""退款多久到账"。但用户不会只问政策。他们问的最多的问题是什么?
"我的订单什么时候到?"
这个问题Agent目前处理不了。因为订单状态不在FAQ文档里——它在公司的订单系统里。Agent需要调用工具去查。
今天做什么
给Agent装上三件工具:
- 查订单:输入订单号,返回订单状态
- 查退货:输入订单号,判断能不能退
- 算退款:输入订单号,计算退多少钱
工具本质上就是带 @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现在能动嘴也能动手了——能回答问题,也能查订单、算退款。

