🔍 给 OpenClaw 装上"眼睛"—— 集成 Tavily 实时搜索

让你的 AI Agent 不再"闭眼做题",实时联网搜索 so easy!

一、先搞清楚:Tavily 是个啥?

简单来说,Tavily 是一个专门为 AI Agent 设计的搜索 API。和传统搜索引擎不同,它返回的结果是"LLM 友好"的 —— 已经帮你做好了内容提取和清洗,Agent 拿到就能直接用,不用再费劲去解析 HTML 了。

🗣️ 用户提问 → 🤖 Agent 调用 Tavily → 🌐 Tavily 搜索并清洗 → ✨ 返回结构化结果

它有几个很香的特点:

  • 🆓 免费额度:个人用户每月 1,000 次调用,够用了
  • 速度快:basic 搜索通常 1-2 秒返回
  • 🎯 AI 优化:返回的内容已经做过提炼,不是原始 HTML
  • 📰 支持新闻模式:可以专门搜最近 N 天的新闻
  • 📄 网页提取:还能直接提取指定 URL 的正文内容

二、两种方案 PK:Skill vs Plugin

OpenClaw 提供了两种扩展方式,先看看哪种适合你:

🌿 Skill 方案 新手友好

  • 只需要写 Markdown 文件
  • 零 TypeScript 代码
  • 5 分钟搞定
  • 适合个人使用、快速验证
  • Agent 通过 curl 调用 API
  • 参数由 Agent 自行构造

🏗️ Plugin 方案 生产就绪

  • TypeScript 编写,类型安全
  • JSON Schema 严格约束参数
  • 完整的错误处理
  • 支持搜索 + 网页提取
  • 可配置化(configSchema)
  • 适合团队使用、正式部署

💡 选择建议:只想快速玩玩 → 选 Skill;要上生产或者你有"完美主义强迫症" → 选 Plugin。两种方案都亲测有效!


三、前置准备(3 分钟)

❶ 获取 Tavily API Key

tavily.com 注册一个账号(支持 GitHub / Google 登录),在 Dashboard 中拿到你的 API Key,格式长这样:tvly-xxxxxxxxxxxxxxxx

❷ 确认 OpenClaw 正常运行

openclaw status

❸ 设置环境变量

# 写入 shell 配置文件(~/.bashrc 或 ~/.zshrc)
export TAVILY_API_KEY="tvly-你的API密钥"

# 立即生效
source ~/.bashrc

四、方案一:Skill 方案(5 分钟极速版)

Skill 本质上不是代码,而是一份结构化的"使用说明书"。Agent 会阅读这份说明书,然后遵循里面的指令去执行 Shell 命令或 HTTP 调用。是不是很酷?😎

STEP 1: 创建 Skill 目录

mkdir -p ~/.openclaw/skills/tavily-search

STEP 2: 创建 SKILL.md 文件

这是整个方案的核心文件,复制粘贴即可:

---
name: tavily-search
description: Search the web in real-time using Tavily Search API, optimized for LLM consumption.
requires:
  env:
    - TAVILY_API_KEY
  bins:
    - curl
    - jq
---

# Tavily Web Search Skill

When the user asks to search the web, find current information, or look up recent events, use the Tavily Search API.

## Basic Search

Write the request JSON to a temp file, then execute with curl:

\`\`\`bash
cat > /tmp/tavily_request.json << 'REQEOF'
{
  "query": "$QUERY",
  "search_depth": "basic",
  "max_results": 5,
  "include_answer": true
}
REQEOF

bash -c 'curl -s -X POST "https://api.tavily.com/search" \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer ${TAVILY_API_KEY}" \
  -d @/tmp/tavily_request.json' | jq '.answer, .results[] | {title, url, content}'
\`\`\`

## Advanced Search (for deep research questions)

Set `"search_depth": "advanced"` for comprehensive results. Note: advanced search uses 2 API credits per request.

## Parameters Guide

- **search_depth**: "basic" (fast, 1 credit) or "advanced" (thorough, 2 credits)
- **max_results**: Number of results (default 5, max 20)
- **include_answer**: Get an AI-generated summary answer
- **include_domains**: Restrict to specific domains, e.g. ["arxiv.org"]
- **exclude_domains**: Exclude specific domains
- **topic**: "general" (default) or "news"
- **days**: For news topic, limit to recent N days

## Response Format

The API returns JSON with:
- `answer`: AI-generated summary answer to the query
- `results`: Array of search results with `title`, `url`, `content`, `score`

Always present the results clearly with source URLs for attribution.

STEP 3: 重启并验证

# 重启 Gateway
openclaw gateway restart

# 直接跟 Agent 对话试试!
# "帮我搜索一下最近的大模型新闻"

🎉 就这么简单! Skill 方案真的就只需要一个 Markdown 文件。放到 ~/.openclaw/skills/tavily-search/SKILL.md,Agent 就自动学会使用 Tavily 搜索了。是不是有种"传功"的感觉?😂 ps:记得修改key


五、方案二:Plugin 方案(进阶完整版)

如果你追求更强的类型安全、参数校验和错误处理,那 Plugin 方案就是你的菜了。我们要写一个正儿八经的 TypeScript 插件,注册两个工具:tavily_search(搜索)和 tavily_extract(网页提取)。

STEP 1: 创建插件目录结构

mkdir -p ~/.openclaw/extensions/tavily-search
cd ~/.openclaw/extensions/tavily-search

最终的文件结构长这样:

~/.openclaw/extensions/tavily-search/
├── openclaw.plugin.json  # 插件元信息 + 配置 Schema
└── index.ts              # 核心逻辑(注册工具)

🚨 划重点!文件名是 openclaw.plugin.json
OpenClaw 插件的配置文件叫 openclaw.plugin.json不是 package.json!这是很多人(包括我自己 😅)第一次写插件时踩的坑。搞错文件名的话,Gateway 根本不会识别你的插件。

STEP 2: 创建 openclaw.plugin.json

{
  "id": "tavily-search",
  "name": "Tavily Search",
  "version": "1.0.0",
  "description": "Real-time web search & page extraction powered by Tavily API",
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["apiKey"],
    "properties": {
      "apiKey": {
        "type": "string",
        "description": "Tavily API key (starts with tvly-)"
      },
      "defaultSearchDepth": {
        "type": "string",
        "enum": ["basic", "advanced"],
        "default": "basic",
        "description": "Default search depth: basic (1 credit) or advanced (2 credits)"
      },
      "maxResults": {
        "type": "number",
        "default": 5,
        "minimum": 1,
        "maximum": 20,
        "description": "Default max number of search results"
      },
      "timeoutSeconds": {
        "type": "number",
        "default": 30,
        "description": "Request timeout in seconds"
      }
    }
  }
}

STEP 3: 创建 index.ts(核心代码)

// index.ts — Tavily Search Plugin for OpenClaw

export default function register(api: any) {
  const config = api.config ?? {};
  const apiKey = config.apiKey;
  const timeoutMs = (config.timeoutSeconds ?? 30) * 1000;

  // 🚨 没有 API Key?直接告警退出
  if (!apiKey) {
    api.log?.warn?.(
      "tavily-search: apiKey is required! " +
        "Check your openclaw.plugin.json configSchema."
    );
    return;
  }

  // =============================================
  // 🔍 Tool 1: tavily_search —— 实时搜索
  // =============================================
  api.registerTool({
    name: "tavily_search",
    description:
      "Search the web in real-time using Tavily Search API. " +
      "Returns relevant, up-to-date results optimized for AI. " +
      "Use when you need current info or facts you're unsure about.",
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "The search query string",
        },
        search_depth: {
          type: "string",
          enum: ["basic", "advanced"],
          description: "'basic' (1 credit) or 'advanced' (2 credits)",
        },
        max_results: {
          type: "number",
          description: "Max results (1-20)",
        },
        topic: {
          type: "string",
          enum: ["general", "news"],
          description: "Use 'news' for recent news articles",
        },
        days: {
          type: "number",
          description: "For 'news' topic: limit to last N days",
        },
        include_domains: {
          type: "array",
          items: { type: "string" },
          description: "Domains to include, e.g. ['arxiv.org']",
        },
        exclude_domains: {
          type: "array",
          items: { type: "string" },
          description: "Domains to exclude",
        },
      },
      required: ["query"],
    },
    handler: async (params: any) => {
      const {
        query,
        search_depth = config.defaultSearchDepth || "basic",
        max_results = config.maxResults || 5,
        topic = "general",
        days,
        include_domains,
        exclude_domains,
      } = params;

      // 组装请求体
      const body: Record<string, any> = {
        query,
        search_depth,
        max_results,
        topic,
        include_answer: true,
      };
      if (days && topic === "news") body.days = days;
      if (include_domains?.length) body.include_domains = include_domains;
      if (exclude_domains?.length) body.exclude_domains = exclude_domains;

      try {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), timeoutMs);

        const res = await fetch("https://api.tavily.com/search", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${apiKey}`,
          },
          body: JSON.stringify(body),
          signal: controller.signal,
        });

        clearTimeout(timer);

        if (!res.ok) {
          const errText = await res.text();
          return { error: `Tavily API error (${res.status}): ${errText}` };
        }

        const data = await res.json();
        const results = (data.results || []).map((r: any) => ({
          title: r.title,
          url: r.url,
          content: r.content,
          score: r.score,
        }));

        return {
          answer: data.answer || null,
          results,
          result_count: results.length,
          response_time: data.response_time,
        };
      } catch (err: any) {
        if (err.name === "AbortError") {
          return { error: `Tavily search timed out after ${config.timeoutSeconds ?? 30}s` };
        }
        return { error: `Tavily search failed: ${err.message}` };
      }
    },
  });

  // =============================================
  // 📄 Tool 2: tavily_extract —— 网页内容提取
  // =============================================
  api.registerTool({
    name: "tavily_extract",
    description:
      "Extract main content from web page URLs using Tavily Extract API. " +
      "Use when you need to read the full content of a specific webpage.",
    inputSchema: {
      type: "object",
      properties: {
        urls: {
          type: "array",
          items: { type: "string" },
          description: "URLs to extract content from (max 5)",
        },
      },
      required: ["urls"],
    },
    handler: async (params: any) => {
      const { urls } = params;

      if (!urls?.length || urls.length > 5) {
        return { error: "Please provide between 1 and 5 URLs" };
      }

      try {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), timeoutMs);

        const res = await fetch("https://api.tavily.com/extract", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${apiKey}`,
          },
          body: JSON.stringify({ urls }),
          signal: controller.signal,
        });

        clearTimeout(timer);

        if (!res.ok) {
          const errText = await res.text();
          return { error: `Tavily Extract error (${res.status}): ${errText}` };
        }

        const data = await res.json();

        return {
          results: (data.results || []).map((r: any) => ({
            url: r.url,
            raw_content: r.raw_content?.slice(0, 8000),
          })),
          failed_urls: data.failed_results?.map((r: any) => r.url) || [],
        };
      } catch (err: any) {
        if (err.name === "AbortError") {
          return { error: `Tavily extract timed out after ${config.timeoutSeconds ?? 30}s` };
        }
        return { error: `Tavily extract failed: ${err.message}` };
      }
    },
  });

  // 🎉 大功告成!
  api.log?.info?.("✅ tavily-search: Plugin loaded (tavily_search + tavily_extract)");
}

🔮 代码小亮点:相比上一版,这次加入了 AbortController 实现超时控制,读取的正是 openclaw.plugin.json 中配置的 timeoutSeconds。字段命名也和 SearXNG 插件保持一致(maxResults / timeoutSeconds),强迫症狂喜 🎊

STEP 4: 在 OpenClaw 主配置中启用插件

编辑 ~/.openclaw/openclaw.json(或对应的全局配置文件),添加插件配置:

{
  "plugins": {
    "entries": {
      "tavily-search": {
        "enabled": true,
        "config": {
          "apiKey": "tvly-你的API密钥",
          "defaultSearchDepth": "basic",
          "maxResults": 5,
          "timeoutSeconds": 30
        }
      }
    }
  }
}

💡 注意:这里 entries 下的 key("tavily-search")必须和 openclaw.plugin.json 中的 "id" 保持一致!config 对象中的字段必须符合你在 configSchema.properties 中定义的 Schema。

STEP 5: 重启并验证

# 重启 Gateway
openclaw gateway restart

# 确认插件已加载
openclaw plugins list

# 查看日志
tail -n 50 /tmp/openclaw-gateway.log | grep tavily
# 期望输出: ✅ tavily-search: Plugin loaded (tavily_search + tavily_extract)

✅ Plugin 方案部署完成! 现在你的 Agent 拥有了两个强力工具:tavily_search 搜索全网,tavily_extract 提取网页正文。双剑合璧,无敌!


六、实测效果展示

部署完成后,我们来实际跑几个场景看看效果:

你说的话 🗣️ Agent 背后做的事 🤖 效果
“搜一下今天的科技新闻” 调用 tavily_search
topic=news, days=1
✅ 返回当日新闻摘要 + 链接
“帮我查 GPT-5 最新进展” 调用 tavily_search
search_depth=advanced
✅ 多篇深度报道 + AI 总结
“读一下这个链接的内容:
https://example.com/article”
调用 tavily_extract ✅ 提取正文并分析总结

七、踩坑记录 & 小贴士

💥 坑 1:配置文件名写错了

插件配置文件叫 openclaw.plugin.json不是 package.json

⚠️ 坑 2:id 和 entries 的 key 对不上

openclaw.plugin.json 中的 "id": "tavily-search" 必须和 openclaw.jsonplugins.entries 下的 key 完全一致。大小写、连字符都要对上,否则插件不会被加载。

⚠️ 坑 3:configSchema 里设了 required,但 config 里没给

我们在 Schema 里写了 "required": ["apiKey"],这意味着 openclaw.json 中的 config 必须包含 apiKey 字段,否则插件启动会报验证错误。

⚠️ 坑 4:advanced 搜索把免费额度用完了

advanced 模式每次消耗 2 个 credits,免费额度一共才 1000 个。
👉 默认用 basic,只有明确需要时才 advanced。defaultSearchDepth: "basic" 已经帮你兜底了。

⚠️ 坑 5:extract 返回内容太长炸了 context window

有些网页正文超级长,直接返回可能撑爆 LLM 的上下文窗口。
👉 代码里已经加了 .slice(0, 8000) 做截断保护。可以根据你使用的模型适当调整。

💡 小贴士汇总

  • 工具命名用 snake_case(如 tavily_search),插件 id 用 kebab-case(如 tavily-search
  • 配置变更后一定要 openclaw gateway restart,不会热加载
  • 搜索结果让 Agent 带上来源 URL,方便用户溯源验证
  • include_domains / exclude_domains 是精准搜索的利器
  • tavily_search 拿 URL,再 tavily_extract 读全文 —— 组合拳 🥊
  • additionalProperties: false 能防止传入未定义的配置项,更安全

八、总结

核心流程

📦 获取 API Key → 📝 创建 Skill / Plugin → ⚙️ 配置 openclaw.json → 🚀 重启 & 验证

Plugin 方案的关键文件清单

文件 作用 注意事项
openclaw.plugin.json 插件元信息 + configSchema ⚠️ 文件名不能错!不是 package.json
index.ts 核心逻辑,注册工具 导出 register(api) 函数
openclaw.json(主配置) 启用插件 + 传入 config entries key 必须 = plugin id

方案对比

对比项 🌿 Skill 方案 🏗️ Plugin 方案
难度 ⭐ 极低 ⭐⭐⭐ 中等
耗时 5 分钟 20-30 分钟
核心文件 1 个 SKILL.md openclaw.plugin.json + index.ts
功能 基础搜索 搜索 + 提取 + 超时控制 + 可扩展
错误处理 依赖 curl 返回 完整 try/catch + AbortController
适用场景 个人玩耍、快速验证 团队协作、生产环境

本文为原创内容,转载请注明出处。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