Skill: whyhit2005/whyhit2005-zhipu-search (version 1.0.2)

--- name: zhipu-search description: | Zhipu AI Web Search Tool - Provides flexible search engine capabilities. Use when: - Need to search web information for latest data - Need specific search engines (Sogou, Quark, Zhipu Search) - Need to filter search results by time range or domain - Need to control result count and detail level Supported search engines: search_std (basic), search_pro (advanced), search_pro_sogou (Sogou), search_pro_quark (Quark) Supported parameters: search intent recognition, result count, time filter, domain filter, content size control metadata: { "openclaw": { "requires": { "env": ["ZHIPU_API_KEY"] }, }, } --- # Zhipu Search Web search via Zhipu AI API, supporting multiple search engines and flexible parameter configuration. ## Quick Start ### Basic Search ```python # Use default parameters search_query = "OpenClaw latest version" search_engine = "search_std" ``` ### Advanced Search (Full Parameters) ```python search_query = "AI development trends" # Required, max 70 chars search_engine = "search_pro" # Required: search_std/search_pro/search_pro_sogou/search_pro_quark search_intent = true # Optional, default false, enable search intent recognition count = 20 # Optional, default 10, range 1-50 search_domain_filter = "example.com" # Optional, whitelist domain filter search_recency_filter = "oneWeek" # Optional: oneDay/oneWeek/oneMonth/oneYear/noLimit content_size = "high" # Optional: medium/high, control content detail level request_id = "unique-request-id" # Optional, unique request identifier user_id = "user-123456" # Optional, end user ID (6-128 chars) ``` ## Usage Methods ### Method 1: Direct Script Call (Recommended) ```bash python scripts/zhipu_search.py \ --query "search content" \ --engine search_pro \ --count 10 ``` ### Method 2: Use OpenClaw Tool System will automatically select appropriate parameters based on needs. ## API Parameter Reference | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | search_query | string | ✅ | - | Search content, recommended ≤70 chars | | search_engine | enum | ✅ | - | search_std/search_pro/search_pro_sogou/search_pro_quark | | search_intent | boolean | - | false | Enable search intent recognition | | count | integer | - | 10 | Result count, 1-50 | | search_domain_filter | string | - | - | Whitelist domain filter | | search_recency_filter | enum | - | noLimit | oneDay/oneWeek/oneMonth/oneYear/noLimit | | content_size | enum | - | - | medium/high, control content length | | request_id | string | - | - | Unique request identifier | | user_id | string | - | - | End user ID (6-128 chars) | ## Search Engine Selection Guide | Engine | Use Case | |--------|----------| | search_std | Basic search, regular Q&A | | search_pro | Advanced search, need more accurate results | | search_pro_sogou | Sogou search, China domestic content | | search_pro_quark | Quark search, specific scenarios | ## Response Structure ```json { "id": "task-id", "created": 1704067200, "request_id": "request-id", "search_intent": [ { "query": "original query", "intent": "SEARCH_ALL", "keywords": "rewritten keywords" } ], "search_result": [ { "title": "title", "content": "content summary", "link": "result link", "media": "site name", "icon": "site icon", "refer": "reference number", "publish_date": "publish date" } ] } ``` ## Environment Requirements - Environment variable `ZHIPU_API_KEY` must be configured - Python 3.7+ - requests library ## Notes 1. search_query should be kept within 70 characters 2. search_pro_sogou count must be 10/20/30/40/50 3. user_id length must be between 6-128 characters if provided 4. Search intent recognition increases response time but improves result relevance

path: scripts/zhipu_search.py
content: #!/usr/bin/env python3
"""
Zhipu AI Web Search API Script
Call web_search tool via chat completions API
"""

import os
import sys
import json
import argparse
import requests
import re
from typing import Optional, Dict, Any, List
from urllib.parse import urlparse


API_BASE_URL = "https://open.bigmodel.cn/api/paas/v4"


