【LangGraph框架入门级】4. 支持联网搜索的智能代理系统

支持联网搜索的智能代理系统

上一篇的快递流程是「画好路线照着走」的工作流。本篇让流程带点 Agent 味道:让大模型自己决定要不要调用搜索工具 。

我们会实现两个核心节点(调模型、执行工具),并认识 LangGraph 预构建的两大利器------ToolNode 和 tools_condition,最后演示图的可视化和流式输出。

目录

  1. 案例流程设计
  2. 准备工作:模型、搜索工具、绑定工具
  3. 定义状态:messages 与计数
  4. 节点一:llm_call 调用大模型
  5. 回顾 tool_calls 与 ToolMessage
  6. 节点二:tool_node 执行工具
  7. 条件边:should_continue 与 tools_condition
  8. 完整代码
  9. 图可视化与流式输出
  10. 本篇小结

1. 案例流程设计

一个能联网的助手,流程是一个「思考 → 行动」的循环:

复制代码
START → llm_call(调模型)
            │
            ├─ 模型要搜索 → tool_node(执行搜索)→ 回到 llm_call(带着结果再答)
            │
            └─ 模型直接答完 → END

模型每次回答后,我们看它有没有「请求调用工具」:有就去执行工具、把结果喂回模型;没有就结束。


2. 准备工作:模型、搜索工具、绑定工具

用 init_chat_model 准备模型,用 Tavily 做联网搜索,再用 bind_tools 把工具绑给模型:

python 复制代码
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch

# 模型
model = init_chat_model("gpt-4o-mini", temperature=0)

# 搜索工具,max_results 控制返回几条结果
search = TavilySearch(max_results=4)
tools = [search]

# 关键:把工具绑定给模型,模型才知道自己能调用哪些工具
model_with_tools = model.bind_tools(tools)

需要安装:pip install -U langchain-tavily,并配置 Tavily 的 API Key(环境变量 TAVILY_API_KEY)。


3. 定义状态:messages 与计数

状态里最核心的是 messages(消息列表) ,它承担三个作用:对话记忆、上下文维护、状态持久化 。用 operator.add 让新消息追加而不是覆盖:

python 复制代码
from typing_extensions import TypedDict
from typing import Annotated
import operator
from langchain.messages import AnyMessage

class State(TypedDict):
    # 消息列表:所有对话和工具结果都在这里,逐次追加
    messages: Annotated[list[AnyMessage], operator.add]
    # 统计模型被调用了多少次
    llm_calls: int

说明:State 里存进去的消息,运行时会被自动反序列化成 LangChain 的消息对象,所以可以用「点属性」访问,例如 state["messages"][-1].tool_calls。


4. 节点一:llm_call 调用大模型

给模型一个系统提示(告诉它可以调用搜索),把历史消息一起传进去:

python 复制代码
from langchain.messages import SystemMessage

def llm_call(state: State) -> dict:
    print("【调用模型】")
    response = model_with_tools.invoke(
        [SystemMessage(content="乐于助人的助手,支持调用工具进行搜索")]
        + state["messages"]
    )
    return {
        "messages": [response],
        "llm_calls": state.get("llm_calls", 0) + 1,
    }

5. 回顾 tool_calls 与 ToolMessage

当模型决定搜索时,返回的 AI 消息(AIMessage)里会带一个 tool_calls 字段,结构大致是:

python 复制代码
AIMessage(
    content="",
    tool_calls=[
        {
            "name": "tavily_search",        # 要调用哪个工具
            "args": {"query": "西安天气"},    # 传给工具的参数
            "id": "call_xxxx",              # 这次调用的唯一 ID
        }
    ],
)

工具执行完,要构造一条 ToolMessage(工具消息) 把结果还回去,并且用 tool_call_id 对应上是哪次调用:

python 复制代码
ToolMessage(
    content="工具返回的结果",
    tool_call_id="call_xxxx",   # 必须和 AIMessage.tool_calls 里的 id 对应
)

6. 节点二:tool_node 执行工具

遍历最后一条 AI 消息里的 tool_calls,逐个执行对应工具,把结果包成 ToolMessage:

python 复制代码
from langchain.messages import ToolMessage

# 工具名 → 工具对象,方便按名字找到工具
tools_by_name = {tool.name: tool for tool in tools}

def tool_node(state: State) -> dict:
    print("【执行工具】")
    results = []
    # 取最后一条 AI 消息里的所有工具调用
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]          # 找到工具
        observation = tool.invoke(tool_call["args"])     # 执行工具
        results.append(
            ToolMessage(
                content=observation,
                tool_call_id=tool_call["id"],             # 对应调用 ID
            )
        )
    return {"messages": results}

7. 条件边:should_continue 与 tools_condition

7.1 自己写路由函数

判断最后一条消息有没有工具调用:有就去 tool_node,没有就 END:

python 复制代码
from langgraph.graph import END

def should_continue(state: State) -> str:
    last_message = state["messages"][-1]
    if last_message.tool_calls:   # 模型请求调用工具
        return "tool_node"
    return END                    # 模型答完,结束

