1. 项目概述:为AI Agent注入实时信息检索能力
在构建和部署AI智能体(Agent)时,一个核心的挑战是如何让它们摆脱静态知识的束缚,获取实时、准确的外部信息。无论是进行市场调研、监控竞争对手动态,还是追踪行业风险信号,一个能够直接、高效地访问互联网的“眼睛”和“耳朵”至关重要。今天要分享的,正是我们团队在OpenClaw AI Agent框架中,为解决这一问题而开发并深度集成的核心技能——Council Tavily Search。
这个技能的本质,是一个专为Agent设计的、经过优化的网络搜索工具。它不像传统的爬虫那样需要处理杂乱的HTML,也不需要进行繁琐的数据清洗。它直接对接Tavily API,将搜索结果以干净、结构化的JSON格式返回,我们的Agent(比如负责研究的Metis、负责监控的Gabriel)可以直接理解和使用这些数据,就像调用一个内部函数一样简单。如果你正在基于类似OpenClaw、LangChain或AutoGen的框架构建需要实时信息输入的AI应用,那么这个技能的设计思路和实现细节,或许能给你带来不少启发。
2. 核心设计思路与方案选型
2.1 为什么选择Tavily API而非通用搜索引擎?
在项目初期,我们评估了多种为Agent提供网络搜索能力的方案,包括直接调用Google Custom Search JSON API、使用SerpAPI,甚至是自行部署无头浏览器进行爬取。最终选择Tavily,是基于以下几个核心考量:
第一,结果质量与Agent友好度。通用搜索引擎的API返回结果往往包含大量用于人类阅读的元数据、广告和冗余信息。Agent(尤其是基于LLM的Agent)需要从这些噪音中提取有效信息,不仅增加了提示词(Prompt)的复杂度,也提高了出错率。Tavily API的设计初衷就是服务于AI应用,它返回的结果是经过预处理的“答案片段”(Answer Snippet)和简洁的摘要,格式高度结构化,几乎可以直接喂给LLM进行下一步推理。这省去了我们大量后处理的工作。
第二,可靠性与合规性。自行爬取面临IP封锁、反爬策略更新、网站结构变动等诸多不稳定因素,维护成本极高。而使用商业API,特别是像Tavily这样专注于AI搜索的服务,其稳定性和法律合规性更有保障。它处理了Robots协议、请求频率限制等底层问题,让我们能专注于业务逻辑。
第三,功能针对性。Tavily提供了“深度搜索”(Deep Search)和“新闻搜索”(Topic Search)等模式,这与我们Agent的不同任务场景完美匹配。例如,Gabriel监控竞争对手时,需要的是最新的新闻和公告,使用--topic news参数就能过滤掉论坛帖子或历史文档,精准获取信息。
第四,开发效率。Tavily API的接口设计非常简洁,认证方式简单(一个API Key),响应格式固定。这让我们能够快速封装成一个独立的技能模块,并通过标准化的[EXEC:]标签机制集成到Agent工作流中,极大提升了开发迭代速度。
2.2 Council技能架构与OpenClaw集成模式
OpenClaw框架(或类似的多Agent协作框架)通常有一个核心的“议事厅”(Council Room)或协调器,来管理多个具有不同角色的Agent(如Metis-研究员、Gabriel-监控员)。每个Agent可以调用一系列预定义的“技能”(Skill)。
我们的council-tavily就是一个这样的技能。它的设计遵循了“单一职责”和“即插即用”原则:
- 独立可执行:技能本身是一个包含脚本和配置的独立目录,可以通过命令行直接测试,例如
node search.mjs “查询词”。 - 环境隔离:技能运行所需的API密钥(
TAVILY_API_KEY)从环境变量或后端统一配置中读取,与技能代码分离,便于安全管理和多环境部署。 - 标准化接口:技能通过标准输出(stdout)返回JSON格式的结果。这使得任何能够执行系统命令并解析输出的Agent框架都可以轻松集成它。
- 参数化设计:通过命令行参数支持不同的搜索模式(
--deep,--topic,-n),使得技能非常灵活,能满足从快速事实核查到深度行业研究的不同需求。
这种架构的好处是解耦。搜索技能的维护和升级独立于Agent核心逻辑。如果未来需要更换搜索服务提供商,或者增加结果缓存、去重等高级功能,只需要修改这个技能模块,而所有依赖它的Agent都无需改动。
3. 技能部署与核心脚本解析
3.1 环境准备与安装步骤
部署这个技能非常简单,但其背后的目录结构和环境配置却体现了模块化设计的思想。
第一步:获取技能包。假设你已经从GitHub仓库克隆或下载了council-tavily目录。其标准结构应类似于:
council-tavily/ ├── scripts/ │ └── search.mjs # 核心搜索脚本 ├── package.json # 可选的Node.js依赖声明 ├── README.md └── ... (其他配置文件)第二步:放置到技能目录。OpenClaw框架通常有一个统一的技能存放位置,例如~/.openclaw/workspace/skills/。执行安装命令:
cp -r council-tavily/ ~/.openclaw/workspace/skills/council-tavily/这个操作相当于在Agent的“技能库”中注册了一个新工具。所有在同一个Council Room中的Agent,只要被授权,就都能调用它。
注意:确保目标路径(
~/.openclaw/workspace/skills/)存在且对你的应用有读写权限。在某些容器化或严格的部署环境中,可能需要通过配置卷(Volume)或环境变量来指定技能路径。
第三步:配置API密钥。这是最关键的一步。技能脚本会优先从环境变量TAVILY_API_KEY中读取密钥。你有两种配置方式:
- 环境变量(推荐,更安全):在启动Agent服务或Council Room后端的环境中加入该变量。
export TAVILY_API_KEY="tvly-dev-xxxxxxxxxxxx" # 或者写入你的 .env 文件,通过dotenv加载 - 后端硬编码(快速测试):如项目正文所示,密钥也可能被硬编码在Council Room的后端配置中。但在生产环境中,强烈建议使用环境变量或密钥管理服务(如AWS Secrets Manager, HashiCorp Vault),避免将敏感信息暴露在代码仓库中。
3.2 核心脚本search.mjs深度拆解
让我们深入看看scripts/search.mjs这个核心脚本可能包含的逻辑(基于其使用方式推断和补充的常见实现)。一个健壮的实现通常会包含以下部分:
#!/usr/bin/env node // search.mjs - Council Tavily Search Skill import { TavilyClient } from '@tavily/core'; // 假设使用官方或社区SDK // 或者使用 fetch 直接调用API // 1. 参数解析 const args = process.argv.slice(2); let query = ''; let options = { topic: 'general', deep: false, maxResults: 5 }; // 默认值 for (let i = 0; i < args.length; i++) { if (args[i].startsWith('--')) { const key = args[i].replace('--', ''); if (key === 'deep') { options.deep = true; } else if (key === 'topic') { options.topic = args[++i]; // 下一个参数是topic值 } } else if (args[i].startsWith('-')) { if (args[i] === '-n') { options.maxResults = parseInt(args[++i], 10); } } else if (!query) { query = args[i]; // 第一个非标志性参数视为查询词 } } if (!query) { console.error('Error: Search query is required.'); process.exit(1); } // 2. 获取API密钥(优先级:环境变量 > 硬编码后备值) const apiKey = process.env.TAVILY_API_KEY || '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'; if (!apiKey || apiKey.includes('your-api-key')) { console.error('Error: TAVILY_API_KEY is not configured.'); process.exit(1); } // 3. 调用Tavily API async function performSearch() { try { const client = new TavilyClient({ apiKey }); const searchParams = { query, searchDepth: options.deep ? 'deep' : 'basic', topic: options.topic, maxResults: options.maxResults, includeAnswer: true, // 包含AI生成的答案摘要,对Agent极有用 includeRawContent: false, // 通常不需要原始HTML,节省token }; const response = await client.search(searchParams); // 4. 格式化输出为Agent可用的JSON const cleanResults = { query, options, answer: response.answer, // AI总结的答案 results: response.results.map(item => ({ title: item.title, url: item.url, content: item.content, // 已清洗的文本内容 score: item.score, // 相关度得分 publishedDate: item.publishedDate, // 对新闻搜索很重要 })), timestamp: new Date().toISOString(), }; // 5. 输出到stdout,供Agent Exec捕获 console.log(JSON.stringify(cleanResults, null, 2)); } catch (error) { console.error('Error performing search:', error.message); // 返回一个结构化的错误信息,方便Agent处理 console.log(JSON.stringify({ error: true, message: `Search failed: ${error.message}`, query, })); process.exit(1); } } performSearch();脚本设计要点解析:
- 参数解析的鲁棒性:脚本需要能处理不同顺序和可选参数。例如,
node search.mjs “Ledgerowl” --topic news -n 8和node search.mjs -n 8 --topic news “Ledgerowl”应该产生相同效果。 - 错误处理与友好输出:脚本必须捕获网络错误、API限流、密钥无效等情况,并以结构化的JSON格式输出错误,而不是让进程崩溃或输出难以解析的堆栈信息。这样调用方的Agent才能根据错误类型做出合理反应(如重试、跳过或上报)。
- 输出标准化:
console.log(JSON.stringify(...))是技能与Agent通信的桥梁。标准化的输出格式(包含查询词、结果列表、时间戳)使得上游的Agent处理逻辑可以保持统一。 - 资源清理:脚本是短生命周期的,执行完毕即退出。要确保没有未释放的资源(如打开的HTTP连接池),避免在频繁调用时造成内存泄漏。
4. AgentExec集成与多角色应用实战
4.1 如何让Agent学会“使用”这个技能?
在OpenClaw的架构中,Agent通常通过一种特殊的指令格式来调用外部技能,例如[EXEC: ...]标签。这要求我们在Agent的配置或“技能允许列表”中显式声明这个技能是可信的。
集成配置示例:在AgentExec(或类似执行器)的配置中,你需要添加如下规则:
# 假设的Agent配置片段 agent_name: Gabriel capabilities: - web_search execution_allowlist: - pattern: "^\\[EXEC: node ~/\\.openclaw/workspace/skills/council-tavily/scripts/search\\.mjs .*\\]$" description: "允许执行Tavily网络搜索技能"这个正则表达式模式确保了Agent只能执行特定路径下的search.mjs脚本,并附带任意参数,防止了任意命令执行的安全风险。
在Agent决策逻辑中的应用:当Gabriel(监控Agent)的LLM大脑根据其目标(“监控竞争对手Mekari的最新动态”)生成行动计划时,它可能会在内部推理中决定需要搜索信息。其输出可能包含这样的指令:
我需要获取Mekari公司最新的融资或产品发布消息。 [EXEC: node ~/.openclaw/workspace/skills/council-tavily/scripts/search.mjs “Mekari funding round OR product launch 2024” --topic news -n 5]AgentExec拦截到这个[EXEC:]指令后,会校验其是否在允许列表中,然后在一个安全的子进程中执行该命令,捕获其标准输出(即我们脚本打印的JSON),最后将这个JSON结果返回给Gabriel的LLM进行后续分析和报告生成。
4.2 多角色Agent的差异化搜索策略
不同的Agent角色对搜索的需求截然不同,这体现在查询词的构建和搜索参数的选用上:
Metis(研究员):
- 场景:进行深入的竞争对手分析或市场进入研究。
- 查询词特点:复杂、组合式。例如:
“印尼 SaaS 会计软件 市场规模 增长预测 2024 site:kontan.co.id OR site:techinasia.com”。会利用高级搜索语法(如site:, OR, “”)来限定范围。 - 参数选择:频繁使用
--deep参数。深度搜索会调用Tavily的增强爬取能力,浏览更多页面、阅读“相关阅读”链接,生成更全面的报告,虽然耗时更长(可能几十秒),但信息质量更高。 - 结果处理:Metis可能需要将多次搜索的结果进行汇总、去重和交叉验证,最终生成一份结构化的分析报告。
Gabriel(监控员):
- 场景:7x24小时监控指定公司(如Ledgerowl, Accurate)的新闻、社交媒体提及、招聘信息等。
- 查询词特点:精准、包含公司名和监控维度。例如:
“Ledgerowl 价格 调整 Indonesia”,“Accurate (software) 新功能 发布”。可能会定期轮询一组预定义的查询词。 - 参数选择:几乎总是使用
--topic news来过滤出时效性强的新闻和公告。-n参数可能设置为一个中等值(如8),以平衡覆盖面和速度。 - 结果处理:Gabriel更关注“新出现”的信息。它需要将本次搜索结果与历史记录对比,识别出新条目,并立即触发警报或生成简讯。
Michael(风控员):
- 场景:扫描网络上的风险信号,如合规问题、数据泄露传闻、重大诉讼等。
- 查询词特点:关注负面关键词和风险领域。例如:
“数据泄露 印尼 金融科技 2024 年 月”,“监管 处罚 SaaS 公司”。 - 参数选择:可能混合使用常规搜索和新闻搜索,取决于风险类型(突发新闻 vs. 长期行业趋势)。
- 结果处理:Michael需要对结果的“情绪”或“风险等级”进行初步判断(可能通过另一个LLM调用),并将高优先级风险摘要推送给人类分析师。
Rafael(战略官):
- 场景:为高层决策收集宏观战略背景信息。
- 查询词特点:宽泛、趋势性。例如:
“东南亚 数字化转型 中小企业 挑战”,“远程办公 常态化 对 SaaS 影响”。 - 参数选择:可能使用常规搜索,并设置较大的
-n值(如10-15),以获取更广泛的视角。 - 结果处理:Rafael的工作是综合与提炼,将分散的信息点整合成趋势洞察和战略建议。
实操心得:查询词构建是门艺术。直接让LLM生成搜索查询词有时会过于笼统或产生偏差。我们的经验是,为每个Agent角色设计一套“查询词模板”或“提示词片段”。例如,Gabriel的提示词中会包含:“请生成用于搜索[公司名]在[国家]市场关于[产品/价格/合作]最新消息的查询词,优先使用当地语言关键词和主要新闻网站限定符”。这能显著提升搜索结果的直接相关性和质量。
5. 性能优化、成本控制与故障排查
5.1 管理API成本与提升响应速度
Tavily API通常是按次计费的,深度搜索比普通搜索更贵。在多个Agent高频使用的场景下,成本优化至关重要。
策略一:实现结果缓存。这是最有效的优化手段。可以为search.mjs脚本增加一个缓存层(例如使用Redis或本地文件缓存)。
- 缓存键设计:将
查询字符串、topic、maxResults组合成一个唯一键。deep参数通常单独处理,因为深度搜索结果变化可能更频繁。 - 缓存过期策略(TTL):
- 新闻搜索(
--topic news):TTL较短,例如5-15分钟,以保证信息的时效性。 - 常规搜索:TTL可以设为1-6小时。
- 深度搜索:由于成本高、耗时长,TTL可以设为12-24小时,甚至更长,适合不追求极实时性的背景研究。
- 新闻搜索(
- 脚本改造:在执行真正的API调用前,先检查缓存。如果命中且未过期,则直接返回缓存结果。这不仅能节省成本,还能将响应时间从秒级降低到毫秒级。
策略二:合并搜索请求。如果监测到多个Agent在短时间内提交了高度相似的查询(例如Gabriel和Michael都在查同一家公司),可以在调度层做一个简单的去重和合并,将一个聚合后的查询发送给Tavily,然后将结果分发给各个Agent。这需要更复杂的中间件支持。
策略三:设置速率限制和预算告警。在调用search.mjs的入口处(如AgentExec),实施速率限制(如每分钟N次)。同时,监控Tavily API的每日使用量,设置预算告警,避免意外超支。
5.2 常见问题与排查指南
在实际运行中,你可能会遇到以下典型问题:
问题1:脚本执行超时或无响应。
- 现象:Agent卡在
[EXEC:]指令处,长时间没有返回。 - 排查:
- 检查网络连通性:在服务器上手动运行
node search.mjs “test”,看是否能收到响应。可能是服务器无法访问api.tavily.com。 - 检查API密钥状态:登录Tavily控制台,确认密钥有效、未过期、且有足够余额。
- 检查
--deep搜索:深度搜索可能耗时30秒以上。确保AgentExec设置的命令执行超时时间足够长(例如60秒)。 - 查看脚本日志:在脚本中增加更详细的错误日志输出,记录到文件,以便诊断卡在哪个环节。
- 检查网络连通性:在服务器上手动运行
问题2:返回结果为空或相关性极差。
- 现象:搜索结果数组为空,或者返回的内容与查询意图完全不相关。
- 排查:
- 分析查询词:检查Agent生成的查询词是否过于复杂、包含歧义词或非主流语言表述。尝试用更简单、直接的关键词在Tavily测试平台手动搜索对比。
- 调整搜索参数:对于新闻搜索,确认
--topic news参数是否正确传递。对于需要广泛结果的查询,尝试增加-n的数量。 - 地域和语言设置:Tavily API可能支持
country或language参数。如果你的目标市场是印尼,确保查询词中包含印尼语关键词,并尝试在API调用中设置地域参数。 - 使用
includeAnswer:如果answer字段返回了有用的摘要,但results为空,可能是API找到了答案,但没有抓取到具体的源页面。这有时是可以接受的。
问题3:Agent无法解析脚本返回的JSON。
- 现象:Agent收到了
[EXEC:]的输出,但在尝试解析为JSON对象时出错。 - 排查:
- 验证JSON格式:手动运行脚本,确保其输出是唯一打印到标准输出的内容,且是合法的JSON。任何调试用的
console.error或意外的console.log都会污染输出。确保错误情况也通过JSON.stringify输出。 - 编码问题:确保脚本输出和Agent读取使用的是相同的字符编码(UTF-8)。
- 字符串转义:如果查询词或搜索结果中包含引号、换行符等特殊字符,JSON序列化时必须正确转义。
- 验证JSON格式:手动运行脚本,确保其输出是唯一打印到标准输出的内容,且是合法的JSON。任何调试用的
问题4:权限错误或“命令未找到”。
- 现象:AgentExec报告无法执行命令,提示权限不足或
node: command not found。 - 排查:
- 路径问题:
~/.openclaw这个路径在非登录Shell环境下可能无法正确展开。在脚本或AgentExec配置中,使用绝对路径更可靠,例如/home/user/.openclaw/workspace/skills/...。 - Node.js环境:确保运行AgentExec的服务进程中,
node命令在系统的PATH环境变量中可用。 - 文件权限:确保
search.mjs脚本文件具有可执行权限(chmod +x search.mjs),并且运行Agent进程的用户有读取该文件的权限。
- 路径问题:
为了便于快速诊断,我将常见问题、可能原因和解决方案整理成了下表:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 执行超时 | 1. 网络阻塞 2. 深度搜索耗时过长 3. API限流 | 1. 测试服务器到api.tavily.com的网络。2. 增加执行超时阈值。 3. 检查Tavily控制台用量和限流策略。 |
| 返回空结果 | 1. 查询词不佳 2. 地域/语言限制 3. API密钥权限问题 | 1. 简化查询词,使用更通用的关键词测试。 2. 在API调用中明确指定 country和language参数。3. 确认API密钥套餐是否支持所需功能。 |
| JSON解析错误 | 1. 脚本输出非纯JSON 2. 控制台有额外输出 3. 特殊字符未转义 | 1. 确保脚本仅通过console.log(JSON.stringify(...))输出结果。2. 将所有调试信息重定向到 console.error或日志文件。3. 使用 JSON.stringify自动处理转义。 |
| 权限错误 | 1. 路径使用~展开失败2. Node.js未安装 3. 文件无执行权限 | 1. 在配置中使用绝对路径。 2. 确认运行环境已安装兼容版本的Node.js。 3. 运行 chmod +x /path/to/search.mjs。 |
| 结果陈旧 | 缓存TTL设置过长 | 根据信息类型调整缓存TTL:新闻(短)、常规搜索(中)、深度研究(长)。 |
6. 扩展思路与高级玩法
一个基础的搜索技能上线后,还可以围绕它构建更强大的能力,让Agent真正变得“耳聪目明”。
扩展一:构建内部知识库联合搜索。Tavily搜索的是公开互联网。对于企业内部文档、会议纪要、产品手册等非公开信息,可以构建一个本地的向量数据库(如使用ChromaDB、Weaviate,结合OpenAI或本地嵌入模型)。然后,你可以改造search.mjs,使其成为一个“混合搜索网关”:先并行查询Tavily(公开网络)和本地向量库(内部知识),然后将两方面的结果去重、排序、融合,最后返回一个统一的答案。这能让Agent的回答既有时效性,又有公司内部背景。
扩展二:增加结果可信度评估与溯源。不是所有网络信息都可信。可以在技能内部增加一个轻量级评估环节:对返回的每个结果,根据其来源域名权威性(可维护一个白名单/黑名单)、内容新鲜度、与其他结果的一致性等维度,计算一个简单的可信度分数,并附加在结果中。这样,像Michael这样的风控Agent,就可以优先处理高可信度的风险信号。
扩展三:实现主动推送与监控流。让Gabriel从“被动查询”变为“主动监控”。可以创建一个后台服务,定期(如每半小时)用一组预设的关键词调用搜索技能,将新出现的结果与上一次的结果对比,通过Webhook或消息队列(如RabbitMQ、Redis Pub/Sub)将“新发现”推送给Gabriel Agent或其他消息终端(如Slack、钉钉)。这就实现了一个简单的网络舆情监控系统。
扩展四:技能组合与工作流编排。搜索技能很少单独使用。它可以成为更复杂Agent工作流的第一个环节。例如:
- 搜索 -> 分析 -> 报告:Metis搜索“竞争对手Q2财报”,将结果交给一个“分析Agent”提取关键财务数据,再交给“报告Agent”生成对比图表和洞察。
- 搜索 -> 总结 -> 翻译:Rafael搜索“全球FinTech趋势”,将英文结果交给“总结Agent”提炼要点,再交给“翻译Agent”转为中文,最终呈现给本地团队。
通过OpenClaw Council的编排能力,这些技能可以像流水线一样串联起来,实现高度自动化的信息处理管道。
最后,我想分享一点最深的体会:为AI Agent构建外部工具,可靠性远比功能强大更重要。一个偶尔超时或返回混乱格式的技能,会严重破坏整个Agent系统的稳定性。因此,在council-tavily技能的设计中,我们花了大量精力在错误处理、日志记录和结果标准化上。确保它在99%的情况下都能返回一个结构清晰、内容明确的JSON对象——哪怕是告诉Agent“搜索失败,原因是XXX”。这种确定性,是多Agent系统能够可靠协同工作的基石。当你开始为你的Agent设计技能时,不妨也从这个角度多思考一下。