快速入门-创建第一个DeepAgent

1. 创建搜索工具

import os
from typing import Literal

from dotenv import load_dotenv, find_dotenv
from langchain.tools import tool
# tavily联网搜索能力
from tavily import TavilyClient

load_dotenv(find_dotenv())

tavily_key = os.getenv("TAVILY_API_KEY")

@tool
def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["news", "finance", "general"] = "general",
    include_raw_content: bool = False,
):
    """
    :param query: 搜索关键词
    :param max_results: 返回结果数量
    :param topic: 查询主题,可选 news、finance、general
    :param include_raw_content: 是否返回更详细的原文内容,include_raw_content=False 时返回摘要内容;True 时会尝试返回更完整的网页原文
    :return: Tavily 搜索结果
    """
    print(
        f"开始调用网络搜索工具,核心参数为:{query},{max_results},{topic},{include_raw_content}"
    )
    return tavily_client.search(
        query=query,
        max_results=max_results,
        topic=topic,
        include_raw_content=include_raw_content,
    )

这里最关键的是 @tool 装饰器。它会把一个普通 Python 函数包装成 LangChain 工具,让 Agent 能够在推理过程中调用它

2. 初始化模型

from langchain.chat_models import init_chat_model

llm_name = os.getenv("LLM_QWEN_MAX")

llm = init_chat_model(
    model=llm_name,
    model_provider="openai",
)

3. 创建deep_agent

from deepagents import create_deep_agent

# 当前示例不配置子智能体,重点观察“主智能体 + 搜索工具”的基本流程
deep_agent = create_deep_agent(
    model=llm,
    tools=[internet_search],
    subagents=[],
    system_prompt="""
    你是一名严谨的研究员,可以使用 internet_search 工具检索网络信息。
    请根据检索结果进行归纳、分析和交叉验证,生成一份结构清晰、信息可靠的中文报告。
    """,
)

4. 用invoke获取结果

# 非流式执行,invoke 会等整条 agent 链路完成后,一次性返回最终状态
result = deep_agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "请查询人工智能和机器人领域的热门新闻信息,并整理为一份简要报告。",
            }
        ]
    }
)

# result 中会保留完整消息轨迹,便于观察模型决策、工具返回和最终回答
print(result)

# messages 的最后一条通常就是 DeepAgent 整理后的最终回答
print(result["messages"][-1].content)

非流式调用: invoke和smessage

invoke() 返回的不是一段纯文本,而是一个包含完整消息过程的字典。

也就是说,一次 Agent 执行大致经历下面几步:

  1. 用户把问题发给 Agent。
  2. Agent 把用户问题、系统提示词、工具描述一起交给模型。
  3. 模型判断需要调用 internet_search
  4. Agent 真正执行搜索工具。
  5. 工具返回搜索结果。
  6. Agent 再把工具结果交给模型。
  7. 模型整理搜索结果,生成最终报告。

这 7 步背后,其实有四个角色在配合: 用户 -> Agent -> 大模型 -> 工具 -> Agent -> 大模型 -> 用户

第一步,用户并不是直接调用大模型,而是把问题交给 deep_agent

  传入的是 messages,DeepAgents 会先把这条用户消息包装成 HumanMessage,放进本次执行状态中

第二步,Agent 会把下面几类信息一起交给大模型:

  1. 用户问题:请查询人工智能和机器人领域的热门新闻信息,并整理为一份简要报告
  2. 系统提示词:你是一名严谨的研究员,可以使用搜索工具生成结构清晰、信息可靠的中文报告
  3. 工具说明:当前可用工具是 internet_search
  4. 工具参数结构:querymax_resultstopicinclude_raw_content

大模型并不是“天然知道”Agent 有哪些工具,而是 Agent 在调用模型时,把工具名称、参数和描述一起放进上下文里。模型读完这些信息后,才会判断:这个问题需要联网搜索,于是生成一次工具调用

 这一步对应 result["messages"] 里的第二条消息:

AIMessage(
    content="",
    tool_calls=[
        {
            "name": "internet_search",
            "args": {
                "query": "人工智能 机器人 热门新闻",
                "topic": "news",
                "max_results": 5,
                "include_raw_content": False
            }
        }
    ]
)

这里要注意:这条 AIMessage 还不是最终回答。它的 content 为空,但 tool_calls 有值,意思是模型在告诉 Agent:“下一步请调用 internet_search 工具,并传入这些参数。”

第三步,Agent 根据 tool_calls 真正去执行工具

工具执行完成后,会生成一条 ToolMessage。这条消息里保存的是 Tavily 返回的原始搜索结果,通常是结构化数据,里面可能包含标题、链接、摘要、发布时间等信息。

结合实际输出,ToolMessage(content=...) 里的 JSON 主要包含这些字段:

字段 含义
query 本次传给 Tavily 的搜索关键词
results 搜索结果列表
url 单条搜索结果的网页链接
title 单条搜索结果的标题
content 单条搜索结果的摘要内容
published_date 新闻发布时间
response_time Tavily 本次请求耗时
request_id Tavily 本次请求 ID

第四步,工具结果不会直接返回给用户。Agent 会把 ToolMessage 再交给大模型,让模型根据搜索结果进行整理、翻译、筛选和润色

这一步对应最后一条 AIMessage

AIMessage( content="## 人工智能与机器人领域热门新闻简报..." )

到了这里,tool_calls 已经没有值,content 有了完整内容,说明模型不再继续调用工具,而是已经生成最终回复

我们取最终回复时,通常直接取最后一条消息:

final_answer = result["messages"][-1].content

