学习AI Agent编程-第二天-LangGraph ReAct模式实现

和DeepSeek聊了一天的技术,不断发散思维,收获不错。 但是剩下的时间已经不够学习更多的东西,所以只好草草学习了下LangGraph的基础 - ReAct模式

一、什么是ReAct模式

ReAct模式就是"决策、执行、评估结果、执行下一步",即reasoning-action,简称ReAct。一个任务发给llm,llm先进行决策,然后决策执行第一步,执行完后再评估执行结果,然后再决定是否继续下一步,还是修复上一步的错误。 如下图所示:

二、ReAct在LangGraph中的实现

严格来说,是在LangChain中的实现,原本在LangGraph中的实现因版本原因被移进了LangChain包。

我们这一次让llm熟悉一个"会议室申请"的流程:

markdown 复制代码
# 申请会议室流程
1. 查看指定时间会议室是否空闲
   * 如果空闲,执行下一步
   * 如果不空闲,则另找空闲时间
2. 向后勤经理和人事经理报备,获得报备号码
3. 提供以下信息,申请会议
    * 会议名称
    * 会议用途
    * 会议时间
    * 后勤经理和人事经理给的报备号码
4. 申请成功会反回申请号码,请妥善保存号码。

这里个流程涉及三个步骤,其中第一步和第三步的结果是一个逻辑分叉,这是一个很典型的适用于ReAct模式,即思考、执行、观察结果循环。

1. 基本代码和资源文件

我们要准备一些资源文件和基础代码,以供调用。 首先是流程规则文件,这个文件用于定义申请会议室的流程规则,用于prompt中,让llm去遵循。

markdown 复制代码
# 申请会议室流程
1. 查看指定时间会议室是否空闲
   * 如果空闲,执行下一步
   * 如果不空闲,则另找空闲时间
2. 向后勤经理和人事经理报备,获得报备号码
3. 提供以下信息,申请会议
    * 会议名称
    * 会议用途
    * 会议时间
    * 后勤经理和人事经理给的报备号码
4. 申请成功会反回申请号码,请妥善保存号码。

然后就是两个功能函数:

python 复制代码
def get_work_flow():
    """
    获得流程规则

    :return: 流程规则
    """
    with open('./work_flow.md', 'r') as f:
        return f.read()

def get_model():
    """
    获得模型客户端

    :return: 模型客户端
    """
    return ChatOpenAI(
        model=os.environ['OPENAI_MODEL_NAME'],
        temperature=0,
        extra_body={
            "enable_thinking": False,
            "thinking": {
                "type": "disabled"
            }
        }
    )

2. 添加工具函数,用于被llm调用

llm需要调用tool来获取他想要的额外信息或者进行相应的操作。所以我们需要实现这些tool。

这个开发过程一般都是慢慢调的,不同的llm的思维方式不同,有些要求很细,有些则要求很粗,所以尽可能跑多几个不同的模型来补上缺失的tool。

python 复制代码
@tool
def check_meeting_room_free(date: datetime.datetime):
    """
    检查会议室是否空闲

    :param date: 会议时间
    :return: 空闲返回true,否则为false
    """
    print(f'checking meeting room free date: {date}')
    if str(date) == '2089-05-25 15:00:00':
        return False
    else:
        return True

@tool
def get_approver_name(position:str):
    """
    获取岗位负责人名字

    :param position: 岗位
    :return: 名字
    """
    if position == '后勤经理':
        return '刘四逼'
    elif position == '人事经理':
        return '脏三疯'
    else:
        return '路人ABC'

@tool
def request_approve(approver: str, date: datetime.datetime, reason: str):
    """
    向上级报备使用会议室

    :param approver: 批准人
    :param date: 会议时间
    :param reason: 使用原因
    :return: 报备号
    """
    print(f'{approver}: handle meeting request for date: {date} - {reason}')
    return str(uuid.uuid4())


@tool
def make_meeting_appointment(name, reason, date: datetime.datetime, codes: list[str]) -> tuple:
    """
    申请会议

    :param name: 名称
    :param reason: 用途
    :param date: 时间
    :param codes: 报备号
    :return: 结果,第一个值是"是否成功",如果成功,第二个值是"申请号码",否则则是"失败原因"
    """
    print(f'handling meeting request: {name} - {date} - {reason} - {codes}')
    if str(date.date()) == '2089-05-26':
        return False, '会议室灯光安排在当日维修,无法使用。'
    else:
        return True, str(uuid.uuid4())

3. 测试代码:

