第 6 章 · 03 工具如何被 Agent 调用


文档摘要

第 6 章 · 03 工具如何被 Agent 调用 本节摘要:前两节分别讲了「注册表」和「exasearch 工具本身」,本节把视角拉到 Agent 一侧,回答最关键的问题:LLM 是怎么「自己决定」调工具的?答案藏在 swarms 框架的 参数里——实例化 Agent 时把函数列表传进去,框架就会把这些函数的签名和 docstring 翻译成 OpenAI 的 function-calling 协议,塞进每次请求。LLM 在生成回复时,可以选择「输出文字」或「输出一个 toolcall」;若是后者,框架在本地真正执行函数,把返回值作为 tool result 再喂回 LLM,LLM 据此继续。

第 6 章 · 03 工具如何被 Agent 调用

本节摘要:前两节分别讲了「注册表」和「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)时部分成立。

学习目标

阅读完本节,你应当能够:

  1. 说清 swarms Agenttools 参数怎么把函数变成 LLM 可调的工具。
  2. 描述 LLM 决定调用工具的完整四步循环(注入说明 → 生成 tool_call → 本地执行 → 结果回灌)。
  3. 确认本项目只有情绪 Agent 绑了工具(tools=[exa_search]),其他三个专家裸奔。
  4. 解释 max_loops=1 与工具调用的关系,以及为什么工具调用不算一次 loop。
  5. 看懂 LLM 输出的 tool_call 在协议层长什么样。

一、tools 参数:Agent 实例化时绑定

回顾第 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 参数背后的协议:function calling

当你写 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 的三个字段来源:

  • nameexa_search.__name__
  • descriptionexa_search.__doc__(上一节讲过的超长 docstring)
  • parametersinspect.signature(exa_search) + 类型注解

每次向 OpenAI 发请求时,swarms 把这个 JSON 放进请求体的 tools 字段一起发出去。LLM 看到的就是这份「工具说明书」,它据此决定要不要用。

💡 回扣前节:现在你明白为什么 exa_search 的 docstring 要写那么长了吧——它会原封不动变成 LLM 看到的 description。docstring 写得糙,LLM 就不知道什么时候该调;写得详尽,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 这次看到了搜索结果,据此生成最终的情绪分析文字。

💡 核心心法:工具调用的本质是模型与本地代码的对话。模型只能「请求」调用某个函数并给参数,真正的执行权在你手里(本地代码)。这层隔离是安全的——模型没法绕过你的代码直接访问网络或文件,它只能调你显式注册的函数。

四、tool_call 在协议层长什么样

OpenAI 协议里,模型决定调工具时,响应不是普通文字,而是这样的结构:

{ "role": "assistant", "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "exa_search", "arguments": "{\"query\": \"最近石油价格下跌的原因\"}" } } ] }

几个细节:

  • arguments字符串化的 JSON,不是对象——这是协议要求,模型生成的是文本。
  • id(如 call_abc123)用于把后续的 tool result 关联回这次调用。
  • 模型可以一次发多个 tool_call(并行调用),swarms 会依次或并发执行。

执行后,本地追加一条 tool 消息:

{ "role": "tool", "tool_call_id": "call_abc123", "content": "[{\"title\": \"...\", \"answer\": \"OPEC+ 增产导致...\"}, ...]" }

content 就是 exa_search 返回的那个字符串。LLM 下一轮看到它,就能引用真实搜索结果写分析。

五、max_loops 与工具调用的关系

四个专家都设了 max_loops=1。这个参数控制 Agent 的「自我反思」轮数,但和工具调用是两回事:

  • max_loops:Agent 把自己上一轮的输出再喂给自己,做几轮自我修正。
  • 工具调用:发生在单次 LLM 请求里(请求 → tool_call → 执行 → 再请求 → 文字输出),这整个链条算「一次」run,不额外消耗 loop。

所以 max_loops=1 不会限制情绪 Agent 调工具的次数——它仍可以在一次 run 里多轮 tool_call(查新闻 → 看完再查别的)。但本项目 prompt 没引导多次调用,实际上情绪 Agent 通常调一两次 exa_search 就结束。

六、为什么只有情绪 Agent 带工具

这是个值得琢磨的问题。从代码能推断的设计意图:

  • 情绪分析最依赖外部信息:新闻、舆情是实时的,模型记忆里没有,必须联网。所以情绪 Agent 配 exa_search 合理。
  • 量化分析本应也需要数据(实时价格、成交量),但 quant_agent 没绑 yahoo_api——这看起来是未完成,而不是有意设计。
  • 风控/执行理论上应基于量化数据,但同样裸奔。

更可能的真相是:作者把情绪 Agent 当作「工具调用」的示范做完跑通了,其他专家的「工具接入」属于计划中但未实现。这跟第 1 章的判断一致——架构完整,实现部分完成。

💡 关键观察:即使是「带工具」的情绪 Agent,它也只有 exa_search 一个工具——只能看新闻,查不了实时股价、历史 K 线、链上数据。所以情绪 Agent 的「实时性」仅限于新闻维度,不是完整的实时行情。

七、Agent 不带工具时会怎样

如果 Agent 没绑 tools(如风控/执行/量化),swarms 发请求时就不带 tools 字段。LLM 此刻:

  • 不会生成 tool_call(协议里没给它这个选项);
  • 只能凭 system_prompt + 上下文里 Director 给的信息,直接输出文字。

所以这三个专家的产出,完全是「LLM 凭知识 + Director 传来的上游结果」硬写出来的。比如执行 Agent 写的「订单结构」(订单类型、数量、入场价、止损)——这些数字不是来自实时行情,而是 LLM 根据 Director 给的「股票 + 风险评估」出来的合理数字。生产环境绝对不能这样下单。

本节要点回顾

  1. tools 参数:Agent 实例化时绑函数列表;swarms 把函数翻译成 function-calling 协议的 JSON(name/description/parameters),随每次请求发给 LLM。
  2. 四步循环:注入工具说明 → LLM 生成文字或 tool_call → 本地执行函数 → 结果作为 tool message 回灌 → LLM 续写。
  3. 本项目只有情绪 Agent 带工具:tools=[exa_search];风控/执行/量化三个专家裸奔,只能凭记忆推理——「实时量化」「自主执行」根本没接通。
  4. 执行权在本地:模型只能「请求」调函数,真正执行永远在你的进程里;模型无法绕过注册的函数直接访问网络/文件。
  5. docstring 即说明书:LLM 看到的工具 description 就是函数 docstring;写得详尽才能让模型准确触发(回扣上节)。
  6. max_loops 与工具调用无关:工具调用在单次 run 内多轮往返,不消耗 loop;max_loops=1 不限制工具调用次数。

至此第 6 章结束,你理解了「Agent 怎么伸手调函数」。下一章我们看另一类工具——市场数据获取(yahoo_api、Jupiter),并澄清 polygon_api 的真相。


发布者: 作者: 灏天文库 转发
评论区 (0)
U