LangGraph--Agent工作流

news2025/6/9 17:17:51

 Agent的工作流

下面展示了如何创建一个“计划并执行”风格的代理。 这在很大程度上借鉴了 计划和解决 论文以及Baby-AGI项目。
核心思想是先制定一个多步骤计划,然后逐项执行。完成一项特定任务后,您可以重新审视计划并根据需要进行修改。
般的计算图如下所示

这与典型的 ReAct 风格的代理进行了比较,在该代理中,您一次思考一步。 这种“计划并执行”风格代理的优势在于
1.明确的长期规划(即使是真正强大的 LLM 也可能难以做到)
2.能够使用更小/更弱的模型来执行步骤,仅在规划步骤中使用更大/更好的模型以下演练演示了如何在 LangGraph 中实现这一点。

根据langgraph搭建一个智能体的工作流,具体如下:

 执行结果如下:

使用LANGSMITH进行数据跟踪,这个需要你注册登录获取key,就可以查看了,我使用的是deepseek,充了钱了,没免费的了。

 源码如下:

TAVILY_API_KEY = "xxxx"# 使用你自己的key
LANGCHAIN_TRACING_V2 = "true"
DEEPSEEK_API_KEY = "xxxx" # 使用你自己的key

LANGSMITH_TRACING="true"
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY="xxxx"# 使用你自己的key
LANGSMITH_PROJECT="pr-glossy-analogue-76"

import os
os.environ["DEEPSEEK_API_KEY"] = DEEPSEEK_API_KEY
os.environ["TAVILY_API_KEY"] = TAVILY_API_KEY
os.environ["LANGCHAIN_TRACING_V2"] = LANGCHAIN_TRACING_V2
os.environ["LANGSMITH_TRACING"] = LANGSMITH_TRACING
os.environ["LANGSMITH_ENDPOINT"] = LANGSMITH_ENDPOINT
os.environ["LANGSMITH_API_KEY"] = LANGSMITH_API_KEY

DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")


# 导入tavily的搜索功能
from langchain_community.tools.tavily_search import TavilySearchResults
# 创建TavilySearchResults工具,设置最大结果数为1
tools = [TavilySearchResults(max_results = 1)]
from langchain import hub
from langchain_openai import ChatOpenAI

from langchain_deepseek import ChatDeepSeek
import asyncio
from langgraph.prebuilt import create_react_agent


# 从langchain的hub获取prompt的模板,可以进行修改 "mintyflinter/best-chatbot", include_model=True
# prompt = hub.pull("wfh/react-agent-executor") # 会报错,官方的api的参数改变了,所以需要修改为下面的
prompt = hub.pull("zhang1career/react-agent-executor")
prompt.pretty_print()

# 选择大模型
# Model
llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=DEEPSEEK_API_KEY,
    #base_url="https://api.deepseek.com",
    #temperature=0.0
)

agent_executor = create_react_agent(llm, tools,prompt=prompt)
#agent_executor.invoke({"input":[("user", "谁是美国公开赛的获胜者?")]}) # 这种方法调用会报错

# 正确调用方式
# response = agent_executor.invoke({"input": "谁是美国公开赛的获胜者?"})
# print(response["messages"])

import operator
from typing import Annotated, List, Tuple, TypedDict,Literal

# 定义一个TYpeDict类 PlanExecute , 用于存储输入、计划、过去的步骤和响应
class PlanExecute(TypedDict):
    input: str
    plan:List[str]
    past_steps:Annotated[List[Tuple], operator.add]
    response:str

from pydantic import BaseModel, Field
# 定义一个plan模型类, 用于描述未来要执行的计划
class Plan(BaseModel):
    """未来要执行的计划"""
    steps:List[str] = Field(
        description="需要执行的不同步骤,应该按顺序排列"
    )


from langchain_core.prompts import ChatPromptTemplate

