Function Calling 完全指南:从一次"AI 假装查了数据库、其实把结果编了出来"看懂大模型工具调用

2024 年我做一个客服 AI 助手,想让它能真的查到数据。第一版的做法很"聪明":在 prompt 里要求模型输出一段 JSON,我用 json.loads 解析出订单号自己去查库。Demo 配合得很好,一上线问题接连冒出:模型输出的不是纯 JSON 而夹带解释文字,解析直接抛异常;模型自作主张编一个我没定义的 action;最让我后背发凉的一次,用户问订单状态模型压根没输出 JSON,直接回了一句"已发货预计明天送达"——说得有鼻子有眼,可我的代码根本没被触发去查库,这个结果是它凭空编的。我以为是 prompt 不够严,改得越来越啰嗦,模型该编还是编。后来才彻底想明白:模型是纯文本生成模型,不能执行代码、不能访问数据库、不能发请求,它唯一能做的是生成下一段文本,你让它查订单,它要么输出它以为你想要的 JSON,要么干脆把查询结果也编给你。要让 AI 真的能用工具,靠的不是 prompt 里写指令求它配合,而是一套规范协议:模型负责说出要调哪个工具传什么参数,你的代码负责真正执行,执行完再把结果喂回给模型,这就是 Function Calling。本文从头梳理。为什么需要:模型只会生成文本永远不会自己去查库。伪工具调用的坑:让模型输出 JSON 自己解析,格式坑、幻觉坑、不可控坑必然爆发。正确姿势:用 tools 参数以 JSON Schema 声明工具的名字描述参数,description 是模型判断何时调用的唯一依据。完整回合:模型请求、你的代码执行、结果作为 role=tool 消息带 tool_call_id 喂回、模型据此生成最终回答。多工具连续调用:用 while 循环且必须设最大轮数上限防无限烧钱。工程坑:工具描述要像写给新人的说明书,模型给的参数是生成的可能编造要当不可信输入校验,工具出错要包装成结果喂回而非抛异常给模型自我修正机会。核心一句:Function Calling 不给模型执行能力,它在模型和你的代码之间架一座结构化的桥,执行永远是你的代码的事。

2024 年我做一个客服 AI 助手,想让它不只是"聊天",还能真的查到数据——用户问"我的订单 A2024 到哪了",它应该去查我们的订单系统,给出真实的物流状态。第一版我的做法很"聪明":在 system prompt 里写一段话,告诉模型"如果用户在问订单,请你输出一段 JSON,格式是 {"action": "query_order", "order_id": "..."}",然后我在代码里把模型的回复拿来 json.loads,解析出 order_id,自己去查库,再把结果拼成话术返回。Demo 里它配合得很好。可一上线,问题就接连冒出来。先是解析失败:模型有时输出的不是纯 JSON,而是"好的,我帮您查询,{"action":...},请稍等"——前后夹带了解释文字,json.loads 直接抛异常。然后是乱编 action:我只定义了 query_order,可模型有时会自作主张输出一个 {"action": "check_logistics"},一个我代码里根本没有的动作。而最让我后背发凉的一次:一个用户问订单状态,模型压根没输出 JSON,直接回了一句"您的订单 A2024 已发货,预计明天送达"——说得有鼻子有眼,可我的代码根本没被触发去查库,这个"已发货、明天送达"是模型凭空编的。我一开始以为是 prompt 写得不够严,把指令改得越来越啰嗦,可模型该编还是编。后来我才彻底想明白:模型不会、也不可能"去查库"——它是一个纯文本生成模型,它不能执行代码、不能访问数据库、不能发网络请求,它唯一能做的事就是生成下一段文本。你让它"查订单",它要么老老实实输出一段它以为你想要的 JSON,要么干脆把"查询结果"也一起给你。要让 AI 真的能"用工具",靠的不是在 prompt 里写指令求它配合,而是一套规范的协议——模型负责说出"我想调用哪个工具、传什么参数",你的代码负责真正执行,执行完再把结果喂回给模型。这就是 Function Calling(工具调用)。我以为 Function Calling 不过是"换个格式让模型输出 JSON",结果真做下来坑一个接一个:工具怎么声明、模型请求工具后这一轮怎么接、结果怎么喂回去、模型连着要调好几个工具怎么办、它乱调或者该调不调怎么办……那次之后我才认真把 Function Calling 从头搞明白。这篇文章就把它梳理一遍:为什么需要 Function Calling、手写"伪工具调用"为什么不靠谱、工具怎么声明、一个完整的调用回合怎么转、多工具怎么处理,以及把工具调用真正做稳要避开的那些坑。

