第 6 章 · 02 exa_search 联网搜索工具


文档摘要

第 6 章 · 02 exasearch 联网搜索工具 本节摘要:本节精读 ——AutoHedge 里情绪 Agent 唯一的工具,也是整个项目里「让 LLM 联网」的关键。它封装了 Exa.ai 这家专做「自然语言搜索」的服务:输入一句问句(如「最近美联储加息的新闻」),Exa 返回带摘要的网页结果。本节逐行拆解这个 85 行的小函数:从环境变量取 API key → 构造带 schema 的 payload(让 Exa 直接返回结构化摘要)→ POST 请求 → 用 把结果转成 JSON 字符串。重点讲三个工程约定:为什么工具一律返回字符串(给 LLM 读)、docstring 为什么写得这么长(它就是给 LLM 的工具说明书)、以及 API key 缺失时的降级处理。

第 6 章 · 02 exa_search 联网搜索工具

本节摘要:本节精读 exa_search_tool.py——AutoHedge 里情绪 Agent 唯一的工具,也是整个项目里「让 LLM 联网」的关键。它封装了 Exa.ai 这家专做「自然语言搜索」的服务:输入一句问句(如「最近美联储加息的新闻」),Exa 返回带摘要的网页结果。本节逐行拆解这个 85 行的小函数:从环境变量取 API key → 构造带 schema 的 payload(让 Exa 直接返回结构化摘要)→ POST 请求 → 用 any_to_str 把结果转成 JSON 字符串。重点讲三个工程约定:为什么工具一律返回字符串(给 LLM 读)、docstring 为什么写得这么长(它就是给 LLM 的工具说明书)、以及 API key 缺失时的降级处理。

内容来源:原项目源码 autohedge/tools/exa_search_tool.py,精读并套用体系化模板。

⚠️ 现实澄清:Exa.ai 是真实存在的搜索服务,需要付费 API key。没 key 这个工具直接抛错,情绪 Agent 就退化成「只能凭记忆瞎编新闻」——这也是为什么本项目情绪分析质量高度依赖外部 key 是否配置。

学习目标

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

  1. 逐行读懂 exa_search 函数的全部 85 行代码。
  2. 说清 Exa.ai 与传统搜索引擎的区别(自然语言 + 内置摘要)。
  3. 理解工具返回字符串(而非 dict)的工程约定,以及 any_to_str 的作用。
  4. 解释 payload 里 schema 字段如何让 Exa 返回结构化摘要
  5. 知道 docstring 在 LLM 工具里的双重身份(人看 + 模型看)。

一、exa_search 全貌

完整代码(exa_search_tool.py,85 行):

import os import httpx from loguru import logger from swarms.utils.any_to_str import any_to_str def exa_search( query: str, ) -> str: """ # ← docstring 极长,见第三节解释 Exa Web Search Tool ... """ api_key = os.getenv("EXA_API_KEY") # ① 取 API key if not api_key: raise ValueError( # ② 没 key 直接抛错 "EXA_API_KEY environment variable is not set" ) characters = 20 # ③ 摘要字符上限(注释里写 20) sources = 2 # ④ 返回结果数 headers = { "x-api-key": api_key, # ⑤ Exa 用 header 鉴权 "content-type": "application/json", } payload = { # ⑥ 构造请求体 "query": query, "type": "auto", "numResults": sources, "contents": { "text": True, "summary": { "schema": { # ⑦ 用 schema 指定返回结构 "type": "object", "required": ["answer"], "additionalProperties": False, "properties": { "answer": { "type": "string", "description": ( "Key insights and findings from the search result" ), } }, } }, "context": {"maxCharacters": characters}, }, } try: logger.info( f"[SEARCH] Executing Exa search for: {query[:50]}..." ) response = httpx.post( # ⑧ POST /search "https://api.exa.ai/search", json=payload, headers=headers, timeout=30, ) response.raise_for_status() json_data = response.json() return any_to_str(json_data) # ⑨ 转字符串返回 except Exception as e: logger.error(f"Exa search failed: {e}") return f"Search failed: {str(e)}. Please try again." # ⑩ 失败返回提示字符串

逻辑链很清晰:query 字符串检查 key构造 payloadPOST转字符串返回;失败时返回一段提示文字。下面把关键步骤拆开讲。

二、Exa.ai 是什么,与传统搜索的区别

Exa.ai(原 Metaphor)是一家定位独特的搜索服务。对比:

维度 Google/Bing 搜索 API Exa.ai
查询方式 关键词(布尔表达式) 自然语言句子(语义检索)
索引方式 关键词倒排 用 embedding 做语义索引
返回 网页标题 + 摘录 + URL 可直接返回正文文本 + 结构化摘要
适合 知道要找什么关键词 用自然语言描述需求

所以 exa_search 的入参 query 是一句话,比如代码 docstring 里的例子:"Show me the latest Python 3.12 documentation on dataclasses""Recent research on transformer architectures for vision tasks"。这种自然语言查询特别适合 LLM——LLM 本来就擅长把分析需求翻译成自然语言。

💡 为什么情绪 Agent 用 Exa:情绪分析需要看「最近的新闻和评论」,这种需求用关键词很难穷尽(你不知道今天会出什么新闻)。Exa 的语义搜索让 Agent 只要用一句话描述「找这只股票最近的负面新闻」,就能拿到结果,远比硬编码一堆关键词灵活。

三、超长 docstring 的双重身份

注意这个函数的 docstring 长得离谱(从第 11 行到第 43 行,占了全文件近一半),里面详细写了 Features、Args、Returns、Example、Notes。为什么这么长?

