AI--框架开发实践

AI–框架开发实践

0.前言

  前面我们简单学习了低代码平台的智能体搭建,也学习了智能体的经典范式构建,从ReAct、Plan-and-Solve 再到 Reflection的核心工作流,本章节主要是讨论如何使用业界没的主流智能体框架,来高效,规范的构建更加可靠的智能体 (学习内容主要参考来源:https://github.com/datawhalechina/hello-agents

  

1.为什么需要智能体框架

  我们首先需要明确,框架的本质,是提供一套经过验证的规范,将智能体共有的,重复性的工作进行了封装和整理,让我们在构建新的智能体时,能够专注于独特的业务逻辑,而非通用的底层实现

  

2.主流框架的选型与对比

  在本次的框架中,我们将学习四个比较具有代表性的框架:AutoGen、AgentScope、CAMEL 和 LangGraph

  AutoGen:AutoGen的核心思想是通过对话实现协作,将多个智能体系统抽象为一个由多个可对话智能体所组成的群聊。开发者赋予不同的角色定位,并设定了交互的规则

  AgentScope:AgentScope 是一个专为多智能体应用设计的、功能全面的开发平台。它的核心特点是易用性和工程化。它提供了一套非常友好的编程接口,让开发者可以轻松定义智能体、构建通信网络,并管理整个应用的生命周期。其内置的消息传递机制和对分布式部署的支持,使其非常适合构建和运维复杂、大规模的多智能体系统。

  CAMEL:CAMEL 提供了一种新颖的、名为角色扮演 (Role-Playing) 的协作方法[3]。其核心理念是,我们只需要为两个智能体,设定好各自的角色和共同的任务目标,它们就能在“初始提示 ”的引导下,自主地进行多轮对话,相互启发、相互配合,共同完成任务。

  LangGraph:作为 LangChain 生态的扩展,LangGraph 另辟蹊径,将智能体的执行流程建模为图 (Graph)。在传统的链式结构中,信息只能单向流动。而 LangGraph 将每一步操作(如调用LLM、执行工具)定义为图中的一个节点 (Node),并用边 (Edge) 来定义节点之间的跳转逻辑。

  

3.AutoGen

  前文提到,AutoGen的核心思想是通过对话实现协作,将复杂的任务解决流程,映射为了不同角色的智能体之间的一些自动化交流。我们这里以0.7.4版本为例。

3.1 Auto的核心机制

  0.7.4版本是AutoGen发布的一个重要节点,对原本框架的底层设计上进行了一次根本性的更新

3.1.1 框架结构上

  如前图所示,引入了清晰的分层和异步优先的设计理念。

  • 分层设计: 框架被拆分为两个核心模块:
    • autogen-core:作为框架的底层基础,封装了与语言模型交互、消息传递等核心功能。它的存在保证了框架的稳定性和未来扩展性。
    • autogen-agentchat:构建于 core 之上,提供了用于开发对话式智能体应用的高级接口,简化了多智能体应用的开发流程。 这种分层策略使得各组件职责明确,降低了系统的耦合度。
  • 异步优先: 新架构全面转向异步编程 (async/await)。在多智能体协作场景中,网络请求(如调用 LLM API)是主要耗时操作。异步模式允许系统在等待一个智能体响应时处理其他任务,从而避免了线程阻塞,显著提升了并发处理能力和系统资源的利用效率。
3.1.2 组件上

  智能体成为了执行任务的基本单元

  • AssistantAgent (助理智能体): 这是任务的主要解决者,其核心是封装了一个大型语言模型(LLM)。它的职责是根据对话历史生成富有逻辑和知识的回复,例如提出计划、撰写文章或编写代码。通过不同的系统消息(System Message),我们可以为其赋予不同的“专家”角色。
1
2
3
4
5
return AssistantAgent(
name="ProductManager",
model_client=model_client,
system_message=system_message,
)
  • UserProxyAgent (用户代理智能体): 这是 AutoGen 中功能独特的组件。它扮演着双重角色:既是人类用户的“代言人”,负责发起任务和传达意图;又是一个可靠的“执行器”,可以配置为执行代码或调用工具,并将结果反馈给其他智能体。这种设计清晰地区分了“思考”与“行动”。
1
2
3
4
5
6
7
8
9
10
11
12
13
#用户代理
def create_user_proxy():
"""创建用户代理智能体"""
return UserProxyAgent(
name="UserProxy",
description="""用户代理,负责以下职责:
1. 代表用户提出开发需求
2. 执行最终的代码实现
3. 验证功能是否符合预期
4. 提供用户反馈和建议

完成测试后请回复 TERMINATE。""",
)
3.1.3 协作上

  当任务需要多个智能体协作时,就需要一个机制来协调对话流程,在现有的新框架中,引入了灵活的Team或者群聊概念,例如 RoundRobinGroupChat

  • 轮询群聊 (RoundRobinGroupChat): 这是一种明确的、顺序化的对话协调机制。它会让参与的智能体按照预定义的顺序依次发言。这种模式非常适用于流程固定的任务,例如一个典型的软件开发流程:产品经理先提出需求,然后工程师编写代码,最后由代码审查员进行检查。
  • 工作流:
    1. 首先,创建一个 RoundRobinGroupChat 实例,并将所有参与协作的智能体(如产品经理、工程师等)加入其中。
    2. 当一个任务开始时,群聊会按照预设的顺序,依次激活相应的智能体。
    3. 被选中的智能体根据当前的对话上下文进行响应。
    4. 群聊将新的回复加入对话历史,并激活下一个智能体。
    5. 这个过程会持续进行,直到达到最大对话轮次或满足预设的终止条件。
1
2
3
4
5
6
7
8
9
10
11
# 定义团队聊天和协作规则
team_chat = RoundRobinGroupChat(
participants=[
pm,
eng,
reviewer,
proxy
],
termination_condition=TextMentionTermination("TERMINATE"),
max_turns=20,
)

  通过这种方式,AutoGen 将复杂的协作关系,简化为一个流程清晰、易于管理的自动化“圆桌会议”。开发者只需定义好每个团队成员的角色和发言顺序,剩下的协作流程便可由群聊机制自主驱动。

  

3.2 实现软件开发团队
3.2.1 明确业务目标

  首先我们明确一个业务目标,我们的目的是开发一个功能明确的 Web 应用:实时显示比特币当前价格。这个任务虽小,却完整地覆盖了软件开发的典型环节:从需求分析、技术选型、编码实现到代码审查和最终测试。这使其成为检验 AutoGen 自动化协作流程的理想场景。

3.2.2 明确智能体团队角色

  为了模拟真实的软件开发流程,我们设计了四个职责分明的智能体角色:

  • ProductManager (产品经理): 负责将用户的模糊需求转化为清晰、可执行的开发计划。
  • Engineer (工程师): 依据开发计划,负责编写具体的应用程序代码。
  • CodeReviewer (代码审查员): 负责审查工程师提交的代码,确保其质量、可读性和健壮性。
  • UserProxy (用户代理): 代表最终用户,发起初始任务,并负责执行和验证最终交付的代码。

  这种角色划分是多智能体系统设计中的关键一步,它将一个复杂任务分解为多个由领域“专家”处理的子任务。

3.2.3 智能体角色的定义

  定义智能体的核心在于编写高质量的系统消息 (System Message)。系统消息就像是给智能体设定的“行为准则”和“专业知识库”,它精确地规定了智能体的角色、职责、工作流程,甚至是与其他智能体交互的方式。一个精心设计的系统消息是确保多智能体系统能够高效、准确协作的关键。在我们的软件开发团队中,我们为每一个角色都创建了一个独立的函数来封装其定义。

  • 产品经理 (ProductManager)

  产品经理负责启动整个流程。它的系统消息不仅定义了其职责,还规范了其输出的结构,并包含了引导对话转向下一环节(工程师)的明确指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def create_product_manager(model_client):
"""创建产品经理智能体"""
system_message = """你是一位经验丰富的产品经理,专门负责软件产品的需求分析和项目规划。

你的核心职责包括:
1. **需求分析**:深入理解用户需求,识别核心功能和边界条件
2. **技术规划**:基于需求制定清晰的技术实现路径
3. **风险评估**:识别潜在的技术风险和用户体验问题
4. **协调沟通**:与工程师和其他团队成员进行有效沟通

当接到开发任务时,请按以下结构进行分析:
1. 需求理解与分析
2. 功能模块划分
3. 技术选型建议
4. 实现优先级排序
5. 验收标准定义

请简洁明了地回应,并在分析完成后说"请工程师开始实现"。"""

return AssistantAgent(
name="ProductManager",
model_client=model_client,
system_message=system_message,
)
  • 工程师 (Engineer)

  工程师的系统消息聚焦于技术实现。它列举了工程师的技术专长,并规定了其在接收到任务后的具体行动步骤,同样也包含了引导流程转向代码审查员的指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def create_engineer(model_client):
"""创建软件工程师智能体"""
system_message = """你是一位资深的软件工程师,擅长 Python 开发和 Web 应用构建。

你的技术专长包括:
1. **Python 编程**:熟练掌握 Python 语法和最佳实践
2. **Web 开发**:精通 Streamlit、Flask、Django 等框架
3. **API 集成**:有丰富的第三方 API 集成经验
4. **错误处理**:注重代码的健壮性和异常处理

当收到开发任务时,请:
1. 仔细分析技术需求
2. 选择合适的技术方案
3. 编写完整的代码实现
4. 添加必要的注释和说明
5. 考虑边界情况和异常处理

请提供完整的可运行代码,并在完成后说"请代码审查员检查"。"""

return AssistantAgent(
name="Engineer",
model_client=model_client,
system_message=system_message,
)
  • 代码审查员 (CodeReviewer)

  代码审查员的定义则侧重于代码的质量、安全性和规范性。它的系统消息详细列出了审查的重点和流程,确保了代码交付前的质量关卡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def create_code_reviewer(model_client):
"""创建代码审查员智能体"""
system_message = """你是一位经验丰富的代码审查专家,专注于代码质量和最佳实践。

你的审查重点包括:
1. **代码质量**:检查代码的可读性、可维护性和性能
2. **安全性**:识别潜在的安全漏洞和风险点
3. **最佳实践**:确保代码遵循行业标准和最佳实践
4. **错误处理**:验证异常处理的完整性和合理性

审查流程:
1. 仔细阅读和理解代码逻辑
2. 检查代码规范和最佳实践
3. 识别潜在问题和改进点
4. 提供具体的修改建议
5. 评估代码的整体质量

请提供具体的审查意见,完成后说"代码审查完成,请用户代理测试"。"""

return AssistantAgent(
name="CodeReviewer",
model_client=model_client,
system_message=system_message,
)
  • 用户代理 (UserProxy)

  UserProxyAgent是一个特殊的智能体,它不依赖 LLM 进行回复,而是作为用户在系统中的代理。它的description字段清晰地描述了其职责,尤其重要的是,它负责在任务最终完成后发出 TERMINATE 指令,以正常结束整个协作流程。

1
2
3
4
5
6
7
8
9
10
11
12
def create_user_proxy():
"""创建用户代理智能体"""
return UserProxyAgent(
name="UserProxy",
description="""用户代理,负责以下职责:
1. 代表用户提出开发需求
2. 执行最终的代码实现
3. 验证功能是否符合预期
4. 提供用户反馈和建议

完成测试后请回复 TERMINATE。""",
)
  • 定义团队协作流程

  在本案例中,软件开发的流程是相对固定的(需求->编码->审查->测试),因此 RoundRobinGroupChat (轮询群聊) 是理想的选择。我们按照业务逻辑顺序,将四个智能体加入到参与者列表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.conditions import TextMentionTermination

# 定义团队聊天和协作规则
team_chat = RoundRobinGroupChat(
participants=[
product_manager,
engineer,
code_reviewer,
user_proxy
],
termination_condition=TextMentionTermination("TERMINATE"),
max_turns=20,
)

  这里需要注意 participants 列表的顺序决定了智能体的发言次序,而termination_condition是控制协作流程何时结束的关键。这里我们设定,当任何消息中包含关键词 “TERMINATE” 时,对话便结束。在我们的设计中,这个指令由 UserProxy 在完成最终测试后发出

  • 启动与运行

  由于 AutoGen 0.7.4 采用异步架构,整个协作流程的启动和运行都在一个异步函数中完成,并最终通过 asyncio.run() 来执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
async def run_software_development_team():
# ... 初始化客户端和智能体 ...

# 定义任务描述
task = """我们需要开发一个比特币价格显示应用,具体要求如下:
核心功能:
- 实时显示比特币当前价格(USD)
- 显示24小时价格变化趋势(涨跌幅和涨跌额)
- 提供价格刷新功能

技术要求:
- 使用 Streamlit 框架创建 Web 应用
- 界面简洁美观,用户友好
- 添加适当的错误处理和加载状态

请团队协作完成这个任务,从需求分析到最终实现。"""

# 异步执行团队协作,并流式输出对话过程
result = await Console(team_chat.run_stream(task=task))
return result

# 主程序入口
if __name__ == "__main__":
result = asyncio.run(run_software_development_team())
3.2.4 最终实现代码
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.agents import AssistantAgent, UserProxyAgent
from autogen_agentchat.ui import Console

from dotenv import load_dotenv
import asyncio
import os

# 加载 .env 文件中的环境变量
load_dotenv()

def openai_model_client():
"""创建并配置 OpenAI 模型客户端"""
return OpenAIChatCompletionClient(
model=os.getenv("LLM_MODEL_ID"),
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL"),
model_info={
"name": os.getenv("LLM_MODEL_ID"),
"manufacturer": "MiniMax",
"version": "1.0",
"supports_function_calling": True,
"supports_streaming": True,
"supports_native_streaming": True,
"vision": False,
"family": "unknown",
"max_tokens": 8192,
"function_calling": True,
"json_output": True,
}
)


#产品经理
def create_product_manager(model_client):
"""创建产品经理智能体"""
system_message = """你是一位经验丰富的产品经理,专门负责软件产品的需求分析和项目规划。

你的核心职责包括:
1. **需求分析**:深入理解用户需求,识别核心功能和边界条件
2. **技术规划**:基于需求制定清晰的技术实现路径
3. **风险评估**:识别潜在的技术风险和用户体验问题
4. **协调沟通**:与工程师和其他团队成员进行有效沟通

当接到开发任务时,请按以下结构进行分析:
1. 需求理解与分析
2. 功能模块划分
3. 技术选型建议
4. 实现优先级排序
5. 验收标准定义

请简洁明了地回应,并在分析完成后说"请工程师开始实现"。"""

return AssistantAgent(
name="ProductManager",
model_client=model_client,
system_message=system_message,
)

#工程师
def create_engineer(model_client):
"""创建软件工程师智能体"""
system_message = """你是一位资深的软件工程师,擅长 Python 开发和 Web 应用构建。

你的技术专长包括:
1. **Python 编程**:熟练掌握 Python 语法和最佳实践
2. **Web 开发**:精通 Streamlit、Flask、Django 等框架
3. **API 集成**:有丰富的第三方 API 集成经验
4. **错误处理**:注重代码的健壮性和异常处理

当收到开发任务时,请:
1. 仔细分析技术需求
2. 选择合适的技术方案
3. 编写完整的代码实现
4. 添加必要的注释和说明
5. 考虑边界情况和异常处理

请提供完整的可运行代码,并在完成后说"请代码审查员检查"。"""

return AssistantAgent(
name="Engineer",
model_client=model_client,
system_message=system_message,
)

#代码审查员
def create_code_reviewer(model_client):
"""创建代码审查员智能体"""
system_message = """你是一位经验丰富的代码审查专家,专注于代码质量和最佳实践。

你的审查重点包括:
1. **代码质量**:检查代码的可读性、可维护性和性能
2. **安全性**:识别潜在的安全漏洞和风险点
3. **最佳实践**:确保代码遵循行业标准和最佳实践
4. **错误处理**:验证异常处理的完整性和合理性

审查流程:
1. 仔细阅读和理解代码逻辑
2. 检查代码规范和最佳实践
3. 识别潜在问题和改进点
4. 提供具体的修改建议
5. 评估代码的整体质量

请提供具体的审查意见,完成后说"代码审查完成,请用户代理测试"。"""

return AssistantAgent(
name="CodeReviewer",
model_client=model_client,
system_message=system_message,
)

#用户代理
def create_user_proxy():
"""创建用户代理智能体"""
return UserProxyAgent(
name="UserProxy",
description="""用户代理,负责以下职责:
1. 代表用户提出开发需求
2. 执行最终的代码实现
3. 验证功能是否符合预期
4. 提供用户反馈和建议

完成测试后请回复 TERMINATE。""",
)

model_client = openai_model_client()
pm = create_product_manager(model_client)
eng = create_engineer(model_client)
reviewer = create_code_reviewer(model_client)
proxy = create_user_proxy()

# 定义团队聊天和协作规则
team_chat = RoundRobinGroupChat(
participants=[
pm,
eng,
reviewer,
proxy
],
termination_condition=TextMentionTermination("TERMINATE"),
max_turns=20,
)


async def run_software_development_team():
# ... 初始化客户端和智能体 ...

# 定义任务描述
task = """我们需要开发一个比特币价格显示应用,具体要求如下:
核心功能:
- 实时显示比特币当前价格(USD)
- 显示24小时价格变化趋势(涨跌幅和涨跌额)
- 提供价格刷新功能

技术要求:
- 使用 Streamlit 框架创建 Web 应用
- 界面简洁美观,用户友好
- 添加适当的错误处理和加载状态

请团队协作完成这个任务,从需求分析到最终实现。"""
# 异步执行团队协作,并流式输出对话过程
result = await Console(team_chat.run_stream(task=task))
return result

# 主程序入口
if __name__ == "__main__":
result = asyncio.run(run_software_development_team())

  执行效果:

3.3 AutoGen 的优势与局限性分析
  • 优势:

  逻辑层面,我们无需为智能体团队设计复杂的状态机或控制流逻辑,而是将一个完整的软件开发流程,自然地映射为产品经理、工程师和审查员之间的对话。这种方式更贴近人类团队的协作模式,显著降低了为复杂任务建模的门槛。开发者可以将更多精力聚焦于定义“谁(角色)”以及“做什么(职责)”,而非“如何做(流程控制)”。

  角色层面,框架允许通过系统消息(System Message)为每个智能体赋予高度专业化的角色。,例如在案例中ProductManager专注于需求,CodeReviewer专注于质量。一个精心设计的智能体可以在不同项目中被复用,易于维护和扩展。

  任务层面,RoundRobinGroupChat这样机制提供了清晰、可预测的协作流程。同时,UserProxyAgent的设计为“人类在环”(Human-in-the-loop)提供了天然的接口。它既可以作为任务的发起者,也可以是流程的监督者和最终的验收者。这种设计确保了自动化系统始终处于人类的监督之下。

  • 局限性

  虽然 RoundRobinGroupChat提供了顺序化的流程,但基于 LLM 的对话本质上具有不确定性。智能体可能会产生偏离预期的回复,导致对话走向意外的分支,甚至陷入循环

  其次是当智能体团队的工作结果未达预期时,调试过程可能非常棘手。与传统程序不同,我们得到的不是清晰的错误堆栈,而是一长串的对话历史。这被称为“对话式调试”的难题。

  

4.AgentScope

  前面介绍了AutoGen的设计是以对话驱动协作,而这次要介绍的AgentScope则代表了另一种技术方案:工程化优先的多智能体平台,是由阿里巴巴达摩院开发,专门为构建大规模、高可靠性的多智能体应用而设计。

4.1 AgentScope的分层架构

  与 AutoGen 相比,AgentScope 的核心差异在于其消息驱动的架构设计和工业级的工程实践。如果说 AutoGen 更像是一个灵活的”对话工作室”,那么 AgentScope 就是一个完整的”智能体操作系统”,为开发者提供了从开发、测试到部署的全生命周期支持。

  该架构自底向上分为四层:

  • 基础组件层:

  提供核心构建块,包括统一多模态消息的 Message、管理长短期记忆的 Memory、抽象大模型调用的 Model API,以及封装外部交互的 Tool 组件。

  • 智能体基础设施层:

  提供高级抽象,内置多种预构建智能体并实现经典 ReAct 范式,支持智能体钩子、并行工具调用与状态管理,且原生具备异步执行与实时控制优势。

  • 多智能体协作层:

  通过 MsgHub 负责消息路由与状态管理,结合支持顺序/并发等模式的 Pipeline 工作流编排,简化复杂多智能体协作场景的构建。

  • 开发与部署层:

  聚焦工程化落地,提供生产级运行时环境(AgentScope Runtime)与完整的可视化开发工具链(AgentScope Studio)。

4.2 架构实现核心
4.2.1 消息驱动

  AgentScope 的核心创新在于其消息驱动架构。在这个架构中,所有的智能体交互都被抽象为消息的发送和接收,而不是传统的函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
from agentscope.message import Msg

# 消息的标准结构
message = Msg(
name="Alice", # 发送者名称
content="Hello, Bob!", # 消息内容
role="user", # 角色类型
metadata={ # 元数据信息
"timestamp": "2024-01-15T10:30:00Z",
"message_type": "text",
"priority": "normal"
}
)
4.2.2 智能体生命周期管理

  在 AgentScope 中,每个智能体都有明确的生命周期(初始化、运行、暂停、销毁等),并基于一个统一的基类 AgentBase 来实现。开发者通常只需要关注其核心的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from agentscope.agents import AgentBase

class CustomAgent(AgentBase):
def __init__(self, name: str, **kwargs):
super().__init__(name=name, **kwargs)
# 智能体初始化逻辑

def reply(self, x: Msg) -> Msg:
# 智能体的核心响应逻辑
response = self.model(x.content)
return Msg(name=self.name, content=response, role="assistant")

def observe(self, x: Msg) -> None:
# 智能体的观察逻辑(可选)
self.memory.add(x)
4.2.3 消息传递机制

  AgentScope 内置了一个消息中心 (MsgHub),它是整个消息驱动架构的中枢。MsgHub 不仅负责消息的路由和分发,还集成了持久化和分布式通信等高级功能,它有以下这些特点。

  • 灵活的消息路由: 支持点对点、广播、组播等多种通信模式,可以构建灵活复杂的交互网络。
  • 消息持久化: 能够将所有消息自动保存到数据库(如 SQLite, MongoDB),确保了长期运行任务的状态可以被恢复。
  • 原生分布式支持: 这是 AgentScope 的标志性特性。智能体可以被部署在不同的进程或服务器上,MsgHub 会通过 RPC(远程过程调用)自动处理跨节点的通信,对开发者完全透明。

  这些由底层架构提供的工程化能力,使得 AgentScope 在处理需要高并发、高可靠性的复杂应用场景时,比传统的对话驱动框架更具优势。当然,这也要求开发者理解并适应消息驱动的异步编程范式。

4.3 三国狼人杀游戏实现

  接下来我们将实现一个狼人杀框架,展示 AgentScope 在处理复杂多智能体交互方面的优势,更重要的是,它演示了如何在一个需要实时协作、角色扮演和策略博弈的场景中,充分发挥消息驱动架构的威力。与传统狼人杀不同,我们的”三国狼人杀”将刘备、关羽、诸葛亮等经典角色引入游戏,每个智能体不仅要完成狼人杀的基本任务(如狼人击杀、预言家查验、村民推理),还要体现出对应三国人物的性格特点和行为模式。

4.3.1 架构设计

  本次的系统设计遵循了分层解耦的原则,将游戏逻辑划分为三个独立的层次,每个层次都映射了 AgentScope 的一个或多个核心组件:

  • 游戏控制层 :

  由一个 ThreeKingdomsWerewolfGame 类作为游戏的主控制器,负责维护全局状态(如玩家存活列表、当前游戏阶段)、推进游戏流程(调用夜晚阶段、白天阶段)以及裁定胜负。

  • 智能体交互层:

  完全由 MsgHub 驱动。所有智能体间的通信,无论是狼人间的秘密协商,还是白天的公开辩论,都通过消息中心进行路由和分发。

  • 角色建模层:

  每个玩家都是一个基于 DialogAgent 的实例。我们通过精心设计的系统提示词,为每个智能体注入了“游戏角色”和“三国人格”的双重身份。

4.3.2 消息驱动的游戏流程

  本案例最核心的设计是以消息驱动代替状态机来管理游戏流程。在传统实现中,游戏阶段的转换通常由一个中心化的状态机(State Machine)控制。而在 AgentScope 的范式下,游戏流程被自然地建模为一系列定义好的消息交互模式。

  例如,狼人阶段的实现,并非一个简单的函数调用,而是通过 MsgHub 动态创建一个临时的、仅包含狼人玩家的私密通信频道:

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
async def werewolf_phase(self, round_num: int):
"""狼人阶段 - 展示消息驱动的协作模式"""
if not self.werewolves:
return None

# 通过消息中心建立狼人专属通信频道
async with MsgHub(
self.werewolves,
enable_auto_broadcast=True,
announcement=await self.moderator.announce(
f"狼人们,请讨论今晚的击杀目标。存活玩家:{format_player_list(self.alive_players)}"
),
) as werewolves_hub:
# 讨论阶段:狼人通过消息交换策略
for _ in range(MAX_DISCUSSION_ROUND):
for wolf in self.werewolves:
await wolf(structured_model=DiscussionModelCN)

# 投票阶段:收集并统计狼人的击杀决策
werewolves_hub.set_auto_broadcast(False)
kill_votes = await fanout_pipeline(
self.werewolves,
msg=await self.moderator.announce("请选择击杀目标"),
structured_model=WerewolfKillModelCN,
enable_gather=False,
)
4.3.3 用结构化输出约束游戏规则

  狼人杀游戏的一个关键挑战是如何确保智能体的行为符合游戏规则。AgentScope 的结构化输出机制为这个问题提供了解决方案。我们为不同的游戏行为定义了严格的数据模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class DiscussionModelCN(BaseModel):
"""讨论阶段的输出格式"""
reach_agreement: bool = Field(
description="是否已达成一致意见",
default=False
)
confidence_level: int = Field(
description="对当前推理的信心程度(1-10)",
ge=1, le=10,
default=5
)
key_evidence: Optional[str] = Field(
description="支持你观点的关键证据",
default=None
)

class WitchActionModelCN(BaseModel):
"""女巫行动的输出格式"""
use_antidote: bool = Field(description="是否使用解药")
use_poison: bool = Field(description="是否使用毒药")
target_name: Optional[str] = Field(description="毒药目标玩家姓名")

  通过这种方式,我们确保了智能体输出的格式一致性,更重要的是实现了游戏规则的自动化约束。

4.3.4 角色的身份和性格建立

  在这个案例中,最有趣的技术挑战是如何让智能体同时扮演好两个层面的角色:游戏功能角色(狼人、预言家等)和文化人格角色(刘备、曹操等)。我们通过提示词工程来解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def get_role_prompt(role: str, character: str) -> str:
"""获取角色提示词 - 融合游戏规则与人物性格"""
base_prompt = f"""你是{character},在这场三国狼人杀游戏中扮演{role}

重要规则:
1. 你只能通过对话和推理参与游戏
2. 不要尝试调用任何外部工具或函数
3. 严格按照要求的JSON格式回复

角色特点:
"""

if role == "狼人":
return base_prompt + f"""
- 你是狼人阵营,目标是消灭所有好人
- 夜晚可以与其他狼人协商击杀目标
- 白天要隐藏身份,误导好人
- 以{character}的性格说话和行动
"""
4.3.5 并发处理与容错机制

  AgentScope 的异步架构在这个多智能体游戏中发挥了重要作用。游戏中经常出现需要同时收集多个智能体决策的场景,比如投票阶段:

1
2
3
4
5
6
7
8
# 并行收集所有玩家的投票决策
vote_msgs = await fanout_pipeline(
self.alive_players,
await self.moderator.announce("请投票选择要淘汰的玩家"),
structured_model=get_vote_model_cn(self.alive_players),
enable_gather=False,
)

  fanout_pipeline 允许我们并行地向所有智能体发送相同的消息,并异步收集它们的响应。这不仅提高了游戏的执行效率,更重要的是模拟了真实狼人杀游戏中”同时投票”的场景。同时,我们在关键环节加入了容错处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
response = await wolf(
"请分析当前局势并表达你的观点。",
structured_model=DiscussionModelCN
)
except Exception as e:
print(f"⚠️ {wolf.name} 讨论时出错: {e}")
# 创建默认响应,确保游戏继续进行
default_response = DiscussionModelCN(
reach_agreement=False,
confidence_level=5,
key_evidence="暂时无法分析"
)

  接下来进行运行即可

4.3.6 最终实现代码
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
# -*- coding: utf-8 -*-
"""
三国狼人杀 - 基于AgentScope的中文版狼人杀游戏
融合三国演义角色和传统狼人杀玩法

本文件由 chapter6/AgentScopeDemo 下的多个脚本合并而成:
- prompt_cn.py -> ChinesePrompts
- game_roles.py -> GameRoles
- structured_output_cn.py -> 结构化输出模型
- utils_cn.py -> 工具函数与 GameModerator
- main_cn.py -> ThreeKingdomsWerewolfGame 与主流程
"""

import asyncio
import os
import random
from typing import List, Dict, Optional, Any, Literal
from collections import Counter

from dotenv import load_dotenv
from pydantic import BaseModel, Field
from agentscope.agent import ReActAgent, AgentBase
from agentscope.model import OpenAIChatModel
from agentscope.pipeline import MsgHub, sequential_pipeline, fanout_pipeline
from agentscope.formatter import OpenAIMultiAgentFormatter
from agentscope.message import Msg

# 加载 .env 中的 LLM 配置(LLM_MODEL_ID / LLM_API_KEY / LLM_BASE_URL)
load_dotenv()


# ======================================================================
# prompt_cn.py - 三国狼人杀中文提示词
# ======================================================================

class ChinesePrompts:
"""中文提示词管理类"""

@staticmethod
def get_role_prompt(role: str, character: str) -> str:
"""获取角色提示词"""
base_prompt = f"""你是{character},在这场三国狼人杀游戏中扮演{role}

请严格按照以下JSON格式回复,不要添加任何其他文字:
{{
"reach_agreement": true/false,
"confidence_level": 1-10的数字,
"key_evidence": "你的证据或观点"
}}

角色特点:
"""

if role == "狼人":
return base_prompt + f"""
- 你是狼人阵营,目标是消灭所有好人
- 夜晚可以与其他狼人协商击杀目标
- 白天要隐藏身份,误导好人
- 以{character}的性格说话和行动
"""
elif role == "预言家":
return base_prompt + f"""
- 你是好人阵营的预言家,目标是找出所有狼人
- 每晚可以查验一名玩家的真实身份
- 要合理公布查验结果,引导好人投票
- 以{character}的智慧和洞察力分析局势
"""
elif role == "女巫":
return base_prompt + f"""
- 你是好人阵营的女巫,拥有解药和毒药各一瓶
- 解药可以救活被狼人击杀的玩家
- 毒药可以毒杀一名玩家
- 要谨慎使用道具,在关键时刻发挥作用
"""
elif role == "猎人":
return base_prompt + f"""
- 你是好人阵营的猎人
- 被投票出局时可以开枪带走一名玩家
- 要在关键时刻使用技能,带走狼人
- 以{character}的勇猛和决断力行动
"""
else: # 村民
return base_prompt + f"""
- 你是好人阵营的村民
- 没有特殊技能,只能通过推理和投票
- 要仔细观察,找出狼人的破绽
- 以{character}的性格参与讨论
"""


# ======================================================================
# game_roles.py - 三国狼人杀角色定义
# ======================================================================

class GameRoles:
"""游戏角色管理类"""

ROLES = {
"狼人": {
"description": "狼人",
"ability": "夜晚可以击杀一名玩家",
"win_condition": "消灭所有好人或与好人数量相等",
"team": "狼人阵营"
},
"预言家": {
"description": "预言家",a
"ability": "每晚可以查验一名玩家的身份",
"win_condition": "消灭所有狼人",
"team": "好人阵营"
},
"女巫": {
"description": "女巫",
"ability": "拥有解药和毒药各一瓶,可以救人或杀人",
"win_condition": "消灭所有狼人",
"team": "好人阵营"
},
"猎人": {
"description": "猎人",
"ability": "被投票出局时可以开枪带走一名玩家",
"win_condition": "消灭所有狼人",
"team": "好人阵营"
},
"村民": {
"description": "村民",
"ability": "无特殊技能,依靠推理和投票",
"win_condition": "消灭所有狼人",
"team": "好人阵营"
},
"守护者": {
"description": "守护者",
"ability": "每晚可以守护一名玩家免受狼人攻击",
"win_condition": "消灭所有狼人",
"team": "好人阵营"
}
}

CHARACTER_TRAITS = {
"刘备": "仁德宽厚,善于团结众人,说话温和有礼",
"关羽": "忠义刚烈,言辞直接,重情重义",
"张飞": "性格豪爽,说话大声直接,容易冲动",
"诸葛亮": "智慧超群,分析透彻,言辞谨慎",
"赵云": "忠勇双全,话语简洁有力",
"曹操": "雄才大略,善于权谋,话语犀利",
"司马懿": "深谋远虑,城府极深,言辞含蓄",
"周瑜": "才华横溢,略显傲气,分析精准",
"孙权": "年轻有为,善于决断,话语果决"
}

@classmethod
def get_role_desc(cls, role: str) -> str:
"""获取角色描述"""
return cls.ROLES.get(role, {}).get("description", "未知角色")

@classmethod
def get_role_ability(cls, role: str) -> str:
"""获取角色技能"""
return cls.ROLES.get(role, {}).get("ability", "无特殊技能")

@classmethod
def get_character_trait(cls, character: str) -> str:
"""获取角色性格特点"""
return cls.CHARACTER_TRAITS.get(character, "性格温和,说话得体")

@classmethod
def is_werewolf(cls, role: str) -> bool:
"""判断是否为狼人"""
return role == "狼人"

@classmethod
def is_villager_team(cls, role: str) -> bool:
"""判断是否为好人阵营"""
return cls.ROLES.get(role, {}).get("team") == "好人阵营"

@classmethod
def get_standard_setup(cls, player_count: int) -> List[str]:
"""获取标准角色配置"""
if player_count == 6:
return ["狼人", "狼人", "预言家", "女巫", "村民", "村民"]
elif player_count == 8:
return ["狼人", "狼人", "狼人", "预言家", "女巫", "猎人", "村民", "村民"]
elif player_count == 9:
return ["狼人", "狼人", "狼人", "预言家", "女巫", "猎人", "守护者", "村民", "村民"]
else:
# 默认配置:约1/3狼人
werewolf_count = max(1, player_count // 3)
roles = ["狼人"] * werewolf_count

# 添加神职
remaining = player_count - werewolf_count
if remaining >= 1:
roles.append("预言家")
remaining -= 1
if remaining >= 1:
roles.append("女巫")
remaining -= 1
if remaining >= 1:
roles.append("猎人")
remaining -= 1

# 剩余为村民
roles.extend(["村民"] * remaining)

return roles


# ======================================================================
# structured_output_cn.py - 三国狼人杀游戏的结构化输出模型
# ======================================================================

class DiscussionModelCN(BaseModel):
"""中文版讨论输出格式"""

reach_agreement: bool = Field(
description="是否已达成一致意见",
)
confidence_level: int = Field(
description="对当前推理的信心程度(1-10)",
ge=1, le=10
)
key_evidence: Optional[str] = Field(
description="支持你观点的关键证据",
default=None
)


def get_vote_model_cn(agents: list[AgentBase]) -> type[BaseModel]:
"""获取中文版投票模型"""

class VoteModelCN(BaseModel):
"""中文版投票输出格式"""

vote: Literal[tuple(_.name for _ in agents)] = Field(
description="你要投票淘汰的玩家姓名",
)
reason: str = Field(
description="投票理由,简要说明为什么选择此人",
)
suspicion_level: int = Field(
description="对被投票者的怀疑程度(1-10)",
ge=1, le=10
)

return VoteModelCN


class WitchActionModelCN(BaseModel):
"""中文版女巫行动模型"""

use_antidote: bool = Field(
description="是否使用解药救人",
default=False
)
use_poison: bool = Field(
description="是否使用毒药杀人",
default=False
)
target_name: Optional[str] = Field(
description="目标玩家姓名(救人或毒杀的对象)",
default=None
)
action_reason: Optional[str] = Field(
description="行动理由",
default=None
)


def get_seer_model_cn(agents: list[AgentBase]) -> type[BaseModel]:
"""获取中文版预言家模型"""

class SeerModelCN(BaseModel):
"""中文版预言家查验格式"""

target: Literal[tuple(_.name for _ in agents)] = Field(
description="要查验的玩家姓名",
)
check_reason: str = Field(
description="查验此人的原因",
)
priority_level: int = Field(
description="查验优先级(1-10)",
ge=1, le=10
)

return SeerModelCN


def get_hunter_model_cn(agents: list[AgentBase]) -> type[BaseModel]:
"""获取中文版猎人模型"""

class HunterModelCN(BaseModel):
"""中文版猎人开枪格式"""

shoot: bool = Field(
description="是否使用开枪技能",
)
target: Optional[Literal[tuple(_.name for _ in agents)]] = Field(
description="开枪目标玩家姓名",
default=None
)
shoot_reason: Optional[str] = Field(
description="开枪理由",
default=None
)

return HunterModelCN


class WerewolfKillModelCN(BaseModel):
"""中文版狼人击杀模型"""

target: str = Field(
description="要击杀的玩家姓名",
)
kill_strategy: str = Field(
description="击杀策略说明",
)
team_coordination: Optional[str] = Field(
description="与狼队友的配合计划",
default=None
)


class GameAnalysisModelCN(BaseModel):
"""中文版游戏分析模型"""

suspected_werewolves: List[str] = Field(
description="怀疑的狼人名单",
default_factory=list
)
trusted_players: List[str] = Field(
description="信任的玩家名单",
default_factory=list
)
key_clues: List[str] = Field(
description="关键线索列表",
default_factory=list
)
next_strategy: str = Field(
description="下一步策略",
)


# ======================================================================
# utils_cn.py - 三国狼人杀游戏工具函数
# ======================================================================

# 游戏常量
MAX_GAME_ROUND = 10
MAX_DISCUSSION_ROUND = 3
CHINESE_NAMES = [
"刘备", "关羽", "张飞", "诸葛亮", "赵云",
"曹操", "司马懿", "典韦", "许褚", "夏侯惇",
"孙权", "周瑜", "陆逊", "甘宁", "太史慈",
"吕布", "貂蝉", "董卓", "袁绍", "袁术"
]


def get_chinese_name(character: str = None) -> str:
"""获取中文角色名"""
if character and character in CHINESE_NAMES:
return character
return random.choice(CHINESE_NAMES)


def format_player_list(players: List[AgentBase], show_roles: bool = False) -> str:
"""格式化玩家列表为中文显示"""
if not players:
return "无玩家"

if show_roles:
return "、".join([f"{p.name}({getattr(p, 'role', '未知')})" for p in players])
else:
return "、".join([p.name for p in players])


def majority_vote_cn(votes: Dict[str, str]) -> tuple[str, int]:
"""中文版多数投票统计"""
if not votes:
return "无人", 0

vote_counts = Counter(votes.values())
most_voted = vote_counts.most_common(1)[0]

return most_voted[0], most_voted[1]


def check_winning_cn(alive_players: List[AgentBase], roles: Dict[str, str]) -> Optional[str]:
"""检查中文版游戏胜利条件"""
alive_roles = [roles.get(p.name, "村民") for p in alive_players]
werewolf_count = alive_roles.count("狼人")
villager_count = len(alive_roles) - werewolf_count

if werewolf_count == 0:
return "好人阵营胜利!所有狼人已被淘汰!"
elif werewolf_count >= villager_count:
return "狼人阵营胜利!狼人数量已达到或超过好人!"

return None


def analyze_speech_pattern(speech: str) -> Dict[str, Any]:
"""分析发言模式(中文优化)"""
analysis = {
"word_count": len(speech),
"confidence_keywords": 0,
"doubt_keywords": 0,
"emotion_score": 0
}

# 中文关键词分析
confidence_words = ["确定", "肯定", "一定", "绝对", "必须", "显然"]
doubt_words = ["可能", "也许", "或许", "怀疑", "不确定", "感觉"]

for word in confidence_words:
analysis["confidence_keywords"] += speech.count(word)

for word in doubt_words:
analysis["doubt_keywords"] += speech.count(word)

# 简单情感分析
positive_words = ["好", "棒", "赞", "支持", "同意"]
negative_words = ["坏", "差", "反对", "不行", "错误"]

for word in positive_words:
analysis["emotion_score"] += speech.count(word)

for word in negative_words:
analysis["emotion_score"] -= speech.count(word)

return analysis


class GameModerator(AgentBase):
"""中文版游戏主持人"""

def __init__(self) -> None:
super().__init__()
self.name = "游戏主持人"
self.game_log: List[str] = []

async def announce(self, content: str) -> Msg:
"""发布游戏公告"""
msg = Msg(
name=self.name,
content=f"📢 {content}",
role="system"
)
self.game_log.append(content)
await self.print(msg)
return msg

async def night_announcement(self, round_num: int) -> Msg:
"""夜晚阶段公告"""
content = f"🌙 第{round_num}夜降临,天黑请闭眼..."
return await self.announce(content)

async def day_announcement(self, round_num: int) -> Msg:
"""白天阶段公告"""
content = f"☀️ 第{round_num}天天亮了,请大家睁眼..."
return await self.announce(content)

async def death_announcement(self, dead_players: List[str]) -> Msg:
"""死亡公告"""
if not dead_players:
content = "昨夜平安无事,无人死亡。"
else:
content = f"昨夜,{format_player_list_str(dead_players)}不幸遇害。"
return await self.announce(content)

async def vote_result_announcement(self, voted_out: str, vote_count: int) -> Msg:
"""投票结果公告"""
content = f"投票结果:{voted_out}{vote_count}票被淘汰出局。"
return await self.announce(content)

async def game_over_announcement(self, winner: str) -> Msg:
"""游戏结束公告"""
content = f"🎉 游戏结束!{winner}"
return await self.announce(content)


def format_player_list_str(players: List[str]) -> str:
"""格式化玩家姓名列表"""
if not players:
return "无人"
return "、".join(players)


def calculate_suspicion_score(player_name: str, game_history: List[Dict]) -> float:
"""计算玩家可疑度分数"""
score = 0.0

for event in game_history:
if event.get("type") == "vote" and event.get("target") == player_name:
score += 0.3
elif event.get("type") == "accusation" and event.get("target") == player_name:
score += 0.2
elif event.get("type") == "defense" and event.get("player") == player_name:
score -= 0.1

return min(max(score, 0.0), 1.0)


async def handle_interrupt(*args: Any, **kwargs: Any) -> Msg:
"""处理游戏中断"""
return Msg(
name="系统",
content="游戏被中断",
role="system"
)


# ======================================================================
# main_cn.py - 游戏主类与主流程
# ======================================================================

class ThreeKingdomsWerewolfGame:
"""三国狼人杀游戏主类"""

def __init__(self):
self.players: Dict[str, ReActAgent] = {}
self.roles: Dict[str, str] = {}
self.moderator = GameModerator()
self.alive_players: List[ReActAgent] = []
self.werewolves: List[ReActAgent] = []
self.villagers: List[ReActAgent] = []
self.seer: List[ReActAgent] = []
self.witch: List[ReActAgent] = []
self.hunter: List[ReActAgent] = []

# 女巫道具状态
self.witch_has_antidote = True
self.witch_has_poison = True

async def create_player(self, role: str, character: str) -> ReActAgent:
"""创建具有三国背景的玩家"""
name = get_chinese_name(character)
self.roles[name] = role

agent = ReActAgent(
name=name,
sys_prompt=ChinesePrompts.get_role_prompt(role, character),
model=OpenAIChatModel(
model_name=os.getenv("LLM_MODEL_ID"),
api_key=os.getenv("LLM_API_KEY"),
client_args={"base_url": os.getenv("LLM_BASE_URL")},
),
formatter=OpenAIMultiAgentFormatter(),
)

# 角色身份确认
await agent.observe(
await self.moderator.announce(
f"【{name}】你在这场三国狼人杀中扮演{GameRoles.get_role_desc(role)},"
f"你的角色是{character}{GameRoles.get_role_ability(role)}"
)
)

self.players[name] = agent
return agent

async def setup_game(self, player_count: int = 6):
"""设置游戏"""
print("🎮 开始设置三国狼人杀游戏...")

# 获取角色配置
roles = GameRoles.get_standard_setup(player_count)
characters = random.sample([
"刘备", "关羽", "张飞", "诸葛亮", "赵云",
"曹操", "司马懿", "周瑜", "孙权"
], player_count)

# 创建玩家
for i, (role, character) in enumerate(zip(roles, characters)):
agent = await self.create_player(role, character)
self.alive_players.append(agent)

# 分配到对应阵营
if role == "狼人":
self.werewolves.append(agent)
elif role == "预言家":
self.seer.append(agent)
elif role == "女巫":
self.witch.append(agent)
elif role == "猎人":
self.hunter.append(agent)
else:
self.villagers.append(agent)

# 游戏开始公告
await self.moderator.announce(
f"三国狼人杀游戏开始!参与者:{format_player_list(self.alive_players)}"
)

print(f"✅ 游戏设置完成,共{len(self.alive_players)}名玩家")

async def werewolf_phase(self, round_num: int):
"""狼人阶段"""
if not self.werewolves:
return None

await self.moderator.announce(f"🐺 狼人请睁眼,选择今晚要击杀的目标...")

# 狼人讨论
async with MsgHub(
self.werewolves,
enable_auto_broadcast=True,
announcement=await self.moderator.announce(
f"狼人们,请讨论今晚的击杀目标。存活玩家:{format_player_list(self.alive_players)}"
),
) as werewolves_hub:
# 讨论阶段
for _ in range(MAX_DISCUSSION_ROUND):
for wolf in self.werewolves:
await wolf(structured_model=DiscussionModelCN)

# 投票击杀
werewolves_hub.set_auto_broadcast(False)
kill_votes = await fanout_pipeline(
self.werewolves,
msg=await self.moderator.announce("请选择击杀目标"),
structured_model=WerewolfKillModelCN,
enable_gather=False,
)

# 统计投票
votes = {}
for i, vote_msg in enumerate(kill_votes):
# 检查vote_msg是否为None或metadata是否存在
if vote_msg is not None and hasattr(vote_msg, 'metadata') and vote_msg.metadata is not None:
votes[self.werewolves[i].name] = vote_msg.metadata.get("target")
else:
# 如果返回无效,随机选择一个目标
print(f"⚠️ {self.werewolves[i].name} 的击杀投票无效,随机选择目标")
valid_targets = [p.name for p in self.alive_players if p.name not in [w.name for w in self.werewolves]]
votes[self.werewolves[i].name] = random.choice(valid_targets) if valid_targets else None

killed_player, _ = majority_vote_cn(votes)
return killed_player

async def seer_phase(self):
"""预言家阶段"""
if not self.seer:
return

seer_agent = self.seer[0]
await self.moderator.announce("🔮 预言家请睁眼,选择要查验的玩家...")

check_result = await seer_agent(
structured_model=get_seer_model_cn(self.alive_players)
)

# 检查返回结果是否有效
if check_result is None or not hasattr(check_result, 'metadata') or check_result.metadata is None:
print(f"⚠️ 预言家查验失败,跳过此阶段")
return

target_name = check_result.metadata.get("target")
if not target_name:
print(f"⚠️ 预言家未选择查验目标,跳过此阶段")
return

target_role = self.roles.get(target_name, "村民")

# 告知预言家结果
result_msg = f"查验结果:{target_name}{'狼人' if target_role == '狼人' else '好人'}"
await seer_agent.observe(await self.moderator.announce(result_msg))

async def witch_phase(self, killed_player: str):
"""女巫阶段"""
if not self.witch:
return killed_player, None

witch_agent = self.witch[0]
await self.moderator.announce("🧙‍♀️ 女巫请睁眼...")

# 告知女巫死亡信息
death_info = f"今晚{killed_player}被狼人击杀" if killed_player else "今晚平安无事"
await witch_agent.observe(await self.moderator.announce(death_info))

# 女巫行动
witch_action = await witch_agent(structured_model=WitchActionModelCN)

saved_player = None
poisoned_player = None

# 检查返回结果是否有效
if witch_action is None or not hasattr(witch_action, 'metadata') or witch_action.metadata is None:
print(f"⚠️ 女巫行动失败,视为不使用技能")
else:
if witch_action.metadata.get("use_antidote") and self.witch_has_antidote:
if killed_player:
saved_player = killed_player
self.witch_has_antidote = False
await witch_agent.observe(await self.moderator.announce(f"你使用解药救了{killed_player}"))

if witch_action.metadata.get("use_poison") and self.witch_has_poison:
poisoned_player = witch_action.metadata.get("target_name")
if poisoned_player:
self.witch_has_poison = False
await witch_agent.observe(await self.moderator.announce(f"你使用毒药毒杀了{poisoned_player}"))

# 确定最终死亡玩家
final_killed = killed_player if not saved_player else None

return final_killed, poisoned_player

async def hunter_phase(self, shot_by_hunter: str):
"""猎人阶段"""
if not self.hunter:
return None

hunter_agent = self.hunter[0]
if hunter_agent.name == shot_by_hunter:
await self.moderator.announce("🏹 猎人发动技能,可以带走一名玩家...")

hunter_action = await hunter_agent(
structured_model=get_hunter_model_cn(self.alive_players)
)

# 检查返回结果是否有效
if hunter_action is None or not hasattr(hunter_action, 'metadata') or hunter_action.metadata is None:
print(f"⚠️ 猎人技能使用失败,视为放弃开枪")
return None

if hunter_action.metadata.get("shoot"):
target = hunter_action.metadata.get("target")
if target:
await self.moderator.announce(f"猎人{hunter_agent.name}开枪带走了{target}")
return target
else:
print(f"⚠️ 猎人选择开枪但未指定目标,视为放弃")
return None

return None

def update_alive_players(self, dead_players: List[str]):
"""更新存活玩家列表"""
for dead_name in dead_players:
if dead_name:
# 从存活列表移除
self.alive_players = [p for p in self.alive_players if p.name != dead_name]
# 从各阵营移除
self.werewolves = [p for p in self.werewolves if p.name != dead_name]
self.villagers = [p for p in self.villagers if p.name != dead_name]
self.seer = [p for p in self.seer if p.name != dead_name]
self.witch = [p for p in self.witch if p.name != dead_name]
self.hunter = [p for p in self.hunter if p.name != dead_name]

async def day_phase(self, round_num: int):
"""白天阶段"""
await self.moderator.day_announcement(round_num)

# 讨论阶段
async with MsgHub(
self.alive_players,
enable_auto_broadcast=True,
announcement=await self.moderator.announce(
f"现在开始自由讨论。存活玩家:{format_player_list(self.alive_players)}"
),
) as all_hub:
# 每人发言一轮
await sequential_pipeline(self.alive_players)

# 投票阶段
all_hub.set_auto_broadcast(False)
vote_msgs = await fanout_pipeline(
self.alive_players,
await self.moderator.announce("请投票选择要淘汰的玩家"),
structured_model=get_vote_model_cn(self.alive_players),
enable_gather=False,
)

# 统计投票
votes = {}
for i, vote_msg in enumerate(vote_msgs):
# 检查vote_msg是否为None或metadata是否存在
if vote_msg is not None and hasattr(vote_msg, 'metadata') and vote_msg.metadata is not None:
votes[self.alive_players[i].name] = vote_msg.metadata.get("vote")
else:
# 如果返回无效,默认弃票
print(f"⚠️ {self.alive_players[i].name} 的投票无效,视为弃票")
votes[self.alive_players[i].name] = None

voted_out, vote_count = majority_vote_cn(votes)
await self.moderator.vote_result_announcement(voted_out, vote_count)

return voted_out

async def run_game(self):
"""运行游戏主循环"""
try:
await self.setup_game()

for round_num in range(1, MAX_GAME_ROUND + 1):
print(f"\n🌙 === 第{round_num}轮游戏开始 ===")

# 夜晚阶段
await self.moderator.night_announcement(round_num)

# 狼人击杀
killed_player = await self.werewolf_phase(round_num)

# 预言家查验
await self.seer_phase()

# 女巫行动
final_killed, poisoned_player = await self.witch_phase(killed_player)

# 更新死亡玩家
night_deaths = [p for p in [final_killed, poisoned_player] if p]
self.update_alive_players(night_deaths)

# 死亡公告
await self.moderator.death_announcement(night_deaths)

# 检查胜利条件
winner = check_winning_cn(self.alive_players, self.roles)
if winner:
await self.moderator.game_over_announcement(winner)
return

# 白天阶段
voted_out = await self.day_phase(round_num)

# 猎人技能
hunter_shot = await self.hunter_phase(voted_out)

# 更新死亡玩家
day_deaths = [p for p in [voted_out, hunter_shot] if p]
self.update_alive_players(day_deaths)

# 检查胜利条件
winner = check_winning_cn(self.alive_players, self.roles)
if winner:
await self.moderator.game_over_announcement(winner)
return

print(f"第{round_num}轮结束,存活玩家:{format_player_list(self.alive_players)}")

except Exception as e:
print(f"❌ 游戏运行出错:{e}")
import traceback
traceback.print_exc()


async def main():
"""主函数"""
# 检查环境变量
if "DASHSCOPE_API_KEY" not in os.environ:
print("❌ 请设置环境变量 DASHSCOPE_API_KEY")
return

print("🎮 欢迎来到三国狼人杀!")

# 创建并运行游戏
game = ThreeKingdomsWerewolfGame()
await game.run_game()


if __name__ == "__main__":
asyncio.run(main())
4.4 AgentScope 的优势与局限性分析

  通过“三国狼人杀”案例,我们验证了 AgentScope 的核心优势:其消息驱动架构将复杂流程转化为并发、异步的消息传递,克服了传统状态机的僵化;结合结构化输出,将规则直接转化为代码约束,提升了系统稳定性。此外,原生并发设计不仅提升了性能,还确保了单点异常下的整体高容错性。

  然而,这也带来了工程化成本。该架构要求开发者具备异步与分布式编程能力,用于简单场景易导致“过度工程化”;且作为新框架,其生态与社区资源尚待完善。因此,AgentScope 更适合构建大规模、高可靠的生产级系统,而快速原型或简单场景建议选择更轻量级的框架。

  

5.CAMEL

  与 AutoGen 和 AgentScope 这样功能全面的框架不同,CAMEL最初的核心目标是探索如何在最少的人类干预下,让两个智能体通过“角色扮演”自主协作解决复杂任务。

5.1 CAMEL的核心概念

  CAMEL 实现自主协作的基石是两大核心概念:角色扮演 (Role-Playing) 和 引导性提示 (Inception Prompting)。

5.1.1 角色扮演

  在 CAMEL 最初的设计中,一个任务通常由两个智能体协作完成。这两个智能体被赋予了互补的、明确定义的“角色”。一个扮演“AI 用户” (AI User),负责提出需求、下达指令和构思任务步骤;另一个则扮演“AI 助理” (AI Assistant),负责根据指令执行具体操作和提供解决方案。

  例如,在一个“开发股票交易策略分析工具”的任务中:

  • AI 用户 的角色可能是一位“资深股票交易员”。它懂市场、懂策略,但不懂编程。
  • AI 助理 的角色则是一位“优秀的 Python 程序员”。它精通编程,但对股票交易一无所知。

  通过这种设定,任务的解决过程就被自然地转化为一场两位“跨领域专家”之间的对话。交易员提出专业需求,程序员将其转化为代码实现,两者协作完成任何一方都无法独立完成的复杂任务。

5.1.2 引导性提示

  仅仅设定角色还不够,如何确保两个 AI 在没有人类持续监督的情况下,能始终“待在自己的角色里”,并且高效地朝着共同目标前进呢?这就是 CAMEL 最核心的技术,引导性提示发挥作用的地方。“引导性提示”是在对话开始前,分别注入给两个智能体的一段精心设计的、结构化的初始指令(System Prompt)。这段指令就像是为智能体植入的“行动纲领”,它通常包含以下几个关键部分:

  • 明确自身角色:例如,“你是一位资深的股票交易员…”
  • 告知协作者角色:例如,“你正在与一位优秀的 Python 程序员合作…”
  • 定义共同目标:例如,“你们的共同目标是开发一个股票交易策略分析工具。”
  • 设定行为约束和沟通协议:这是最关键的一环。例如,指令会要求 AI 用户“一次只提出一个清晰、具体的步骤”,并要求 AI 助理“在完成上一步之前不要追问更多细节”,同时规定双方需在回复的末尾使用特定标志来标识任务的完成。
5.2 实现AI科普电子书

  为了理解 CAMEL 框架的角色扮演能力,我们将构建一个具有实际价值的协作案例:让一位 AI 心理学家与一位 AI 作者合作,共同创作一本关于”拖延症心理学”的短篇电子书。这个案例体现了 CAMEL 的核心优势,让两个智能体在各自专业领域发挥所长,协作完成单个智能体难以胜任的复杂创作任务。

5.2.1 明确任务设定

  场景设定:创作一本面向普通读者的拖延症心理学科普电子书,要求既有科学严谨性,又具备良好的可读性。

  智能体角色:

  • 心理学家(Psychologist):具备深厚的心理学理论基础,熟悉认知行为科学、神经科学等相关领域,能够提供专业的学术见解和实证研究支持
  • 作家(Writer):拥有优秀的写作技巧和叙述能力,善于将复杂的学术概念转化为生动易懂的文字,注重读者体验和内容的可读性
5.2.2 定义协作任务

  我们需要明确两位 AI 专家的共同目标。我们通过一个内容详实的字符串 task_prompt 来定义这个任务

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
from colorama import Fore
from camel.societies import RolePlaying
from camel.utils import print_text_animated
from camel.models import ModelFactory
from camel.types import ModelPlatformType
from dotenv import load_dotenv
import os

load_dotenv()
LLM_API_KEY = os.getenv("LLM_API_KEY")
LLM_BASE_URL = os.getenv("LLM_BASE_URL")
LLM_MODEL = os.getenv("LLM_MODEL")

#创建模型,在这里以Qwen为例,调用的百炼大模型平台API
model = ModelFactory.create(
model_platform=ModelPlatformType.QWEN,
model_type=LLM_MODEL,
url=LLM_BASE_URL,
api_key=LLM_API_KEY
)

# 定义协作任务
task_prompt = """
创作一本关于"拖延症心理学"的短篇电子书,目标读者是对心理学感兴趣的普通大众。
要求:
1. 内容科学严谨,基于实证研究
2. 语言通俗易懂,避免过多专业术语
3. 包含实用的改善建议和案例分析
4. 篇幅控制在8000-10000字
5. 结构清晰,包含引言、核心章节和总结
"""

print(Fore.YELLOW + f"协作任务:\n{task_prompt}\n")

  task_prompt 是整个协作的“任务说明书”。它不仅是我们要完成的目标,也将在幕后被 CAMEL 用来生成“引导性提示”,确保两位智能体的对话始终围绕这个核心目标展开

5.2.3 初始化角色扮演“社会”

  接下来创建RolePlaying 会话实例,这是 CAMEL 的核心操作,它根据我们提供的角色和任务,快速构建一个双智能体协作“社会”。

1
2
3
4
5
6
7
8
9
10
11
12
# 初始化角色扮演会话
# AI 作家作为 "user",负责提出写作结构和要求
# AI 心理学家作为 "assistant",负责提供专业知识和内容
role_play_session = RolePlaying(
assistant_role_name="心理学家",
user_role_name="作家",
task_prompt=task_prompt,
model=model,
with_task_specify=False, # 在本例中,我们直接使用给定的task_prompt
)

print(Fore.CYAN + f"具体任务描述:\n{role_play_session.task_prompt}\n")

  RolePlaying 是 CAMEL 提供的高级 API,它封装了复杂的提示工程。我们只需传入两个角色的名称和任务即可。在 CAMEL 的设计中,user 角色是对话的“推动者”和“需求方”,而 assistant 角色是“执行者”和“方案提供方”。因此,我们将负责规划结构的“作家”分配给 user_role_name,将负责提供专业知识的“心理学家”分配给 assistant_role_name。

  5.2.4 启动并运行自动化对话

  最后,我们编写一个循环来驱动整个对话过程,让两位 AI 专家开始它们的自动化协作。

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
# 开始协作对话
chat_turn_limit, n = 30, 0
# 调用 init_chat() 来获得由 AI 生成的初始对话消息
input_msg = role_play_session.init_chat()

while n < chat_turn_limit:a
n += 1
# step() 方法驱动一轮完整的对话,AI 用户和 AI 助理各发言一次
assistant_response, user_response = role_play_session.step(input_msg)

# 检查是否有消息返回,防止对话提前终止
if assistant_response.msg is None or user_response.msg is None:
break

print_text_animated(Fore.BLUE + f"作家 (AI User):\n\n{user_response.msg.content}\n")
print_text_animated(Fore.GREEN + f"心理学家 (AI Assistant):\n\n{assistant_response.msg.content}\n")

# 检查任务完成标志
if "<CAMEL_TASK_DONE>" in user_response.msg.content or "<CAMEL_TASK_DONE>" in assistant_response.msg.content:
print(Fore.MAGENTA + "✅ 电子书创作完成!")
break

# 将助理的回复作为下一轮对话的输入
input_msg = assistant_response.msg

print(Fore.YELLOW + f"总共进行了 {n} 轮协作对话")

  这段 while 循环是自动化协作的核心。对话由 init_chat() 方法基于任务和角色自动开启,无需人工编写开场白。循环的每一步都通过调用 step() 来驱动一轮完整的交互(作家提需求、心理学家给内容),并将上一轮心理学家的输出作为下一轮的输入,形成环-环相扣的创作链。整个过程将持续进行,直到达到预设的对话轮次上限,或任一智能体输出任务完成标志 后自动终止。

5.2.4 最终实现代码
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
from colorama import Fore
from camel.societies import RolePlaying
from camel.utils import print_text_animated
from camel.models import ModelFactory
from camel.types import ModelPlatformType
from dotenv import load_dotenv
import os

load_dotenv()
LLM_API_KEY = os.getenv("LLM_API_KEY")
LLM_BASE_URL = os.getenv("LLM_BASE_URL")
LLM_MODEL = os.getenv("LLM_MODEL_ID") # .env 中变量名为 LLM_MODEL_ID

# 创建模型:使用 OpenAI 兼容平台对接 .env 中的 MiniMax 端点
model = ModelFactory.create(
model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
model_type=LLM_MODEL,
url=LLM_BASE_URL,
api_key=LLM_API_KEY,
)

# 定义协作任务
task_prompt = """
创作一本关于"拖延症心理学"的短篇电子书,目标读者是对心理学感兴趣的普通大众。
要求:
1. 内容科学严谨,基于实证研究
2. 语言通俗易懂,避免过多专业术语
3. 包含实用的改善建议和案例分析
4. 篇幅控制在8000-10000字
5. 结构清晰,包含引言、核心章节和总结
"""

print(Fore.YELLOW + f"协作任务:\n{task_prompt}\n")

# 初始化角色扮演会话
# AI 作家作为 "user",负责提出写作结构和要求
# AI 心理学家作为 "assistant",负责提供专业知识和内容
role_play_session = RolePlaying(
assistant_role_name="心理学家",
user_role_name="作家",
task_prompt=task_prompt,
model=model,
with_task_specify=False, # 在本例中,我们直接使用给定的task_prompt
)

print(Fore.CYAN + f"具体任务描述:\n{role_play_session.task_prompt}\n")

# 开始协作对话
chat_turn_limit, n = 30, 0
# 调用 init_chat() 来获得由 AI 生成的初始对话消息
input_msg = role_play_session.init_chat()

while n < chat_turn_limit:
n += 1
# step() 方法驱动一轮完整的对话,AI 用户和 AI 助理各发言一次
assistant_response, user_response = role_play_session.step(input_msg)

# 检查是否有消息返回,防止对话提前终止
if assistant_response.msg is None or user_response.msg is None:
break

print_text_animated(Fore.BLUE + f"作家 (AI User):\n\n{user_response.msg.content}\n")
print_text_animated(Fore.GREEN + f"心理学家 (AI Assistant):\n\n{assistant_response.msg.content}\n")

# 检查任务完成标志
if "<CAMEL_TASK_DONE>" in user_response.msg.content or "<CAMEL_TASK_DONE>" in assistant_response.msg.content:
print(Fore.MAGENTA + "✅ 电子书创作完成!")
break

# 将助理的回复作为下一轮对话的输入
input_msg = assistant_response.msg

print(Fore.YELLOW + f"总共进行了 {n} 轮协作对话")
5.3 CAMEL 的优势与局限性分析

  通过电子书创作案例,我们验证了 CAMEL 框架独特的角色扮演范式。以下是其核心优势与局限性分析,以辅助实际项目的技术选型:

  核心优势

  • “轻架构、重提示”:相比 AutoGen 的复杂对话管理和 AgentScope 的分布式架构,CAMEL 依赖精心设计的初始提示即可实现高质量协作,比硬编码工作流更灵活高效。
  • 功能全面且快速演进:已超越基础的双智能体框架,具备:
    • 多模态与工具:支持文本/图像/音频协作,内置搜索、计算、代码执行等丰富工具。
    • 广泛适配与联动:支持主流闭源/开源 LLM,并与 LangChain、CrewAI、AutoGen 等框架实现互操作。

  主要局限性

  • 高度依赖提示工程:提示设计门槛高,调试困难(难定位是角色、任务还是规则问题),且不同 LLM 对同一提示的理解存在一致性挑战。
  • 协作规模受限:在大规模多智能体场景中,缺乏复杂的对话路由(对比 AutoGen)、分布式状态管理(对比 AgentScope)及有效的冲突仲裁机制。
  • 特定场景适用性弱:虽擅长深度协作与创造性任务,但在严格流程控制(LangGraph 更优)、大规模并发(AgentScope 更优)及复杂决策树(AutoGen 更优)场景下并非最优解。

  CAMEL 通过“以人为本”的角色扮演设计,将复杂的系统工程转化为直观的人际协作模式。随着生态的持续完善,它已成为构建灵活、创造性智能协作系统的重要选择。


AI--框架开发实践
https://one-null-pointer.github.io/2026/03/25/AI--框架开发实践/
Author
liaoyue
Posted on
March 25, 2026
传送口