和DeepSeek聊了一天的技术,不断发散思维,收获不错。
但是剩下的时间已经不够学习更多的东西,所以只好草草学习了下LangGraph的基础 - ReAct模式
一、什么是ReAct模式
ReAct模式就是“决策、执行、评估结果、执行下一步”,即reasoning-action,简称ReAct。一个任务发给llm,llm先进行决策,然后决策执行第一步,执行完后再评估执行结果,然后再决定是否继续下一步,还是修复上一步的错误。
如下图所示:

二、ReAct在LangGraph中的实现
严格来说,是在LangChain中的实现,原本在LangGraph中的实现因版本原因被移进了LangChain包。
我们这一次让llm熟悉一个“会议室申请”的流程:
# 申请会议室流程
1. 查看指定时间会议室是否空闲
* 如果空闲,执行下一步
* 如果不空闲,则另找空闲时间
2. 向后勤经理和人事经理报备,获得报备号码
3. 提供以下信息,申请会议
* 会议名称
* 会议用途
* 会议时间
* 后勤经理和人事经理给的报备号码
4. 申请成功会反回申请号码,请妥善保存号码。
这里个流程涉及三个步骤,其中第一步和第三步的结果是一个逻辑分叉,这是一个很典型的适用于ReAct模式,即思考、执行、观察结果循环。
1. 基本代码和资源文件
我们要准备一些资源文件和基础代码,以供调用。
首先是流程规则文件,这个文件用于定义申请会议室的流程规则,用于prompt中,让llm去遵循。
# 申请会议室流程
1. 查看指定时间会议室是否空闲
* 如果空闲,执行下一步
* 如果不空闲,则另找空闲时间
2. 向后勤经理和人事经理报备,获得报备号码
3. 提供以下信息,申请会议
* 会议名称
* 会议用途
* 会议时间
* 后勤经理和人事经理给的报备号码
4. 申请成功会反回申请号码,请妥善保存号码。
然后就是两个功能函数:
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。
@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. 测试代码:
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)
这个测试代码共进行了四轮会话:
- 第一轮测试无空闲会议室的情况
- 第二轮测试有空闲会议室但是却申请不成功的情况
- 第三轮是正常通过的情况
- 第四轮是与流程无关的消息llm能不能正确处理。
输出日志分析:
# 第一轮,可以看到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模式去做。但是,为了正确性,一般顶层任务还是明确执行步骤为上。