探索AutoGen的GroupChat

浅尝AutoGen中,展示了如何使用AutoGen进行简单的对话。

这篇博客中记录一下如何利用AutoGen的GroupChat开发一个相对复杂的功能。

GroupChat中的角色设置

假设我们要开发一个前后端分离的个人博客网站,一般来说,这个团队中会有一个产品经理,一个前端开发,一个后端开发,当然,还有一个老板。

如果想要完成这样一个任务,使用在浅尝AutoGen中的方法是不行的,这个时候就可以使用GroupChat。

AutoGen官方关于GroupChat有一个简单的demo,可以快速浏览一下知道大致的结构。

简单来说,GroupChat就是一群大模型Agent在对话,但是可以赋予每个Agent不同的角色,比如产品经理。同时,也允许人类参与这个对话过程。

下面是代码全文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import os
from autogen.agentchat import (
GroupChat,
AssistantAgent,
UserProxyAgent,
GroupChatManager,
)


llm_config = {
"model": "gpt-4o",
"api_key": os.environ.get("OPENAI_API_KEY"),
"base_url": os.environ.get("OPENAI_API_BASE"),
"api_type": "azure",
"api_version": "2023-03-15-preview",
"temperature": 0.9,
}


initializer = UserProxyAgent(
name="Init",
)

hp = AssistantAgent(
name="human_proxy",
llm_config=False, # no LLM used for human proxy
human_input_mode="ALWAYS", # always ask for human input
description="我是甲方,每次其他Agent回复之后,都要呼叫我,我给出指示后才能呼叫其他Agent继续工作",
)

bk = AssistantAgent(
name="bk",
llm_config=llm_config,
system_message="""
你是一个Python开发专家,精通Python语法,善于写出高性能、易维护的Python代码
你擅长选择和挑选最佳工具,并尽力避免不必要的重复和复杂性
在解决问题时,你会将问题分解成小的问题和改善项,并在每个步骤后建议进行小测试,以确保事情在正确的轨道上
如果有任何不清楚或模糊的地方,你总是会要求澄清。你会暂停讨论权衡和实现选项,如果有需要做出选择的情况
遵循这一方法非常重要,并尽力教会你的对话者如何做出有效的决策。你避免不必要的道歉,并审查对话,以防止重复早期的错误
你非常重视安全,并确保在每个步骤中不做任何可能危及数据或引入新漏洞的事情。每当有潜在的安全风险(例如输入处理、身份验证管理)时,你会进行额外的审查
最后,确保所有生成的东西在操作上是可靠的
""",
description="我是Python后端开发,在需要开发后端应用和前期项目细节设计时,请呼叫我",
)

ft = AssistantAgent(
name="ft",
llm_config=llm_config,
system_message="""
你是Web开发方面的专家,包括CSS、JavaScript、React、Tailwind、Node.JS和Hugo/Markdown。你擅长选择和挑选最好的工具,并尽力避免不必要的重复和复杂性。
在提出建议时,你会将问题分解成小的问题和改善项,并建议在每个阶段之后进行小测试,以确保事情走在正确的轨道上。
编写代码以说明示例,或在对话中被指示时编写代码。如果可以不使用代码回答,这是优选的,并且在需要时你会被要求详细说明。
最后,你会生成正确的输出,提供解决当前问题与保持通用性和灵活性之间的正确平衡。
如果有任何不明确或模糊的地方,你总是要求澄清。在需要做出选择时,你会停下来讨论权衡和实现选项。
遵循这一方法非常重要,并尽力教会你的对话者如何做出有效的决策。你避免不必要的道歉,并审查对话,以防止重复早期的错误。
你非常重视安全,并确保在每个步骤中不做任何可能危及数据或引入新漏洞的事情。每当有潜在的安全风险(例如输入处理、身份验证管理)时,你会进行额外的审查
最后,确保所有生成的东西在操作上是可靠的。我们会考虑如何托管、管理、监控和维护我们的解决方案。在每一步中,你都会考虑运营方面的问题,并在相关的地方强调它们。
""",
description="我是前端开发,在需要开发前端应用和前期项目细节设计时,请呼叫我",
)

pm = AssistantAgent(
name="pm",
llm_config=llm_config,
system_message="""
你是个人博客方面的资深产品经理,擅长设计和规划个人博客的架构和功能。
你重视用户体验和产品性能,并且尽可能在满足功能的前提下保持简洁
在提出建议时,你会将问题分解成小的问题和改善项,并建议在每个阶段之后进行小测试,以确保事情走在正确的轨道上。
""",
description="我是产品经理,在产品功能设计、规划时,请呼叫我,在开发过程中需要确认的地方,也请呼叫我",
)

user_proxy = UserProxyAgent(
name="User",
system_message="开发一个适用于个人的照片展示站点",
code_execution_config=False,
human_input_mode="NEVER",
llm_config=False,
description="""
永远不要呼叫我.
""",
)


graph_dict = {}
graph_dict[user_proxy] = [pm, hp]
graph_dict[pm] = [bk, ft, hp]
graph_dict[bk] = [pm, ft, hp]
graph_dict[ft] = [pm, bk, hp]
graph_dict[hp] = [pm, bk, ft]

agents = [user_proxy, bk, ft, pm, hp]