为什么模型知道调用哪一个工具

模型本身并不会主动扫描你的 Python 代码,也不会自己知道项目里有哪些函数。真正发生的是:当我们通过 create_deep_agent() 创建智能体时,已经把工具列表传给了 Agent。

执行 invoke() 时,Agent 会把 internet_search 的名称、描述和参数结构整理给模型。模型读完以后,才会决定是否生成 tool_calls

工具调用的判断链路可以这样记:

工具注册到 Agent -> Agent 把工具描述交给模型 -> 模型判断是否需要调用工具 -> 模型生成 tool_calls -> Agent 根据 tool_calls 执行真实工具

四类消息

消息类型 在执行链路中的作用 常见特征
HumanMessage 用户输入的问题 content 是用户原始问题
AIMessage 模型的思考结果,可能是调用工具,也可能是回复 有 tool_calls 时表示准备调工具
ToolMessage 工具真实执行后的返回结果 name 通常是工具名
AIMessage 模型基于工具结果整理出的最终回答 content 有最终自然语言结果

流式调用: stream和chunk

invoke() 属于非流式执行。它的特点是调用后一直等待,直到整个智能体任务执行完,再一次性返回完整结果。

这种方式写起来简单,但用户体验不够好。尤其是 DeepAgents 这类长链路任务,智能体可能会搜索、阅读、调用多个工具、分派子任务。如果前端一直没有反馈,用户很难判断系统是在处理,还是已经卡住

后面做 Web 项目时,这些过程通常会通过 SSE 或 WebSocket 推送给前端。本项目完整版本采用的是 WebSocket,因为它更适合持续推送 Agent 的执行状态和工具日志

把 invoke() 换成 stream() 后,返回值就不再是完整结果,而是一个可以不断迭代的流:

stream = deep_agent.stream(
    {
        "messages": [
            {
                "role": "user",
                "content": "请查询人工智能和机器人领域的热门新闻信息,并整理为一份简要报告。",
            }
        ]
    }
)
# 每个 chunk 是一个按节点名组织的字典
for chunk in stream:
    print(chunk)

每个 chunk 都表示某个节点刚刚产生了一次状态更新。由于 DeepAgents 底层基于 LangGraph,流式输出会带有节点信息

解析流式输出时,重点看两层信息:

  1. 外层节点名:当前是 model 节点,还是 tools 节点
  2. 内层最后一条消息:模型是决定调用工具,还是已经给出最终回答

chunk的四种状态

状态 如何识别 含义
模型决定调用工具 node_name == "model" 且 last_msg.tool_calls 中工具名不是 task 模型判断下一步要调用某个普通工具
模型决定调用子智能体 node_name == "model" 且 tool_call["name"] == "task" 模型判断下一步要把任务分派给某个子智能体
工具执行完成 node_name == "tools" Agent 真正调用工具,并拿到了工具返回结果
模型返回最终结果 node_name == "model" 且没有 tool_calls,但 last_msg.content 有内容 模型基于前面结果生成最终回复

解析chunk的完整代码:

# 流式执行,stream 会在每个图节点完成后产出一个 chunk
stream = deep_agent.stream(
    {
        "messages": [
            {
                "role": "user",
                "content": "请查询人工智能和机器人领域的热门新闻信息,并整理为一份简要报告。",
            }
        ]
    }
)

for chunk in stream:
    # chunk 是一个按节点名组织的字典,例如
    # {"model": {"messages": [...]}} 或 {"tools": {"messages": [...]}}
    # 第一,外层循环取的是 node_name 和 state
    # node_name 用来判断当前是哪类节点产生了输出,例如 model 或 tools。state 里才是真正的状态数据
    for node_name, state in chunk.items():
        # DeepAgents 内部中间件也可能产出空状态或非消息状态,这里只解析消息类状态
        
        # 第二,有些中间件节点不包含 messages,要先跳过
        if not state or "messages" not in state:
            continue

        messages = state["messages"]

        if not messages or not isinstance(messages, list):
            continue

        # 每个 chunk 的最后一条消息,通常就是这个节点本次产出的核心信息
        # 第三,只取 messages[-1]
        last_msg = messages[-1]

        if node_name == "model":
            # model 节点有两类重点事件:
            # 1. tool_calls 非空,模型决定下一步调用工具或子智能体
            # 2. content 非空,模型已经生成最终回答
            if last_msg.tool_calls:
                for tool_call in last_msg.tool_calls:
                    if tool_call["name"] == "task":
                        print(
                            f"【大模型】决定调用子智能体:{tool_call['args']['subagent_type']}"
                        )
                    else:
                        print(
                            f"【大模型】决定调用工具:{tool_call['name']} 传入的参数:{tool_call['args']}"
                        )

            elif last_msg.content:
                print(f"【大模型】最终执行的结果:{last_msg.content}")

        elif node_name == "tools":
            # tools 节点返回的是具体工具的执行结果,通常可以推送给前端展示执行进度
            tool_return_result = last_msg.content[:100] + "..."
            tool_name = last_msg.name
            print(f"【agent】调用了{tool_name}工具,返回的结果为:{tool_return_result}")

 

 第四,model 节点要继续区分两类情况

  • last_msg.tool_calls 有值:模型不是最终回答,而是在决定下一步调用什么。
  • last_msg.content 有值:模型已经生成最终回复。

第五,tools 节点代表工具已经真的执行完了。这里可以把工具名和返回结果截断后推送给前端,避免一大段搜索结果把界面刷满

posted @ 2026-06-18 14:32  幻影之舞  阅读(18)  评论(0)    收藏  举报