快速入门-创建第一个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 执行大致经历下面几步:
- 用户把问题发给 Agent。
- Agent 把用户问题、系统提示词、工具描述一起交给模型。
- 模型判断需要调用
internet_search。 - Agent 真正执行搜索工具。
- 工具返回搜索结果。
- Agent 再把工具结果交给模型。
- 模型整理搜索结果,生成最终报告。
这 7 步背后,其实有四个角色在配合: 用户 -> Agent -> 大模型 -> 工具 -> Agent -> 大模型 -> 用户
第一步,用户并不是直接调用大模型,而是把问题交给 deep_agent:
传入的是 messages,DeepAgents 会先把这条用户消息包装成 HumanMessage,放进本次执行状态中
第二步,Agent 会把下面几类信息一起交给大模型:
- 用户问题:
请查询人工智能和机器人领域的热门新闻信息,并整理为一份简要报告 - 系统提示词:你是一名严谨的研究员,可以使用搜索工具生成结构清晰、信息可靠的中文报告
- 工具说明:当前可用工具是
internet_search - 工具参数结构:
query、max_results、topic、include_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,流式输出会带有节点信息
解析流式输出时,重点看两层信息:
- 外层节点名:当前是
model节点,还是tools节点 - 内层最后一条消息:模型是决定调用工具,还是已经给出最终回答
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 节点代表工具已经真的执行完了。这里可以把工具名和返回结果截断后推送给前端,避免一大段搜索结果把界面刷满

浙公网安备 33010602011771号