LangChain | LangGraph V1教程 #3 从路由器到ReAct架构

路由器

上一步我们手写了工具调用节点,langgraph在条件边内提供了toos_condition用来判断是否调用工具和ToolNode类用来搭建工具调用节点

新建根据工具结果输出节点和工具节点,上一步直接打印了工具输出结果,应当将工具结果给模型生成答案,因此需要将工具输出的结果再传回。

python 复制代码
builder = StateGraph(MessagesState)
builder.add_node("llm_with_tools", llm_with_tools)  # 添加节点
builder.add_node("tools", ToolNode([multiply_values])) # 定义工具节点
builder.add_node("answer_multiply", answer_multiply_node)


builder.add_edge(START, "llm_with_tools")  # 从START到llm_with_tools的边
builder.add_conditional_edges("llm_with_tools",
                              # 如果助手的最新消息(结果)是工具调用 -> tools_condition 路由到 tools
                              # 如果助手的最新消息(结果)不是工具调用 -> tools_condition 路由到 END
                              tools_condition) # 根据LLM输出决定路由
builder.add_edge("tools", "llm_with_tools")
graph = builder.compile()  # 编译图

tools_condition是官方提供函数,实现了ReAct风格的标准条件逻辑,若模型未调用工具将转到END节点。

add_conditional_edges中,我们还可以自己编写判断函数,返回下一个节点名称,以下是一个示例:

python 复制代码
def condition_edge(state: TestCaseState):
    """
    条件边(节点4):逻辑判断,控制边的走向

    判断逻辑:
    - 如果通过 or 评审次数>3,则执行格式化节点
    - 否则返回节点1(重新编写)
    """
    print("\n=== 条件边判断 ===")

    is_approved = state.get("is_approved", False)
    review_count = state.get("review_count", 0)

    print(f"是否通过评审:{is_approved}")
    print(f"评审次数:{review_count}")

    if is_approved or review_count > 3:
        print("决策:进入格式化节点")
        return "format_test_case_node"
    else:
        print("决策:返回节点1(重新编写测试用例)")
        return "write_test_case_node"

不需要显式添加到 END 的边

ReAct架构

ReAct架构是一种融合推理 (Reasoning)与行动(Acting)的通用智能体框架,通过"思考→行动→观察"的循环机制,使大语言模型能够动态调用工具、获取外部信息并迭代优化决策,从而实现对复杂现实任务的自主解决。

下面进行一个小案例,搭建一个互联网搜索工具,爬取指定内容并智能生成报告。

首先,先创建互联网搜索工具,这里用到了阿里云的联网搜索api

调用联网搜索API-智能开放搜索 OpenSearch-阿里云

之前的工具定义中,我们使用了@tool注解,现在我们尝试一个进阶写法,通过继承BaseModel来实现复杂工具的定义:

python 复制代码
import aiohttp
from pydantic.fields import Field
from langchain_core.tools import BaseTool
from pydantic import create_model
from config import ALIYUN_OPENSEARCH_KEY, ALIYUN_OPENSEARCH_URL



def get_title_and_link(result: list) -> list:
    """
    获取标题和链接
    """
    data = []
    for i in result:
        data.append({i['title']: i['link']})
    return data


class SearchTool(BaseTool):
    # 工具名称(在 LangChain 中显示)
    name: str = "互联网搜索工具"
    # 工具描述(告诉 Agent 这个工具能做什么)
    description: str = "进行互联网查询"
    # 是否直接返回结果(False 表示结果会经过后续处理或整合)
    return_direct: bool = False

    def __init__(self):
        super().__init__()
        # 动态创建一个 Pydantic 模型,用于校验和描述工具的输入参数
        self.args_schema = create_model(
            "SearchArgs",  # 模型名称
            query=(str, Field(..., description="需要进行查询的信息")),
            num=(int, Field(..., description="返回结果的数量,可根据需求修改,默认为5,最大支持20")),
            no_content=(bool, Field(..., description="是否仅返回标题和链接。True 表示不返回网页内容,False 则返回摘要内容"))
        )

    async def _arun(self, query: str, num: int = 5, no_content: bool = False) -> str:
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {ALIYUN_OPENSEARCH_KEY}"
        }

        data = {
            "query": query,
            "query_rewrite": False,
            "top_k": num,
            "content_type": "snippet"
        }

        async with aiohttp.ClientSession() as session:
            async with session.post(ALIYUN_OPENSEARCH_URL, headers=headers, json=data) as response:
                if response.status != 200:
                    error_text = await response.text()
                    return "搜索工具调用出错,错误信息:" + error_text[:500]
                result = await response.json()
                if no_content:
                    result = get_title_and_link(result["result"]["search_result"])
                else:
                    result = result["result"]["search_result"]
                return str(result)

    def _run(self, query: str, num: int = 5, no_content: bool = False) -> str:
        import asyncio
        return asyncio.run(self._arun(query, num))


if __name__ == "__main__":
    tool = SearchTool()
    print(tool.invoke({"query": "中国最新科技成果", "num": 5, "no_content": False}))

然后再构建图:

python 复制代码
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition
from src.agent.llm import model
from src.agent.tools.get_time import get_time
from src.agent.tools.online_search import SearchTool
from src.agent.utils.show_graph import show_graph

search_tool = SearchTool()

# 绑定工具到 LLM
llm_tools = model.bind_tools([get_time, search_tool])

# 定义节点
def llm_with_tools(state: MessagesState):
    return {"messages": [llm_tools.invoke(state["messages"])]}

# 构建图
builder = StateGraph(MessagesState)

# 实例化 ToolNode 并传入工具列表
tool_node = ToolNode(tools=[get_time, search_tool])

builder.add_node("llm_with_tools", llm_with_tools)
builder.add_node("tools", tool_node)  # 传入实例

builder.add_edge(START, "llm_with_tools")
builder.add_conditional_edges("llm_with_tools", tools_condition)
builder.add_edge("tools", "llm_with_tools")

graph = builder.compile()

# 可视化图
show_graph(graph)
messages = []

while True:
    user_input = input("请输入:")
    messages.append(HumanMessage(content=user_input))
    result = graph.invoke(MessagesState(messages=messages))
    for m in result["messages"]:
        m.pretty_print()

以上就实现了一个简易的智能体,所有的消息都在messages列表,那么我们之前使用create_agent来创建了一个智能体:

python 复制代码
from langchain.agents import create_agent

agent = create_agent(
    model=basic_model,  # 默认模型
    tools=tools
)

这个案例实际上就是它的简化版本,也就是说你已经基本理解了create_agent的工作原理,langgraph封装了强大的 create_agent,可以帮助我们快速构建智能体,搭建工作流,因此以后我们将聚焦于create_agent的学习。

相关推荐
柒.梧.2 小时前
HTML入门指南:30分钟掌握网页基础
前端·javascript·html
用户54277848515402 小时前
Promise :从基础原理到高级实践
前端
用户4099322502122 小时前
Vue3条件渲染中v-if系列指令如何合理使用与规避错误?
前端·ai编程·trae
Mr_Swilder2 小时前
2025-12-20 vue3中 eslint9+和prettier配置
前端
code_YuJun2 小时前
脚手架开发工具——判断文件是否存在 path-exists
前端
code_YuJun2 小时前
脚手架开发工具——root-check
前端
用户54277848515402 小时前
XMLHttpRequest、AJAX、Fetch 与 Axios
前端
打小就很皮...2 小时前
React 实现富文本(使用篇&Next.js)
前端·react.js·富文本·next.js
智算菩萨3 小时前
实战:高级中文自然语言处理系统的Python设计与实现
前端·javascript·easyui