# 创建一个计划生成的提示模板
planner_prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            """对于给定的目标,提出一个简单的逐步计划。这个计划应该包含独立的任务,如果正确执行将得出正确答案,不要添加任何多余的步骤,最后一步的结果应该是最终答案。确保每一步都有所有必要的信息 - 不要跳过步骤。"""
        ),
        ("placeholder","{messages}")
    ]
)
# 使用指定的提示词模板创建一个计划生成器,使用deepseek模型
planner = planner_prompt|ChatDeepSeek(
   model="deepseek-chat",
    api_key=DEEPSEEK_API_KEY,
    #base_url="https://api.deepseek.com",
    # temperature=0.0
).with_structured_output(Plan)

#planner.invoke({"messages":[("user", "现任澳网冠军的家乡是哪里?")]})

from typing import Union

# 定义一个响应模型类,用于描述用户的响应
class Response(BaseModel):
    """用户响应"""
    response: str

# 定义 一个行为模型类,用于描述要执行的行为,该类继承自BaseModel
# 类中有一个属性action, 类型为Union[Response, Plan],表示可以是Responese 或Plan的类型
# action 属性的描述为: 要执行的行为,如果要回应用户,使用Response: 如果需要进一步使用工具获取答案,使用plan
class Act(BaseModel):
    """要执行的行为"""
    action: Union[Response,Plan] = Field(
        description="要执行的行为, 如果要回应用户,使用Response,如果需要进一步使用工具获取答案,使用Plan。"
    )

replanner_prompt = ChatPromptTemplate.from_template(
    """对于给定的目标,提出一个简单的逐步计划。这个计划应该包含独立的任务,如果正确执行将得出正确答案,不要添加任何多余的步骤,最后一步的结果应该是最终答案。确保每一步都有所有必要的信息 - 
    你的目标是:
    {input}

    你的原计划是:
    {plan}

    你目前已完成的步骤是:
    {past_steps}

    相应的更新你的计划,如果不需要更多的步骤并且可以返回给用户,那么就这样响应。如果需要,填写计划。只添加仍然需要完成的步骤,不要返回已完成的步骤
    """
    
)   

# 使用指定的提示模板创建一个重新计划生成器,使用deepseek
replanner = replanner_prompt|ChatDeepSeek(
   model="deepseek-chat",
    api_key=DEEPSEEK_API_KEY,
    #base_url="https://api.deepseek.com",
    # temperature=0.0
).with_structured_output(Act)

# 定义一个异步函数
async def main():
    # 定义一个异步函数,用于生成计划步骤
    async def plan_step(state: PlanExecute):
        plan = await planner.ainvoke({"messages": [("user", state["input"])]})
        return {"plan": plan.steps}
    
    # 定义一个异步函数执行步骤
    async def execute_step(state: PlanExecute):
        plan = state["plan"]
        plan_str = "\n".join(f"{i+1}.{step}" for i, step in enumerate(plan))
        task = plan[0]
        task_formatted = f"""对于一下计划:
         {plan_str}\n\n你的任务执行第{1}步,{task}.
           """
        agent_response = await agent_executor.ainvoke(
            {"messages":[("user", task_formatted)]}
        )
        return {"past_steps": state["past_steps"]+[(task, agent_response["messages"][-1].content)]}
    

    # 定义一个异步函数,用于重新计划步骤
    async def replan_step(state: PlanExecute):
        output = await replanner.ainvoke(state)
        if isinstance(output.action, Response):
            return {"response": output.action.response}
        else:
             return {"plan":output.action.steps}
    # 定义一个函数用于判断是否结束
    def should_end(state: PlanExecute)->Literal["agent","__end__"]:
        if("response" in state and state["response"]):
            return "__end__"
        else:
            return "agent"
        
    from langgraph.graph import StateGraph, START


    # 创建一个状态图,初始化PlanEexecute
    workflow = StateGraph(PlanExecute)

    # 添加计划节点
    workflow.add_node("planner",plan_step)
    # 添加执行步骤节点
    workflow.add_node("agent", execute_step)
    # 重新计划节点
    workflow.add_node("replan", replan_step)

    # 从开始到计划节点的边
    workflow.add_edge(START, "planner")
    # 设置从计划到代理节点的边
    workflow.add_edge("planner", "agent")
    # 设置从代理到重新计划节点的边
    workflow.add_edge("agent","replan")

    # 添加条件边,用于判断下一步操作
    workflow.add_conditional_edges(
        "replan",
        should_end,
    )

    app = workflow.compile()
    graph_png = app.get_graph().draw_mermaid_png()
    with open("agent_workflow.png", "wb") as f:
        f.write(graph_png)
    
    # 设置配置,递归限制为50次
    config = {"recursion_limit":50}
    inputs = {"input":"2024年巴黎奥运会100米自由泳赛冠军的家乡是哪里?请用中文回答"}

    async for event in app.astream(inputs, config=config):
        for k,v in event.items():
            if k!="__end__":
                print(v)