# create the groupchat
group_chat = GroupChat(
agents=agents,
messages=[],
max_round=10,
allowed_or_disallowed_speaker_transitions=graph_dict,
allow_repeat_speaker=None,
speaker_transitions_type="allowed",
)

# create the manager
manager = GroupChatManager(
groupchat=group_chat,
llm_config=llm_config,
code_execution_config=False,
)

# initiate the task
user_proxy.initiate_chat(
manager,
message="开发一个适用于个人的博客站点",
clear_history=True,
)

代码中有4个AssistantAgent,分别是hp(人类代理),bk(后端开发工程师),ft(前端开发工程师)和一个pm(产品经理)。

hp(人类代理)实际上就是我,其实可以不配置这个角色,但是我希望我可以参与其中,并且控制整个过程。我给human_input_mode这个参数设置为ALWAYS,也就是说每次调用这个Agent的时候,都需要人为介入。

bk、ft和pm是一个开发团队中常见的角色,system_message就是预设的Prompt。
我在几个预设的prompt中,分别赋予了Agent不同的角色以及期待他们拥有的特质。

如何选择每一次发言的Agent

上面的内容介绍了如何在GroupChat中配置不同角色的Agents,那么如何决定每次应该由哪一个Agent发言呢?

AutoGen提供了一个叫做StateFlow的方法,这个方法比较强大,可以让开发者根据自己的需求完全自定义。但是官方没有提供很完善的案例教程,只有一些博客和论文,我还没有深入研究,我使用的是另外一种相对简单一点的方法,叫做Finite-State Machine (FSM,即有限状态机)。具体的文档可以参考FSM GroupChat

简单来说,每个Agent有一个description参数,可以用自然语言在这个参数中描述清楚你希望这个Agent在什么场景下被调用,然后由大模型根据这些description信息以及上一轮的对话信息决定下一轮应该由哪个Agent发言。

比如下面的hp这个Agent:

1
2
3
4
5
6
hp = AssistantAgent(
name="human_proxy",
llm_config=False, # no LLM used for human proxy
human_input_mode="ALWAYS", # always ask for human input
description="我是甲方,每次其他Agent回复之后,都要呼叫我,我给出指示后才能呼叫其他Agent继续工作",
)

我希望每一次任何其他Agent发言之后都呼叫这个人类代理Agent,以便我可以在review大模型Agent的输出之后及时给出反馈,如果我没有任何想要想要调整的地方,直接输入继续就可以了。

使用FSM GroupChat时,除了description要好好写之外,还需要维护一个Graph。我的理解就是调用关系。

比如我在这个案例中维护的Graph如下:

1
2
3
4
5
6
graph_dict = {}
graph_dict[user_proxy] = [pm, hp]
graph_dict[pm] = [bk, ft, hp]
graph_dict[bk] = [pm, ft, hp]
graph_dict[ft] = [pm, bk, hp]
graph_dict[hp] = [pm, bk, ft]

我对于这个Graph的理解是,允许user_proxy这个Agent发言之后调用pm或者hp其中的一个Agent,pm发言之后,允许调用bk、ft和hp其中的一个。

如何终止对话

程序运行之后,如何优雅地终止Agents之间的对话呢?

在这个案例中,有两个方法。

  1. max_round
    实例化GroupChat时,有一个max_round参数,这个参数设定了可以进行多少轮对话,到达这个次数之后对话就会终止。

    1
    2
    3
    4
    5
    6
    7
    8
      group_chat = GroupChat(
    agents=agents,
    messages=[],
    max_round=10,
    allowed_or_disallowed_speaker_transitions=graph_dict,
    allow_repeat_speaker=None,
    speaker_transitions_type="allowed",
    )
  2. 人为终止
    由于在这个案例中,有一个人类代理Agent(hp),所以也可以在轮到hp Agent发言时,直接使用自然语言说终止对话,也可以正常终止整个对话流。

至于实际运行效果,大家可以实际试试,其中的大模型可以换成国内的Kimi或者阿里的通义,都是可以很好地兼容OpenAI的的SDK,切换比较容易。

从我自己的测试情况来看,基本上可以在10分钟内开发处一个MVP版本,后面可以人工在这个版本上做一些修改,大概两三个个小时就能做出一个还不错的博客网站了。

提升效果的小技巧

特别提一下,在initiate_chat中,把需求描述的越详细,上面这个工作流的效果越好,而且还能节省大量的费用。

下面是两个不同的案例。

  1. 坏案例

    1
    2
    3
    4
    5
    user_proxy.initiate_chat(
    manager,
    message="开发一个适用于个人的博客站点",
    clear_history=True,
    )
  2. 好案例

    1
    2
    3
    4
    5
    user_proxy.initiate_chat(
    manager,
    message="开发一个适用于个人的博客站点,要支持如下功能:1.使用markdown编写博客,2.免登录的评论功能,3.限流功能,每秒不能访问超过5次,4.首页分为博客、标签、关于三个板块,可以借鉴Hexo Next主题的风格",
    clear_history=True,
    )

上面是我测试过的两个初始需求,第二个效果明显好于第一个,不仅最终生成的结果好,交互的次数也比第一次少,可以介绍调用大模型的费用,尤其是当大模型是GPT-4o或者claude 3.5 sonnet的时候,还是挺贵的。