Skip to content

Conversation

@PaloMiku
Copy link
Contributor

@PaloMiku PaloMiku commented Sep 18, 2025

fixes #2811


Motivation / 动机

为 AstrBot WebSearch 增加了智谱搜索 API 的接入支持,优势具体介绍请查看相关 Issue .

Modifications / 改动点

引入了智谱 AI 搜索的 Python 包,以及它的API支持,修改了和增加 WebSearch 部分的组件代码。

Verification Steps / 验证步骤

Clone 并启动代码,在设置中增加智谱 AI (zai)搜索信息,结合 LLM 测试。

Screenshots or Test Results / 运行截图或测试结果

图片 图片
 [04:59:43] [Core] [INFO] [core.event_bus:52]: [default] [webchat(webchat)] PaloMiku/PaloMiku: Astrbot和其相关信息,最新版本  

 [04:59:43] [Plug] [INFO] [web_searcher.main:458]: web_searcher - provider: zai, enabled: True 

 [04:59:43] [Core] [INFO] [respond.stage:161]: Prepare to send - PaloMiku/PaloMiku:  

 [04:59:43] [Core] [INFO] [respond.stage:173]: 应用流式输出(webchat) 

 [04:59:45] [Core] [INFO] [runners.tool_loop_agent_runner:187]: Agent 使用工具: ['web_search_zai'] 

 [04:59:45] [Core] [INFO] [runners.tool_loop_agent_runner:199]: 使用工具:web_search_zai,参数:{'query': 'Astrbot 最新版本 2024 2025', 'max_results': 10, 'content_size': 'medium'} 

 [04:59:45] [Plug] [INFO] [web_searcher.main:399]: web_searcher - search_from_zai: Astrbot 最新版本 2024 2025 

 [04:59:54] [Core] [WARN] [result_decorate.stage:111]: 启用流式输出时,依赖发送消息前事件钩子的插件可能无法正常工作 

 [04:59:54] [Core] [INFO] [result_decorate.stage:130]: 流式输出已启用,跳过结果装饰阶段 

Compatibility & Breaking Changes / 兼容性与破坏性变更

  • 这是一个破坏性变更 (Breaking Change)。/ This is a breaking change.
  • 这不是一个破坏性变更。/ This is NOT a breaking change.

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Sourcery 总结

将智谱 AI (ZAI) 作为 AstrBot 的新 WebSearch 提供商,通过添加 SDK 依赖、API 包装器、配置选项、动态工具注册以及配套文档。

新功能:

  • 为 AstrBot 添加智谱 AI (ZAI) WebSearch 提供商集成,包括 API 包装器和 LLM 工具

改进:

  • 扩展动态工具管理,以根据提供商设置启用或禁用 ZAI 搜索
  • 降低配置中的默认 max_agent_step,以减少冗余的 API 调用

构建:

  • 引入 zai-sdk 依赖用于 ZAI 集成

文档:

  • 添加详细的用户文档,用于智谱 AI WebSearch 集成

杂项:

  • 添加配置默认值和 Schema 条目,用于 websearch_zai 键、引擎、结果计数、内容大小和时效性过滤器
Original summary in English

Summary by Sourcery

Integrate Zhipu AI (ZAI) as a new WebSearch provider in AstrBot by adding SDK dependency, API wrapper, configuration options, dynamic tool registration, and accompanying documentation.

New Features:

  • Add Zhipu AI (ZAI) WebSearch provider integration for AstrBot, including API wrapper and LLM tool

Enhancements:

  • Extend dynamic tool management to enable or disable ZAI search based on provider settings
  • Lower default max_agent_step in config to reduce redundant API calls

Build:

  • Introduce zai-sdk dependency for ZAI integration

Documentation:

  • Add detailed user-facing documentation for Zhipu AI WebSearch integration

Chores:

  • Add configuration defaults and schema entries for websearch_zai keys, engine, result count, content size, and recency filter

