用户说"查订单 ORD-001",Agent 调用函数获取数据,再把结果组织成自然语言回复:
You: 查一下订单 ORD-001
[调用工具: query_order({"order_id": "ORD-001"})]
AI: 您的订单 ORD-001(智能蓝牙耳机 Pro,299元)已发货,正在配送中。
def handle_order(user_input):
if "ORD-001" in user_input:
return "您的订单 ORD-001 已发货"
elif "ORD-002" in user_input:
return "您的订单 ORD-002 正在处理中"
else:
return "请提供订单号"
工具调用(查订单)的触发条件应该是 LLM 的判断,而不是写死的字符串匹配。LLM 能理解"查一下 001 那个订单"是在查什么,代码不行。
Function Calling 让 LLM 能看到你定义的工具列表,并且自主决定要不要调用、调用哪个、传什么参数。
tools = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "查询订单状态和物流信息",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "订单编号"}
},
"required": ["order_id"],
},
},
},
]
每个工具需要三样东西:
如果不调工具,LLM 返回:
{"content": "直接回复用户", "tool_calls": None}
如果要调工具,LLM 返回:
{
"content": None,
"tool_calls": [
{
"id": "call_xxx",
"function": {
"name": "query_order",
"arguments": '{"order_id": "ORD-001"}'
}
}
]
}
content 是 None,因为 LLM 没有直接回复——它决定由工具来提供信息。
初学者最容易犯的错误:工具返回了 JSON 就直接拼接字符串返回给用户。
工具返回: {"found": true, "status": "shipped", "product": "智能蓝牙耳机 Pro"}
直接返回: "您的订单查询结果:{"found": true, "status": "shipped"}"
← 用户看到 JSON,体验很差
第一轮:LLM 决定调用 query_order
→ LLM 说"调 query_order,参数 order_id=ORD-001"
→ 我们执行 get_order("ORD-001")
→ 返回 {"found": true, "status": "shipped", "product": "智能蓝牙耳机 Pro"}
第二轮:把工具结果以 "tool" role 发回给 LLM
→ messages 变为:
system: "你是电商客服系统的工具调用助手"
user: "查一下订单 ORD-001"
assistant: (tool_calls=...)
tool: '{"found": true, "status": "shipped", "product": "智能蓝牙耳机 Pro"}'
→ LLM 看到结果后生成:
"您的订单 ORD-001(智能蓝牙耳机 Pro)已发货,正在配送中。"
工具返回的是 JSON 原始数据,LLM 需要:
这叫"推理"(reasoning),不是简单的字符串拼接。
如果工具返回的已经是可以直接展示的内容(比如查百科返回了一段文字),那可以省略。但多数情况下,工具返回的是结构化数据,建议走两轮。
def _execute_tool(name, args):
if name == "query_order":
order = get_order(args["order_id"])
if order:
return json.dumps({"found": True, "order": asdict(order)})
return json.dumps({"found": False, "message": "未找到"})
# ... 其他工具
注意结果统一用 json.dumps 序列化成字符串。因为 tool role 的 content 字段是字符串类型。
def handle_with_tools(user_input):
messages = [
{"role": "system", "content": "根据用户问题调用合适的工具获取信息。"},
{"role": "user", "content": user_input},
]
# 第一轮:LLM 决定是否调用工具
msg = call_llm(messages, tools=tools, tool_choice="auto")
tool_calls = msg.get("tool_calls")
if not tool_calls:
return msg.get("content", "")
# 第二轮:执行工具 + 结果回传
messages.append({"role": "assistant", "content": None, "tool_calls": tool_calls})
for tc in tool_calls:
name = tc["function"]["name"]
args = json.loads(tc["function"]["arguments"])
result = _execute_tool(name, args)
messages.append({"role": "tool", "tool_call_id": tc["id"], "content": result})
final_msg = call_llm(messages, temperature=0.3)
return final_msg.get("content", "")
"auto": LLM 自主决定要不要调工具(默认)"required": 强制 LLM 必须调一个工具(用于"先查再说"的场景){"type": "function", "function": {"name": "query_order"}}: 强制调指定工具第 4 课用 "auto" 就够了。
| 概念 | 你做了什么 | 为什么 |
|---|---|---|
| Function Calling | 定义 tools schema + tool_choice | LLM 自主决定调用,不靠关键词匹配 |
| 工具描述 | name + description + parameters | LLM 靠 description 判断何时调用 |
| 两轮调用 | 决策 → 执行 → 推理 | 工具返回 JSON,需要 LLM 翻译成自然语言 |
| tool role | 把工具结果按 role 传回 | 符合 OpenAI 标准,LLM 能理解这是"工具说的" |
get_discount 工具,根据用户等级(vip/normal)返回折扣信息tool_choice 改成 "required",观察 LLM 的行为变化"Function Calling 的 tool_choice=auto 和 tool_choice=required 有什么区别?分别用于什么场景?"
auto: LLM 自主决定(多数场景)。required: 强制调用工具(如"必须先查数据库再回答"的场景)。"工具执行后为什么要把结果以 tool role 发回给 LLM?直接拼接字符串不行吗?"
工具返回的是原始数据,LLM 需要推理后生成自然语言。而且 tool role 的消息在后续对话中可以作为上下文,方便用户追问。