网络搜索子智能体与Tavily工具

Description: 写清什么时候找我

网络搜索助手的 description 要解决的是路由问题:主智能体看到用户任务后,能不能判断该不该把任务交给它

比如用户问:查询 2025 年某医药政策的最新公开信息。这类问题需要外部公开资料,应该交给网络搜索助手。

但如果用户问:查询公司数据库里布洛芬的库存。这类问题属于内部业务数据,不应该交给网络搜索助手,而应该交给数据库查询助手

System_prompt: 写清怎么搜索

system_prompt 要约束网络搜索助手自己的执行方式。对这个助手来说,最重要的是两件事:搜索要有覆盖面,搜索次数要有上限

约束 作用
至少从 3 个角度检索 避免只搜一个关键词就结束
最多搜索 5 次 避免无限调用工具造成死循环

tools: 真正接入的能力

工具名 作用
internet_search 使用 Tavily 查询互联网公开信息,返回结构化搜索结果

完善prompts.yaml中的配置

# DeepAgents 提示词配置:集中管理主智能体和各子智能体的名称、路由描述与系统提示词
# 主智能体读取 description 做任务分派,子智能体读取 system_prompt 约束自己的执行方式

# 子智能体配置:description 给主智能体判断是否调用,system_prompt 给子智能体约束执行方式
sub_agents:
  # 网络搜索助手只处理互联网公开信息;数据库和 RAGFlow 私有知识库由其他助手负责
  tavily:
    name: "网络搜索助手"
    description: |
      负责进行网络知识搜索的智能体助手,当需要从网络中查询数据的时候,可以执行数据检索,在检索后会返回一段检索结果。
      注意:在需要进行非内部信息,不是数据库数据和 RAGFlow 知识库数据的公开信息查询时,务必使用此助手进行查询。
    system_prompt: |
      你是一个专业的网络信息查询助手,你可以根据用户的问题,从互联网中检索相关信息。
      你掌握的工具包括 internet_search 工具,此工具可以根据用户的问题,从互联网中检索非内部的公开信息。
      在检索网络知识的时候,至少检索 3 个角度的该问题,一共最多进行 5 次检索,如果超过 5 次,则不允许继续检索。

读这段配置时,先抓住一个边界:网络搜索助手只负责外部公开信息

准备Tavily配置

在.env中添加tavily key

为什么使用tavily

Tavily 是面向大模型使用的搜索 API,它返回的结果通常已经整理成结构化字段,比较适合 Agent 继续阅读和总结

实现并验证internet_search工具

 

整工具可以先按五段理解:

  1. 导入依赖和加载 .env
  2. 创建 TavilyClient
  3. 用 @tool 把普通函数注册成 Agent 可以调用的工具。
  4. 在工具内部调用 monitor.report_tool(...),把搜索参数推给前端。
  5. 提供 __main__ 本地调试入口,方便先验证 Key 和 Tavily API 是否可用。
import os
from typing import Literal

from dotenv import load_dotenv
from langchain_core.tools import tool
from tavily import TavilyClient

from app.api.monitor import monitor

load_dotenv()


# TavilyClient 是实际访问搜索服务的客户端;模块级复用可避免每次工具调用重复初始化
tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))


# @tool 会把函数签名和 docstring 暴露给 DeepAgents,模型据此决定是否调用以及如何填参
@tool
def internet_search(
    query: str,
    topic: Literal["news", "finance", "general"] = "general",
    max_results: int = 5,
    include_raw_content: bool = False,
):
    """
    根据用户问题检索互联网公开信息

    注意:本工具只用于外部公开网页、新闻、政策等信息,不用于查询业务数据库或 RAGFlow 私有知识库
    :param query: 搜索关键词或自然语言问题
    :param topic: 搜索主题,可选 news、finance、general
    :param max_results: 返回的最大结果数
    :param include_raw_content: 是否返回网页原文内容;False 返回摘要,True 尝试返回更完整正文
    :return: Tavily 返回的结构化搜索结果
    """
    # 工具内部埋点比外层 stream 解析更直接:只要工具被调用,前端就能看到本次搜索参数
    # 这里只上报查询参数,不上报搜索结果正文,避免监控事件体过大
    monitor.report_tool(
        tool_name="网络搜索工具",
        args={
            "query": query,
            "topic": topic,
            "max_results": max_results,
            "include_raw_content": include_raw_content,
        },
    )

    # Tavily 返回 query、results、title、url、content 等结构化字段,后续由子智能体阅读并汇总
    return tavily_client.search(
        query=query,
        topic=topic,
        max_results=max_results,
        include_raw_content=include_raw_content,
    )


if __name__ == "__main__":
    from pprint import pprint

    # 本地调试入口:直接运行本文件可验证 TAVILY_API_KEY 和 Tavily API 是否可用
    pprint(
        internet_search.invoke(
            {"query": "2026中国法定节假日放假安排表,我天天都想要放假"}
        )
    )

参数说明:

参数 说明
query 搜索关键词或搜索问题
topic 搜索主题,可选 generalnewsfinance
max_results 最多返回多少条结果,本项目默认 5 条
include_raw_content 是否返回更完整的原始内容,默认只返回精简内容

返回样式:

{
  "query": "2026 年 AI 行业政策",  // 本次真实提交给 Tavily 的搜索问题
  "results": [   // 搜索结果列表
    {
      "title": "网页标题",  // 网页标题,帮助模型快速判断来源主题
      "url": "https://example.com/article",  // 原始网页地址,方便后续追溯来源
      "content": "搜索结果摘要或正文片段",  // Tavily 提取出的摘要内容,是模型主要阅读的信息
      "score": 0.91,  // 搜索结果相关性分数,可用于判断结果是否贴近问题
      "raw_content": "更完整的网页原文内容"  // 原始正文内容,只有打开 include_raw_content 时才更完整
    }
  ]
}

 这就是为什么本项目使用 Tavily,而不是让模型直接“想象网页内容”。工具返回的是可追溯的数据:模型可以阅读 content,必要时保留 url,主智能体后面整理报告时也能知道信息来自哪里

进度上报: moniter和stream

monitor.py; 作用是把工具调用、子智能体调用和任务结果推给前端

monitor.report_tool(
    tool_name="网络搜索工具",
    args={
        "query": query,
        "topic": topic,
        "max_results": max_results,
        "include_raw_content": include_raw_content,
    },
)

只要 Agent 调用了 internet_search,前端就能看到“正在调用网络搜索工具”,并且能看到这次搜索的参数

两种向前端推送进度的方式:

image

stream/astream 更偏框架运行状态,适合后端判断模型、工具和子智能体调用;monitor 更偏业务展示事件,适合把“正在搜索什么”这种人能看懂的消息推给前端。两者配合起来,页面既能看到 Agent 的真实执行过程,也能看到更友好的业务说明

方式一:在 stream 流式输出里统一解析

DeepAgents 底层是图执行逻辑,所以主智能体可以用 stream / astream 流式执行。流式执行时,后端不是等整个任务结束,而是不断拿到一个个 chunk

主智能体开始执行
  -> stream 产出 chunk
  -> 后端判断这个 chunk 代表什么事件
  -> 如果是调用工具,就推送 tool 事件
  -> 如果是调用子智能体,就推送 assistant 事件
  -> 前端实时展示进度
async for chunk in main_agent.astream(
    {"messages": [{"role": "user", "content": task_query}]},
    config=config,
    subgraphs=True,
):
    # 统一处理主智能体和子智能体执行过程中吐出来的片段
    if is_tool_call_chunk(chunk):
        monitor.report_tool(
            tool_name=get_tool_name(chunk),
            args=get_tool_args(chunk),
        )

    elif is_sub_agent_chunk(chunk):
        monitor.report_assistant(
            assistant_name=get_assistant_name(chunk),
            args=get_assistant_args(chunk),
        )

方式二:在工具内部直接埋点

不等外层 stream 去解析,工具一被调用,就在工具函数内部主动上报

逻辑很简单:

Agent 调用 internet_search
  -> internet_search 内部先调用 monitor.report_tool
  -> monitor 根据当前 thread_id 找到对应前端连接
  -> WebSocket 把“正在执行网络搜索工具”推给前端
  -> 工具继续调用 Tavily,返回搜索结果

 优点是稳定、直观,并且和 chunk 结构解耦。只要这个工具真的被调用了,它就会自己推送一条进度

代价是:每个工具都要主动写一行类似的埋点代码。如果工具很多,就要保持统一规范,比如工具名怎么写、参数要不要脱敏、前端展示什么字段

两种方式可以这样对比:

方案 核心做法 优点 注意事项
stream 流式解析 在主智能体 stream / astream 循环里解析 chunk 统一入口,适合构建完整执行时间线 子智能体内部过程需要关注 subgraphs=True,还要解析 chunk 结构
工具内部埋点 在每个工具函数里调用 monitor.report_tool(...) 简单直接,工具被调用就能推送 每个工具都要写埋点,并保持事件格式一致

组装network_search_agent

把 YAML 里的网络助手配置和 internet_search 工具组装成 DeepAgents 认识的字典

"""
网络搜索子智能体配置模块

将 app/prompt/prompts.yml 中的 tavily 配置与 internet_search 工具组装成
DeepAgents 可识别的字典式子智能体。主智能体后续会根据 description
决定是否把公开网络信息查询任务分派给它。
"""

from app.agent.prompts import sub_agents_content
from app.tools.tavily_tool import internet_search


# 字典式子智能体的核心字段来自 YAML,便于后续只改配置就能调整路由描述和行为约束
# tools 列表声明该子智能体可以调用的真实外部能力
network_search_agent = {
    "name": sub_agents_content["tavily"]["name"],
    "description": sub_agents_content["tavily"]["description"],
    "system_prompt": sub_agents_content["tavily"]["system_prompt"],
    "tools": [internet_search],
}

这里采用的是 DeepAgents 最常见的字典式子智能体写法。注意,network_search_agent.py 并没有把提示词硬编码在 Python 文件里,而是从 app.agent.prompts 中读取 sub_agents_content。在 app/agent/prompts.py 里,sub_agents_content 来自 prompt_yaml_content["sub_agents"],也就是前面配置的 prompts.yml

字段 来源 作用
name prompts.yml 子智能体名称
description prompts.yml 给主智能体判断何时调用
system_prompt prompts.yml 给网络搜索助手自己的行为约束
tools app/tools/tavily_tool.py 子智能体可以调用的真实工具

image

要注意,网络搜索助手不是最终回答用户的角色。它只负责把外部资料查回来,然后把结果交给主智能体。最终怎么组织答案、是否生成 Markdown 或 PDF,是主智能体后续的工作

 

posted @ 2026-07-03 10:35  幻影之舞  阅读(3)  评论(0)    收藏  举报