问题背景

先把那次的现象和我的误判讲清楚,后面所有的设计都是冲着纠正这个误判去的。

现象:让 AI 助手"查订单",用的是"在 prompt 里要求模型输出 JSON、代码再解析执行"的土办法,结果出现一连串问题——模型输出的 JSON 夹带解释文字导致解析失败、模型编造没定义过的动作、模型甚至跳过工具直接把查询结果凭空编出来。

我当时的错误认知:"只要 prompt 写得够清楚,模型就会按我说的去'查数据';它输出 JSON,我解析执行,就等于它会用工具了。"

真相:大模型是一个纯文本生成模型,它不能执行代码、不能访问数据库、不能发请求,它唯一的能力是生成文本。它永远不会"自己去查库"。Function Calling 解决这个问题的方式,不是让模型获得执行能力,而是定义一套协议:你预先用结构化的方式声明有哪些工具可用、每个工具要什么参数;模型在需要时,产出一个结构化的"工具调用请求"(调哪个、传什么参数);你的代码负责真正执行这个工具;执行结果再喂回给模型,模型据此生成最终回答。整个过程是多轮的——执行工具的永远是你,不是模型。

要把 Function Calling 做稳,需要几块认知:

  • 为什么模型不能"自己执行",Function Calling 到底补的是什么;
  • 为什么"让模型输出 JSON、自己解析"这种土办法不可靠;
  • 工具怎么用结构化的方式声明给模型;
  • 一个完整的工具调用回合是怎么在"模型—你的代码"之间来回流转的;
  • 多工具、参数校验、模型乱调/不调这些工程坑怎么处理。

一、为什么需要 Function Calling:模型只会生成文本

先把这件最根本的事钉死:大模型不会执行任何东西

它不是一个程序运行环境,它是一个"预测下一个 token"的模型。你给它一段上文,它生成一段在统计上最可能的下文——仅此而已。它没有联网能力,没有数据库连接,不能读文件、不能跑代码。所以当你在 prompt 里写"请查询订单 A2024 的状态"时,模型面前只有两条路:要么生成一段它猜测你想要的文本(比如一段 JSON),要么干脆把"查询结果"本身也生成出来——而后者,就是幻觉。下面这段代码,就是最典型的"求模型配合"的土办法:

import json
from openai import OpenAI

client = OpenAI()

# 反面教材:在 prompt 里用文字"请求"模型输出 JSON,再自己解析。
SYSTEM = """你是客服助手。如果用户在询问订单,请【只】输出一段 JSON:
{"action": "query_order", "order_id": "订单号"}
不要输出任何其他内容。"""


def handle(user_input: str):
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "system", "content": SYSTEM},
                  {"role": "user", "content": user_input}],
    )
    text = resp.choices[0].message.content
    # 把模型的回复当成 JSON 来解析 —— 这一步随时可能炸
    data = json.loads(text)
    return query_order(data["order_id"])
    # 问题:模型是【文本模型】,它不保证只输出 JSON。
    # 它可能夹带解释文字、可能编一个没定义的 action、
    # 甚至可能压根不输出 JSON,直接把"查询结果"编给你。

这段代码的脆弱,根子上不是 prompt 写得不够好,而是它把一件该由协议保证的事,寄希望于模型的"自觉"。你用自然语言"请求"模型只输出 JSON,但自然语言指令对模型从来都是软约束——它大概率会听,但不保证。你的代码 json.loads(text) 却是个硬假设:它假设 text 一定是合法 JSON。软约束撑不起硬假设,这就是一切问题的来源。要解决,就得有一套不靠模型自觉的机制,让"工具调用"这件事变得结构化、可校验。这就是 Function Calling。

二、伪 Function Calling 的坑:解析、幻觉、不可控

把上一节那个土办法的坑摊开看清楚,才明白正规的 Function Calling 每一处设计是在解决什么。它的坑有三类。

第一类是格式坑。你要求模型"只输出 JSON",但它是个聊天模型,它的本能是"说话"。它经常会在 JSON 前后加上"好的""请稍等"这类客套,或者把 JSON 包在 Markdown 的代码块标记里。你的 json.loads 面对这些就直接抛异常。你可能会想用正则去抠出那段 JSON,但这只是在给一个有缺陷的方案打补丁。