python 复制代码
if __name__ == '__main__':
    load_dotenv()
    agent = create_agent(
        # 模型
        model=get_model(),
        # 工具
        tools=[check_meeting_room_free, request_approve, make_meeting_appointment, get_approver_name],
        # 流程规则 - prompt
        system_prompt=get_work_flow(),
        # 实现多轮会话
        checkpointer=MemorySaver(),
    )
    # 实现多轮会话
    config = RunnableConfig(configurable={"thread_id": "user_thread_1"})
    
    # 第一轮会话
    state = {
        "messages": [
            ("user", "我要申请下周三(2089年5月25日)下午3点整的会议室,名称是"批头会",用途"批头大叫"")
        ]
    }
    response = agent.invoke(state, config=config)
    print(response['messages'][-1].content)

    # 第二轮会话
    state = {
        "messages": [
            ("user", "那就下周四上午9点。")
        ]
    }
    response = agent.invoke(state, config=config)
    print(response['messages'][-1].content)

    # 第三轮会话
    state = {
        "messages": [
            ("user", "那就下周二下午4点?")
        ]
    }
    response = agent.invoke(state, config=config)
    print(response['messages'][-1].content)

    # 第四轮会话
    state = {
        "messages": [
            ("user", "谢谢")
        ]
    }
    response = agent.invoke(state, config=config)
    print(response['messages'][-1].content)

这个测试代码共进行了四轮会话:

  1. 第一轮测试无空闲会议室的情况
  2. 第二轮测试有空闲会议室但是却申请不成功的情况
  3. 第三轮是正常通过的情况
  4. 第四轮是与流程无关的消息llm能不能正确处理。

输出日志分析:

python 复制代码
# 第一轮,可以看到llm能正确调用tool,并且能正确评估tool结果
checking meeting room free date: 2089-05-25 15:00:00
下周三(2089年5月25日)下午3点整的会议室不空闲,请您另选一个时间再申请。

# 第二轮:可以看到llm能正确调用tool获得岗位主管的名字,并且正确被处理申请和返回申报号,而且最后能识别申请失败的原因,并以自然语言的方式回复。最重要的是,日期正确。
checking meeting room free date: 2089-05-26 09:00:00
刘四逼: handle meeting request for date: 2089-05-26 09:00:00 - 批头大叫
脏三疯: handle meeting request for date: 2089-05-26 09:00:00 - 批头大叫
handling meeting request: 批头会 - 2089-05-26 09:00:00 - 批头大叫 - ['d3558796-d523-4e01-acaa-5023dc85023d', 'e25791d2-a22d-46f9-9862-f4414e0075cc']
很抱歉,虽然下周四上午9点会议室时间空闲且已获得后勤经理和人事经理的报备号,但由于当日会议室灯光安排维修,无法使用。请您另选其他时间再申请。

# 第三轮:正常申请成功的流程。
checking meeting room free date: 2089-05-23 16:00:00
刘四逼: handle meeting request for date: 2089-05-23 16:00:00 - 批头大叫
脏三疯: handle meeting request for date: 2089-05-23 16:00:00 - 批头大叫
handling meeting request: 批头会 - 2089-05-23 16:00:00 - 批头大叫 - ['480fb94e-e142-4b0d-acfd-463c0fc843f5', '9e441bb8-e586-420b-9f44-2b11e1fa3a18']
会议室申请成功!您的申请号码为:2f03df27-2e34-4ad8-a99b-1b7dc3a606e3,请妥善保存。会议时间为下周二(2089年5月23日)下午4点整,名称"批头会",用途"批头大叫"。

# 第四轮:llm能够正确区分非业务消息并正确客套回复。
不客气!祝您会议顺利,如有其他需要随时联系我。

三、总结

由测试结果可以得出,现在的llm能正确处理三步左右的问题,所以可以将子步骤交由ReAct模式去做。但是,为了正确性,一般顶层任务还是明确执行步骤为上。

相关推荐
桦说编程5 分钟前
我让 AI 加了一个开关,结果代码走了原本不该走的分支
人工智能·代码规范
fly spider5 分钟前
AI 到底是怎么访问网页的?从爬虫、Browser Agent 到 Computer Use
人工智能·爬虫
Lee川38 分钟前
RAG 实战:从一篇掘金文章出发,拆解检索增强生成的全链路
前端·人工智能·后端
码农小旋风42 分钟前
Codex小白入门使用教程
人工智能·chatgpt·claude
Lee川1 小时前
MCP 高德地图实战:当 AI 学会使用工具,一个协议如何重塑大模型的行动边界
前端·人工智能·后端
凌杰1 小时前
AI 学习笔记:Agent 的应用演示
人工智能
程序员cxuan1 小时前
Codex 把我家烂网给优化后,我 TM 直接原地起飞了。
人工智能·后端·程序员
IT_陈寒1 小时前
Redis批量删除踩了坑,原来DEL命令不是万能的
前端·人工智能·后端
xinhuanjieyi1 小时前
gpt-sovits测试语音克隆
人工智能·gpt
星辰AI1 小时前
Transformers 架构核心原理:从注意力机制到 GPT
人工智能·ai·语言模型