tool calling 没那么玄:我用 Prompt 手搓了一遍

我让模型「调用」一个天气工具,它照做了,还返回了北京的天气。 但那个工具从头到尾根本不存在------执行它的,是我自己。

这篇用一次最朴素的实验,把 tool calling 拆到只剩三个角色。 读完你会顺手搞懂一件很多人栽过跟头的事:为什么在 prompt 里写「你可以用 X 工具」基本没用。

这是《从零写一个通用 Agent 内核》的第 0 步。在敲下任何一行「工具分发」代码之前,我先没写代码,纯靠对话把 tool calling 跑了一遍。

一段「不太对劲」的对话

手头只有一个最朴素的 agent 循环:DeepSeek 的 OpenAI 兼容接口 + 一个 read_file 工具。我跟模型约了个「君子协定」:

makefile 复制代码
You: 你是一名天气专家。当我问某地天气时,你用 get_weather(<地点>) 来回复,
     然后我会告诉你那个地方的天气。

AI: 明白!现在我就是你的专属天气专家,随时准备调用 get_weather(<地点>)。

You: 北京天气怎么样?

AI: 好的,正在为您查询北京天气,请稍等!☀️
    get_weather(北京)

看上去模型「调用」了 get_weather。但盯着 API 返回看,有个关键细节:

这一轮的 finish_reasonstop,不是 tool_calls 那行 get_weather(北京) 只是模型打印出来的一句普通文本,它压根没发起任何真正的工具调用。

那谁来「执行」这个 get_weather?我。

vbnet 复制代码
You: 微风小雨        ← 这一步是我在扮演 get_weather,把"查询结果"喂回去

AI: 北京此刻微风伴小雨,适合披件薄外套撑伞散步~🌧️(根据我喂的值产出最终答复)

一个完整的 tool-calling 循环就这么跑完了------只不过全程手动。

别急着喊「翻车」,这是我故意的

模型没真调用工具,但这正是我要的:用最原始的方式,把平时被 API 黑盒掉的过程亲手走一遍。

tool calling 听着玄,剥开只有三个角色:

角色 这场实验里是谁 真实 agent 里是谁
决定调用、发出调用、解读返回值 模型 模型(这层永远是模型的活)
真正执行工具、把结果递回去 我(人肉后端) 执行器代码
调用和结果怎么来回传递(协议) 普通聊天文本 tool_calls / role:tool / tool_call_id

模型那一列从头到尾没变。变的只有中间那行------「谁来执行」和「结果怎么传回」。而这恰恰就是一个 agent 框架的全部活儿:把人肉执行器换成代码,把聊天文本换成结构化协议。

真机长什么样:换一个「真的」工具

为了对照,我换个问题------这次问的是 read_file 能覆盖的事:

makefile 复制代码
You: agent.py 文件是做什么的?

模型这次的返回,finish_reason 变成了 tool_calls

python 复制代码
tool_calls = [ read_file(arguments='{"path": "agent.py"}') ]

一次结构化的、真正的工具调用。紧接着,是我的 Python 把它执行了:

python 复制代码
for tool_call in assistant_message.tool_calls:
    tool_name = tool_call.function.name                    # "read_file"
    tool_args = json.loads(tool_call.function.arguments)   # {"path": "agent.py"}
    result = read_file(**tool_args)                        # ← 真正的执行在这行
    conversation.append({
        "role": "tool",
        "content": str(result),
        "tool_call_id": tool_call.id,                      # ← 协议把结果递回去
    })

结果通过 role: tool 喂回模型,模型读完文件产出分析。

同样一套循环,这次没有「我」的位置了------那个执行的人,被 read_file(**tool_args) 这行取代了。

划重点:那条最容易栽的边界

两次运行唯一的差别:

  • 天气实验里,get_weather 从没进过 tools 列表;
  • 第二次里,read_file 进了 tools 列表。

仅此而已,结果却天差地别:前者只能「用文字假装调用」,后者才触发真机的 tool_calls

📌 提示词里写「你可以使用 X 工具」是无效的。 模型能真正调用什么,只由你传给 API 的 tools 参数决定。不在列表里的,模型最多在文本里「演」一遍。

一句话:能力不是说出来的,是注册进去的。

这也解释了为什么 prompt 里苦口婆心写一堆「你拥有以下能力」常常不管用。

接下来:把「人肉执行器」工业化

这场手动实验给整个系列定了主线:

我先自己当了一回工具后端,把 tool calling 玩明白了。接下来要做的,就是把「我这个人」换成一段「执行器」代码。

而一旦你认真去替代「那个人」,会发现人肉执行时你下意识在做、却从没显式写下来的一堆事,代码必须一件件接住:

  • 模型给的参数是坏 JSON?(人会皱眉重读,代码直接崩)
  • 工具跑了 10 秒没返回?(人会喊停,代码默认死等)
  • 工具吐回 5000 行全塞给模型?(人会摘重点,代码会把模型淹掉)
  • 这个操作会删文件,要不要先确认?(人有常识,代码需要一道「确认门」)
  • 出错了返回 "Error" 还是 "file not found, did you mean config.yaml?"?(前者让模型抓瞎,后者让它当场自我纠错)

这些就是横切关注点:不属于任何一个具体工具,而属于「执行」这件事本身。下一篇,我们就从把它们抽出来、统一塞进一条执行管道开始------这条管道,才是决定一个 agent 到底「聪不聪明」的地基。


系列《从零写一个通用 Agent 内核》一路记录这个小核心从零长大的设计取舍。不堆术语,只讲「本来长什么样、为什么疼、然后怎么长成现在这样」。下一篇见。

相关推荐
字节跳动开源17 小时前
火山引擎开源 Agent 驱动的搜索自迭代技术
数据库·开源·agent
阿里云大数据AI技术19 小时前
阿里云 Elasticsearch 9.4 Agent Builder 实战
人工智能·elasticsearch·agent
明明如月学长19 小时前
Skill、Agent 和 Subagent 的区别是什么?我用大白话讲清楚
agent
uccs20 小时前
把工具组装成应用:代码分析、Research Agent、Vibe Coding
agent·ai编程·claude
扯蛋43820 小时前
从脱敏到审批:我用 LangChain 11 个中间件给羽毛球 AI 助手装了一整套"安全阀"
langchain·llm·agent
DigitalOcean21 小时前
Claude Opus 5 现已上线 DigitalOcean AI 推理云
agent
沉默王二1 天前
腾讯面试官:“你说你做了一个终端Agent,那说说 LLM 和 Agent的区别,ReAct、MCP、Tool、Memory、Skills?”我信誓旦旦开始背了
面试·agent·腾讯
后端小肥肠1 天前
做个人 IP 不用真人出镜,我做了个一键生成 IP 动画视频的 Skill
人工智能·aigc·agent
uncle_ll1 天前
LangGraph 深度解析:用图结构构建下一代智能代理与多智能体系统
langchain·llm·agent·graph·langgraph
武子康1 天前
从世界状态到可执行控制:Cosmos 3 Edge 与机器人控制器之间应建立什么合同
人工智能·agent·nvidia