第二类是幻觉坑,也是最危险的。模型不仅可能编造一个你没定义的 action,它甚至可能跳过整个工具调用。用户问订单状态,它直接回"您的订单已发货"——它没有任何途径知道真实状态,这句话是纯编的。在土办法里,你没有任何机制能区分"模型想调工具"和"模型在直接作答"——你只能拿到一段文本,然后猜它是哪种。

第三类是不可控坑。模型该调用工具时不调用,不该调用时乱调用,你无从约束。你定义了三个工具,模型可能只认得其中一个;你希望它一次调一个,它可能在一段文本里塞三个。这些都因为"工具"这个概念,在土办法里根本没有被正式地告诉过模型——它只是 prompt 里的一段文字描述,模型对它没有结构化的认知。

这三类坑指向同一个结论:"工具有哪些、长什么样、模型什么时候算是要调用工具",这些都不能靠 prompt 里的自然语言去暗示,必须用一种模型和你的代码双方都严格遵守的结构化协议来表达。Function Calling 就是这套协议——它由模型厂商在 API 层面专门支持,而不是你在 prompt 里手搓出来的。

三、正确姿势:用 tools 参数声明工具

Function Calling 的第一步,是用一个结构化的 JSON Schema 把工具声明给模型。你不再在 prompt 里用文字描述工具,而是通过 API 的 tools 参数,告诉模型:有这么一个工具,它叫什么、是干什么的、需要哪些参数、每个参数是什么类型。

# 用 JSON Schema 结构化地声明工具 —— 这才是"正式地"告诉模型有什么工具。
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "query_order",
            # description 极其重要:模型靠它判断【何时】该用这个工具
            "description": "根据订单号查询订单的物流状态和预计送达时间。"
                           "当用户询问某个具体订单的进度时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "订单号,通常以字母开头,如 A2024",
                    },
                },
                "required": ["order_id"],   # 标明哪些参数是必填的
            },
        },
    }
]

这个声明里,每一个字段都有它的作用。name 是工具的唯一标识,模型请求调用时会原样报出这个名字。description最关键的一项——模型完全靠这段描述来判断"用户当前的问题,该不该用这个工具",描述写得含糊,模型就会该调不调、或者乱调。parameters 用标准的 JSON Schema 描述了这个工具接收哪些参数、类型是什么、哪些必填。把这个 tools 传给 API,模型就对"我有哪些工具可用"有了结构化的、确定的认知:

def ask(messages: list[dict]):
    """带着工具声明发起请求 —— 模型现在'知道'自己有哪些工具了。"""
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=TOOLS,            # 把工具声明一起传进去
        # tool_choice="auto" 是默认值:由模型自己判断要不要调工具
    )

声明了工具之后,模型的行为就变了:当它判断需要用工具时,它不会再在 content 里输出一段 JSON 给你猜,而是在响应里返回一个专门的、结构化的字段 tool_calls——明确地、机器可读地告诉你"我要调用 query_order,参数是这些"。下一节就来看这个字段怎么接。

四、一个完整的调用回合:请求、执行、喂回

声明了工具,只是让模型"知道有什么工具"。真正的工具调用,是一个在模型和你的代码之间来回好几趟的回合。把它拆开,一共四步。

第一步,模型发出工具调用请求。你带着 tools 发起请求后,如果模型决定用工具,响应里 message.tool_calls 会有内容。你要先判断:这次模型是想调工具,还是直接回答了?

def query_order(order_id: str) -> dict:
    """这才是【真正执行】的代码 —— 由你的程序跑,不是模型。"""
    # 实际项目里这里会去查数据库 / 调订单系统的接口
    fake_db = {"A2024": {"status": "已发货", "eta": "2024-06-02"}}
    return fake_db.get(order_id, {"status": "查无此订单"})


# 工具名 -> 真实函数的映射表,执行时按名字找函数
TOOL_IMPL = {"query_order": query_order}