因为 LLM 工具的 docstring 有双重身份:

  1. 给人看:维护代码的工程师。
  2. 给模型看:swarms 框架会把 exa_search.__doc__ 抽出来,塞进给 LLM 的「可用工具清单」。LLM 读这段文字,判断「当前任务要不要调这个工具」「该怎么传参」。

所以这段 docstring 本质是写给 LLM 看的招领广告——它写得越清楚,LLM 越知道什么时候该用、什么时候别用。比如 Notes 里特意强调「searching for documentation 时把库名和 API 名都带上」,就是在教 LLM 怎么构造更好的 query。

💡 核心心法:在 LLM 工具体系里,docstring 是 prompt 的一部分。写工具说明 = 写给模型的提示词。这点和普通函数完全不同——普通函数 docstring 写得糙点不影响功能,工具函数 docstring 写得糙,LLM 就不会正确调用它。

四、payload 的精妙:schema 驱动的结构化摘要

看 payload 的 contents 部分:

"contents": { "text": True, # 返回正文文本 "summary": { "schema": { # 用 JSON Schema 指定摘要结构 "type": "object", "required": ["answer"], "additionalProperties": False, "properties": { "answer": { "type": "string", "description": "Key insights and findings from the search result", } }, } }, "context": {"maxCharacters": characters}, },

这段是 exa_search 设计的精华。它不只是「搜网页」,而是:

  • "text": True:让 Exa 把命中网页的正文文本也抓回来。
  • "summary.schema":给 Exa 一个 JSON Schema,让它按这个结构返回摘要。这里要求摘要是一个对象,必须有 answer 字段(字符串),内容是「该结果的关键洞察」。
  • "additionalProperties": False:严格限定只返回 answer,不许多塞字段。

效果是:每条搜索结果不是一堆杂乱文本,而是一个结构化的 {answer: "..."} 对象,直接喂给 LLM 就能用。Exa 在服务端用模型把网页内容压成你指定的结构——这是「搜索 + 摘要 + 结构化」三合一。

💡 省 token 的关键:characters = 20 这个值偏小(注释这么写),实际生产里会调大。限制字符数 + 用 summary,是为了控制返回给 LLM 的 token 量——搜索结果动辄上万字,全塞进上下文既贵又稀释注意力。先压成精炼摘要,是工程上常见的做法。

五、鉴权与错误处理

鉴权(第 ①⑤ 步):

api_key = os.getenv("EXA_API_KEY") if not api_key: raise ValueError("EXA_API_KEY environment variable is not set") ... headers = {"x-api-key": api_key, "content-type": "application/json"}
  • API key 从环境变量 EXA_API_KEY 读(通过 .env 加载,见 env_loader)。
  • Exa 的鉴权方式是 HTTP header x-api-key,不是 query 参数——这样 key 不会进 URL 日志,稍安全。
  • 没 key 直接抛错,不让请求发出去(否则会被服务端拒,日志更难看)。

错误处理(第 ⑩ 步):

except Exception as e: logger.error(f"Exa search failed: {e}") return f"Search failed: {str(e)}. Please try again."

注意这里没有 raise,而是返回一段提示字符串。这是个有意的设计:工具函数如果抛异常,可能让上层 Agent 直接崩;返回字符串则让 LLM 看到「搜索失败」,它可以决定是重试还是换思路。这也是 LLM 工具的常见约定——尽量返回字符串而不是抛异常,把决策权交回模型。

六、为什么返回字符串:any_to_str 的作用

第 ⑨ 步:

return any_to_str(json_data)

any_to_str 来自 swarms.utils,作用是把任意 Python 对象(dict/list/对象)转成字符串表示。为什么工具要返回字符串?

因为 LLM 的上下文是文本流。Agent 框架拿到工具返回值后,会把它作为一条「tool result」消息塞回对话历史。这条消息必须是文本——LLM 不懂 Python 对象,只懂字符串。所以工具函数统一约定:

  • 入参是字符串/基本类型(LLM 生成的参数);
  • 返回值是字符串(通常 JSON 字符串,LLM 也能读)。

any_to_str 内部通常就是 json.dumps 一类,把 dict 转成 JSON 文本。这样 LLM 看到的是:

[{"title": "...", "url": "...", "answer": "美联储 7 月加息 25 基点..."}, ...]

它读得懂,也就能基于这些信息继续分析。

七、整条调用链串起来

把 exa_search 放进情绪 Agent 的视角,完整流程:

注意:调用 exa_search 的决策不是写死在代码里,而是 LLM 在推理时自己判断「我需要查新闻」才触发。下一节会详讲这个「LLM 决定何时调」的机制。

本节要点回顾

  1. exa_search 全貌:85 行,逻辑链 query → 检查 key → 构造 payload → POST → any_to_str 返回;失败返回提示字符串而非抛异常。
  2. Exa.ai 是语义搜索:自然语言 query、embedding 索引、可直接返回正文+摘要,适合 LLM 用一句话描述需求(尤其情绪分析查新闻)。
  3. docstring 是给 LLM 的说明书:工具函数 docstring 是 prompt 的一部分,写得越清楚,LLM 调用越准——这是 LLM 工具与普通函数的根本区别。
  4. schema 驱动的结构化摘要:payload 里 summary.schema 让 Exa 按指定 JSON Schema 返回精炼摘要,既省 token 又便于 LLM 消费。
  5. 鉴权:API key 从 EXA_API_KEY 读,放 x-api-key header;没 key 直接抛错,不让请求发出。
  6. 返回字符串约定:工具统一返回字符串(用 any_to_str 转),因为 LLM 上下文是文本流;失败也返回提示字符串,把决策权交回模型。

下一节,我们把视角拉到 Agent 一侧,看 swarms 框架如何让 LLM「自己决定」何时调 exa_search,以及结果怎么回灌进上下文。


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