def search(
    search_query: str,
    search_engine: str = "search_pro_quark",
    search_intent: bool = False,
    count: int = 10,
    search_domain_filter: Optional[str] = None,
    search_recency_filter: Optional[str] = None,
    content_size: Optional[str] = None,
    request_id: Optional[str] = None,
    user_id: Optional[str] = None,
) -> Dict[str, Any]:
    """
    Call Zhipu Search API
    
    Parameters consistent with Zhipu API, providing maximum flexibility
    """
    [REDACTED_SECRET]"ZHIPU_API_KEY")
    if not api_key:
        raise ValueError("ZHIPU_API_KEY environment variable is not set")
    
    # Build tool call parameters
    tool_params = {
        "search_query": search_query,
        "search_engine": search_engine,
        "search_intent": search_intent,
        "count": count,
    }
    
    # Add optional parameters
    if search_domain_filter:
        tool_params["search_domain_filter"] = search_domain_filter
    if search_recency_filter:
        tool_params["search_recency_filter"] = search_recency_filter
    if content_size:
        tool_params["content_size"] = content_size
    
    # Build request body - using function calling
    payload: Dict[str, Any] = {
        "model": "glm-4-flash",
        "messages": [
            {"role": "system", "content": "You are an AI assistant capable of using search tools. When users need to search for information, use the web_search tool."},
            {"role": "user", "content": search_query}
        ],
        "tools": [
            {
                "type": "web_search",
                "web_search": {
                    "enable": True,
                    **tool_params
                }
            }
        ],
        "tool_choice": "auto",
    }
    
    # Add optional metadata
    if request_id:
        payload["request_id"] = request_id
    if user_id:
        payload["user_id"] = user_id
    
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }
    
    response = requests.post(
        f"{API_BASE_URL}/chat/completions",
        headers=headers,
        json=payload,
        timeout=60,
    )
    
    response.raise_for_status()
    result = response.json()
    
    # Extract search results
    return extract_search_results(result, search_query)


def extract_search_results(response: Dict[str, Any], original_query: str) -> Dict[str, Any]:
    """Extract search results from API response"""
    output = {
        "id": response.get("id", ""),
        "created": response.get("created", 0),
        "request_id": response.get("request_id", ""),
        "search_query": original_query,
        "search_intent": [],
        "search_result": [],
        "raw_response": response,
    }
    
    # Try to extract tool call results from choices
    choices = response.get("choices", [])
    if not choices:
        return output
    
    message = choices[0].get("message", {})
    
    # Check tool_calls (structured search results)
    tool_calls = message.get("tool_calls", [])
    for tool_call in tool_calls:
        if tool_call.get("type") == "web_search":
            web_search_result = tool_call.get("web_search", {})
            if "search_intent" in web_search_result:
                output["search_intent"] = web_search_result["search_intent"]
            if "search_result" in web_search_result:
                output["search_result"] = web_search_result["search_result"]
    
    # If no structured results, try parsing from content
    content = message.get("content", "")
    if content and not output["search_result"]:
        # Try parsing JSON
        try:
            if isinstance(content, str) and content.strip().startswith("{"):
                parsed = json.loads(content)
                if "search_result" in parsed:
                    output["search_result"] = parsed["search_result"]
                elif "results" in parsed:
                    output["search_result"] = parsed["results"]
        except:
            pass
        
        # If JSON parsing fails, try extracting links and info from text
        if not output["search_result"]:
            parsed_results = parse_text_to_results(content)
            if parsed_results:
                output["search_result"] = parsed_results
    
    output["model_response"] = content
    return output


def parse_text_to_results(text: str) -> List[Dict[str, Any]]:
    """Try to extract search results from text content"""
    results = []
    
    # Match URL patterns
    url_pattern = r'https?://[^\s\)\]\>\"\']+'
    urls = re.findall(url_pattern, text)
    
    # Split by paragraphs
    paragraphs = text.split('\n\n')
    
    for i, para in enumerate(paragraphs):
        # Find paragraphs containing URLs
        para_urls = re.findall(url_pattern, para)
        if para_urls or (para.strip() and len(para) > 20):
            # Try to extract title (usually shorter sentence or bold content)
            lines = para.strip().split('\n')
            title = lines[0][:100] if lines else f"Result {i+1}"
            
            # Clean title
            title = re.sub(r'^\d+\.\s*', '', title)
            title = re.sub(r'^[\*\-\#]+\s*', '', title)
            
            result = {
                "title": title,
                "content": para[:500],
                "link": para_urls[0] if para_urls else "",
                "media": extract_domain(para_urls[0]) if para_urls else "",
            }
            results.append(result)
    
    return results[:10]  # Max 10 results