第二步,你的代码真正执行工具。tool_calls 里取出模型要调的工具名和参数,在你的映射表里找到对应的真实函数,执行它。注意参数是模型以 JSON 字符串形式给的,要先 json.loads第三步,把执行结果喂回给模型。这一步有个固定格式:你要把模型刚才那条带 tool_calls 的回复原样加回 messages,再为每一个工具调用追加一条 role="tool" 的消息,消息里带上对应的 tool_call_id 和执行结果。第四步,模型拿到结果生成最终回答。带着这些新消息再请求一次,模型就能基于真实的工具结果来回答了:

import json


def run_once(user_input: str) -> str:
    messages = [{"role": "user", "content": user_input}]
    # —— 第 1 步:发起请求,看模型是否要调工具 ——
    resp = ask(messages)
    msg = resp.choices[0].message
    if not msg.tool_calls:
        return msg.content          # 模型没要调工具,直接就是答案
    # 模型要调工具:先把它这条"调用请求"原样存回历史
    messages.append(msg)
    # —— 第 2 步:逐个真正执行工具 ——
    for call in msg.tool_calls:
        name = call.function.name
        args = json.loads(call.function.arguments)   # 参数是 JSON 字符串
        result = TOOL_IMPL[name](**args)             # 调真实函数
        # —— 第 3 步:把结果作为 role=tool 消息喂回,带上 tool_call_id ——
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,                 # 必须对应上是哪次调用
            "content": json.dumps(result, ensure_ascii=False),
        })
    # —— 第 4 步:带着工具结果再问一次,模型生成最终回答 ——
    final = client.chat.completions.create(
        model="gpt-4o-mini", messages=messages,
    )
    return final.choices[0].message.content

这段代码就是文章开头那个问题的正解。它和土办法最本质的区别有两点。其一,"判断要不要调工具"和"判断结果"完全分开了:模型要不要调工具,看 tool_calls 这个结构化字段,不用再猜文本;其二,执行工具的永远是你的代码(TOOL_IMPL[name](**args)),模型从头到尾没碰过数据库。所以模型再也编不出"已发货"——那个状态字段,是 query_order 真实查出来、再喂回给它的。tool_call_id 这个细节要留意:一轮可能有多个工具调用,每条 role="tool" 的结果消息必须用 tool_call_id 精确对应到是哪一次调用的结果,不能错位。

五、多工具与连续调用:一个 while 循环

真实的助手不会只有一个工具,而且一个问题可能需要连着调好几次工具。比如用户问"帮我查下北京天气,如果下雨就提醒我带伞"——模型可能先调"查天气"工具,拿到"下雨",再调"发提醒"工具。上一节的 run_once 只处理了"调一轮工具就结束",不够。

先把工具声明扩成多个——只要在 TOOLS 列表和 TOOL_IMPL 映射里各加一项即可:

def query_weather(city: str) -> dict:
    """查天气工具的真实实现。"""
    return {"city": city, "weather": "小雨", "temp": 22}


TOOLS.append({
    "type": "function",
    "function": {
        "name": "query_weather",
        "description": "查询指定城市的实时天气。当用户问到天气时使用。",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市名,如 北京"},
            },
            "required": ["city"],
        },
    },
})
TOOL_IMPL["query_weather"] = query_weather

处理"连续调用"的正确结构,是一个 while 循环:每一轮请求模型,只要它还在返回 tool_calls,就执行工具、喂回结果、再请求;直到某一轮模型不再要工具、而是给出了文字回答,循环才结束。这是 Function Calling 应用的标准主干:

def run_conversation(user_input: str, max_turns: int = 5) -> str:
    """工具调用主循环:模型要工具就执行喂回,直到它给出最终回答。"""
    messages = [{"role": "user", "content": user_input}]
    for _ in range(max_turns):                  # max_turns:防止无限循环
        resp = ask(messages)
        msg = resp.choices[0].message
        if not msg.tool_calls:
            return msg.content                  # 模型给出最终回答,结束
        messages.append(msg)
        for call in msg.tool_calls:
            name = call.function.name
            args = json.loads(call.function.arguments)
            # 用 .get 兜底:模型可能报出一个没注册的工具名
            impl = TOOL_IMPL.get(name)
            result = impl(**args) if impl else {"error": f"未知工具 {name}"}
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": json.dumps(result, ensure_ascii=False),
            })
    return "已达到最大工具调用轮数,请简化问题后重试。"

这个循环里有个不能省的 max_turns:工具调用是模型自己决定的,万一它陷入"调了又调"的怪圈(比如工具一直返回它不满意的结果),没有这个上限,你的程序就会无限循环、无限烧钱。任何"由模型驱动的循环",都必须有一个由你的代码掌握的硬性次数上限。

