Appearance
第六阶段:Function Calling 工具调用(项目 2:客服 / 工单 Agent)
RAG 解决了“模型不知道公司资料”的问题;Function Calling 让模型能动手办事:查订单、建工单……这是 Agent 的核心能力。
一、原理
- 把函数说明书(名称、用途、参数的 JSON Schema)随请求一起交给模型;
- 模型判断需要时,返回的不是答案,而是
tool_calls(“请帮我调用某函数,参数是……”); - 你的代码执行这个函数,把结果以
role="tool"加上对应的tool_call_id回传; - 模型拿到结果后,再组织成自然语言回答。
关键认识
函数始终由你的代码执行,模型只负责“决定调不调、调哪个、传什么参数”。
一次完整交互的时序:
text
用户 你的代码 模型 函数
│ 提问 ──▶ │ │ │
│ │ messages + tools ───────▶ │ │
│ │ ◀─────── tool_calls ───── │(决定调用) │
│ │ 执行函数 ───────────────────────────────────▶ │
│ │ ◀──────────────────────────────────── 结果 ── │
│ │ role=tool 回传结果 ─────▶ │ │
│ │ ◀────── 最终回答 ──────── │(或再次 tool_calls)│
│ ◀── 回答 ─ │ │ │代码中的内层 while 循环就是为了允许模型连续多次调用工具,直到它不再请求调用、给出最终回答为止。
二、06_function_call.py 完整代码
以下代码与仓库中的文件一致:
python
import os, json
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL"))
MODEL = os.getenv("LLM_MODEL")
# 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"]}}},
]
FUNCS = {"query_order": query_order, "create_ticket": create_ticket}
messages = [{"role": "system", "content": "你是电商客服,需要查询或办理时调用工具,不要编造订单信息"}]
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](**args)
# 把执行结果交回给模型,tool_call_id 用来对应是哪一次调用
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})代码补充说明
ORDERS字典:用假数据模拟订单系统,真实项目里换成数据库或接口查询即可。tools列表:每个工具的parameters就是 JSON Schema——properties列出参数及类型,required列出必填参数。模型只能看到这份说明书,看不到函数实现。FUNCS字典:把模型返回的函数名(字符串)映射到真正的 Python 函数。json.loads(call.function.arguments):模型给出的参数是 JSON 字符串,要先解析成字典,再用**args展开传给函数。messages.append(msg):模型带tool_calls的那条回复也必须放进聊天记录,否则后面的role="tool"消息找不到对应的调用。tool_call_id:一次可能有多个调用,靠这个 id 让模型知道每个结果对应哪一次调用。
三、实测结果(真实运行数据)
你:我的订单 1001 到哪了
text
[调用 query_order,参数 {'order_id': '1001'}]
客服:您的订单已发货,顺丰 SF123456,预计明天送达。你:你好 —— 不调用任何工具,直接问候。
你:1001 的键盘按键失灵了,我要售后
text
[调用 create_ticket,参数 {'order_id': '1001', 'problem': '键盘按键失灵,用户要求售后'}]
客服:已为您创建售后工单 T100101,客服会在 24 小时内联系您。你:查一下 1002,顺便说说 1003 —— 模型并行调用两次 query_order:
text
[调用 query_order,参数 {'order_id': '1002'}]
[调用 query_order,参数 {'order_id': '1003'}]
客服:1002 订单待付款;1003 查无此订单,请您核对订单号。四、要点
- 模型自主决定:是否调用、调用哪个、从用户的话里提炼参数(如把“键盘按键失灵了,我要售后”整理成
problem)。 - 并行调用:一句话涉及多件事时,模型可以一次返回多个
tool_calls。 - 查无结果不编造:函数返回“查无此订单”,模型如实转告并请用户核对——系统提示里的“不要编造订单信息”起了作用。
- description 写清楚很关键:模型完全依赖说明书来判断何时用哪个工具。
create_ticket写明“用户反映商品质量问题、要求售后时”,模型才知道什么场景该建工单。
下一节预告
把第五阶段的知识库也接成一个工具,做一个 RAG + Agent 组合的客服:既能查订单、建工单,也能回答公司政策问题。
👉 下一阶段:RAG + Agent 智能客服