def extract_domain(url: str) -> str:
    """Extract domain from URL"""
    try:
        parsed = urlparse(url)
        return parsed.netloc.replace('www.', '')
    except:
        return ""


def format_results(data: Dict[str, Any]) -> str:
    """Format search results to readable text"""
    lines = []
    
    lines.append(f"🔍 Search: {data.get('search_query', 'N/A')}")
    lines.append("")
    
    # Search intent info
    if data.get("search_intent"):
        lines.append("=== Search Intent ===")
        for intent in data["search_intent"]:
            lines.append(f"Original Query: {intent.get('query', 'N/A')}")
            lines.append(f"Intent: {intent.get('intent', 'N/A')}")
            lines.append(f"Keywords: {intent.get('keywords', 'N/A')}")
        lines.append("")
    
    # Search results
    results = data.get("search_result", [])
    if results:
        lines.append(f"=== Search Results ({len(results)} total) ===")
        for idx, result in enumerate(results, 1):
            lines.append(f"\n[{idx}] {result.get('title', 'No Title')}")
            if result.get('media'):
                lines.append(f"    Source: {result['media']}")
            if result.get('link'):
                lines.append(f"    Link: {result['link']}")
            if result.get('publish_date'):
                lines.append(f"    Published: {result['publish_date']}")
            content = result.get('content', '')
            if content:
                lines.append(f"    Summary: {content[:200]}{'...' if len(content) > 200 else ''}")
    else:
        lines.append("No structured search results found")
        # Show model response
        if data.get("model_response"):
            lines.append("\n=== Model Response ===")
            lines.append(data["model_response"][:1000])
    
    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="Zhipu AI Web Search Tool")
    
    # Required parameters
    parser.add_argument(
        "--query", "-q",
        required=True,
        help="Search content (search_query), recommended max 70 chars"
    )
    parser.add_argument(
        "--engine", "-e",
        default="search_pro_quark",
        choices=["search_std", "search_pro", "search_pro_sogou", "search_pro_quark"],
        help="Search engine (search_engine), default: search_pro_quark"
    )
    
    # Optional parameters
    parser.add_argument(
        "--intent", "-i",
        action="store_true",
        help="Enable search intent recognition (search_intent)"
    )
    parser.add_argument(
        "--count", "-c",
        type=int,
        default=10,
        help="Result count (count), range 1-50, default: 10"
    )
    parser.add_argument(
        "--domain-filter", "-d",
        help="Domain whitelist filter (search_domain_filter)"
    )
    parser.add_argument(
        "--recency", "-r",
        choices=["oneDay", "oneWeek", "oneMonth", "oneYear", "noLimit"],
        help="Time range filter (search_recency_filter)"
    )
    parser.add_argument(
        "--content-size", "-s",
        choices=["medium", "high"],
        help="Content size control (content_size): medium(summary) / high(detailed)"
    )
    parser.add_argument(
        "--request-id",
        help="Unique request identifier (request_id)"
    )
    parser.add_argument(
        "--user-id", "-u",
        help="End user ID (user_id), 6-128 chars"
    )
    parser.add_argument(
        "--json", "-j",
        action="store_true",
        help="Output raw JSON format"
    )
    
    args = parser.parse_args()
    
    try:
        result = search(
            search_query=args.query,
            search_engine=args.engine,
            search_intent=args.intent,
            count=args.count,
            search_domain_filter=args.domain_filter,
            search_recency_filter=args.recency,
            content_size=args.content_size,
            request_id=args.request_id,
            user_id=args.user_id,
        )
        
        if args.json:
            # Remove raw_response to reduce output
            output = {k: v for k, v in result.items() if k != "raw_response"}
            print(json.dumps(output, ensure_ascii=False, indent=2))
        else:
            print(format_results(result))
            
    except requests.exceptions.RequestException as e:
        print(f"API request failed: {e}", file=sys.stderr)
        sys.exit(1)
    except ValueError as e:
        print(f"Configuration error: {e}", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"Unknown error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()


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