六、工程坑:工具描述、参数校验、错误喂回

主干通了,但要把 Function Calling 真正做稳,还有几个绕不开的工程坑。

坑 1:工具的 description 要像写给新人的说明书。模型完全description 来判断"何时该用这个工具""参数该传什么"。描述写成"查订单"三个字,模型就会困惑——什么情况算查订单?用户随口提一句订单号算吗?描述要写清楚:这个工具做什么什么场景下该用什么场景下不该用。参数的 description 也一样,要把格式、示例都写上。一句话:工具描述写得好不好,直接决定模型调得准不准,它的重要性不亚于代码本身

坑 2:模型给的参数必须校验,不能直接信。模型产出的参数,是它生成的,不是它计算的——它可能给你一个格式不对的订单号,可能漏掉一个你以为必填的字段,甚至可能把参数值也编出来(用户没说订单号,它编一个)。所以工具函数的入口,必须像对待外部不可信输入一样做参数校验:

def query_order_safe(order_id: str) -> dict:
    """工具实现里要做参数校验 —— 模型给的参数是生成的,不可全信。"""
    # 模型可能给空值、给格式不对的订单号,甚至凭空编一个
    if not order_id or not isinstance(order_id, str):
        return {"error": "订单号缺失或类型不对"}
    if not order_id.startswith("A"):
        return {"error": f"订单号 {order_id} 格式不对,应以 A 开头"}
    return query_order(order_id)

坑 3:工具执行出错,要把错误"喂回"给模型,而不是直接抛异常。工具执行失败是常态——订单查不到、网络超时、参数非法。这时不要让异常冲出去中断整个流程,而应该把错误信息包装成一个正常的工具结果(就像上面 query_order_safe 返回的 {"error": ...}),通过 role="tool" 消息喂回给模型。模型看到这个错误,往往能自己想办法:它可能反问用户"您的订单号好像不太对,能再确认一下吗",或者换个参数重试。把错误喂回给模型,模型就有了"自我修正"的机会;直接抛异常,这个机会就没了。

坑 4:别一股脑塞几十个工具。工具不是越多越好。给模型的工具一多,它的选择负担就重,容易选错、容易混淆相似的工具。如果工具确实多,常见的做法是分组:先用一个粗粒度的判断确定用户意图属于哪一类,再只把那一类相关的几个工具声明给模型。给模型的工具列表,任何时候都应该是当前场景下精挑细选的一小撮。下面这张图,把一次带工具调用的完整对话流程串起来:

关键概念速查

概念 / 手段 说明
纯文本模型 大模型只会生成文本,不能执行代码、查库、发请求,永远不会"自己去查"
Function Calling 一套协议:模型说出要调哪个工具传什么参数,你的代码执行,结果再喂回
伪工具调用 prompt 里要求模型输出 JSON 自己解析,靠模型自觉,格式和幻觉都不可控
tools 参数 用 JSON Schema 结构化声明工具的名字、描述、参数,正式告诉模型
description 模型靠它判断何时用工具、参数传什么,写得好坏直接决定调用准确率
tool_calls 响应里专门的结构化字段,模型要调工具时通过它返回,无需猜文本
role=tool 消息 把工具执行结果喂回模型的固定格式,需带 tool_call_id 精确对应
调用主循环 模型要工具就执行喂回再请求,直到它给出文字回答,需设最大轮数
参数校验 模型给的参数是生成的可能编造或格式错,工具入口要当不可信输入校验
错误喂回 工具出错不要抛异常,包装成结果喂回模型,给它自我修正的机会