asyncio.run(main())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2405625.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot 常用注解面试题深度解析

🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot 常用注解面试题深度解析一、核心…

Linux系统的CentOS7发行版安装MySQL80

文章目录 前言Linux命令行内的”应用商店”安装CentOS的安装软件的yum命令安装MySQL1. 配置yum仓库2. 使用yum安装MySQL3. 安装完成后,启动MySQL并配置开机自启动4. 检查MySQL的运行状态 MySQL的配置1. 获取MySQL的初始密码2. 登录MySQL数据库系统3. 修改root密码4.…

408第一季 - 数据结构 - 栈与队列

栈 闲聊 栈是一个线性表 栈的特点是后进先出 然后是一个公式 比如123要入栈,一共有5种排列组合的出栈 栈的数组实现 这里有两种情况,,一个是有下标为-1的,一个没有 代码不用看,真题不会考 栈的链式存储结构 L ->…

【RTP】Intra-Refresh模式下的 H.264 输出,RTP打包的方式和普通 H.264 流并没有本质区别

对于 Intra-Refresh 模式下的 H.264 输出,RTP 打包 的方式和普通 H.264 流并没有本质区别:你依然是在对一帧一帧的 NAL 单元进行 RTP 分包,只不过这些 NAL 单元内部有部分宏块是 “帧内编码” 而已。下面分步骤说明: 1. 原理回顾:RFC 6184 H.264 over RTP 按照 RFC 6184 …

Redis实战-消息队列篇

前言: 讲讲做消息队列遇到的问题。 今日所学: 异步优化消息队列基于stream实现异步下单 1. 异步优化 1.1 需求分析 1.1.1 现有下单流程: 1.查询优惠劵 2.判断是否是秒杀时间,库存是否充足 3.实现一人一单 在这个功能中&…

(三)Linux性能优化-CPU-CPU 使用率

CPU使用率 user(通常缩写为 us),代表用户态 CPU 时间。注意,它不包括下面的 nice 时间,但包括了 guest 时间。nice(通常缩写为 ni),代表低优先级用户态 CPU 时间,也就是进…

佰力博科技与您探讨材料介电性能测试的影响因素

1、频率依赖性 材料的介电性能通常具有显著的频率依赖性。在低频下,偶极子的取向极化占主导,介电常数较高;而在高频下,偶极子的取向极化滞后,导致介电常数下降,同时介电损耗增加。例如,VHB4910…

K8S认证|CKS题库+答案| 4. RBAC - RoleBinding

目录 4. RBAC - RoleBinding 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、查看SA和role 3)、编辑 role-1 权限 4)、检查role 5)、创建 role和 rolebinding 6&#xff0…

React 新项目

使用git bash 创建一个新项目 建议一开始就创建TS项目 原因在Webpack中改配置麻烦 编译方法:ts compiler 另一种 bable 最好都配置 $ create-react-app cloundmusic --template typescript 早期react项目 yarn 居多 目前npm包管理居多 目前pnpm不通用 icon 在public文件夹中…

AI编程在BOSS项目的实践经验分享