7.2 用预构建的 tools_condition(更省事)

LangGraph 已经内置了同样逻辑的 tools_condition ,它返回 "tools" 或 "__end__":

python 复制代码
from langgraph.prebuilt import tools_condition

同样,执行工具也可以直接用预构建的 ToolNode,不必自己写 tool_node:

python 复制代码
from langgraph.prebuilt import ToolNode

tool_node = ToolNode(tools)   # 直接传入工具列表即可

8. 完整代码

python 复制代码
from typing_extensions import TypedDict
from typing import Annotated
import operator

from langchain.chat_models import init_chat_model
from langchain.messages import AnyMessage, SystemMessage, HumanMessage
from langchain_tavily import TavilySearch
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition

# 1. 模型与工具
model = init_chat_model("gpt-4o-mini", temperature=0)
search = TavilySearch(max_results=4)
tools = [search]
model_with_tools = model.bind_tools(tools)

# 2. 状态
class State(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]
    llm_calls: int

# 3. 节点
def llm_call(state: State) -> dict:
    response = model_with_tools.invoke(
        [SystemMessage(content="乐于助人的助手,支持调用工具进行搜索")]
        + state["messages"]
    )
    return {"messages": [response], "llm_calls": state.get("llm_calls", 0) + 1}

tool_node = ToolNode(tools)

# 4. 构建图
builder = StateGraph(State)
builder.add_node("llm_call", llm_call)
builder.add_node("tool_node", tool_node)

builder.add_edge(START, "llm_call")
builder.add_conditional_edges(
    "llm_call",
    tools_condition,                    # 预构建路由
    {"tools": "tool_node", "__end__": END},
)
builder.add_edge("tool_node", "llm_call")  # 工具结果返回模型

graph = builder.compile()

# 5. 调用
result = graph.invoke(
    {"messages": [HumanMessage(content="今天西安天气怎么样?")], "llm_calls": 0}
)
print(result["messages"][-1].content)

9. 图可视化与流式输出

9.1 可视化图结构

编译后的图可以导出 Mermaid 流程图,xray=True 显示更详细的内部结构:

python 复制代码
image = graph.get_graph(xray=True).draw_mermaid_png()
with open("graph.png", "wb") as f:
    f.write(image)

也可以配合 matplotlib 直接显示,或把 Mermaid 文本贴到在线 Mermaid 工具里渲染。

9.2 流式输出 stream

用 stream() 可以按图的执行步骤逐步返回,每个 chunk 是一个字典:键是节点名,值是该节点将要更新的状态:

python 复制代码
for chunk in graph.stream(
    {"messages": [HumanMessage(content="今天西安天气怎么样?")], "llm_calls": 0}
):
    print(chunk)

这样能清楚看到「llm_call 一步、tool_node 一步」的实时执行过程。流式的完整玩法后面有专篇详解。


10. 本篇小结

  1. 搜索 Agent 是一个「llm_call ⇄ tool_node」的循环:模型要工具就执行、结果喂回,不要工具就结束。
  2. bind_tools 把工具绑给模型;模型想调用时,AIMessage 里会带 tool_calls(name/args/id)。
  3. 工具结果要包成 ToolMessage ,并用 tool_call_id 对应调用 ID。
  4. 状态核心是 messages ,用 operator.add 追加,承担对话记忆、上下文、持久化三件事。
  5. 预构建利器:ToolNode(自动执行工具)和 tools_condition(自动判断要不要去工具节点),能省掉大量样板代码。
  6. get_graph().draw_mermaid_png() 可视化,stream() 按步骤流式执行。

下一篇我们做更贴近实战的案例三:代理式 RAG 系统------让 AI 自己决定是否检索、对检索结果做质量打分,不合格就重写问题重新检索,形成一个质量闭环。

相关推荐
Rain的Java大神实战圈1 小时前
Java 工程师做 AI,核心机会在「落地」而非「训练」
ai编程
运维@小兵1 小时前
安装Pycharm并破解
ide·python·pycharm
冬、不眠的夜1 小时前
Nuitka 将 python 程序编译成 exe 指南
开发语言·python
范中勤2 小时前
Python Asyncio 协程与 LLM 异步并发原理技术文档(与 Java 对比|彻底澄清所有误区)
python·llm·协程·并发编程·asyncio
Dmz丶2 小时前
前端转 AI 100 天 Day 21:FastAPI 入门——从命令行到 HTTP 服务
python
QQ14220784492 小时前
基于 Flask 的「自动任务控制台」设计与踩坑实录
后端·python·flask
吴佳浩 Alben2 小时前
一文讲透推理引擎性能指标:TTFT、TPOT、KV命中率、并发,到底对应 vLLM / SGLang 的哪个参数?
人工智能·python·语言模型·架构·ai编程·vllm·sglang
用户298698530142 小时前
Python 实现 Word 转 XPS、PostScript 与 OFD 格式
后端·python·api
OpsEye2 小时前
开源 AI 网关自研 vs 商用,企业该怎么权衡取舍
javascript·ai编程