避坑清单

  1. 大模型是纯文本生成模型,不能执行代码、查库、发请求;你让它"查数据",它要么输出 JSON 要么把结果编出来。
  2. 在 prompt 里要求模型输出 JSON、自己解析执行,是把"该由协议保证的事"寄希望于模型自觉,格式坑和幻觉坑必然爆发。
  3. 土办法里你无法区分"模型想调工具"和"模型在直接作答",只能拿到一段文本去猜,最危险的是它跳过工具直接编结果。
  4. 正规做法是用 tools 参数以 JSON Schema 声明工具,让模型对"有哪些工具"有结构化认知,而非靠 prompt 文字暗示。
  5. 工具的 description 是模型判断何时调用、参数传什么的唯一依据,要像写给新人的说明书一样清楚,重要性不亚于代码。
  6. 模型要调工具时通过响应里的 tool_calls 结构化字段返回;判断走 tool_calls,不要再去解析 content 文本。
  7. 一个完整回合是四步:模型请求、你的代码执行、结果作为 role=tool 消息喂回、模型据此生成最终回答。
  8. 结果喂回时每条 role=tool 消息必须带 tool_call_id,精确对应是哪一次工具调用,多工具时不能错位。
  9. 处理连续调用要用 while 循环,且必须设最大轮数上限,防止模型陷入"调了又调"的怪圈无限烧钱。
  10. 模型给的参数是生成的可能编造或格式错,工具入口要做参数校验;工具出错要包装成结果喂回而非抛异常。

总结

回头看那个"假装查了数据库、其实把结果编出来"的客服助手,以及我后来在 Function Calling 上接连踩的坑,最该记住的不是某一段 tool_calls 的解析代码,而是我动手前那个想当然的判断——"只要 prompt 写得够清楚,模型就会按我说的去查数据"。这句话错得很彻底:模型从来没有"查数据"这个能力,你的 prompt 再清楚,也不可能给它装上一个数据库连接。它能做的只有生成文本,你让它查,它就生成一段看起来像查询结果的文本。把这件事看清,你就明白 Function Calling 为什么是这样设计的——它不试图给模型"执行能力",它做的是在模型和你的代码之间,架起一座结构化的桥:模型负责它擅长的"判断该用什么工具、参数是什么",执行这件事,从头到尾都该是、也只能是你的代码。

所以做 Function Calling,真正的工程量不在"调用一次模型"那一下。把 tools 传进去、把 tool_calls 取出来,Demo 里它也确实能跑通一个来回。真正的工程量在那座"桥"的两端:模型那一端,你的工具声明、尤其是 description 写得够不够清楚,直接决定它调得准不准;你的代码那一端,模型给的参数你校验了吗、工具执行出错你是抛异常还是喂回给模型、连续调用你设了轮数上限吗。这篇文章的几节,其实就是顺着这条思路展开的:先想清楚模型为什么不能"自己执行",再看土办法的三类坑,然后是工具声明、完整调用回合这两个主干,最后是多工具、参数校验、错误喂回这几个把工具调用真正做稳的工程细节。

你会发现,Function Calling 的思路和我们做任何"系统集成"的工程经验都是相通的。两个不能直接对话的系统要协作,我们从不让它们"猜"对方的意图,而是定义一份双方都严格遵守的接口契约——请求长什么样、响应长什么样、出错了怎么表示。Function Calling 就是大模型和你的业务代码之间的那份契约:tools 是接口声明,tool_calls 是请求,role="tool" 消息是响应。模型不再是一个你只能"求它配合"的黑盒,而是契约的一方——它按契约提出工具调用请求,你按契约执行并回应。

最后想说,Function Calling 做没做扎实,差距永远不会在 Demo 里暴露——Demo 里你问的都是工具能干净利落答上来的问题,参数也规规矩矩,跑一个来回就漂亮收尾。它只在真实用户面前才显形:那些说不清订单号的用户、那些一句话要查三样东西的用户、那些让工具查出"查无此订单"的场景。那时候它会用最难堪的方式给你结账——一个根本没查过的物流状态被信誓旦旦地告诉用户,一个模型编造的订单号被你的代码当真去执行,一个陷入死循环的工具调用把你的 API 账单刷上天。所以别等用户拿着错误的信息去做了错误的决定再来找你,在你声明第一个工具的时候就该想清楚:这个工具的描述,模型看得懂什么时候该用吗?它给我的参数,我校验了吗?它调用出错时,我是让它自我修正,还是直接崩掉?它要是调个没完,我拦得住吗?这几个问题都有了答案,你的 AI 助手才不只是 Demo 里那个对答如流的演示,而是一个真的能用上工具、并且每一个"事实"都来自真实工具、而非凭空编造的可靠助手。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

分布式锁完全指南:从一次"扩容后对账任务跑了三遍、结算被重复打款"看懂分布式锁

2026-5-21 19:26:30

技术教程

数据库连接池完全指南:从一次"流量一高数据库就报 Too many connections"看懂连接池

2026-5-21 19:37:30

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索