前言 在人工智能技术革新浪潮的推动下,智能编程助手正以前所未有的速度重塑开发领域。这些基于AI的代码辅助工具通过智能提示生成、实时错误检测和自动化重构等功能,显著提升了软件工程的全流程效率。无论是初入行业的开发者还是资深程序员,…

数学:”度量空间”了解一下?

度量空间是现代数学中一种基本且重要的抽象空间。以下是对它的详细介绍: 定义 相关概念 常见的度量空间举例 度量空间的类型 度量空间的作用 度量空间是拓扑空间的一种特殊情况,它为拓扑空间的研究提供了具体的模型和实例。同时,度量空间在…

jenkins脚本查看及备份

位置与备份 要完整备份 Jenkins 的所有脚本和相关配置,包括 Jenkinsfile、构建脚本(如 .sh / .bat)、Job 配置、插件、凭据等,你可以从两个层面入手: ✅ 一、完整备份 Jenkins 主目录(最全面) …

用电脑通过网口控制keysight示波器

KEYSIGHT示波器HD304MSO性能 亮点: 体验 200 MHz 至 1 GHz 的带宽和 4 个模拟通道。与 12 位 ADC 相比,使用 14 位模数转换器 (ADC) 将垂直分辨率提高四倍。使用 10.1 英寸电容式触摸屏轻松查看和分析您的信号。捕获 50 μVRMS 本底噪声的较小信号。使用独有区域触摸在几秒…

《如何使用MinGW-w64编译OpenCV和opencv_contrib》

《如何使用MinGW-w64编译OpenCV和opencv_contrib》 在Windows环境下使用MinGW编译OpenCV和opencv_contrib是一个常见需求,尤其是对于那些希望使用GCC工具链而非Visual Studio的开发者。下面我将详细介绍这个过程。 准备工作 首先需要安装和准备以下工具和库: MinGW(建议使…

模拟搭建私网访问外网、外网访问服务器服务的实践操作

目录 实验环境 实践要求 一、准备工作 1、准备四台虚拟机,分别标号 2、 防火墙额外添加两块网卡,自定义网络连接模式 3、 关闭虚拟机的图形管理工具 4、关闭防火墙 5、分别配置四台虚拟机的IP地址,此处举一个例子(使用的临…

vue中Echarts的使用

文章目录 Echarts概述什么是EchartsEcharts的好处 Vue中Echarts的使用Echarts的安装Echarts的引入 Echarts概述 什么是Echarts Apache ECharts:一个基于 JavaScript 的开源可视化图表库。 其官网如下:https://echarts.apache.org/zh/index.html Echar…

【C++项目】负载均衡在线OJ系统-1

文章目录 前言项目结果演示技术栈:结构与总体思路compiler编译功能-common/util.hpp 拼接编译临时文件-common/log.hpp 开放式日志-common/util.hpp 获取时间戳方法-秒级-common/util.hpp 文件是否存在-compile_server/compiler.hpp 编译功能编写(重要&a…

Spring Boot微服务架构(十一):独立部署是否抛弃了架构优势?

Spring Boot 的独立部署(即打包为可执行 JAR/WAR 文件)本身并不会直接丧失架构优势,但其是否体现架构价值取决于具体应用场景和设计选择。以下是关键分析: 一、独立部署与架构优势的关系 内嵌容器的优势保留 Spring Boot 独立部署…

(四)Linux性能优化-CPU-软中断

软中断 中断其实是一种异步的事件处理机制,可以提高系统的并发处理能力 由于中断处理程序会打断其他进程的运行,所以,为了减少对正常进程运行调度的影响,中断处理程序就需要尽可能快地运行 Linux 将中断处理过程分成了两个阶段&a…

QT的工程文件.pro文件

文章目录 QT的工程文件.pro文件QT5中的基本模块Qt CoreQt GUIQt WidgetsQt QMLQt QuickQt NetworkQt SQLQt MultimediaQt ConcurrentQt WebEngineQt TestLib TARGET 可选择的模版CONFIG的配置项 QT的工程文件.pro文件 每一个QT项目都至少有一个.pro文件,用来配置项目…