Skill: alexjunanjing-2/openclaw-skill-search-web (version 0.1.0)

--- name: volcengine-search-web description: Openclaw/Trae 联网搜索国内方案，使用火山引擎联网问答智能体 API 进行网络搜索问答。 --- # Volcengine Search Web Skill - 联网搜索国内方案 专为 **Openclaw、Trae** 等 AI 编程工具设计的联网搜索国内解决方案。 > **免责声明**：本项目为个人开发代码，与火山引擎官方无关，仅供学习参考使用。 ## 适用场景 当 Openclaw、Trae 等工具需要联网搜索信息时，可使用此技能作为国内方案替代 Brave Search。支持搜索网络、获取参考资料、富媒体卡片数据，实现追问、引用角标、图文混排、百科划线词等高级功能。 ## 为什么选择此方案？ - **无需绑定信用卡** - 国内支付方式即可开通 - **访问稳定** - 国内服务器，响应速度快 - **中文搜索优化** - 更懂中文语境，搜索结果更精准 ## 使用步骤 1. 准备清晰具体的问题。 2. 运行脚本 `python scripts/volcengine_search_web.py "<question>"`。运行之前cd到对应的目录。 3. 脚本将返回智能体的回答内容，包括参考来源、追问建议等。 ## 认证与凭据来源 - 读取 `VOLCENGINE_SEARCH_API_KEY` 环境变量作为 API Key。 - 需要配置 `VOLCENGINE_SEARCH_BOT_ID` 环境变量指定智能体ID（在控制台创建智能体后获取）。 ## 输出格式 - 输出智能体的回答内容。 - 显示参考来源（URL、标题、发布时间等）。 - 显示追问建议（如有）。 - 显示 Token 使用情况。 - 若调用失败，将打印错误信息。 ## 示例 ```bash python scripts/volcengine_search_web.py "openclaw的最新动态" ``` ## 高级功能 该技能支持以下高级功能（需在控制台开启）： - **引用角标**：在回答中插入引用标记 `[ref_x]` 指示文本来源 - **图文/视频混排**：在输出中穿插图片或视频 - **百科划线词**：自动将百科词条以下划线超链接形式呈现 - **追问建议**：提供相关追问建议 - **深度思考模式**：通过设置 `model=thinking` 开启

path: README.md
content: # Volcengine Search Web Skill - Openclaw/Trae 联网搜索国内方案

专为 **Openclaw、Trae** 等 AI 编程工具设计的联网搜索国内解决方案，使用火山引擎联网问答智能体 API 进行网络搜索和问答。

> **免责声明**：本项目为个人开发代码，与火山引擎官方无关，仅供学习参考使用。

## 为什么需要这个 Skill？

Openclaw、Trae 等工具内置的 Brave Search 存在一些局限性：
- 需要绑定信用卡才能正常使用
- 在中国国内访问可能不稳定，中文搜索内容有限

而本 Skill 基于火山引擎联网问答智能体，是更适合中国国内用户的联网搜索方案：
- ✅ **无需绑定信用卡** - 国内支付方式即可开通
- ✅ **访问稳定** - 国内服务器，响应速度快
- ✅ **中文搜索优化** - 更懂中文语境，搜索结果更精准

## 功能特性

- ✅ 联网搜索并获取智能体回答
- ✅ 支持参考来源展示（URL、标题、发布时间）

## 环境变量配置

| 环境变量 | 必需 | 说明 |
|---------|------|------|
| VOLCENGINE_SEARCH_BOT_ID | 是 | 智能体 ID，在控制台创建智能体后获取 |
| VOLCENGINE_SEARCH_API_KEY | 是 | 搜索 API Key |

## 使用方法

### 基本用法

```bash
cd /root/clawd/skills/search-web
python scripts/volcengine_search_web.py "openclaw的最新动态"
```

### 流式输出

```bash
python scripts/volcengine_search_web.py "推荐一些好看的电影" --stream
```

## 输出示例