@auto-assign auto-assign bot requested review from Larch-C and anka-afk September 18, 2025 21:05
@PaloMiku PaloMiku changed the title feat: 为 AstrBot WebSearch 添加智谱 AI 搜索引擎支持及相关配置 Feature: 为 AstrBot WebSearch 添加智谱 AI 搜索引擎支持及相关配置 Sep 18, 2025
@PaloMiku PaloMiku changed the title Feature: 为 AstrBot WebSearch 添加智谱 AI 搜索引擎支持及相关配置 Feature: 为 WebSearch 添加智谱 AI 搜索引擎支持及相关配置 Sep 18, 2025
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • There’s duplicated ZAI HTTP call logic in main.py and engines/zai.py—consolidate into a single wrapper to avoid redundancy.
  • The repeated remove_tool calls in edit_web_search_tools could be extracted into a helper or loop to reduce boilerplate and improve readability.
  • The change of default max_agent_step from 30 to 10 is orthogonal to ZAI integration—consider separating or justifying that adjustment in its own PR.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There’s duplicated ZAI HTTP call logic in main.py and engines/zai.py—consolidate into a single wrapper to avoid redundancy.
- The repeated remove_tool calls in edit_web_search_tools could be extracted into a helper or loop to reduce boilerplate and improve readability.
- The change of default max_agent_step from 30 to 10 is orthogonal to ZAI integration—consider separating or justifying that adjustment in its own PR.

## Individual Comments

### Comment 1
<location> `packages/web_searcher/main.py:205-214` </location>
<code_context>
+
+        logger.info(f"ZAI搜索 - 查询: {query} | 引擎: {self.search_engine}")
+
+        async with aiohttp.ClientSession(trust_env=True) as session:
+            try:
+                async with session.post(
+                    f"{self.base_url}/web_search",
+                    json=payload,
+                    headers=headers,
+                    timeout=aiohttp.ClientTimeout(total=self.timeout),
+                ) as response:
+                    if response.status != 200:
+                        error_text = await response.text()
+                        raise Exception(
+                            f"ZAI API错误: {response.status} - {error_text}"
+                        )
+
+                    data = await response.json()
+                    results = self._parse_search_results(data)
+                    logger.info(f"ZAI搜索完成 - 返回 {len(results)} 个结果")
</code_context>

<issue_to_address>
**suggestion (performance):** Timeout value for ZAI search is set to 6 seconds, which may be too short for some network conditions.

The aiohttp timeout here is much shorter than the 30 seconds used elsewhere in the ZAI engine wrapper. Increasing it would help prevent failures on slow networks or with large queries.
</issue_to_address>

