Skip to content

第六阶段:Function Calling 工具调用(项目 2:客服 / 工单 Agent) ​

RAG 解决了“模型不知道公司资料”的问题;Function Calling 让模型能动手办事:查订单、建工单……这是 Agent 的核心能力。

一、原理 ​

  1. 把函数说明书(名称、用途、参数的 JSON Schema)随请求一起交给模型;
  2. 模型判断需要时,返回的不是答案,而是 tool_calls(“请帮我调用某函数,参数是……”);
  3. 你的代码执行这个函数,把结果以 role="tool" 加上对应的 tool_call_id 回传;
  4. 模型拿到结果后,再组织成自然语言回答。

关键认识

函数始终由你的代码执行,模型只负责“决定调不调、调哪个、传什么参数”。

一次完整交互的时序:

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 查无此订单,请您核对订单号。

四、要点 ​

  1. 模型自主决定:是否调用、调用哪个、从用户的话里提炼参数(如把“键盘按键失灵了,我要售后”整理成 problem)。
  2. 并行调用:一句话涉及多件事时,模型可以一次返回多个 tool_calls。
  3. 查无结果不编造:函数返回“查无此订单”,模型如实转告并请用户核对——系统提示里的“不要编造订单信息”起了作用。
  4. description 写清楚很关键:模型完全依赖说明书来判断何时用哪个工具。create_ticket 写明“用户反映商品质量问题、要求售后时”,模型才知道什么场景该建工单。

下一节预告 ​

把第五阶段的知识库也接成一个工具,做一个 RAG + Agent 组合的客服:既能查订单、建工单,也能回答公司政策问题。

👉 下一阶段:RAG + Agent 智能客服