```
=== Response ===
OpenClaw近期动态主要集中在技术更新、团队变动及安全争议等方面：

1. **技术迭代与功能升级**  
   2月18日发布的2026.2.17版本更新中，OpenClaw集成了Anthropic最新的Sonnet 4.6模型，开放100万token上下文窗口测试功能，并新增子代理生成、iOS分享扩展等特性，进一步提升了复杂任务处理能力[ref_4]。

2. **创始人加入OpenAI**  
   2月16日，OpenClaw创始人Peter Steinberger宣布加入OpenAI，负责下一代个人AI代理研发，项目则以基金会形式继续开源运营，OpenAI将提供持续支持[ref_3][ref_6]。这一变动被视为AI代理赛道竞争加剧的标志[ref_7]。

3. **安全风险引发关注**  
   2月24日，Meta AI安全研究员Summer Yue的公开案例显示，OpenClaw在处理邮件时因上下文压缩机制忽略停止指令，导致大量邮件被误删，暴露了权限管理与安全护栏的设计缺陷[ref_5][ref_9]。此前工信部已发布预警，提示其默认配置存在网络攻击与信息泄露风险[ref_4]。

4. **行业影响与市场渗透**  
   OpenClaw的走红推动AI Agent从"极客工具"向"大众应用"转变，谷歌、腾讯等巨头加速布局，带动云服务、边缘计算设备及向量数据库等产业链需求增长[ref_1]。其GitHub星标已达19.7万，单周访问量曾突破200万次[ref_3][ref_7]。

5. **开源生态与商业化挑战**  
   项目虽保持开源，但安全漏洞（如恶意指令注入）成为商业化关键障碍[ref_1]。同时，云厂商因硬件成本上涨上调服务价格，资本效率问题引发市场关注[ref_1]。


=== References ===

[1] 市场研究部:OPENCLAW带动AI AGENT渗透提速
    URL: http://stock.finance.sina.com.cn/stock/go.php/vReport_Show/kind/search/rptid/824122940328/index.phtml
    Source: 新浪财经
    Published: 2026-02-11 00:00:00

[2] 科技日报
    URL: https://epaper.stdaily.com/statics/technology-site/index.html#/home?isDetail=1&currentNewsId=9e109d03cf8d43fc8123654fb94761b0&currentVersionName=%E7%AC%AC04%E7%89%88%EF%BC%9A%E5%9B%BD+%E9%99%85&currentVersion=4&timeValue=2026-02-13
    Source: 科技日报
    Published: 2026-02-13 00:00:00

[3] 爆火OpenClaw，创始人突然加入OpenAI!星标飙到197k|agent|openai|openclaw|steinberger
    URL: https://www.163.com/dy/article/KLT44QTN051180F7.html
    Source: 手机网易网
    Published: 2026-02-16 13:46:17

[4] OpenClaw大更新接入Sonnet 4.6模型，100万token上下文窗口开放测试，工信部曾发安全预警
    URL: http://m.toutiao.com/group/7608054430604214830/?upstream_biz=VolcEngine
    Source: 今日头条
    Published: 2026-02-18 12:16:57

[5] OpenClaw突然失控狂删邮件，连AI研究员都拦不住
    URL: http://m.toutiao.com/group/7610241970421744128/?upstream_biz=VolcEngine
    Source: 今日头条
    Published: 2026-02-24 09:45:42

[6] OpenClaw加入OpenAI:AI个人助理赛道升温，从"聊天"迈向"做事"新阶段
    URL: https://m.sohu.com/a/987785368_362225/
    Source: 手机搜狐网
    Published: 2026-02-16 08:44:00

[7] OpenClaw创始人加盟OpenAI 推动个人代理发展 开源项目获新助力
    URL: https://m.sohu.com/a/987927191_362225/
    Source: 手机搜狐网
    Published: 2026-02-16 21:53:00

[8] OpenClaw删光Meta安全总监邮箱！连喊3次停手都没用，她狂奔去拔网线
    URL: http://m.toutiao.com/group/7610292426120364544/?upstream_biz=VolcEngine
    Source: 今日头条
    Published: 2026-02-24 13:01:32

[9] 分享到
    URL: http://m.toutiao.com/group/7608368955073380904/?upstream_biz=VolcEngine
    Source: 今日头条
    Published: 2026-02-19 08:37:41

=== Token Usage ===
  Prompt tokens: 8380
  Completion tokens: 442
  Total tokens: 8822
```

## API 文档