### Comment 2
<location> `packages/web_searcher/main.py:413` </location>
<code_context>
+            "websearch_zai_content_size", "high"
+        )
+
+        if search_recency_filter == "oneWeek":
+            search_recency_filter = prov_settings.get(
+                "websearch_zai_recency_filter", "oneWeek"
</code_context>

<issue_to_address>
**issue (bug_risk):** The logic for search_recency_filter may override explicit user input.

This assignment replaces the user's 'oneWeek' input with the config value. To preserve user intent, check if search_recency_filter is None before applying the default.
</issue_to_address>

### Comment 3
<location> `packages/web_searcher/main.py:466-471` </location>
<code_context>

         if not websearch_enable:
-            # pop tools
             for tool_name in self.TOOLS:
-                tool_set.remove_tool(tool_name)
+                try:
+                    tool_set.remove_tool(tool_name)
+                except Exception:
+                    pass
             return
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Suppressing all exceptions when removing tools may hide underlying issues.

Instead of passing silently, log the exception or only catch specific expected errors to avoid hiding bugs.

```suggestion
            for tool_name in self.TOOLS:
                try:
                    tool_set.remove_tool(tool_name)
                except Exception as e:
                    logger.warning(f"Failed to remove tool '{tool_name}': {e}", exc_info=True)
            return
```
</issue_to_address>

### Comment 4
<location> `packages/web_searcher/main.py:449` </location>
<code_context>
+        return ret
+
     @filter.on_llm_request(priority=-10000)
     async def edit_web_search_tools(
         self, event: AstrMessageEvent, req: ProviderRequest
-    ) -> str:
</code_context>

<issue_to_address>
**issue (complexity):** Consider centralizing provider-to-tool mapping and key rotation logic to reduce duplication and improve maintainability.

```python
# 1. Consolidate provider→tool mapping at the class level
class Main(star.Star):
    TOOLS = {
        "default": ["web_search", "fetch_url"],
        "tavily": ["web_search_tavily", "tavily_extract_web_page"],
        "zai":    ["web_search_zai"],
    }
    ALL_TOOLS = sum(TOOLS.values(), [])

    @filter.on_llm_request(priority=-10000)
    async def edit_web_search_tools(self, event, req: ProviderRequest):
        cfg = self.context.get_config(umo=event.unified_msg_origin)
        prov = cfg.get("provider_settings", {})
        websearch_enabled = prov.get("web_search", False)
        provider = prov.get("websearch_provider", "default")
        func_mgr = self.context.get_llm_tool_manager()

        # normalize func_tool
        tool_set = req.func_tool
        if isinstance(tool_set, FunctionToolManager):
            tool_set = req.func_tool = tool_set.get_full_tool_set()

        if not websearch_enabled:
            for t in self.ALL_TOOLS:
                tool_set.remove_tool(t, ignore_errors=True)
            return

        # calculate add/remove lists
        keep = set(self.TOOLS.get(provider, self.TOOLS["default"]))
        remove = set(self.ALL_TOOLS) - keep

        for name in remove:
            tool_set.remove_tool(name, ignore_errors=True)
        for name in keep:
            fn = func_mgr.get_func(name)
            if fn:
                tool_set.add_tool(fn)
```

```python
# 2. Merge _get_tavily_key and _get_zai_key into one rotation helper
class Main(...):
    def __init__(...):
        self._key_indexes = defaultdict(int)
        self._key_locks = defaultdict(asyncio.Lock)

    async def _rotate_key(self, cfg: AstrBotConfig, key_name: str) -> str:
        keys = cfg.get("provider_settings", {}).get(key_name, [])
        if not keys:
            raise ValueError(f"Error: {key_name} is not configured")
        lock = self._key_locks[key_name]
        async with lock:
            idx = self._key_indexes[key_name]
            key = keys[idx]
            self._key_indexes[key_name] = (idx + 1) % len(keys)
        return key

    # existing methods now become thin wrappers:
    async def _get_tavily_key(self, cfg):
        return await self._rotate_key(cfg, "websearch_tavily_key")

    async def _get_zai_key(self, cfg):
        return await self._rotate_key(cfg, "websearch_zai_keys")
```
These changes collapse duplicated branching in `edit_web_search_tools` into a single loop driven by a mapping, and unify the key‐rotation logic into one helper, preserving all current behavior.
</issue_to_address>

### Comment 5
<location> `packages/web_searcher/engines/zai.py:102-104` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 6
<location> `packages/web_searcher/engines/zai.py:112` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 7
<location> `packages/web_searcher/main.py:211-213` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 8
<location> `packages/web_searcher/engines/zai.py:70` </location>
<code_context>
    def _build_payload(
        self,
        query: str,
        count: int,
        search_domain_filter: str,
        search_recency_filter: str,
        content_size: str,
    ) -> Dict[str, Any]:
        """构建请求参数"""
        payload = {
            "search_engine": self.search_engine,
            "search_query": query,
            "count": min(max(count, 1), 50),
        }

        if search_domain_filter:
            payload["search_domain_filter"] = search_domain_filter
        if search_recency_filter != "noLimit":
            payload["search_recency_filter"] = search_recency_filter
        if content_size in ["low", "medium", "high"]:
            payload["content_size"] = content_size

        return payload

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use set when checking membership of a collection of literals ([`collection-into-set`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/collection-into-set/))

```suggestion
        if content_size in {"low", "medium", "high"}:
```
</issue_to_address>

### Comment 9
<location> `packages/web_searcher/engines/zai.py:111-112` </location>
<code_context>
    async def search(
        self,
        query: str,
        count: int = 10,
        search_domain_filter: str = "",
        search_recency_filter: str = "oneWeek",
        content_size: str = "high",
    ) -> List[SearchResult]:
        """使用 Z.AI Web Search API 进行搜索"""
        api_key = self._get_random_api_key()
        headers = self._build_headers(api_key)
        payload = self._build_payload(
            query, count, search_domain_filter, search_recency_filter, content_size
        )

        logger.info(f"ZAI搜索 - 查询: {query} | 引擎: {self.search_engine}")

        async with aiohttp.ClientSession(trust_env=True) as session:
            try:
                async with session.post(
                    f"{self.base_url}/web_search",
                    json=payload,
                    headers=headers,
                    timeout=aiohttp.ClientTimeout(total=self.timeout),
                ) as response:
                    if response.status != 200:
                        error_text = await response.text()
                        raise Exception(
                            f"ZAI API错误: {response.status} - {error_text}"
                        )

                    data = await response.json()
                    results = self._parse_search_results(data)
                    logger.info(f"ZAI搜索完成 - 返回 {len(results)} 个结果")
                    return results

            except asyncio.TimeoutError:
                raise Exception("ZAI API请求超时")
            except Exception as e:
                logger.error(f"ZAI搜索失败: {e}")
                raise

</code_context>

<issue_to_address>
**suggestion (code-quality):** Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))

