简而言之: 介绍Stateflow,一种将LLM支持的复杂任务解决过程概念化为状态机的任务解决范例。介绍如何使用GroupChat通过自定义的演讲者选择功能来实现这一想法。
介绍
使用大语言模型(LLMs)解决复杂任务,例如需要一系列动作以及与工具和外部环境动态交互的任务,已成为一个显著趋势。在本文中,我们提出了StateFlow,一种基于LLM的新型任务解决范式,该范式将复杂任务解决过程概念化为状态机。在StateFlow中,我们区分了“过程基础”(通过状态和状态转换)和“子任务解决”(通过状态内的动作),从而增强了任务解决过程的控制性和可解释性。状态表示运行进程的状态。状态之间的转换由启发式规则或LLM做出的决策控制,允许动态和自适应的进展。进入状态后,将执行一系列动作,不仅包括由不同提示引导的LLM调用,还包括根据需要利用外部工具。
StateFlow
有限状态机(FSMs)被用作控制系统来监控实际应用,例如交通灯控制。 定义的有限状态机是一种行为模型,它根据当前状态决定要做什么。状态表示有限状态机可能处于的一种情况。 从这个概念出发,我们希望使用有限状态机来模拟LLM的任务解决过程。当使用LLM解决一个多步骤任务时,任务解决过程的每一步都可以映射到一个状态。
让我们以一个SQL任务为例(见下图)。 对于这个任务,期望的流程是:
- 收集数据库中表和列的信息,
- 构建查询以检索所需信息,
- 最后验证任务是否解决并结束流程。
对于每个步骤,我们创建一个相应的状态。同时,我们定义了一个错误状态来处理失败情况。
在图中,执行结果通过红色箭头表示失败,绿色箭头表示成功。
状态的转换基于特定的规则。例如,当“提交”命令成功时,模型将转换到结束状态。
当到达某个状态时,将执行一系列定义的输出函数(例如,M_i -> E 表示首先调用模型,然后执行SQL命令)。
实验
InterCode: 我们在InterCode基准测试中的SQL任务和Bash任务上对StateFlow进行了评估,使用了GTP-3.5-Turbo和GPT-4-Turbo。 我们记录了不同的指标以进行综合比较。'SR'(成功率)衡量了性能,'Turns'表示与环境的交互次数,'Error Rate'表示执行的命令的错误率。 我们还记录了LLM使用的成本。
我们与以下基线进行比较: (1) ReAct: 一种少样本提示方法,提示模型生成思维和行动。 (2) Plan & Solve: 一种两步提示策略,首先要求模型提出计划,然后执行。
Bash 任务的结果如下所示:
ALFWorld: 我们还对ALFWorld基准进行了实验,这是一个在TextWorld环境中实现的基于文本的合成游戏。 我们使用GPT-3.5-Turbo进行了测试,并取了3次尝试的平均值。
我们通过以下方式进行评估: (1) ReAct:我们使用ReAct中的两段提示。请注意,每种类型的任务都有特定的提示。 (2) ALFChat(2代理人):来自AutoGen的双代理人系统设置,包括一个助手代理人和一个执行代理人。ALFChat基于ReAct,修改了ReAct提示以遵循对话方式。 (3) ALFChat(3代理人):基于双代理人系统,它引入了一个基础代理人,每当助手连续三次输出相同的动作时,提供常识性事实。
对于这两项任务,StateFlow以最低的成本实现了最佳性能。更多详情,请参考我们的论文。
使用GroupChat实现StateFlow
我们展示了如何使用 GroupChat 构建 StateFlow。上一篇博客 FSM Group Chat 介绍了 GroupChat 的一个新功能,该功能允许我们输入一个转换图来限制代理的转换。这要求我们在代理的 description
参数中使用自然语言描述 FSM 的转换条件,然后使用 LLM 接收描述并决定下一个代理。在本博客中,我们利用了传递给 GroupChat
对象的 speaker_selection_method
的自定义发言者选择函数。该函数允许我们自定义代理之间的转换逻辑,并且可以与 FSM Group Chat 中介绍的转换图一起使用。当前的 StateFlow 实现还允许用户覆盖转换图。这些转换可以基于当前的发言者和上下文历史的静态检查(例如,检查最后一条消息中是否包含“错误”)。
我们展示了一个示例,说明如何使用GroupChat构建面向状态的工作流。
我们定义了一个自定义的演讲者选择函数,将其传递给GroupChat的speaker_selection_method
参数。
这里的任务是与给定主题相关的研究论文,并为这些论文创建一个markdown表格。
我们定义以下代理:
- 初始化器:通过发送任务来启动工作流。
- Coder:通过编写代码从互联网上检索论文。
- 执行器:执行代码。
- 科学家:阅读论文并撰写摘要。
# Define the agents, the code is for illustration purposes and is not executable.
initializer = autogen.UserProxyAgent(
name="Init"
)
coder = autogen.AssistantAgent(
name="Coder",
system_message="""You are the Coder. Write Python Code to retrieve papers from arxiv."""
)
executor = autogen.UserProxyAgent(
name="Executor",
system_message="Executor. Execute the code written by the Coder and report the result.",
)
scientist = autogen.AssistantAgent(
name="Scientist",
system_message="""You are the Scientist. Please categorize papers after seeing their abstracts printed and create a markdown table with Domain, Title, Authors, Summary and Link. Return 'TERMINATE' in the end.""",
)
在图中,我们定义了一个简单的研究工作流程,包含4个状态:初始化、检索、研究和结束。在每个状态下,我们将调用不同的代理来执行任务。
- 初始化:我们使用初始化器来启动工作流程。
- 检索:我们将首先调用编码员编写代码,然后调用执行器来执行代码。
- 研究:我们将请科学家阅读论文并撰写摘要。
- 结束:我们将结束工作流程。
然后我们定义一个自定义函数来控制状态之间的转换:
def state_transition(last_speaker, groupchat):
messages = groupchat.messages
if last_speaker is initializer:
# init -> retrieve
return coder
elif last_speaker is coder:
# retrieve: action 1 -> action 2
return executor
elif last_speaker is executor:
if messages[-1]["content"] == "exitcode: 1":
# retrieve --(execution failed)--> retrieve
return coder
else:
# retrieve --(execution success)--> research
return scientist
elif last_speaker == "Scientist":
# research -> end
return None
groupchat = autogen.GroupChat(
agents=[initializer, coder, executor, scientist],
messages=[],
max_round=20,
speaker_selection_method=state_transition,
)
我们建议为每个发言者在自定义函数中实现转换逻辑。类似于状态机,状态转换函数根据当前状态和输入决定下一个状态。
我们还可以返回一个字符串从['auto', 'manual', 'random', 'round_robin']
中选择默认方法使用,而不是返回代表下一个发言者的Agent
类。
例如,我们总是可以默认使用内置的auto
方法,采用基于LLM的群聊管理器来选择下一个发言者。
当返回None
时,群聊将终止。请注意,一些转换,例如“initializer” -> “coder”可以使用转换图来定义。