Appearance
第七阶段:RAG + Agent 智能客服
一、思路
把第五阶段的知识库包装成第三个工具 search_manual:用 index.as_retriever 只检索,把说明书原文交回给模型,由模型自己组织回答。
这样模型可以自主决定:查订单(query_order)、开工单(create_ticket)、还是查说明书(search_manual)。
关键认识:RAG 也可以作为 Agent 的一个工具。 检索只是"取资料",什么时候取、取来怎么用,交给模型决定。
二、07_support_agent.py 完整代码
以下代码与仓库中的文件一致(运行前把 model_name 改成你 LM Studio 中的 Embedding 模型名,并确保 kb_storage 是第五阶段建好的索引):
python
import os, json
from dotenv import load_dotenv
from openai import OpenAI
from llama_index.core import Settings, StorageContext, load_index_from_storage
from llama_index.embeddings.openai_like import OpenAILikeEmbedding
load_dotenv()
client = OpenAI(api_key=<redacted>("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL"))
MODEL = os.getenv("LLM_MODEL")
Settings.embed_model = OpenAILikeEmbedding(model_name="这里填你的 Embedding 模型名",
api_base="http://localhost:1234/v1", api_key="<redacted>")
index = load_index_from_storage(StorageContext.from_defaults(persist_dir="./kb_storage"))
retriever = index.as_retriever(similarity_top_k=3) # 只检索,不让 LlamaIndex 自己回答
def search_manual(question):
"""在产品说明书里检索相关段落,原文交回给模型"""
nodes = retriever.retrieve(question)
return "\n---\n".join(n.node.get_content() for n in nodes)
# 1. 真正干活的函数(这里用假数据模拟订单系统)
ORDERS = {"1001": "已发货,顺丰 SF123456,预计明天到", "1002": "待付款"}
def query_order(order_id):
return ORDERS.get(order_id, "查无此订单")
def create_ticket(order_id, problem):
return f"工单已创建,编号 T{order_id}01,问题:{problem},客服 24 小时内联系您"
# 2. 函数说明书:模型只看得到这个,所以描述要写清楚
tools = [
{"type": "function", "function": {
"name": "query_order",
"description": "根据订单号查询订单状态和物流",
"parameters": {"type": "object",
"properties": {"order_id": {"type": "string", "description": "订单号"}},
"required": ["order_id"]}}},
{"type": "function", "function": {
"name": "create_ticket",
"description": "用户反映商品质量问题、要求售后时,创建售后工单",
"parameters": {"type": "object",
"properties": {"order_id": {"type": "string"},
"problem": {"type": "string", "description": "问题描述"}},
"required": ["order_id", "problem"]}}},
{"type": "function", "function": {
"name": "search_manual",
"description": "查询折叠键盘的产品说明书,用于回答配对、指示灯、充电、保修等产品使用问题",
"parameters": {"type": "object",
"properties": {"question": {"type": "string", "description": "要查的问题"}},
"required": ["question"]}}},
]
FUNCS = {"query_order": query_order, "create_ticket": create_ticket, "search_manual": search_manual}
messages = [{"role": "system", "content": "你是折叠键盘的电商客服。订单问题用 query_order,售后用 create_ticket,产品使用问题必须先用 search_manual 查说明书,只根据查到的内容回答,查不到就如实说不知道,不要编造"}]
while True:
q = input("\n你:")
if q == "exit":
break
messages.append({"role": "user", "content": q})
# 3. 循环:模型可能连续调用好几次工具,直到给出最终回答
while True:
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
msg = resp.choices[0].message
messages.append(msg) # 模型的回复(包括它的调用请求)也要加进聊天记录
if not msg.tool_calls: # 没有要调用的工具,说明这就是最终回答
print("客服:", msg.content)
break
for call in msg.tool_calls: # 逐个执行模型要求调用的函数
args = json.loads(call.function.arguments)
print(f" [调用 {call.function.name},参数 {args}]")
result = FUNCS[call.function.name]
# 把执行结果交回给模型,tool_call_id 用来对应是哪一次调用
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})与第六阶段相比,新增了三处:
- 顶部的检索部分:加载
kb_storage索引,as_retriever(similarity_top_k=3)只返回最相关的 3 段原文,用---拼接后交回给模型; tools里的第三个说明书:search_manual,description 列出了它能回答的问题类型(配对、指示灯、充电、保修……);- system 提示:明确告诉模型三个工具分别什么时候用,并要求查不到就如实说。
三、常见错误(实际踩过的坑)
(a) tools 里加了 search_manual,但 FUNCS 没登记
模型调用 search_manual 时,FUNCS[call.function.name] 找不到这个键,程序直接 KeyError 崩溃。新增工具要改两处:tools(给模型看的说明书)和 FUNCS(名字到函数的映射)。
(b) system 提示没更新
模型不知道什么时候该查说明书,可能凭自己的知识瞎答。新增工具后,要同步在 system 提示里说明用途和规则。
(c) 只检索时不需要 Settings.llm
用 retriever 只做检索,生成回答由 client 负责,所以只需设置 Settings.embed_model,不需要 Settings.llm。并且 Embedding 模型必须与建索引时是同一个——问题向量和文档向量要在同一个"向量空间"里才能比较。
四、实测结果(真实运行数据)
你:键盘怎么连手机
text
[调用 search_manual,参数 {'question': '折叠键盘怎么连接手机(蓝牙配对步骤)'}]客服给出步骤:开机 → Fn+A / S / D 选择 BT1~BT3 并点按 Connect → 手机搜索 BOW 连接;并说明键盘使用 HID 协议,支持 iOS / Android / Windows / MacOS / HarmonyOS。
注意模型把"键盘怎么连手机"改写成了更完整的检索问题,提高了检索命中率。
你:订单 1001 的键盘连不上蓝牙,怎么办?
模型同时调用了 query_order {'order_id': '1001'} 和 search_manual,然后:
- 发现订单状态是"预计明天到",与"已经连不上"矛盾,主动请用户确认;
- 给出配对步骤;
- 说明书里没有专门的故障排查内容,不乱猜;
- 提出可以帮忙登记售后工单。
你:能连 PS5 吗?
调用 search_manual 后,模型说明:说明书只列出了上述系统,没有提到 PS5,查不到就不下结论。
五、要点
- 并行多工具:一句话涉及订单和产品问题时,模型同时调用多个工具。
- 发现矛盾主动确认:模型会把不同工具的结果放在一起推理,而不是机械拼接。
- 防编造:system 提示 + 只根据检索原文回答,查不到就如实说。
下一节预告
用 LangGraph 重写客服 Agent:用有状态的流程图管理对话,加入"转人工"等分支。
👉 下一阶段:LangGraph 重写客服 Agent