
Ultimate Search
- 3 installs
- Updated March 4, 2026
- hamsterider-m/ultimatesearchskill
Runs dual-engine web search with Grok and Tavily, then hands off to a browser automation tool for dynamic or login-protected pages, cross-verifying sources.
About
A dual-engine search skill that combines Grok AI search, Tavily structured results, page fetching, and agent-browser interaction. A developer uses it to gather and cross-verify current web evidence, routing queries by intent (X discussions, news, deep analysis).
- Query routing table picks Grok, Tavily, fetch, or browser by intent
- Requires two independent sources for any factual conclusion
Ultimate Search by the numbers
- 3 all-time installs (skills.sh)
- Ranked #1,816 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hamsterider-m/ultimatesearchskill --skill ultimate-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| Last updated | March 4, 2026 |
| Repository | hamsterider-m/ultimatesearchskill ↗ |
What it does
Runs dual-engine web search with Grok and Tavily, then hands off to a browser automation tool for dynamic or login-protected pages, cross-verifying sources.
Files
UltimateSearch
为 Pi/OpenClaw agent 提供双引擎网络搜索能力:Grok AI 搜索(实时联网 + AI 分析)+ Tavily 搜索(结构化结果 + 网页抓取)。
---
可用工具
在 Bash 中调用以下脚本(确保已加入 PATH 且已 source .env):
| 工具 | 命令 | 用途 |
|---|---|---|
| Grok 搜索 | grok-search.sh --query "..." | AI 驱动的深度搜索,Grok 自带联网,返回综合分析 |
| Tavily 搜索 | tavily-search.sh --query "..." | 结构化搜索结果,带评分和排序 |
| 网页抓取 | web-fetch.sh --url "..." | 提取指定 URL 的完整内容,返回 Markdown |
| 站点映射 | web-map.sh --url "..." | 发现网站结构,获取所有 URL |
| 双引擎搜索 | dual-search.sh --query "..." | 并行执行 Grok + Tavily,交叉验证 |
| 浏览器自动化(联动) | agent-browser open/snapshot/click/fill/... | 处理登录、动态渲染、按钮触发、反爬挑战等交互页面 |
各工具参数详见 --help。
---
与 agent-browser 联动
当页面不是“直接 URL 抓取”场景时,按以下规则联动 agent-browser:
触发条件
- 页面依赖 JS 动态渲染,
web-fetch.sh返回内容不完整 - 需要登录、点击按钮、分页、展开折叠后才能看到目标内容
- 存在 Cloudflare/人机验证或强交互式页面流程
- 需要截图留证(页面状态、关键字段、提交结果)
协同闭环(必须执行)
1. 搜索定位:先用 dual-search.sh / tavily-search.sh 找候选 URL 2. 浏览器交互:用 agent-browser 完成打开、快照、点击、填表、等待加载 3. 结果回流:把最终落地 URL 交回 web-fetch.sh / web-map.sh 做结构化抓取 4. 证据输出:提供最终 URL、关键截图路径、核心结论来源链接
最小命令模板
# 1) 先搜索定位目标页面
dual-search.sh --query "官网 pricing enterprise plan"
# 2) 动态页面交互
agent-browser open "https://example.com/pricing"
agent-browser snapshot -i
agent-browser click @e12
agent-browser wait --load networkidle
agent-browser get url
agent-browser screenshot --full
# 3) 回流到结构化抓取(把 get url 的结果填回)
web-fetch.sh --url "https://example.com/pricing?tab=enterprise"联动约束
agent-browser负责“到达信息”,web-fetch.sh负责“提取信息”- 仅当静态抓取不足时才进入浏览器流程,避免过度自动化
- 登录态/验证码属于高风险流程时,明确标注“基于当前会话状态”
---
平台路由优先级(含 X/Twitter)
为避免“会搜,但搜错引擎”,先按查询意图做路由:
| 查询意图 | 首选 | 备选 | 原因 |
|---|---|---|---|
| X/Twitter 讨论、实时舆情、热点争议 | grok-search.sh --platform "X" | dual-search.sh | Grok 对 X 平台语境和实时讨论更强 |
| 通用网页事实、新闻、可结构化结果 | tavily-search.sh | dual-search.sh | Tavily 结构化结果稳定、便于引用 |
| 结论风险高、容易冲突的问题 | dual-search.sh | 无 | 默认双源交叉验证 |
| 需要最终页面全文 | web-fetch.sh | agent-browser 后回流 web-fetch.sh | 先定位来源,再抓取正文 |
X 场景强制规则
- 用户提到
X、Twitter、推特、帖子讨论、时间线观点时,先执行:
grok-search.sh --query "..." --platform "X"- 若需“可引用链接 + 交叉证据”,第二步再跑:
tavily-search.sh --query "..." --topic news --time-range week---
凭据获取联动(agent-browser headed)
当用户要求“在浏览器里获取 Grok SSO Token / cf_clearance”时:
1. 使用 agent-browser --headed 打开目标站点并人工完成登录/验证 2. 通过浏览器开发者工具读取 cookie(sso、cf_clearance) 3. 立即写入本地 .env 或导入脚本,不在对话中回显完整敏感值 4. 用最小健康检查验证是否可用(grok-search.sh --query "test")
最小流程示例:
agent-browser --headed open "https://grok.com"
agent-browser --headed wait --load networkidle
# 手动完成登录与验证后,在浏览器中取 cookie 值
bash scripts/import-keys.sh
bash scripts/grok-search.sh --query "test" --model "grok-4.1-mini"---
搜索决策流程
收到需要搜索的请求时,按以下流程决策:
第一步:判断是否需要搜索
需要搜索的情况:
- 用户明确要求搜索/查询外部信息
- 涉及实时性数据(最新版本、近期事件、当前价格等)
- 需要验证内部知识的准确性
- 涉及具体的 URL、项目、产品的最新状态
- 技术问题需要查阅官方文档最新版
不需要搜索的情况:
- 纯粹的代码编写/调试任务(已有足够上下文且不涉及外部 API/库版本)
- 用户明确表示不需要搜索
- ⚠️ 通用编程概念也可能过时——当涉及具体版本、最佳实践或 API 用法时,仍应搜索验证
第二步:选择工具
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 简单事实查询 | dual-search.sh | 双源交叉验证,确保准确性 |
| 复杂/争议性问题 | dual-search.sh | 双引擎交叉验证,减少幻觉 |
| 需要 AI 深度分析 | grok-search.sh | Grok 自带联网搜索,返回综合分析报告 |
| 需要抓取特定页面 | web-fetch.sh --url "..." | 提取完整页面内容 |
| 探索网站结构 | web-map.sh --url "..." | 发现文档/API 目录结构 |
| 需要最新新闻 | tavily-search.sh --topic news | Tavily 新闻模式专门优化 |
| 需要高质量深度结果 | tavily-search.sh --depth advanced | 高级搜索,多维度匹配 |
| 搜索结果中有关键链接 | 先搜索,再 web-fetch.sh | 搜索定位 → 抓取详情 |
| 动态页面/需登录后可见 | agent-browser + web-fetch.sh | 先交互到目标页,再回流结构化抓取 |
| X/Twitter 讨论类查询 | grok-search.sh --platform "X" | 优先匹配 X 平台语境与实时讨论 |
第三步:评估搜索复杂度
- Level 1(2-3 次搜索):单个明确问题
- 示例:「FastAPI 最新版本是什么」
- 操作:
dual-search.sh获取双源结果;或先tavily-search.sh再用grok-search.sh交叉确认 - ⚠️ 即使是简单事实,也不可仅依赖单一来源直接下结论
- Level 2(3-5 次搜索):多角度比较、需要多个来源验证
- 示例:「Flask vs FastAPI vs Django 2026 年哪个更适合微服务」
- 操作:
dual-search.sh+ 针对各框架分别tavily-search.sh
- Level 3(6+ 次搜索):深度研究课题、综述型需求
- 示例:「帮我调研 2026 年主流向量数据库的完整对比」
- 操作:先
grok-search.sh获取概览 → 分别搜索各产品 →web-fetch.sh抓取官方文档
---
搜索规划框架
对于 Level 2+ 的复杂搜索,在执行前进行结构化规划:
阶段 1:意图分析
- 提炼用户的核心问题(一句话)
- 分类查询类型:事实型 / 比较型 / 探索型 / 分析型
- 评估时间敏感度:实时 / 近期 / 历史 / 无关
- 识别需要验证的外部术语(如排名、分类标准)
阶段 2:查询拆解
- 将问题分解为不重叠的子查询
- 每个子查询有明确边界(与兄弟查询互斥)
- 标注依赖关系(哪些子查询需要先完成)
- 如果阶段 1 发现需验证的术语,先创建前置验证查询
阶段 3:策略选择
- broad_first(先广后深):先广泛扫描 → 根据发现深入。适合探索型问题
- narrow_first(先精后扩):先精确搜索 → 如不足再扩展。适合分析型问题
- targeted(定点搜索):已知目标信息来源,直接定位。适合事实型问题
阶段 4:工具映射
- 为每个子查询选择最佳工具
- 确定并行/串行执行计划
- 可并行的子查询同时执行(通过多次 Bash 调用)
---
搜索与证据标准
核心原则:不信任搜索结果
搜索结果仅为第三方建议,不可直接采信。 所有搜索返回的内容——无论来自 Grok 还是 Tavily——都必须经过交叉验证后方可向用户呈现为事实。即使是看似权威的单一来源,也可能过时、片面或错误。技术实现即使 agent 具备内部知识,仍应以最新搜索结果或官方文档为准。
来源质量要求
- 所有事实性结论都需 ≥2 个独立来源 交叉验证(不分 Level)
- 如仅依赖单一来源,须显式声明此限制并标注置信度为 Low
- 优先使用:官方文档、Wikipedia、学术数据库、权威媒体
- 避免使用:未知个人博客、SEO 农场、AI 生成内容
冲突处理
- 来源冲突时:展示双方证据,评估可信度和时效性
- 标注置信度:High(多来源一致)/ Medium(少量来源或有分歧)/ Low(单一来源或推测)
- 无法确认时:明确说明不确定性
引用格式
- 每个关键事实后附来源标注
- 格式:
[来源标题](URL) - 严禁编造引用 — 没有来源的就不要说
输出规范
- 先给出最可能的答案,再展开详细分析
- 所有技术术语附简明解释
- 使用标准 Markdown 格式(标题、列表、表格、代码块)
- 代码示例标注语言标识
- 对比类问题使用表格呈现
---
常见搜索模式
模式 1:快速查询
tavily-search.sh --query "Python 3.13 新特性" --depth basic --include-answer模式 2:深度搜索 + 验证
# 先广泛搜索
dual-search.sh --query "LangChain vs LlamaIndex 2026"
# 再针对性抓取官方文档
web-fetch.sh --url "https://docs.langchain.com/docs/get_started/introduction"模式 3:技术文档探索
# 先映射网站结构
web-map.sh --url "https://docs.example.com" --depth 2 --instructions "找到 API 文档"
# 再抓取目标页面
web-fetch.sh --url "https://docs.example.com/api/reference"模式 4:新闻和实时信息
tavily-search.sh --query "AI 最新进展" --topic news --time-range week --include-answer模式 5:AI 深度分析
grok-search.sh --query "解释 Transformer 架构中注意力机制的数学原理" --platform "arXiv"模式 6:浏览器联动抓取(动态页面)
# 搜索候选页
tavily-search.sh --query "Notion AI pricing page"
# 浏览器交互到最终页面
agent-browser open "https://www.notion.so/product/ai"
agent-browser wait --load networkidle
agent-browser snapshot -i
agent-browser click @e3
agent-browser get url
# 回流抓取可复用内容
web-fetch.sh --url "https://www.notion.so/product/ai/pricing"模式 7:X/Twitter 讨论检索(会话来源对齐)
# 先用 Grok 聚焦 X 平台语境
grok-search.sh --query "检索 X 上有关伊朗战争讨论" --platform "X"
# 再用 Tavily 补充结构化链接,做交叉验证
tavily-search.sh --query "Iran war discussion on X" --topic news --time-range week---
BDD 可靠性验证
对“什么时候用 Grok / Tavily / agent-browser”做场景化回归,避免路由漂移。
- 场景文件:
docs/bdd/ultimate-search-routing.feature - 最低验收标准:
- X/Twitter 讨论类查询必须先触发
grok-search.sh --platform "X" - 动态页面类查询必须进入
agent-browser并回流web-fetch.sh - 事实型结论必须有双源交叉验证证据
# ===========================================
# UltimateSearchSkill 环境变量配置
# ===========================================
# 使用方法:复制此文件为 .env 并填入实际值
# cp .env.example .env
# === grok2api 配置 ===
GROK2API_PORT=8100
# grok2api 管理面板密码(必须修改!)
GROK2API_APP_KEY=changeme-to-strong-password
# 调用 grok2api 的 API Key(必须设置,否则 API 无认证!)
GROK2API_API_KEY=
# === TavilyProxyManager 配置 ===
TAVILY_PROXY_PORT=8200
# TavilyProxyManager 的 Master Key(首次启动后从日志获取,然后填入此处)
TAVILY_MASTER_KEY=
# === 搜索脚本配置 ===
GROK_API_URL=http://127.0.0.1:8100
GROK_API_KEY=
GROK_MODEL=grok-4.1-fast
TAVILY_API_URL=http://127.0.0.1:8200
TAVILY_API_KEY=
# === FireCrawl 配置(web-fetch 的降级方案)===
# FireCrawl API 地址(默认官方 API,也支持自托管)
FIRECRAWL_API_URL=https://api.firecrawl.dev/v2
# FireCrawl API Key(可选,未配置则跳过 FireCrawl 降级)
# 获取: https://www.firecrawl.dev/
FIRECRAWL_API_KEY=
# ===========================================
# 批量 Key 导入(逗号分隔,用于 import-keys.sh 自动注册到对应服务)
# ===========================================
# Tavily API Keys(逗号分隔)
# 获取: https://www.tavily.com/ 免费 1000次/月
TAVILY_API_KEYS=
# FireCrawl API Keys(逗号分隔)
# 获取: https://www.firecrawl.dev/
FIRECRAWL_API_KEYS=
.env
data/
logs/
*.log
.DS_Store
export_sso.txt
*.txt.bak
services:
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: ultimate-search-flaresolverr
environment:
- LOG_LEVEL=info
- TZ=Asia/Shanghai
restart: unless-stopped
grok2api:
image: ghcr.io/chenyme/grok2api:latest
container_name: ultimate-search-grok2api
ports:
- "127.0.0.1:${GROK2API_PORT:-8100}:8000"
environment:
- DATA_DIR=/data
- LOG_FILE_ENABLED=true
- LOG_LEVEL=INFO
- SERVER_STORAGE_TYPE=local
volumes:
- ./data/grok2api:/data
- ./data/grok2api/logs:/app/logs
depends_on:
- flaresolverr
restart: unless-stopped
tavily-proxy:
image: ghcr.io/xuncv/tavilyproxymanager:latest
container_name: ultimate-search-tavily-proxy
ports:
- "127.0.0.1:${TAVILY_PROXY_PORT:-8200}:8080"
environment:
- LISTEN_ADDR=:8080
- DATABASE_PATH=/app/data/proxy.db
- TAVILY_BASE_URL=https://api.tavily.com
- UPSTREAM_TIMEOUT=150s
volumes:
- ./data/tavily-proxy:/app/data
# - /etc/localtime:/etc/localtime:ro # Linux only
restart: unless-stopped
UltimateSearchSkill 架构说明
设计理念
为什么不用 MCP?
OpenClaw 的底层 agent Pi 的设计哲学是:agent 通过 Bash 执行代码来扩展自己,而非通过 MCP 加载外部工具。MCP 工具需要注入到模型的系统上下文中,增加 token 消耗,且不支持热重载。
因此我们选择了 Skill + Shell 脚本 的方案:
- SKILL.md 引导 agent 的搜索决策和方法论
- Shell 脚本通过 Bash 工具直接调用
- 无额外进程、无上下文开销
三层架构
┌─────────────────────────────────────────────────┐
│ Layer 1: Skill 层 (SKILL.md) │
│ 搜索方法论 | 决策流程 | 证据标准 | 输出规范 │
├─────────────────────────────────────────────────┤
│ Layer 2: 脚本层 (scripts/) │
│ grok-search | tavily-search | web-fetch │
│ web-map | dual-search | (联动) agent-browser │
├─────────────────────────────────────────────────┤
│ Layer 3: 基础设施层 (Docker) │
│ grok2api (多Token聚合) | TavilyProxy (多Key聚合)│
└─────────────────────────────────────────────────┘数据流
Grok 搜索流
Agent → Bash → grok-search.sh
→ curl POST grok2api:8100/v1/chat/completions
→ grok2api 选择可用 Token
→ Grok Web (x.ai) 执行联网搜索
→ 返回 AI 综合分析结果关键特点:Grok 模型自带实时联网搜索能力,通过精心设计的 system prompt 触发搜索行为。grok2api 负责多 Token 的负载均衡、自动刷新和故障切换。
Tavily 搜索流
Agent → Bash → tavily-search.sh / web-fetch.sh / web-map.sh
→ curl POST TavilyProxyManager:8200/search|extract|map
→ TavilyProxyManager 选择最优 Key
→ Tavily API (tavily.com)
→ 返回结构化搜索/抓取结果关键特点:Tavily 提供专业的搜索 API,支持结构化结果、评分排序、时间过滤。TavilyProxyManager 负责多 Key 聚合、余额优先调度、自动故障切换。
双引擎搜索流
Agent → Bash → dual-search.sh
├─ (后台) grok-search.sh → grok2api → Grok
└─ (后台) tavily-search.sh → TavilyProxy → Tavily
→ wait (等待两者完成)
→ jq 合并结果
→ 输出 {"grok": {...}, "tavily": {...}}动态页面联动流(agent-browser)
Agent → Bash → dual-search.sh / tavily-search.sh
→ 发现目标页面需要交互
→ agent-browser open/snapshot/click/fill/wait
→ 获取最终可访问 URL
→ web-fetch.sh / web-map.sh
→ 返回结构化内容 + 浏览器证据(URL/截图)关键特点:agent-browser 负责“页面状态推进”(登录、点击、展开、翻页),UltimateSearch 脚本负责“结构化提取与可引用证据”。两者结合可以覆盖静态抓取失败的动态站点。
与 GrokSearch MCP 的对比
| 维度 | GrokSearch MCP | UltimateSearchSkill |
|---|---|---|
| 运行时 | 独立 Python MCP 进程 | 无额外进程,Shell 脚本 |
| 安装 | Python + uvx + FastMCP | Docker + bash |
| Agent 集成 | 需要 MCP 客户端支持 | Bash 原生调用 |
| Token 管理 | 单个 Key | 多 Key 聚合(grok2api) |
| 搜索规划 | MCP tool(search_planning) | Skill 指令引导 agent 自行规划 |
| 上下文开销 | 工具定义占用 context | 仅 Skill 指令,按需加载 |
| 维护 | 依赖上游更新 | 自己掌控 |
安全加固
网络隔离
所有 Docker 端口绑定到 127.0.0.1,不暴露到外部网络:
ports:
- "127.0.0.1:8100:8000" # grok2api
- "127.0.0.1:8200:8080" # TavilyProxyManager认证
- grok2api:必须配置
api_key(默认为空=无认证,极度危险) - TavilyProxyManager:自动生成随机 Master Key
远程管理
通过 SSH 端口转发安全访问管理面板:
# 转发管理面板端口
ssh -L 8100:127.0.0.1:8100 -L 8200:127.0.0.1:8200 用户@服务器
# 浏览器访问
# grok2api: http://localhost:8100/admin
# TavilyProxy: http://localhost:8200密钥管理
- 所有密钥存储在
.env文件中(已加入 .gitignore) - grok2api Token 存储在
data/grok2api/目录(已加入 .gitignore) - Tavily Key 存储在
data/tavily-proxy/proxy.db(已加入 .gitignore)
Feature: UltimateSearch routing reliability
Ensure the skill chooses Grok/Tavily/agent-browser consistently for different query intents.
Scenario: X/Twitter discussion should route to Grok first
Given a query "检索一下X上有关伊朗战争讨论"
When the agent selects search tools
Then the first command must be "grok-search.sh --query \"...\" --platform \"X\""
And the follow-up verification should include Tavily or another independent source
Scenario: Full-text extraction after search
Given a query requiring complete page content
When the agent has identified target URLs
Then it should run "web-fetch.sh --url \"...\""
And output source links for key claims
Scenario: Dynamic page requires browser collaboration
Given target content hidden behind JS rendering, login, or button interactions
When direct extraction is incomplete
Then it must use "agent-browser open/snapshot/click/wait"
And feed the final resolved URL back into "web-fetch.sh"
Scenario: Fact answer must be cross-verified
Given a factual query with potential staleness
When the answer is prepared
Then evidence must include at least 2 independent sources
And confidence must be labeled High/Medium/Low
UltimateSearchSkill 实施计划
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 构建一个完整的 Pi/OpenClaw 搜索 Skill,整合 grok2api(多 Grok Token 聚合)+ TavilyProxyManager(多 Tavily Key 聚合),提供双引擎搜索能力。
Architecture: 三层架构 —— SKILL.md 提供搜索方法论和规划框架;Shell 脚本封装 API 调用供 agent 通过 Bash 使用;Docker Compose 部署 grok2api + TavilyProxyManager 作为基础设施。搜索策略和提示词来自 GrokSearch MCP 项目的精华提炼。
Tech Stack: Shell (bash/curl/jq)、Docker Compose、grok2api (Python/FastAPI)、TavilyProxyManager (Go)
---
项目结构
UltimateSearchSkill/
├── README.md # 项目说明 + 快速上手指南
├── SKILL.md # Pi/OpenClaw Skill 核心定义
├── docker-compose.yml # 一键部署 grok2api + TavilyProxyManager
├── .env.example # 环境变量模板
├── scripts/
│ ├── setup.sh # 安装部署脚本(检测环境、拉取镜像、启动服务)
│ ├── grok-search.sh # Grok AI 智能搜索(调用 grok2api)
│ ├── tavily-search.sh # Tavily 搜索(调用 TavilyProxyManager)
│ ├── web-fetch.sh # 网页内容抓取(Tavily Extract)
│ ├── web-map.sh # 站点映射(Tavily Map)
│ └── dual-search.sh # 双引擎聚合搜索(并行 Grok + Tavily)
└── docs/
├── plans/ # 实施计划
└── architecture.md # 架构说明文档组件交互关系
Agent (Pi/OpenClaw)
│
├─ SKILL.md 指导搜索策略
│ ├─ 判断搜索复杂度(简单/中等/复杂)
│ ├─ 选择合适工具(grok-search / tavily-search / dual-search / web-fetch / web-map)
│ └─ 交叉验证规则
│
└─ Bash 调用脚本
├─ grok-search.sh → grok2api (:8100) → Grok Web (x.ai)
│ 特点:AI 驱动搜索,Grok 自带联网,返回综合分析
│
├─ tavily-search.sh → TavilyProxyManager (:8200) → Tavily API
│ 特点:结构化搜索结果,评分排序,支持时间过滤
│
├─ web-fetch.sh → TavilyProxyManager (:8200) → Tavily Extract
│ 特点:提取 URL 内容,返回 Markdown,突破反爬
│
├─ web-map.sh → TavilyProxyManager (:8200) → Tavily Map
│ 特点:发现网站结构,获取 URL 列表
│
└─ dual-search.sh → 并行调用 grok-search + tavily-search
特点:双引擎结果合并,交叉验证与 grok2api 的集成方式
项目地址: https://github.com/chenyme/grok2api 部署方式: Docker Compose 核心能力:
- 多 Grok Token 聚合(号池),自动负载均衡
- OpenAI 兼容 API (
/v1/chat/completions) - 流/非流式对话
- Token 自动刷新,失败自动切换
- 管理面板 (
/admin)
与 Skill 的对接:
grok-search.sh调用http://localhost:8100/v1/chat/completions- 使用 GrokSearch MCP 中的
search_prompt作为 system prompt - Grok 模型自带实时联网搜索能力,通过 chat 接口触发
- 返回 AI 综合分析结果,由脚本解析提取
与 TavilyProxyManager 的集成方式
项目地址: https://github.com/xuncv/TavilyProxyManager 部署方式: Docker Compose 核心能力:
- 多 Tavily Key 聚合,优先使用余额最高的 Key
- 透明代理,API 完全兼容 Tavily 官方格式
- 自动故障切换(401/429/432/433 自动换 Key)
- Master Key 鉴权
- 管理面板 (Web UI)
与 Skill 的对接:
tavily-search.sh调用http://localhost:8200/searchweb-fetch.sh调用http://localhost:8200/extractweb-map.sh调用http://localhost:8200/map- 统一使用 Master Key 鉴权
---
Task 1: 创建项目基础文件
Files:
- Create:
UltimateSearchSkill/.env.example - Create:
UltimateSearchSkill/.gitignore
Step 1: 创建 .env.example
# === grok2api 配置 ===
GROK2API_PORT=8100
GROK2API_APP_KEY=changeme # grok2api 管理面板密码
GROK2API_API_KEY=sk-ultimate-search # 调用 grok2api 的 API Key
# === TavilyProxyManager 配置 ===
TAVILY_PROXY_PORT=8200
# === 搜索脚本配置 ===
GROK_API_URL=http://localhost:8100
GROK_API_KEY=sk-ultimate-search
GROK_MODEL=grok-4.1-fast
TAVILY_API_URL=http://localhost:8200
TAVILY_API_KEY= # TavilyProxyManager 的 Master Key(首次启动后获取)Step 2: 创建 .gitignore
.env
data/
logs/
*.log
.DS_StoreStep 3: Commit
git add .env.example .gitignore
git commit -m "chore: init project with env template and gitignore"---
Task 2: 创建 Docker Compose 部署文件
Files:
- Create:
UltimateSearchSkill/docker-compose.yml
Step 1: 编写 docker-compose.yml
services:
grok2api:
image: ghcr.io/chenyme/grok2api:latest
container_name: ultimate-search-grok2api
ports:
- "${GROK2API_PORT:-8100}:8000"
environment:
- DATA_DIR=/data
- LOG_FILE_ENABLED=true
- LOG_LEVEL=INFO
- SERVER_STORAGE_TYPE=local
volumes:
- ./data/grok2api:/data
restart: unless-stopped
tavily-proxy:
image: ghcr.io/xuncv/tavilyproxymanager:latest
container_name: ultimate-search-tavily-proxy
ports:
- "${TAVILY_PROXY_PORT:-8200}:8080"
environment:
- LISTEN_ADDR=:8080
- DATABASE_PATH=/app/data/proxy.db
- TAVILY_BASE_URL=https://api.tavily.com
- UPSTREAM_TIMEOUT=150s
volumes:
- ./data/tavily-proxy:/app/data
- /etc/localtime:/etc/localtime:ro
restart: unless-stoppedStep 2: 验证 compose 文件语法
docker compose configExpected: 输出规范化的 YAML,无报错
Step 3: Commit
git add docker-compose.yml
git commit -m "feat: add docker-compose for grok2api + TavilyProxyManager"---
Task 3: 创建安装部署脚本
Files:
- Create:
UltimateSearchSkill/scripts/setup.sh
Step 1: 编写 setup.sh
功能: 1. 检测 Docker 是否安装 2. 检测 jq 是否安装 3. 复制 .env.example → .env(如不存在) 4. 启动 Docker Compose 5. 等待服务就绪 6. 输出 TavilyProxyManager 的 Master Key 7. 将脚本目录加入 PATH 提示
Step 2: 赋予执行权限
chmod +x scripts/setup.shStep 3: Commit
git add scripts/setup.sh
git commit -m "feat: add setup script for one-click deployment"---
Task 4: 创建 grok-search.sh
Files:
- Create:
UltimateSearchSkill/scripts/grok-search.sh
Step 1: 编写脚本
核心逻辑: 1. 读取环境变量 GROK_API_URL、GROK_API_KEY、GROK_MODEL 2. 接收参数:--query "查询内容" --platform "可选平台" --stream 3. 使用 GrokSearch MCP 的 search_prompt 作为 system prompt 4. 自动注入当前时间上下文(检测时间相关查询) 5. 调用 grok2api 的 /v1/chat/completions 6. 解析响应,提取内容和信源 7. 输出结构化 JSON 结果
关键参考: GrokSearch MCP 的 search_prompt(来自 utils.py)和时间注入逻辑(来自 providers/grok.py)
Step 2: 赋予执行权限并测试
chmod +x scripts/grok-search.sh
# 测试(需先启动服务)
./scripts/grok-search.sh --query "test"Step 3: Commit
git add scripts/grok-search.sh
git commit -m "feat: add grok-search script with AI-powered web search"---
Task 5: 创建 tavily-search.sh
Files:
- Create:
UltimateSearchSkill/scripts/tavily-search.sh
Step 1: 编写脚本
核心逻辑: 1. 读取环境变量 TAVILY_API_URL、TAVILY_API_KEY 2. 接收参数:--query "查询内容" --depth basic|advanced --max-results N --topic general|news|finance --time-range day|week|month|year --include-answer 3. 调用 TavilyProxyManager 的 POST /search 4. 使用 Bearer Token 鉴权 5. 输出结构化 JSON 结果
API 格式参考:
curl -X POST "$TAVILY_API_URL/search" \
-H "Authorization: Bearer $TAVILY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "...",
"search_depth": "advanced",
"max_results": 10,
"include_answer": true,
"include_raw_content": "markdown"
}'Step 2: 赋予执行权限
Step 3: Commit
git add scripts/tavily-search.sh
git commit -m "feat: add tavily-search script"---
Task 6: 创建 web-fetch.sh
Files:
- Create:
UltimateSearchSkill/scripts/web-fetch.sh
Step 1: 编写脚本
核心逻辑: 1. 接收参数:--url "URL" --depth basic|advanced --format markdown|text 2. 调用 TavilyProxyManager 的 POST /extract 3. 提取 results[0].raw_content 4. 输出 Markdown 内容
API 格式参考:
curl -X POST "$TAVILY_API_URL/extract" \
-H "Authorization: Bearer $TAVILY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": ["https://example.com"],
"extract_depth": "advanced",
"format": "markdown"
}'Step 2: Commit
git add scripts/web-fetch.sh
git commit -m "feat: add web-fetch script for URL content extraction"---
Task 7: 创建 web-map.sh
Files:
- Create:
UltimateSearchSkill/scripts/web-map.sh
Step 1: 编写脚本
核心逻辑: 1. 接收参数:--url "URL" --depth N --breadth N --limit N --instructions "说明" 2. 调用 TavilyProxyManager 的 POST /map 3. 输出站点 URL 列表
Step 2: Commit
git add scripts/web-map.sh
git commit -m "feat: add web-map script for site structure discovery"---
Task 8: 创建 dual-search.sh
Files:
- Create:
UltimateSearchSkill/scripts/dual-search.sh
Step 1: 编写脚本
核心逻辑: 1. 接收参数:--query "查询内容" 2. 并行调用 grok-search.sh 和 tavily-search.sh(使用后台进程 + wait) 3. 合并两个引擎的结果 4. 输出合并后的 JSON:{ "grok": {...}, "tavily": {...} } 5. agent 可根据 SKILL.md 指引进行交叉验证
Step 2: Commit
git add scripts/dual-search.sh
git commit -m "feat: add dual-search script for cross-engine aggregation"---
Task 9: 创建 SKILL.md(核心)
Files:
- Create:
UltimateSearchSkill/SKILL.md
Step 1: 编写 SKILL.md
这是整个项目的核心,内容来源:
- GrokSearch MCP 的搜索规划方法论(6 阶段规划)
- GrokSearch MCP 的搜索和证据标准
- 双引擎工具使用指南
结构大纲:
# UltimateSearch Skill
## 工具清单
- `grok-search.sh` — AI 驱动搜索(Grok 联网搜索)
- `tavily-search.sh` — 结构化搜索(Tavily)
- `web-fetch.sh` — 网页内容抓取(Tavily Extract)
- `web-map.sh` — 站点结构映射(Tavily Map)
- `dual-search.sh` — 双引擎聚合搜索
## 搜索决策流程
### 何时使用哪个工具
- 简单事实查询 → `tavily-search.sh --depth basic`
- 复杂/探索性问题 → `dual-search.sh`(双引擎交叉验证)
- 需要 AI 分析的搜索 → `grok-search.sh`
- 抓取指定 URL 内容 → `web-fetch.sh`
- 探索网站结构 → `web-map.sh`
### 搜索复杂度评估
- Level 1(1-2 次搜索):单个明确问题
- Level 2(3-5 次搜索):多角度比较/分析
- Level 3(6+ 次搜索):深度研究课题
### 搜索规划流程
1. 意图分析:明确核心问题
2. 复杂度评估:确定搜索深度
3. 查询拆解:分解为不重叠的子查询
4. 策略选择:broad_first / narrow_first / targeted
5. 工具映射:为每个子查询选择最佳工具
6. 执行顺序:确定并行/串行执行计划
### 证据标准
- 关键事实需 ≥2 个独立来源支持
- 来源冲突时:展示双方证据,评估可信度
- 经验性结论标注置信度(High/Medium/Low)
- 引用格式:[作者/组织, 年份, URL]
- 严禁编造引用
### 输出规范
- 先给出最可能的答案,再展开分析
- 所有技术术语附简明解释
- 使用标准 Markdown 格式
- 每个结论注明信源Step 2: Commit
git add SKILL.md
git commit -m "feat: add SKILL.md with search methodology and tool guide"---
Task 10: 创建 README.md
Files:
- Create:
UltimateSearchSkill/README.md
Step 1: 编写 README.md
内容:
- 项目简介(一句话描述 + 架构图)
- 特性列表
- 快速开始(3 步:clone → setup → 使用)
- 前置条件
- 详细安装步骤
- 使用示例
- 配置说明
- 致谢(GrokSearch MCP、grok2api、TavilyProxyManager)
- License (MIT)
Step 2: Commit
git add README.md
git commit -m "docs: add README with quick start guide"---
Task 11: 创建架构文档
Files:
- Create:
UltimateSearchSkill/docs/architecture.md
Step 1: 编写架构文档
内容:
- 设计理念(Skill vs MCP 的选择)
- 三层架构详解
- 数据流图
- 与 GrokSearch MCP 的对比
- 扩展指南
Step 2: Commit
git add docs/architecture.md
git commit -m "docs: add architecture documentation"---
Task 12: 集成测试
Step 1: 在 OpenClaw 服务器上运行 setup.sh
ssh -p 2222 ckckck-ubuntu@192.168.1.2
cd UltimateSearchSkill
./scripts/setup.shStep 2: 配置 grok2api Token
访问 http://192.168.1.2:8100/admin,添加 Grok Token。
Step 3: 配置 TavilyProxyManager Key
访问 http://192.168.1.2:8200,添加 Tavily API Key。
Step 4: 测试各脚本
# 测试 Grok 搜索
./scripts/grok-search.sh --query "FastAPI 最新用法"
# 测试 Tavily 搜索
./scripts/tavily-search.sh --query "FastAPI latest features" --depth advanced
# 测试网页抓取
./scripts/web-fetch.sh --url "https://fastapi.tiangolo.com/"
# 测试站点映射
./scripts/web-map.sh --url "https://fastapi.tiangolo.com/" --depth 1
# 测试双引擎搜索
./scripts/dual-search.sh --query "FastAPI vs Flask 2026 comparison"Step 5: 将 Skill 注册到 OpenClaw
# 将 SKILL.md 链接或复制到 OpenClaw 的 skills 目录
ln -s ~/UltimateSearchSkill/SKILL.md ~/.openclaw/skills/ultimate-search/SKILL.md
# 将脚本目录加入 PATH
echo 'export PATH="$HOME/UltimateSearchSkill/scripts:$PATH"' >> ~/.bashrc
source ~/.bashrcStep 6: 端到端测试
在 OpenClaw 中发送消息,验证 agent 能够: 1. 识别需要搜索的请求 2. 选择合适的搜索工具 3. 返回有信源引用的回答
---
执行顺序总结
| 阶段 | Task | 说明 | 依赖 |
|---|---|---|---|
| 基础 | Task 1 | 项目初始化 | - |
| 基础 | Task 2 | Docker Compose | Task 1 |
| 基础 | Task 3 | 安装脚本 | Task 2 |
| 核心 | Task 4-8 | 5 个搜索脚本 | Task 1 |
| 核心 | Task 9 | SKILL.md | Task 4-8 |
| 文档 | Task 10-11 | README + 架构文档 | Task 9 |
| 验证 | Task 12 | 集成测试 | All |
并行可能性:
- Task 4-8(5 个脚本)可并行开发
- Task 10-11(文档)可并行编写
UltimateSearchSkill
为 OpenClaw / Pi agent 打造的双引擎网络搜索 Skill。
用户提问 → Agent (SKILL.md 指导)
├─ grok-search.sh → grok2api (多Token聚合) → Grok 联网搜索
├─ tavily-search.sh → TavilyProxyManager (多Key聚合) → Tavily 搜索
├─ web-fetch.sh → Tavily Extract → FireCrawl Scrape (自动降级)
├─ web-map.sh → TavilyProxyManager → Tavily Map (站点映射)
├─ dual-search.sh → 并行调用以上,交叉验证
└─ agent-browser → 动态交互/登录后回流到 web-fetch特性
- 双引擎搜索:Grok(AI 联网搜索)+ Tavily(结构化搜索),互补协作
- 多账户聚合:通过 grok2api 和 TavilyProxyManager 聚合多个账号,自动负载均衡
- FireCrawl 托底:web-fetch 三级降级链(Tavily Extract → FireCrawl Scrape → 报错)
- Cloudflare 自动绕过:FlareSolverr 自动获取并定期刷新
cf_clearance - 零 MCP 依赖:纯 Shell 脚本 + Skill 指令,agent 通过 Bash 原生调用
- 浏览器联动:可与
agent-browser协同,处理登录态、动态渲染、按钮触发等页面 - X/Twitter 优先路由:讨论类舆情查询优先走
grok-search --platform "X" - 安全加固:端口绑定 127.0.0.1,API 认证,SSH 隧道访问管理面板
- 搜索方法论:内置 GrokSearch MCP 的搜索规划框架和证据标准
- BDD 可靠性场景:提供路由验收场景,防止技能退化(见
docs/bdd/ultimate-search-routing.feature)
快速开始
前置条件
- Docker + Docker Compose
- curl、jq
- Grok 账号的 SSO Session Token(至少 1 个,详见获取方法)
- Tavily API Key(免费 1000 次/月,注册:https://www.tavily.com/)
- FireCrawl API Key(可选,作为 web-fetch 降级方案,注册:https://www.firecrawl.dev/)
安装
git clone https://github.com/你的用户名/UltimateSearchSkill.git
cd UltimateSearchSkill
# 复制环境变量模板
cp .env.example .env部署服务
# 创建数据目录
mkdir -p data/grok2api/logs data/tavily-proxy
# 拉取镜像并启动(包含 FlareSolverr + grok2api + TavilyProxyManager)
docker compose pull
docker compose up -d---
Key 导入指南
部署完成后,需要将各类 Key/Token 导入到对应服务中。
1. 获取 Grok SSO Session Token
grok2api 需要 Grok 网页版的 SSO Session Token(JWT 格式),不是 API Key。
方式一:浏览器手动获取
1. 用浏览器登录 https://grok.com 2. 打开开发者工具(F12)→ Application → Cookies → https://grok.com 3. 找到名为 sso 的 Cookie,复制其值(以 eyJ 开头的长字符串) 4. 每个 Grok 账号对应一个 Token
方式二:批量导出(推荐)
如果你有多个 Grok 账号的 SSO Cookie,可以将它们保存到 export_sso.txt 文件中,每行一个 Token:
eyJhbGciOiJIUzI1NiJ9.xxx...(第1个账号)
eyJhbGciOiJIUzI1NiJ9.yyy...(第2个账号)
eyJhbGciOiJIUzI1NiJ9.zzz...(第3个账号)⚠️export_sso.txt已加入.gitignore,不会被提交到 Git。
Token 额度说明
| 账号类型 | 额度 | 刷新周期 |
|---|---|---|
| Basic(免费) | 80 次 | 每 20 小时 |
| Super(付费) | 140 次 | 每 2 小时 |
2. 导入 Grok Token 到 grok2api
方式一:使用 import-keys.sh 脚本(推荐)
# 确保 export_sso.txt 在项目根目录
bash scripts/import-keys.sh脚本会自动:
- 读取
export_sso.txt中的 Token - 通过 grok2api 管理 API 批量导入到
ssoBasicToken Pool - 获取 TavilyProxyManager 的 Master Key 并更新
.env - 导入
.env中配置的 Tavily/FireCrawl Key
方式二:通过 API 手动导入
# 单个 Token
curl -X POST http://127.0.0.1:8100/v1/admin/tokens \
-H "Authorization: Bearer grok2api" \
-H "Content-Type: application/json" \
-d '{"ssoBasic": ["eyJhbGci...你的Token"]}'
# 批量导入(从文件)
TOKENS=$(cat export_sso.txt | jq -R 'select(length > 0)' | jq -s '.')
curl -X POST http://127.0.0.1:8100/v1/admin/tokens \
-H "Authorization: Bearer grok2api" \
-H "Content-Type: application/json" \
-d "{\"ssoBasic\": $TOKENS}"方式三:通过 Web 管理面板
# SSH 隧道(远程服务器)
ssh -L 8100:127.0.0.1:8100 你的服务器
# 浏览器打开 http://localhost:8100/admin
# 默认密码: grok2api注意:Token Pool 名称必须是ssoBasic(Basic 账号)或ssoSuper(Super 账号),否则 grok2api 无法调度。
3. Cloudflare 绕过(FlareSolverr)
grok2api 访问 Grok 官网时会被 Cloudflare 拦截(403)。项目已集成 FlareSolverr 来自动处理:
- FlareSolverr 使用无头 Chrome 自动通过 Cloudflare JS Challenge
- 不需要 Grok 账号密码,只需访问 grok.com 首页即可
- 获取的
cf_clearance每 3600 秒自动刷新 cf_clearance与 IP 绑定,换服务器需要重新获取(FlareSolverr 会自动处理)
首次启动时,需确保 grok2api 的 config.toml 已启用 FlareSolverr:
# 查看是否已自动配置(启动后自动生成 config.toml)
cat data/grok2api/config.toml | grep -A2 'flaresolverr'如果 enabled = false 或 flaresolverr_url 为空,需修改:
# Linux 服务器上(Docker 生成的文件需要 sudo)
sudo sed -i 's|^enabled = false|enabled = true|' data/grok2api/config.toml
sudo sed -i 's|^flaresolverr_url = ""|flaresolverr_url = "http://ultimate-search-flaresolverr:8191"|' data/grok2api/config.toml
# 重启 grok2api 使配置生效
docker compose restart grok2api验证 cf_clearance 是否获取成功:
docker compose logs grok2api | grep "配置已更新"
# 应看到: 配置已更新: cf_cookies (长度 xxxx), 指纹: chromeXXX4. 导入 Tavily API Key
方式一:配置到 .env 后使用 import-keys.sh
编辑 .env,将 Tavily Key 填入 TAVILY_API_KEYS(多个用逗号分隔):
TAVILY_API_KEYS=tvly-xxx111,tvly-xxx222,tvly-xxx333然后运行:
bash scripts/import-keys.sh方式二:通过 API 手动导入
# 先获取 Master Key
docker compose logs tavily-proxy | grep "master key"
# 添加 Key
curl -X POST http://127.0.0.1:8200/api/keys \
-H "Authorization: Bearer 你的MasterKey" \
-H "Content-Type: application/json" \
-d '{"key": "tvly-你的key", "alias": "账号A", "total_quota": 1000}'方式三:通过 Web 管理面板
ssh -L 8200:127.0.0.1:8200 你的服务器
# 浏览器打开 http://localhost:8200TavilyProxyManager 首次启动时自动生成 Master Key,import-keys.sh会自动获取并更新到.env。
5. 配置 FireCrawl Key(可选)
FireCrawl 作为 web-fetch.sh 的降级方案,当 Tavily Extract 失败时自动切换。
编辑 .env:
# 单个 Key(脚本直接使用)
FIRECRAWL_API_KEY=fc-你的key
# 或批量配置(import-keys.sh 会取第一个)
FIRECRAWL_API_KEYS=fc-key1,fc-key2FireCrawl 直接调用官方 API(https://api.firecrawl.dev/v2/scrape),无需代理服务。
6. 一键导入流程总结
# 1. 编辑 .env,填入 Tavily 和 FireCrawl Key
vim .env
# 2. 准备 Grok SSO Token 文件
# 将 Token 保存到 export_sso.txt,每行一个
# 3. 一键导入所有 Key
bash scripts/import-keys.sh
# 4. 确认 FlareSolverr 配置(首次需要)
docker compose logs grok2api | grep "配置已更新"
# 如果没有,按上面 "Cloudflare 绕过" 章节操作
# 5. 测试
bash scripts/tavily-search.sh --query "test" --max-results 1
bash scripts/grok-search.sh --query "hello" --model "grok-4.1-mini"
bash scripts/web-fetch.sh --url "https://example.com"---
使用
# 加载环境变量
source .env
# Grok AI 搜索
grok-search.sh --query "FastAPI 最新特性"
# Tavily 搜索
tavily-search.sh --query "Python web frameworks comparison" --depth advanced
# 双引擎搜索
dual-search.sh --query "Rust vs Go 2026"
# 抓取网页内容(Tavily → FireCrawl 自动降级)
web-fetch.sh --url "https://docs.python.org/3/whatsnew/3.13.html"
# 站点映射
web-map.sh --url "https://docs.tavily.com" --depth 2
# 动态页面联动(agent-browser 先交互,再回流抓取)
agent-browser open "https://example.com/pricing"
agent-browser wait --load networkidle
agent-browser snapshot -i
agent-browser click @e3
agent-browser get url
web-fetch.sh --url "https://example.com/pricing?tab=enterprise"
# X/Twitter 讨论类检索(优先 Grok)
grok-search.sh --query "检索 X 上有关伊朗战争讨论" --platform "X"
tavily-search.sh --query "Iran war discussion on X" --topic news --time-range week注册为 Skill
OpenClaw / Pi 集成
1. 注册 Skill
# 创建 skill 目录并软链接 SKILL.md
mkdir -p ~/.openclaw/workspace/skills/ultimate-search
ln -sf $(pwd)/SKILL.md ~/.openclaw/workspace/skills/ultimate-search/SKILL.md
# 将脚本加入 PATH
grep -q 'UltimateSearchSkill/scripts' ~/.bashrc || \
echo 'export PATH="$HOME/UltimateSearchSkill/scripts:$PATH"' >> ~/.bashrc
# 加载环境变量
grep -q 'UltimateSearchSkill/.env' ~/.bashrc || \
echo '[ -f ~/UltimateSearchSkill/.env ] && source ~/UltimateSearchSkill/.env' >> ~/.bashrc
source ~/.bashrcOpenClaw 启动时会自动发现 ~/.openclaw/workspace/skills/ultimate-search/SKILL.md,agent 在需要搜索时会自动加载。
2. 设为默认搜索方式
在 ~/.openclaw/workspace/AGENTS.md 的 ## Tools 部分添加路由规则:
### 搜索工具
**默认搜索方式是 ultimate-search skill。** 任何需要网络搜索的场景,先加载 `ultimate-search` skill 并按其指引操作。支持以下能力:
- `grok-search.sh` — AI 驱动的深度搜索(Grok 联网)
- `tavily-search.sh` — 结构化搜索结果(带评分排序)
- `dual-search.sh` — 双引擎并行搜索(交叉验证)
- `web-fetch.sh` — 网页内容抓取(Tavily → FireCrawl 降级)
- `web-map.sh` — 站点结构映射如有其他搜索 skill(如 ddg-search、jina-search 等),建议移除以避免冲突。
3. 全局提示词配置(推荐)
SKILL.md 已内置搜索方法论和证据标准。如需在 agent 层面强制执行通用行为规范,可在 AGENTS.md 中添加:
## 工作准则
### 语言
- 工具交互和内部思考使用英文,输出使用中文
- 使用标准 Markdown 格式,代码块标注语言
### 推理与表达
- 简洁、直接、信息密集:离散项用列表,论证用段落
- 遇到用户逻辑错误时,用证据指出具体问题
- 所有结论必须标注:适用条件、范围边界、已知限制
- 不确定时:先陈述未知及原因,再给出已确认的事实
- 不说废话、不寒暄、不用填充词
### 搜索与证据标准
- 严格区分内部知识与外部知识,不确定时必须搜索验证
- 技术实现即使有内部知识,仍应以最新搜索结果或官方文档为准
- 关键事实需 ≥2 个独立来源支持,单一来源须显式声明
- 来源冲突时:展示双方证据,评估可信度和时效性
- 标注置信度:High(多来源一致)/ Medium(有分歧)/ Low(单一来源或推测)
- 引用格式:`[来源标题](URL)`,严禁编造引用提示词层次说明:
- SKILL.md(Skill 层):搜索决策流程、工具选择策略、搜索规划框架、证据标准 — 仅在 agent 加载 skill 时生效
- AGENTS.md(全局层):通用行为规范(语言、推理、搜索标准)— 始终在 agent 上下文中
- SOUL.md(人格层):agent 的身份和响应风格 — 不建议在此添加工具相关指令
架构说明
详见 docs/architecture.md。
安全说明
- 所有端口绑定到
127.0.0.1,外部无法直接访问 export_sso.txt、.env、data/已加入.gitignore,不会提交到 Git- grok2api 默认管理密码
grok2api,建议修改data/grok2api/config.toml中的app_key - TavilyProxyManager 使用随机生成的 Master Key
- 远程管理通过 SSH 隧道访问
- 详见 安全加固指南
致谢
- GrokSearch MCP — 搜索方法论和提示词的灵感来源
- grok2api — Grok Token 聚合服务
- TavilyProxyManager — Tavily Key 聚合服务
- FlareSolverr — Cloudflare 自动绕过
- FireCrawl — 网页抓取降级方案
License
MIT
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 加载 .env(如果环境变量未设置)
if [[ -f "$SCRIPT_DIR/../.env" ]]; then
while IFS='=' read -r key value; do
key="$(echo "$key" | xargs)"
[[ -z "$key" || "$key" == \#* ]] && continue
value="$(echo "$value" | xargs | sed -e "s/^['\"]//;s/['\"]$//")"
if [[ -z "${!key:-}" ]]; then
export "$key=$value"
fi
done < "$SCRIPT_DIR/../.env"
fi
usage() {
cat <<EOF
用法: $(basename "$0") [选项]
双引擎聚合搜索 — 并行调用 Grok AI 搜索和 Tavily 结构化搜索,合并结果
选项:
--query "查询内容" 必需,搜索查询内容
--tavily-depth basic|advanced 可选,Tavily 搜索深度(默认: basic)
--help 显示此帮助信息
示例:
$(basename "$0") --query "FastAPI vs Flask comparison"
$(basename "$0") --query "最新 AI 新闻" --tavily-depth advanced
EOF
exit 0
}
error_exit() {
echo "{\"error\": \"$1\"}"
exit 1
}
QUERY=""
TAVILY_DEPTH="basic"
if [[ $# -eq 0 ]]; then
usage
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--query)
QUERY="$2"
shift 2
;;
--tavily-depth)
TAVILY_DEPTH="$2"
shift 2
;;
--help)
usage
;;
*)
error_exit "未知参数: $1"
;;
esac
done
[[ -z "$QUERY" ]] && error_exit "缺少必需参数 --query"
# 创建临时文件
GROK_TMP=$(mktemp)
TAVILY_TMP=$(mktemp)
# 确保退出时清理临时文件
cleanup() {
rm -f "$GROK_TMP" "$TAVILY_TMP"
}
trap cleanup EXIT
# 并行调用两个搜索引擎
"$SCRIPT_DIR/grok-search.sh" --query "$QUERY" > "$GROK_TMP" 2>&1 &
GROK_PID=$!
"$SCRIPT_DIR/tavily-search.sh" --query "$QUERY" --depth "$TAVILY_DEPTH" > "$TAVILY_TMP" 2>&1 &
TAVILY_PID=$!
# 等待两个进程完成
GROK_EXIT=0
TAVILY_EXIT=0
wait "$GROK_PID" || GROK_EXIT=$?
wait "$TAVILY_PID" || TAVILY_EXIT=$?
# 读取结果
GROK_RESULT=$(cat "$GROK_TMP")
TAVILY_RESULT=$(cat "$TAVILY_TMP")
# 验证 JSON 有效性,无效则包装为错误
if ! echo "$GROK_RESULT" | jq empty 2>/dev/null; then
GROK_RESULT="{\"error\": \"Grok 搜索失败: $(echo "$GROK_RESULT" | head -1 | sed 's/"/\\"/g')\"}"
fi
if ! echo "$TAVILY_RESULT" | jq empty 2>/dev/null; then
TAVILY_RESULT="{\"error\": \"Tavily 搜索失败: $(echo "$TAVILY_RESULT" | head -1 | sed 's/"/\\"/g')\"}"
fi
# 合并结果
jq -n \
--argjson grok "$GROK_RESULT" \
--argjson tavily "$TAVILY_RESULT" \
'{
grok: $grok,
tavily: $tavily
}'
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 加载 .env(如果环境变量未设置)
if [[ -f "$SCRIPT_DIR/../.env" ]]; then
while IFS='=' read -r key value; do
key="$(echo "$key" | xargs)"
[[ -z "$key" || "$key" == \#* ]] && continue
value="$(echo "$value" | xargs | sed -e "s/^['\"]//;s/['\"]$//")"
if [[ -z "${!key:-}" ]]; then
export "$key=$value"
fi
done < "$SCRIPT_DIR/../.env"
fi
GROK_API_URL="${GROK_API_URL:-}"
GROK_API_KEY="${GROK_API_KEY:-}"
GROK_MODEL="${GROK_MODEL:-grok-4.1-fast}"
usage() {
cat <<EOF
用法: $(basename "$0") [选项]
Grok AI 智能搜索 — 通过 Grok 模型的联网能力进行 AI 驱动搜索
选项:
--query "查询内容" 必需,搜索查询内容
--platform "平台" 可选,聚焦平台(如 Twitter, GitHub, Reddit)
--model "模型" 可选,覆盖默认模型 ($GROK_MODEL)
--help 显示此帮助信息
示例:
$(basename "$0") --query "FastAPI 最新用法"
$(basename "$0") --query "React 19 新特性" --platform "GitHub"
$(basename "$0") --query "latest AI news" --model "grok-3"
EOF
exit 0
}
error_exit() {
echo "{\"error\": \"$1\"}"
exit 1
}
QUERY=""
PLATFORM=""
MODEL="$GROK_MODEL"
if [[ $# -eq 0 ]]; then
usage
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--query)
QUERY="$2"
shift 2
;;
--platform)
PLATFORM="$2"
shift 2
;;
--model)
MODEL="$2"
shift 2
;;
--help)
usage
;;
*)
error_exit "未知参数: $1"
;;
esac
done
[[ -z "$QUERY" ]] && error_exit "缺少必需参数 --query"
[[ -z "$GROK_API_URL" ]] && error_exit "未设置 GROK_API_URL"
[[ -z "$GROK_API_KEY" ]] && error_exit "未设置 GROK_API_KEY"
# system prompt(来自 GrokSearch MCP 的 search_prompt)
SYSTEM_PROMPT='# Core Instruction
1. User needs may be vague. Think divergently, infer intent from multiple angles, and leverage full conversation context to progressively clarify their true needs.
2. **Breadth-First Search**—Approach problems from multiple dimensions. Brainstorm 5+ perspectives and execute parallel searches for each. Consult as many high-quality sources as possible before responding.
3. **Depth-First Search**—After broad exploration, select ≥2 most relevant perspectives for deep investigation into specialized knowledge.
4. **Evidence-Based Reasoning & Traceable Sources**—Every claim must be followed by a citation. More credible sources strengthen arguments. If no references exist, remain silent.
5. Before responding, ensure full execution of Steps 1–4.
# Search Instruction
1. Think carefully before responding—anticipate the user'\''s true intent to ensure precision.
2. Verify every claim rigorously to avoid misinformation.
3. Follow problem logic—dig deeper until clues are exhaustively clear. Use multiple parallel tool calls per query and ensure answers are well-sourced.
4. Search in English first (prioritizing English resources for volume/quality), but switch to Chinese if context demands.
5. Prioritize authoritative sources: Wikipedia, academic databases, books, reputable media/journalism.
6. Favor sharing in-depth, specialized knowledge over generic or common-sense content.
# Output Style
1. Lead with the **most probable solution** before detailed analysis.
2. **Define every technical term** in plain language.
3. **Respect facts and search results—use statistical rigor to discern truth**.
4. **Every sentence must cite sources**. More references = stronger credibility.
5. **Strictly format outputs in polished Markdown**.'
# 构建 user message
USER_MESSAGE="$QUERY"
# 时间相关关键词检测 → 注入当前日期时间
TIME_KEYWORDS='今天|最新|当前|latest|recent|today|current|now|这几天|本周|本月|近期|最近'
if echo "$QUERY" | grep -qiE "$TIME_KEYWORDS"; then
CURRENT_TIME="$(date '+%Y-%m-%d %H:%M:%S %Z')"
USER_MESSAGE="[Current date and time: $CURRENT_TIME]
$USER_MESSAGE"
fi
# 平台聚焦
if [[ -n "$PLATFORM" ]]; then
USER_MESSAGE="$USER_MESSAGE
You should focus on these platform: $PLATFORM"
fi
# 构建请求 JSON
REQUEST_JSON=$(jq -n \
--arg model "$MODEL" \
--arg system "$SYSTEM_PROMPT" \
--arg user "$USER_MESSAGE" \
'{
model: $model,
stream: false,
messages: [
{ role: "system", content: $system },
{ role: "user", content: $user }
]
}')
# 调用 API
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "$GROK_API_URL/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GROK_API_KEY" \
-d "$REQUEST_JSON")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" -ne 200 ]]; then
error_exit "API 请求失败 (HTTP $HTTP_CODE): $BODY"
fi
# 提取结果
echo "$BODY" | jq '{
content: .choices[0].message.content,
model: .model,
usage: .usage
}'
#!/usr/bin/env bash
set -eo pipefail
# =========================================================
# import-keys.sh — 从 .env 批量导入 Key 到各服务
# =========================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; }
# 加载 .env
if [[ ! -f "$PROJECT_DIR/.env" ]]; then
error ".env 文件不存在,请先创建"
exit 1
fi
set -a; source "$PROJECT_DIR/.env"; set +a
GROK2API_PORT="${GROK2API_PORT:-8100}"
TAVILY_PROXY_PORT="${TAVILY_PROXY_PORT:-8200}"
GROK2API_APP_KEY="${GROK2API_APP_KEY:-grok2api}"
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 批量导入 Key 到服务${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# ==========================================
# 1. 导入 Grok SSO Tokens(从文件)
# ==========================================
GROK_SSO_FILE="${1:-${PROJECT_DIR}/export_sso.txt}"
if [[ -f "$GROK_SSO_FILE" ]]; then
info "导入 Grok SSO Tokens(从 $GROK_SSO_FILE)..."
# 读取文件中的 token,每行一个
TOKENS_JSON="["
FIRST=true
while IFS= read -r line || [[ -n "$line" ]]; do
line="$(echo "$line" | xargs)" # trim whitespace
[[ -z "$line" || "$line" == \#* ]] && continue
# 去掉可能的 sso= 前缀
line="${line#sso=}"
if [[ "$FIRST" == "true" ]]; then
TOKENS_JSON+="\"$line\""
FIRST=false
else
TOKENS_JSON+=",\"$line\""
fi
done < "$GROK_SSO_FILE"
TOKENS_JSON+="]"
TOKEN_COUNT=$(echo "$TOKENS_JSON" | jq 'length')
info "发现 $TOKEN_COUNT 个 Grok Token,正在导入..."
RESULT=$(curl -s -w "\n%{http_code}" \
-X POST "http://127.0.0.1:$GROK2API_PORT/v1/admin/tokens" \
-H "Authorization: Bearer $GROK2API_APP_KEY" \
-H "Content-Type: application/json" \
-d "{\"ssoBasic\": $TOKENS_JSON}")
HTTP_CODE=$(echo "$RESULT" | tail -1)
BODY=$(echo "$RESULT" | sed '$d')
if [[ "$HTTP_CODE" -eq 200 ]]; then
ok "Grok Tokens 导入成功($TOKEN_COUNT 个)"
else
error "Grok Tokens 导入失败 (HTTP $HTTP_CODE): $BODY"
fi
else
warn "未找到 Grok SSO 文件: $GROK_SSO_FILE,跳过"
fi
echo ""
# ==========================================
# 2. 获取 TavilyProxyManager Master Key
# ==========================================
TAVILY_MASTER_KEY="${TAVILY_MASTER_KEY:-}"
if [[ -z "$TAVILY_MASTER_KEY" ]]; then
info "TAVILY_MASTER_KEY 未设置,尝试自动发现..."
cd "$PROJECT_DIR"
# 优先从日志读取(兼容旧版本输出)
if command -v docker &>/dev/null; then
TAVILY_MASTER_KEY=$(docker compose logs tavily-proxy 2>&1 | grep -oE 'master_key=[^ ]+' | head -1 | cut -d= -f2 || true)
fi
# 新版本日志可能不输出 master key,回退到本地 sqlite
if [[ -z "$TAVILY_MASTER_KEY" && -f "$PROJECT_DIR/data/tavily-proxy/proxy.db" ]] && command -v sqlite3 &>/dev/null; then
TAVILY_MASTER_KEY=$(sqlite3 "$PROJECT_DIR/data/tavily-proxy/proxy.db" "select value from settings where key='master_key';" 2>/dev/null || true)
fi
if [[ -n "$TAVILY_MASTER_KEY" ]]; then
ok "已发现 Master Key: ${TAVILY_MASTER_KEY:0:10}..."
warn "请将此 Key 填入 .env 的 TAVILY_MASTER_KEY 和 TAVILY_API_KEY"
else
error "无法自动获取 Master Key"
fi
fi
# ==========================================
# 3. 导入 Tavily API Keys
# ==========================================
TAVILY_API_KEYS="${TAVILY_API_KEYS:-}"
if [[ -n "$TAVILY_API_KEYS" && -n "$TAVILY_MASTER_KEY" ]]; then
info "导入 Tavily API Keys..."
IFS=',' read -ra KEYS <<< "$TAVILY_API_KEYS"
SUCCESS=0
FAIL=0
for key in "${KEYS[@]}"; do
key="$(echo "$key" | xargs)" # trim whitespace
[[ -z "$key" ]] && continue
RESULT=$(curl -s -w "\n%{http_code}" \
-X POST "http://127.0.0.1:$TAVILY_PROXY_PORT/api/keys" \
-H "Authorization: Bearer $TAVILY_MASTER_KEY" \
-H "Content-Type: application/json" \
-d "{\"key\": \"$key\", \"alias\": \"批量导入\", \"total_quota\": 1000}")
HTTP_CODE=$(echo "$RESULT" | tail -1)
if [[ "$HTTP_CODE" -eq 200 ]]; then
SUCCESS=$((SUCCESS + 1))
else
FAIL=$((FAIL + 1))
BODY=$(echo "$RESULT" | sed '$d')
warn "Tavily Key ${key:0:10}... 导入失败: $BODY"
fi
done
ok "Tavily Keys 导入完成:成功 $SUCCESS 个,失败 $FAIL 个"
elif [[ -z "$TAVILY_API_KEYS" ]]; then
warn "TAVILY_API_KEYS 未设置,跳过 Tavily Key 导入"
elif [[ -z "$TAVILY_MASTER_KEY" ]]; then
error "TAVILY_MASTER_KEY 未设置,无法导入 Tavily Keys"
fi
echo ""
# ==========================================
# 4. 导入 FireCrawl API Keys(预留)
# ==========================================
FIRECRAWL_API_KEYS="${FIRECRAWL_API_KEYS:-}"
if [[ -n "$FIRECRAWL_API_KEYS" ]]; then
info "FireCrawl Keys 已配置,当前版本暂不支持自动导入(需要 FireCrawl 代理服务)"
IFS=',' read -ra KEYS <<< "$FIRECRAWL_API_KEYS"
info " 发现 ${#KEYS[@]} 个 FireCrawl Key"
fi
echo ""
# ==========================================
# 5. 更新 .env 中的 TAVILY_MASTER_KEY 和 TAVILY_API_KEY
# ==========================================
if [[ -n "$TAVILY_MASTER_KEY" ]]; then
# 检查 .env 中是否已设置
CURRENT_MASTER=$(grep '^TAVILY_MASTER_KEY=' "$PROJECT_DIR/.env" | cut -d= -f2- || true)
CURRENT_API=$(grep '^TAVILY_API_KEY=' "$PROJECT_DIR/.env" | cut -d= -f2- || true)
if [[ -z "$CURRENT_MASTER" || -z "$CURRENT_API" ]]; then
info "自动更新 .env 中的 TAVILY_MASTER_KEY 和 TAVILY_API_KEY..."
sed -i.bak "s|^TAVILY_MASTER_KEY=.*|TAVILY_MASTER_KEY=$TAVILY_MASTER_KEY|" "$PROJECT_DIR/.env"
sed -i.bak "s|^TAVILY_API_KEY=.*|TAVILY_API_KEY=$TAVILY_MASTER_KEY|" "$PROJECT_DIR/.env"
rm -f "$PROJECT_DIR/.env.bak"
ok ".env 已更新"
fi
fi
# ==========================================
# 验证结果
# ==========================================
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 验证服务状态${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# 检查 grok2api Token 数量
GROK_TOKENS=$(curl -s \
-H "Authorization: Bearer $GROK2API_APP_KEY" \
"http://127.0.0.1:$GROK2API_PORT/v1/admin/tokens" 2>/dev/null || echo "{}")
GROK_COUNT=$(echo "$GROK_TOKENS" | jq '[.[] | length] | add // 0' 2>/dev/null || echo "?")
info "grok2api Token 数量: $GROK_COUNT"
# 检查 TavilyProxyManager Key 数量
if [[ -n "$TAVILY_MASTER_KEY" ]]; then
TAVILY_KEYS_RESULT=$(curl -s \
-H "Authorization: Bearer $TAVILY_MASTER_KEY" \
"http://127.0.0.1:$TAVILY_PROXY_PORT/api/keys" 2>/dev/null || echo "{}")
TAVILY_COUNT=$(echo "$TAVILY_KEYS_RESULT" | jq '.items | length' 2>/dev/null || echo "?")
info "TavilyProxyManager Key 数量: $TAVILY_COUNT"
fi
echo ""
ok "导入完成!"
#!/usr/bin/env bash
set -euo pipefail
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
cd "$PROJECT_DIR"
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} UltimateSearchSkill 部署脚本${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# 检查依赖
for cmd in docker curl jq; do
if ! command -v "$cmd" &>/dev/null; then
error "$cmd 未安装,请先安装"
fi
ok "$cmd 已安装"
done
if docker compose version &>/dev/null; then
COMPOSE="docker compose"
elif command -v docker-compose &>/dev/null; then
COMPOSE="docker-compose"
else
error "docker compose 未安装"
fi
ok "docker compose 可用"
# 配置文件
if [ ! -f .env ]; then
cp .env.example .env
warn ".env 已从模板创建,请编辑 .env 填入实际配置"
warn "特别注意修改 GROK2API_APP_KEY 和 GROK2API_API_KEY"
fi
# 加载环境变量
set -a; source .env; set +a
# 创建数据目录
mkdir -p data/grok2api/logs data/tavily-proxy
# 拉取镜像并启动
info "拉取 Docker 镜像..."
$COMPOSE pull
info "启动服务..."
$COMPOSE up -d
# 等待服务就绪
info "等待服务就绪..."
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:${GROK2API_PORT:-8100}/" >/dev/null 2>&1; then
ok "grok2api 就绪"
break
fi
[ "$i" -eq 30 ] && warn "grok2api 30秒内未就绪,请检查日志: $COMPOSE logs grok2api"
sleep 1
done
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:${TAVILY_PROXY_PORT:-8200}/healthz" >/dev/null 2>&1; then
ok "TavilyProxyManager 就绪"
break
fi
[ "$i" -eq 30 ] && warn "TavilyProxyManager 30秒内未就绪,请检查日志: $COMPOSE logs tavily-proxy"
sleep 1
done
# 获取 TavilyProxyManager Master Key
echo ""
info "TavilyProxyManager Master Key:"
MASTER_KEY=$($COMPOSE logs tavily-proxy 2>&1 | grep -oP 'key=\K\S+' | head -1 || true)
if [ -n "$MASTER_KEY" ]; then
echo -e " ${GREEN}$MASTER_KEY${NC}"
echo ""
warn "请将此 Master Key 填入 .env 的 TAVILY_MASTER_KEY 和 TAVILY_API_KEY"
else
warn "未能自动获取 Master Key,请手动查看: $COMPOSE logs tavily-proxy | grep 'master key'"
fi
# 后续步骤
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 部署完成!后续步骤:${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo "1. 修改 .env 中的密码和 Key"
echo "2. 访问 grok2api 管理面板添加 Grok Token:"
echo " ssh -L 8100:127.0.0.1:${GROK2API_PORT:-8100} 你的服务器"
echo " 然后浏览器打开 http://localhost:8100/admin"
echo ""
echo "3. 访问 TavilyProxyManager 添加 Tavily Key:"
echo " ssh -L 8200:127.0.0.1:${TAVILY_PROXY_PORT:-8200} 你的服务器"
echo " 然后浏览器打开 http://localhost:8200"
echo ""
echo "4. 将脚本加入 PATH:"
echo " echo 'export PATH=\"$PROJECT_DIR/scripts:\$PATH\"' >> ~/.bashrc"
echo " source ~/.bashrc"
echo ""
echo "5. 加载环境变量(每次使用前):"
echo " source $PROJECT_DIR/.env"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 加载 .env(如果环境变量未设置)
if [[ -f "$SCRIPT_DIR/../.env" ]]; then
while IFS='=' read -r key value; do
key="$(echo "$key" | xargs)"
[[ -z "$key" || "$key" == \#* ]] && continue
value="$(echo "$value" | xargs | sed -e "s/^['\"]//;s/['\"]$//")"
if [[ -z "${!key:-}" ]]; then
export "$key=$value"
fi
done < "$SCRIPT_DIR/../.env"
fi
TAVILY_API_URL="${TAVILY_API_URL:-}"
TAVILY_API_KEY="${TAVILY_API_KEY:-}"
usage() {
cat <<EOF
用法: $(basename "$0") [选项]
Tavily 结构化搜索 — 使用 Tavily Search API 进行结构化网络搜索
选项:
--query "查询内容" 必需,搜索查询内容
--depth basic|advanced 可选,搜索深度(默认: basic)
--max-results N 可选,最大结果数(默认: 5)
--topic general|news|finance 可选,搜索主题(默认: general)
--time-range day|week|month|year 可选,时间范围过滤
--include-answer 可选,在结果中包含 AI 生成的答案
--include-raw 可选,包含原始内容
--help 显示此帮助信息
示例:
$(basename "$0") --query "Python web frameworks comparison"
$(basename "$0") --query "AI news" --topic news --time-range week --include-answer
$(basename "$0") --query "stock market" --depth advanced --max-results 10
EOF
exit 0
}
error_exit() {
echo "{\"error\": \"$1\"}"
exit 1
}
QUERY=""
DEPTH="basic"
MAX_RESULTS=5
TOPIC="general"
TIME_RANGE=""
INCLUDE_ANSWER=false
INCLUDE_RAW=false
if [[ $# -eq 0 ]]; then
usage
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--query)
QUERY="$2"
shift 2
;;
--depth)
DEPTH="$2"
shift 2
;;
--max-results)
MAX_RESULTS="$2"
shift 2
;;
--topic)
TOPIC="$2"
shift 2
;;
--time-range)
TIME_RANGE="$2"
shift 2
;;
--include-answer)
INCLUDE_ANSWER=true
shift
;;
--include-raw)
INCLUDE_RAW=true
shift
;;
--help)
usage
;;
*)
error_exit "未知参数: $1"
;;
esac
done
[[ -z "$QUERY" ]] && error_exit "缺少必需参数 --query"
[[ -z "$TAVILY_API_URL" ]] && error_exit "未设置 TAVILY_API_URL"
[[ -z "$TAVILY_API_KEY" ]] && error_exit "未设置 TAVILY_API_KEY"
# 构建请求 JSON
REQUEST_JSON=$(jq -n \
--arg query "$QUERY" \
--arg depth "$DEPTH" \
--argjson max_results "$MAX_RESULTS" \
--arg topic "$TOPIC" \
--argjson include_answer "$INCLUDE_ANSWER" \
--argjson include_raw "$INCLUDE_RAW" \
'{
query: $query,
search_depth: $depth,
max_results: $max_results,
topic: $topic,
include_answer: $include_answer,
include_raw_content: $include_raw
}')
# 添加可选的 time_range
if [[ -n "$TIME_RANGE" ]]; then
REQUEST_JSON=$(echo "$REQUEST_JSON" | jq --arg tr "$TIME_RANGE" '. + {time_range: $tr}')
fi
# 调用 API
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "$TAVILY_API_URL/search" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TAVILY_API_KEY" \
-d "$REQUEST_JSON")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" -ne 200 ]]; then
error_exit "API 请求失败 (HTTP $HTTP_CODE): $BODY"
fi
echo "$BODY" | jq '.'
#!/usr/bin/env bash
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 加载 .env(如果环境变量未设置)
if [[ -f "$SCRIPT_DIR/../.env" ]]; then
while IFS='=' read -r key value; do
key="$(echo "$key" | xargs)"
[[ -z "$key" || "$key" == \#* ]] && continue
value="$(echo "$value" | xargs | sed -e "s/^['\"]//;s/['\"]$//")"
if [[ -z "${!key:-}" ]]; then
export "$key=$value"
fi
done < "$SCRIPT_DIR/../.env"
fi
TAVILY_API_URL="${TAVILY_API_URL:-}"
TAVILY_API_KEY="${TAVILY_API_KEY:-}"
FIRECRAWL_API_URL="${FIRECRAWL_API_URL:-https://api.firecrawl.dev/v2}"
FIRECRAWL_API_KEY="${FIRECRAWL_API_KEY:-}"
# 兼容批量 Key 配置:如果单个 Key 未设置,取 FIRECRAWL_API_KEYS 的第一个
if [[ -z "$FIRECRAWL_API_KEY" && -n "${FIRECRAWL_API_KEYS:-}" ]]; then
FIRECRAWL_API_KEY=$(echo "$FIRECRAWL_API_KEYS" | cut -d',' -f1 | xargs)
fi
usage() {
cat <<EOF
用法: $(basename "$0") [选项]
网页内容抓取 — 三级降级:Tavily Extract → FireCrawl Scrape → 返回错误
选项:
--url "URL" 必需,目标网页 URL(可多次指定)
--depth basic|advanced 可选,提取深度(默认: basic)
--format markdown|text 可选,输出格式(默认: markdown)
--help 显示此帮助信息
示例:
$(basename "$0") --url "https://example.com"
$(basename "$0") --url "https://a.com" --url "https://b.com" --depth advanced
$(basename "$0") --url "https://example.com" --format text
EOF
exit 0
}
error_exit() {
echo "{\"error\": \"$1\"}"
exit 1
}
URLS=()
DEPTH="basic"
FORMAT="markdown"
if [[ $# -eq 0 ]]; then
usage
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--url)
URLS+=("$2")
shift 2
;;
--depth)
DEPTH="$2"
shift 2
;;
--format)
FORMAT="$2"
shift 2
;;
--help)
usage
;;
*)
error_exit "未知参数: $1"
;;
esac
done
[[ ${#URLS[@]} -eq 0 ]] && error_exit "缺少必需参数 --url"
# ==========================================
# Tavily Extract(第一级)
# ==========================================
tavily_extract() {
local urls_json="$1"
[[ -z "$TAVILY_API_URL" || -z "$TAVILY_API_KEY" ]] && return 1
local request_json
request_json=$(jq -n \
--argjson urls "$urls_json" \
--arg depth "$DEPTH" \
--arg format "$FORMAT" \
'{
urls: $urls,
extract_depth: $depth,
format: $format
}')
local response http_code body
response=$(curl -s -w "\n%{http_code}" \
--connect-timeout 6 --max-time 30 \
-X POST "$TAVILY_API_URL/extract" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TAVILY_API_KEY" \
-d "$request_json")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [[ "$http_code" -ne 200 ]]; then
return 1
fi
# 检查是否有实际内容
local has_content
has_content=$(echo "$body" | jq '[.results[]? | select(.raw_content != null and .raw_content != "")] | length' 2>/dev/null || echo "0")
if [[ "$has_content" -eq 0 ]]; then
return 1
fi
echo "$body" | jq '{source: "tavily", results: .results}'
return 0
}
# ==========================================
# FireCrawl Scrape(第二级降级)
# ==========================================
firecrawl_scrape() {
local urls_json="$1"
[[ -z "$FIRECRAWL_API_KEY" ]] && return 1
local api_url="${FIRECRAWL_API_URL%/}"
local all_results="[]"
# FireCrawl scrape 是单 URL 接口,需要逐个调用
local url_count
url_count=$(echo "$urls_json" | jq 'length')
for (( i=0; i<url_count; i++ )); do
local url
url=$(echo "$urls_json" | jq -r ".[$i]")
local request_json
request_json=$(jq -n \
--arg url "$url" \
'{
url: $url,
formats: ["markdown"]
}')
local response http_code body
response=$(curl -s -w "\n%{http_code}" \
--connect-timeout 6 --max-time 60 \
-X POST "$api_url/scrape" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $FIRECRAWL_API_KEY" \
-d "$request_json")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [[ "$http_code" -eq 200 ]]; then
local content
content=$(echo "$body" | jq -r '(.data.markdown // .data.content // "") | ltrimstr(" ") | rtrimstr(" ")' 2>/dev/null || echo "")
if [[ -n "$content" && "$content" != "null" ]]; then
all_results=$(echo "$all_results" | jq \
--arg url "$url" \
--arg content "$content" \
'. + [{url: $url, raw_content: $content}]')
fi
fi
done
local result_count
result_count=$(echo "$all_results" | jq 'length')
if [[ "$result_count" -eq 0 ]]; then
return 1
fi
jq -n --argjson results "$all_results" '{source: "firecrawl", results: $results}'
return 0
}
# ==========================================
# 主流程:三级降级
# ==========================================
# 构建 URL 数组 JSON
URLS_JSON=$(printf '%s\n' "${URLS[@]}" | jq -R . | jq -s .)
# 第一级:Tavily Extract
if result=$(tavily_extract "$URLS_JSON" 2>/dev/null); then
echo "$result" | jq '.'
exit 0
fi
# 第二级:FireCrawl Scrape
if result=$(firecrawl_scrape "$URLS_JSON" 2>/dev/null); then
echo "$result" | jq '.'
exit 0
fi
# 都失败
error_exit "Tavily Extract 和 FireCrawl Scrape 均失败"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 加载 .env(如果环境变量未设置)
if [[ -f "$SCRIPT_DIR/../.env" ]]; then
while IFS='=' read -r key value; do
key="$(echo "$key" | xargs)"
[[ -z "$key" || "$key" == \#* ]] && continue
value="$(echo "$value" | xargs | sed -e "s/^['\"]//;s/['\"]$//")"
if [[ -z "${!key:-}" ]]; then
export "$key=$value"
fi
done < "$SCRIPT_DIR/../.env"
fi
TAVILY_API_URL="${TAVILY_API_URL:-}"
TAVILY_API_KEY="${TAVILY_API_KEY:-}"
usage() {
cat <<EOF
用法: $(basename "$0") [选项]
站点结构映射 — 使用 Tavily Map API 发现网站 URL 结构
选项:
--url "URL" 必需,目标站点 URL
--depth N 可选,爬取深度,范围 1-5(默认: 1)
--breadth N 可选,每层爬取宽度(默认: 20)
--limit N 可选,最大 URL 数量(默认: 50)
--instructions "说明" 可选,爬取指令说明
--help 显示此帮助信息
示例:
$(basename "$0") --url "https://example.com"
$(basename "$0") --url "https://docs.example.com" --depth 2 --limit 100
$(basename "$0") --url "https://example.com" --instructions "只抓取文档页面"
EOF
exit 0
}
error_exit() {
echo "{\"error\": \"$1\"}"
exit 1
}
URL=""
DEPTH=1
BREADTH=20
LIMIT=50
INSTRUCTIONS=""
if [[ $# -eq 0 ]]; then
usage
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--url)
URL="$2"
shift 2
;;
--depth)
DEPTH="$2"
shift 2
;;
--breadth)
BREADTH="$2"
shift 2
;;
--limit)
LIMIT="$2"
shift 2
;;
--instructions)
INSTRUCTIONS="$2"
shift 2
;;
--help)
usage
;;
*)
error_exit "未知参数: $1"
;;
esac
done
[[ -z "$URL" ]] && error_exit "缺少必需参数 --url"
[[ -z "$TAVILY_API_URL" ]] && error_exit "未设置 TAVILY_API_URL"
[[ -z "$TAVILY_API_KEY" ]] && error_exit "未设置 TAVILY_API_KEY"
# 验证 depth 范围
if [[ "$DEPTH" -lt 1 || "$DEPTH" -gt 5 ]]; then
error_exit "--depth 必须在 1-5 范围内"
fi
# 构建请求 JSON
REQUEST_JSON=$(jq -n \
--arg url "$URL" \
--argjson depth "$DEPTH" \
--argjson breadth "$BREADTH" \
--argjson limit "$LIMIT" \
'{
url: $url,
depth: $depth,
breadth: $breadth,
limit: $limit
}')
# 添加可选的 instructions
if [[ -n "$INSTRUCTIONS" ]]; then
REQUEST_JSON=$(echo "$REQUEST_JSON" | jq --arg inst "$INSTRUCTIONS" '. + {instructions: $inst}')
fi
# 调用 API
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST "$TAVILY_API_URL/map" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TAVILY_API_KEY" \
-d "$REQUEST_JSON")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" -ne 200 ]]; then
error_exit "API 请求失败 (HTTP $HTTP_CODE): $BODY"
fi
echo "$BODY" | jq '.'