```suggestion
            except asyncio.TimeoutError as e:
                raise Exception("ZAI API请求超时") from e
```
</issue_to_address>

### Comment 10
<location> `packages/web_searcher/main.py:32` </location>
<code_context>
    def __init__(self, context: star.Context) -> None:
        self.context = context
        self.tavily_key_index = 0
        self.tavily_key_lock = asyncio.Lock()
        self.zai_key_index = 0
        self.zai_key_lock = asyncio.Lock()

        # 将 str 类型的 key 迁移至 list[str],并保存
        cfg = self.context.get_config()
        provider_settings = cfg.get("provider_settings")
        if provider_settings:
            tavily_key = provider_settings.get("websearch_tavily_key")
            if isinstance(tavily_key, str):
                logger.info(
                    "检测到旧版 websearch_tavily_key (字符串格式),自动迁移为列表格式并保存。"
                )
                if tavily_key:
                    provider_settings["websearch_tavily_key"] = [tavily_key]
                else:
                    provider_settings["websearch_tavily_key"] = []
                cfg.save_config()

        self.bing_search = Bing()
        self.sogo_search = Sogo()
        self.google = Google()

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))
</issue_to_address>

### Comment 11
<location> `packages/web_searcher/main.py:504-505` </location>
<code_context>
    @filter.on_llm_request(priority=-10000)
    async def edit_web_search_tools(
        self, event: AstrMessageEvent, req: ProviderRequest
    ):
        """动态管理搜索工具的可用性"""
        cfg = self.context.get_config(umo=event.unified_msg_origin)
        prov_settings = cfg.get("provider_settings", {})
        websearch_enable = prov_settings.get("web_search", False)
        provider = prov_settings.get("websearch_provider", "default")

        logger.info(f"web_searcher - provider: {provider}, enabled: {websearch_enable}")

        tool_set = req.func_tool
        if isinstance(tool_set, FunctionToolManager):
            req.func_tool = tool_set.get_full_tool_set()
            tool_set = req.func_tool

        if not websearch_enable:
            for tool_name in self.TOOLS:
                try:
                    tool_set.remove_tool(tool_name)
                except Exception:
                    pass
            return

        func_tool_mgr = self.context.get_llm_tool_manager()

        if provider == "default":
            web_search_t = func_tool_mgr.get_func("web_search")
            fetch_url_t = func_tool_mgr.get_func("fetch_url")
            if web_search_t:
                tool_set.add_tool(web_search_t)
            if fetch_url_t:
                tool_set.add_tool(fetch_url_t)
            for tool in [
                "web_search_tavily",
                "tavily_extract_web_page",
                "web_search_zai",
            ]:
                try:
                    tool_set.remove_tool(tool)
                except Exception:
                    pass
        elif provider == "tavily":
            web_search_tavily = func_tool_mgr.get_func("web_search_tavily")
            tavily_extract_web_page = func_tool_mgr.get_func("tavily_extract_web_page")
            if web_search_tavily:
                tool_set.add_tool(web_search_tavily)
            if tavily_extract_web_page:
                tool_set.add_tool(tavily_extract_web_page)
            for tool in ["web_search", "fetch_url", "web_search_zai"]:
                try:
                    tool_set.remove_tool(tool)
                except Exception:
                    pass
        elif provider == "zai":
            web_search_zai = func_tool_mgr.get_func("web_search_zai")
            if web_search_zai:
                tool_set.add_tool(web_search_zai)
            for tool in [
                "web_search",
                "fetch_url",
                "web_search_tavily",
                "tavily_extract_web_page",
            ]:
                try:
                    tool_set.remove_tool(tool)
                except Exception:
                    pass

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))