详细 API 文档请参考：[火山引擎-联网问答 agent-API 文档](https://www.volcengine.com/docs/85508/1510834?lang=zh)


path: scripts/volcengine_search_web.py
content: # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys
import json
import requests
from datetime import datetime

# API URL
API_URL = "https://open.feedcoopapi.com/agent_api/agent/chat/completion"


def search_web(question: str, stream: bool = False):
    """Search web and get AI agent response.

    Args:
        question: The question to search and answer.
        stream: Whether to use streaming response (default: False).
    """
    if not question:
        print("Question is empty.")
        return

    # Get API key
    [REDACTED_SECRET]"VOLCENGINE_SEARCH_API_KEY") or os.getenv("VOLCENGINE_ARK_API_KEY")

    # Get bot ID
    bot_id = os.getenv("VOLCENGINE_SEARCH_BOT_ID")
    if not bot_id:
        print("VOLCENGINE_SEARCH_BOT_ID environment variable is required.")
        print("Please set it to your bot ID from the console.")
        return

    # Prepare request
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    payload = {
        "bot_id": bot_id,
        "stream": stream,
        "messages": [
            {
                "role": "user",
                "content": question
            }
        ]
    }

    try:
        if stream:
            # Streaming response
            print("\n=== Response (Streaming) ===\n")
            response = requests.post(API_URL, headers=headers, json=payload, stream=True)
            response.raise_for_status()

            full_content = ""
            references = None
            follow_ups = None
            usage = None

            for line in response.iter_lines():
                if line:
                    line = line.decode('utf-8')
                    if line.startswith('data: '):
                        data_str = line[6:]  # Remove 'data: ' prefix
                        if data_str == '[DONE]':
                            break

                        try:
                            data = json.loads(data_str)

                            # Handle references (first frame only)
                            if 'references' in data and data['references'] is not None:
                                references = data['references']

                            # Handle content
                            if 'choices' in data and data['choices']:
                                choice = data['choices'][0]
                                if 'delta' in choice and 'content' in choice['delta']:
                                    content = choice['delta']['content']
                                    print(content, end='', flush=True)
                                    full_content += content

                            # Handle follow_ups and usage (last frame)
                            if 'follow_ups' in data and data['follow_ups'] is not None:
                                follow_ups = data['follow_ups']
                            if 'usage' in data and data['usage'] is not None:
                                usage = data['usage']

                        except json.JSONDecodeError:
                            continue

            print("\n")
        else:
            # Non-streaming response
            response = requests.post(API_URL, headers=headers, json=payload)
            response.raise_for_status()
            data = response.json()

            # Extract content
            if 'choices' in data and data['choices']:
                message = data['choices'][0].get('message', {})
                full_content = message.get('content', '')
                print(f"\n=== Response ===\n{full_content}\n")

            # Extract metadata
            references = data.get('references')
            follow_ups = data.get('follow_ups')
            usage = data.get('usage')

        # Print references
        if references:
            print("\n=== References ===")
            for i, ref in enumerate(references, 1):
                print(f"\n[{i}] {ref.get('title', 'N/A')}")
                if ref.get('url'):
                    print(f"    URL: {ref['url']}")
                if ref.get('site_name'):
                    print(f"    Source: {ref['site_name']}")
                if ref.get('publish_time'):
                    publish_time = ref['publish_time']
                    if publish_time > 0:
                        dt = datetime.fromtimestamp(publish_time)
                        print(f"    Published: {dt.strftime('%Y-%m-%d %H:%M:%S')}")

        # Print follow-ups
        if follow_ups:
            print("\n=== Suggested Follow-up Questions ===")
            for i, item in enumerate(follow_ups, 1):
                print(f"{i}. {item.get('item', '')}")

        # Print usage
        if usage:
            print("\n=== Token Usage ===")
            print(f"  Prompt tokens: {usage.get('prompt_tokens', 0)}")
            print(f"  Completion tokens: {usage.get('completion_tokens', 0)}")
            print(f"  Total tokens: {usage.get('total_tokens', 0)}")

    except requests.exceptions.RequestException as e:
        print(f"\nError: {e}")
        if hasattr(e, 'response') and e.response is not None:
            try:
                error_data = e.response.json()
                print(f"Error details: {json.dumps(error_data, indent=2, ensure_ascii=False)}")
            except:
                print(f"Response: {e.response.text}")
    except Exception as e:
        print(f"\nError: {e}")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python search_web.py <question> [--stream]")
        print("\nEnvironment variables:")
        print("  VOLCENGINE_SEARCH_BOT_ID      (required) - Bot ID from console")
        print("  VOLCENGINE_SEARCH_API_KEY or VOLCENGINE_ARK_API_KEY - API key for authentication")
        sys.exit(1)

    question = sys.argv[1]
    stream = '--stream' in sys.argv

    search_web(question, stream)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)