第 6 章 · 03 工具如何被 Agent 调用 本节摘要:前两节分别讲了「注册表」和「exasearch 工具本身」,本节把视角拉到 Agent 一侧,回答最关键的问题:LLM 是怎么「自己决定」调工具的?答案藏在 swarms 框架的 参数里——实例化 Agent 时把函数列表传进去,框架就会把这些函数的签名和 docstring 翻译成 OpenAI 的 function-calling 协议,塞进每次请求。LLM 在生成回复时,可以选择「输出文字」或「输出一个 toolcall」;若是后者,框架在本地真正执行函数,把返回值作为 tool result 再喂回 LLM,LLM 据此继续。
本节摘要:前两节分别讲了「注册表」和「exa_search 工具本身」,本节把视角拉到 Agent 一侧,回答最关键的问题:**LLM 是怎么「自己决定」调工具的?**答案藏在 swarms 框架的
tools参数里——实例化 Agent 时把函数列表传进去,框架就会把这些函数的签名和 docstring 翻译成 OpenAI 的 function-calling 协议,塞进每次请求。LLM 在生成回复时,可以选择「输出文字」或「输出一个 tool_call」;若是后者,框架在本地真正执行函数,把返回值作为 tool result 再喂回 LLM,LLM 据此继续。本节还澄清一个本项目的重要事实:四个专家里只有情绪 Agent 真的绑了工具(exa_search),其他三个(量化/风控/执行)都是「裸」Agent,只能凭记忆推理——这是项目未完成的又一证据。
内容来源:综合
autohedge/workers.py(Agent 实例化)与 swarms 框架的 tools 机制,精读并套用体系化模板。
⚠️ 现实澄清:「工具调用」听起来很美,但要记住——本项目里大部分专家根本没有工具,只能凭模型记忆瞎猜行情。营销文案里的「实时分析」只在情绪 Agent(且配了 Exa key)时部分成立。
阅读完本节,你应当能够:
Agent 的 tools 参数怎么把函数变成 LLM 可调的工具。tools=[exa_search]),其他三个专家裸奔。max_loops=1 与工具调用的关系,以及为什么工具调用不算一次 loop。tool_call 在协议层长什么样。回顾第 3 章见过的 workers.py。四个专家的实例化:
sentiment_agent = Agent( agent_name="Sentiment-Agent", system_prompt=SENTIMENT_PROMPT + _SYSTEM_SUFFIX, model_name="gpt-4o-mini", verbose=True, max_loops=1, tools=[exa_search], # ← 唯一带工具的专家 ) risk_agent = Agent( agent_name="Risk-Manager", system_prompt=RISK_PROMPT.strip() + ... + _SYSTEM_SUFFIX, model_name="gpt-4.1", output_type="str", max_loops=1, verbose=True, context_length=16000, # 注意:没有 tools 参数 ) execution_agent = Agent( agent_name="Execution-Agent", system_prompt=EXECUTION_PROMPT.strip() + ..., model_name="gpt-4.1", output_type="str", max_loops=1, verbose=True, context_length=16000, # 没有 tools 参数 ) quant_agent = Agent( agent_name="Quant-Analyst", system_prompt=QUANT_PROMPT.strip() + ..., model_name="gpt-4.1", output_type="str", max_loops=1, verbose=True, context_length=16000, # 没有 tools 参数 )
这张对照表非常关键:
| 专家 | 模型 | 是否绑工具 | 工具 |
|---|---|---|---|
| 情绪 Agent | gpt-4o-mini | ✅ | exa_search(联网搜新闻) |
| 风控 Agent | gpt-4.1 | ❌ | 无 |
| 执行 Agent | gpt-4.1 | ❌ | 无 |
| 量化 Agent | gpt-4.1 | ❌ | 无 |
⚠️ 现实澄清:这意味着风控、执行、量化三个专家完全凭模型记忆做判断。量化 Agent 没法查实时行情(没绑 yahoo_api),执行 Agent 没法真下单(没绑 ultra_tools)——所谓「实时量化」「自主执行」在这三个专家身上根本没接通。第 1 章说的「多数工具存在却未接入 Agent」,这里就是铁证。
当你写 tools=[exa_search],swarms 在背后做了什么?它把每个函数对象翻译成 OpenAI function-calling 协议的一个 JSON,大致是这样:
{ "type": "function", "function": { "name": "exa_search", "description": "Exa Web Search Tool. This function provides advanced...", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "The natural language search query..." } }, "required": ["query"] } } }
这个 JSON 的三个字段来源:
name ← exa_search.__name__description ← exa_search.__doc__(上一节讲过的超长 docstring)parameters ← inspect.signature(exa_search) + 类型注解每次向 OpenAI 发请求时,swarms 把这个 JSON 放进请求体的 tools 字段一起发出去。LLM 看到的就是这份「工具说明书」,它据此决定要不要用。
💡 回扣前节:现在你明白为什么 exa_search 的 docstring 要写那么长了吧——它会原封不动变成 LLM 看到的
description。docstring 写得糙,LLM 就不知道什么时候该调;写得详尽,LLM 才能准确触发。
当 Agent 的 run(task) 被调用,工具调用的完整循环是:
逐步解释:
第 1 步:注入工具说明。swarms 把 tools 列表翻译成上面那种 JSON,随请求发给 LLM。LLM 此刻「知道」自己手上有 exa_search 这个工具。
第 2 步:LLM 推理并生成。LLM 读 task(比如「分析石油市场情绪」),它有两种选择:
tool_call,表示「我要先查一下新闻」。这个选择完全由模型决定,代码里没有「if 涉及新闻就调 exa」的硬规则。模型会综合判断:task 要不要新信息、这个工具能不能帮上忙。
第 3 步:本地执行。如果模型输出 tool_call,swarms 在本地真正调用 exa_search(query=...),拿到返回的字符串(JSON 搜索结果)。这一步发生在你的进程里,不是 LLM 在调——LLM 只说「我要调」,实际执行永远在本地。
第 4 步:结果回灌。swarms 把执行结果作为一条 tool 角色的消息加进对话历史,再次请求 LLM。LLM 这次看到了搜索结果,据此生成最终的情绪分析文字。
💡 核心心法:工具调用的本质是模型与本地代码的对话。模型只能「请求」调用某个函数并给参数,真正的执行权在你手里(本地代码)。这层隔离是安全的——模型没法绕过你的代码直接访问网络或文件,它只能调你显式注册的函数。
OpenAI 协议里,模型决定调工具时,响应不是普通文字,而是这样的结构:
{ "role": "assistant", "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "exa_search", "arguments": "{\"query\": \"最近石油价格下跌的原因\"}" } } ] }
几个细节:
arguments 是字符串化的 JSON,不是对象——这是协议要求,模型生成的是文本。id(如 call_abc123)用于把后续的 tool result 关联回这次调用。执行后,本地追加一条 tool 消息:
{ "role": "tool", "tool_call_id": "call_abc123", "content": "[{\"title\": \"...\", \"answer\": \"OPEC+ 增产导致...\"}, ...]" }
content 就是 exa_search 返回的那个字符串。LLM 下一轮看到它,就能引用真实搜索结果写分析。
四个专家都设了 max_loops=1。这个参数控制 Agent 的「自我反思」轮数,但和工具调用是两回事:
max_loops:Agent 把自己上一轮的输出再喂给自己,做几轮自我修正。所以 max_loops=1 不会限制情绪 Agent 调工具的次数——它仍可以在一次 run 里多轮 tool_call(查新闻 → 看完再查别的)。但本项目 prompt 没引导多次调用,实际上情绪 Agent 通常调一两次 exa_search 就结束。
这是个值得琢磨的问题。从代码能推断的设计意图:
更可能的真相是:作者把情绪 Agent 当作「工具调用」的示范做完跑通了,其他专家的「工具接入」属于计划中但未实现。这跟第 1 章的判断一致——架构完整,实现部分完成。
💡 关键观察:即使是「带工具」的情绪 Agent,它也只有 exa_search 一个工具——只能看新闻,查不了实时股价、历史 K 线、链上数据。所以情绪 Agent 的「实时性」仅限于新闻维度,不是完整的实时行情。
如果 Agent 没绑 tools(如风控/执行/量化),swarms 发请求时就不带 tools 字段。LLM 此刻:
所以这三个专家的产出,完全是「LLM 凭知识 + Director 传来的上游结果」硬写出来的。比如执行 Agent 写的「订单结构」(订单类型、数量、入场价、止损)——这些数字不是来自实时行情,而是 LLM 根据 Director 给的「股票 + 风险评估」编出来的合理数字。生产环境绝对不能这样下单。
name/description/parameters),随每次请求发给 LLM。tools=[exa_search];风控/执行/量化三个专家裸奔,只能凭记忆推理——「实时量化」「自主执行」根本没接通。description 就是函数 docstring;写得详尽才能让模型准确触发(回扣上节)。max_loops=1 不限制工具调用次数。至此第 6 章结束,你理解了「Agent 怎么伸手调函数」。下一章我们看另一类工具——市场数据获取(yahoo_api、Jupiter),并澄清 polygon_api 的真相。