```suggestion
            if web_search_zai := func_tool_mgr.get_func("web_search_zai"):
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@PaloMiku
Copy link
Contributor Author

理论上很好,但是有时候会循环查询某一个问题,瞬间就蒸发大量搜索额度(

@PaloMiku
Copy link
Contributor Author

理论上很好,但是有时候会循环查询某一个问题,瞬间就蒸发大量搜索额度(

 [05:37:06] [Core] [INFO] [runners.tool_loop_agent_runner:187]: Agent 使用工具: ['web_search_zai'] 

 [05:37:06] [Core] [INFO] [runners.tool_loop_agent_runner:199]: 使用工具:web_search_zai,参数:{'query': 'Misskey 最新版本 release 2024', 'max_results': 10} 

 [05:37:06] [Plug] [INFO] [web_searcher.main:383]: web_searcher - search_from_zai: Misskey 最新版本 release 2024 

 [05:37:06] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 release 2024 | 引擎: search_pro 

 [05:37:08] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:10] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝帮您搜索Misskey的最新版本和相关信息哦!  

 [05:37:10] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 2024 2025 | 引擎: search_pro 

 [05:37:12] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:14] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您查找Misskey的最新版本和相关信息!  

 [05:37:14] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 2024 2025 | 引擎: search_pro 

 [05:37:15] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:16] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您查找Misskey的最新版本信息!  

 [05:37:16] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 release 2024 | 引擎: search_pro 

 [05:37:18] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:20] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您查找Misskey的最新版本和相关信息哦!  

 [05:37:21] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 2024 2025 | 引擎: search_pro 

 [05:37:22] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:24] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝帮您查找Misskey的最新版本和相关信息!  

 [05:37:24] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 2024 2025 | 引擎: search_pro 

 [05:37:26] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:29] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您查找Misskey的最新版本和相关信息!  

 [05:37:29] [Core] [INFO] [runners.tool_loop_agent_runner:187]: Agent 使用工具: ['web_search_zai'] 

 [05:37:29] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 2024 2025 | 引擎: search_pro 

 [05:37:31] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:33] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您查找Misskey的最新版本信息!  

 [05:37:33] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 2024 | 引擎: search_pro 

 [05:37:35] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:36] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您查找Misskey的最新版本信息!  

 [05:37:36] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 release | 引擎: search_pro 

 [05:37:38] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:39] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您搜索Misskey的最新版本信息!  

 [05:37:39] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 release 2024 2025 | 引擎: search_pro 

 [05:37:41] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

 [05:37:43] [Core] [INFO] [respond.stage:161]: Prepare to send - NukiMiku/a80cfh1slda207r3: 主人,蓝来帮您查找Misskey的最新版本和相关信息!  

 [05:37:43] [Plug] [INFO] [engines.zai:108]: ZAI搜索 - 查询: Misskey 最新版本 2024 | 引擎: search_pro 

 [05:37:45] [Plug] [INFO] [engines.zai:126]: ZAI搜索完成 - 返回 10 个结果 

@Soulter
Copy link
Member

Soulter commented Sep 22, 2025

有没有不需要新增依赖的方案 > <

@PaloMiku
Copy link
Contributor Author

PaloMiku commented Sep 22, 2025

有没有不需要新增依赖的方案 > <

这不大现实,调用他们搜索 api sdk 的基本是他们依赖包给的

而且现在还有 bug(反复调用,我还没看出来问题在模型还是代码上(~_~💧))

@PaloMiku
Copy link
Contributor Author

有没有不需要新增依赖的方案 > <

这不大现实,调用他们搜索 api sdk 的基本是他们依赖包给的

而且现在还有 bug(反复调用,我还没看出来问题在模型还是代码上(~_~💧))

不需要新增依赖的方案也有,用智谱的 MCP,但是可自定义部分内容很少而且我自己测试就没成功调用过

@PaloMiku
Copy link
Contributor Author

有没有不需要新增依赖的方案 > <

这不大现实,调用他们搜索 api sdk 的基本是他们依赖包给的

而且现在还有 bug(反复调用,我还没看出来问题在模型还是代码上(~_~💧))

换个别人现成的 MCP 倒是成功调用了

反复调用应该是模型的问题(为什么智谱的 GLM 4.5 加自家 AI 搜索会不停调用啊QAQ,自家模型和自家产品适配都做不好吗?换 Moonshot 和 Deepseek 模型都不会有反复调用的问题)

@PaloMiku
Copy link
Contributor Author

有没有不需要新增依赖的方案 > <

这不大现实,调用他们搜索 api sdk 的基本是他们依赖包给的

而且现在还有 bug(反复调用,我还没看出来问题在模型还是代码上(~_~💧))

换个别人现成的 MCP 倒是成功调用了

反复调用应该是模型的问题(为什么智谱的 GLM 4.5 加自家 AI 搜索会不停调用啊QAQ,自家模型和自家产品适配都做不好吗?换 Moonshot 和 Deepseek 模型都不会有反复调用的问题)

好吧也有但是智谱的最严重

@PaloMiku PaloMiku closed this Sep 26, 2025
@PaloMiku PaloMiku deleted the feature/zai-search branch September 26, 2025 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] WebSearch 增加智谱联网搜索 API

2 participants