从原理到代码,拆解AgentScope框架开发实践

(一)引言

前文我们介绍了AutoGen框架,其设计核心思想是"以对话驱动协作",本文将介绍另一种新的agent框架------AgentScope。

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

(二)AgentScope的核心机制

AgentScope采用消息驱动的架构设计和工业级的工程实践,为开发者提供了从开发、测试到部署的全生命周期支持。与许多框架采用的继承式设计不同,AgentScope选择了组合式架构和消息驱动模式

1. 分层架构体系

从底层的基础组件到上层的应用编排,通过分层模块化设计形成了一个完整的智能体开发生态。

(1)基础组件层

它为整个架构提供了核心的模块。

  • Message组件定义了统一的消息格式,支持从简单的文本交互到复杂的多模态内容;
  • Memory组件提供了短期和长期的记忆管理;
  • Model API层抽象了对不同的大语言模型的调用;
  • Tool组件封装了智能体与外部世界交互的能力;

(2)智能体基础设施层

这一层不仅包含了各种预构建的智能体(BUilt-in Agents),还实现了经典的ReAct范式,支持智能体钩子、并行工具调用、状态管理等高级特性。相比于其他框架而言,这一层的原生支持异步执行与实时控制的机制是AgentScope的一大优势。

(3)多智能体协作层

本层是AgentScope的核心创新所在。

  • MsgHub作为消息中心,负责智能体间的消息路由和状态管理
  • Pipeline系统则提供了灵活的工作流编排能力,支持顺序、并发等多种执行模式。

(4)开发与部署层

这一层体现了AgentScope对工程化的重视。

  • AgentScope Runtime提供了生产级的运行时环境
  • AgentScope Studio为开发者提供了完整的可视化开发工具链

2. 消息驱动机制

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

python 复制代码
from agentscope.message import Msg

# 消息的标准格式
message = Msg(
	name="Alice",
	content="Hello",
	role="user",
	metadata={
		"timestamp": "2026-05-26T22:21:00Z",
		"message_type": "text",
		"priority": "normal"
	}
)

将消息作为智能体之间交互的基础单元有以下优势:

  • 异步解耦:消息的发送方和接收方在时间上解耦,无需相互等待,天然支持高并发场景。
  • 智能体位置透明:一个智能体无需关心另一个智能体是在本地进程上还是在远程服务器上,消息系统会自动处理路由。
  • 可观测性:每一条消息都可以被记录、追踪和分析,极大地简化了复杂系统的调试与监控。
  • 可靠性:消息可被持久化存储和重试,即使系统出现故障,也能保证交互的最终一致性,提升了系统的容错能力。

3. 智能体生命周期管理

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

python 复制代码
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="assitant")
		
	def observe(self, x: Msg) -> None:
		# 智能体的观察逻辑
		self.memory.add(x)

这种设计方式的优点是分离了智能体的内部逻辑和外部通信,开发者只需要在 reply 方法中定义智能体"思考与回应"的方式即可。

4. 消息传递机制

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

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

(三)AgentScope的工程实现

1. 需求分析

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

狼人:夜晚击杀好人,白天隐藏身份

预言家:每晚查验一名玩家身份

女巫:拥有解药和毒药各一瓶

猎人:被投票出局时可开枪带走一名玩家

村民:通过推理和投票找出狼人

三国人物

刘备:仁德宽厚,善于团结众人

关羽:忠义刚烈,言辞直接

张飞:性格豪爽,容易冲动

诸葛亮:智慧超群,分析透彻

曹操:雄才大略,善于权谋

司马懿:深谋远虑,城府极深

2. AgentScope框架编码实现

(1)架构设计与核心组件

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

游戏控制层 (ThreeKingdomsWerewolfGame)

├── 游戏状态管理

├── 流程控制

└── 胜负判定

智能体交互层 (MsgHub)

├── 消息路由

├── 并发处理

└── 状态同步

角色建模层 (DialogAgent)

├── 角色提示词

├── 结构化输出

└── 行为约束

  • 角色建模层:每一个玩家都是一个基于 DialogAgent 实例。通过设计系统提示词可以为每个智能体注入"游戏角色"和"三国人格"的双重身份。
  • 智能体交互层:完全由 MsgHub驱动。所有智能体间的通信、无论是狼人之间的秘密协商,还是白天的公开辩论,都是通过消息中心进行路由和转发。
  • 游戏控制层:由一个 ThreeKingdomWerewolfGame 类作为游戏的主控制器,负责维护全局状态(如玩家存活列表、当前游戏阶段)、推进游戏流程(调用夜晚阶段、白天阶段)以及裁定胜负。

(2)消息驱动的游戏流程

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

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

python 复制代码
# 狼人阶段:展示消息驱动的协作模式
async def werewolf_phase(self round_num: int):
	# 安全检查:如果当前没有存活的狼人,则直接返回 None,结束该阶段
	if not self.werewolves:
		return None
	
	
	# 1. 通过消息中心建立狼人专属通信频道
	# 这个 Hub 只对 self.werewolves(存活狼人列表)开放,形成私密频道。
	async with MsgHub(
		self.werewolves,
		enable_auto_broadcast=True,
		announcement=await self.moderator.announce(
			f"狼人们,请讨论今晚的击杀目标。存活玩家:{format_player_list(self.alive_player)}"
		),
	) as werewolves_hub:
		# 2. 讨论阶段:狼人通过消息交换策略
		# 循环 MAX_DISCUSSION_ROUND 次,模拟多轮的战术交流与信息交换
		for _ in range(MAX_DISCUSSION_ROUND):
			# 遍历每一个存活的狼人
			for wolf in self.werewolves:
				# 调用狼人的大模型接口,传入 DiscussionModelCN(中文讨论结构化模型),
                # 让 AI 根据当前局势生成发言内容并发送到 MsgHub 中
				await wolf(structured_model=DiscussionModelCN)
		
		# 3. 投票阶段:收集并统计狼人的击杀决策
		# 关闭自动广播功能,进入静默投票环节,防止投票意向提前泄露或互相干扰
		werewolves_hub.set_auto_broadcast(False)
		# 使用 fanout_pipeline(扇出管道)向所有狼人并行分发指令并收集结果
		kill_votes = await fanout_pipline(
			self.werewolves,
			msg=await self.moderator.announce("请选择击杀目标"),
			structured_model=WerewolfKillModelCN,
			enable_gather=False, # 采集模式开关(根据底层框架逻辑,此处用于控制是否立即汇总展示结果)
		)
	

这种设计的优势在于,游戏逻辑被清晰地表达为"在特定上下文中,以何种模式进行消息交互",而不是一连串僵硬的状态转换。白天讨论(全员广播)、预言家查验(点对点请求)等阶段也都遵循同样的设计范式。

(3)用结构化输出约束游戏规则

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

python 复制代码
class DiscussionModelCN(BaseModel):
	# 讨论阶段的输出格式,专为狼人夜间讨论设计
	reach_agreement: bool = Field(
		description="是否已达成一致意见",
		default=False
	)
	
	confidence_level: int = Field(
		description="对当前推理的信心程度(1-10)",
		ge=1, le=10, # 约束:必须为1-10的整数(ge=大于等于, le=小于等于)
		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)角色建模的双重挑战

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

python 复制代码
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}的性格说话和行动
"""
        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}的性格参与讨论
"""

这种设计会让我们看到一个有趣的现象:不同的三国人物在扮演相同的游戏角色时,会表现出截然不同的策略和说话风格。比如,扮演狼人的曹操可能会表现得更加狡猾和善于伪装,而扮演狼人的张飞则显得更加冲动和直接。

(5)并发处理机制

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

python 复制代码
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 允许我们并行地向所有的智能体发送相同的信息,并异步收集它们的响应。这不仅提高了游戏的执行效率,更重要的是模拟了真实狼人杀游戏中的"同时投票"的场景。

(6)AgentScope框架的完整实现

以Ubuntu22.04系统,Python3.10版本为例进行展示。

首先我们新建一个项目文件夹 agentscope_demo 来存放我们的所有文件。 agentscope_demo/

├── main_cn.py - 主要游戏逻辑和控制逻辑

├── prompt_cn.py - 中文提示词管理

├── game_roles.py - 游戏角色定义与配置

├── structured_output_cn.py - 结构化输出

├── utils_cn.py - 游戏工具函数和辅助方法

├── .env

└── README.md

游戏流程设置

夜晚阶段
  1. 狼人讨论:狼人通过 MsgHub 协商击杀目标
  2. 预言家查验:预言家选择查验对象
  3. 女巫行动:女巫决定是否使用解药/毒药
白天阶段
  1. 死亡公布:公布夜晚死亡玩家
  2. 自由讨论:所有存活玩家参与讨论
  3. 投票淘汰:投票选择淘汰对象
  4. 猎人技能:被淘汰的猎人可开枪

(i)安装依赖与配置APIKey

创建虚拟并激活环境

python -m venv agentscope_venv

source agentscope_venv/bin/activate

在终端输入

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

txt 复制代码
# requirements.txt 
# AgentScope "三国狼人杀"游戏
agentscope 
dashscope 
pydantic

创建阿里云 DashScope API Key:

bailian.console.aliyun.com/

.env

ini 复制代码
DASHSCOPE_API_KEY=sk-91c2*********a698

prompt_cn.py

python 复制代码
# -*- coding: utf-8 -*-
"""三国狼人杀中文提示词"""

class ChinesePrompts:
    """中文提示词管理类"""
    
    @staticmethod
    def get_role_prompt(role: str, character: str) -> str:
        """获取角色提示词,role是游戏中的角色,character是三国中的角色"""
        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}的性格参与讨论
"""

这段代码实现了一个 "三国狼人杀"游戏的中文提示词(Prompt)管理功能 。 具体来说,它定义了一个名为 ChinesePrompts 的 Python 类,其中包含一个静态方法 get_role_prompt。它的核心作用是根据传入的游戏角色(如狼人、预言家)和对应的三国人物名字,动态生成一段用于指导 AI 进行角色扮演和逻辑推理的系统指令(Prompt)。

game_roles.py

python 复制代码
# -*- coding: utf-8 -*-
"""三国狼人杀角色定义"""
from typing import Dict, List


class GameRoles:
    """游戏角色管理类"""
    
    ROLES = {
        "狼人": {
            "description": "狼人",
            "ability": "夜晚可以击杀一名玩家",
            "win_condition": "消灭所有好人或与好人数量相等",
            "team": "狼人阵营"
        },
        "预言家": {
            "description": "预言家",
            "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
            
                      

这段代码实现了一个 "三国狼人杀"游戏的角色与人物配置管理中心

它定义了一个名为 GameRoles 的 Python 类,主要作为静态数据仓库和工具库,为游戏提供核心的规则数据、角色属性以及三国人物的性格设定。

utils_cn.py

python 复制代码
# -*- coding: utf-8 -*-
"""三国狼人杀游戏工具函数"""
import asyncio
import random
from typing import List, Dict, Optional, Any
from collections import Counter

from agentscope.agent import AgentBase
from agentscope.message import Msg

# 游戏常量
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"
    )

这段代码实现了"三国狼人杀"游戏的核心工具函数库。它就像一个精密运转的后台系统,为整个游戏提供了从基础数据、逻辑判定到流程播报的全方位支持。

structured_output_cn.py

python 复制代码
# -*- coding: utf-8 -*-
"""三国狼人杀游戏的结构化输出模型"""
from typing import Literal, Optional, List
from pydantic import BaseModel, Field
from agentscope.agent import AgentBase


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="下一步策略",
    )

这段代码是"三国狼人杀"游戏的AI 决策规范与结构化输出模型定义

如果把大语言模型(LLM)比作一个天马行空的"演员",那么这段代码就是强制要求演员必须填写的标准剧本表格。它利用 Pydantic 库定义了严格的数据结构(Schema),核心作用是解决 AI "说话模棱两可"的问题,确保游戏程序能够精准解析并执行 AI 的意图。

main.py

python 复制代码
# -*- coding: utf-8 -*-
"""
三国狼人杀 - 基于AgentScope的中文版狼人杀游戏
融合三国演义角色和传统狼人杀玩法
"""
import asyncio
import os
import random
from typing import List, Dict, Optional
from dotenv import load_dotenv

from agentscope.agent import ReActAgent # 使用ReAct行为模式代理
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import MsgHub, sequential_pipeline, fanout_pipeline
from agentscope.formatter import DashScopeMultiAgentFormatter

from prompt_cn import ChinesePrompts
from game_roles import GameRoles
from structured_output_cn import (
    DiscussionModelCN,
    get_vote_model_cn,
    WitchActionModelCN,
    get_seer_model_cn,
    get_hunter_model_cn,
    WerewolfKillModelCN
)
from utils_cn import (
    check_winning_cn,
    majority_vote_cn,
    get_chinese_name,
    format_player_list,
    GameModerator, # 游戏主持人
    MAX_GAME_ROUND, # 最大游戏轮数
    MAX_DISCUSSION_ROUND, # 最大讨论轮数
)


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=DashScopeChatModel(
                model_name="qwen-max",
                api_key=os.environ["DASHSCOPE_API_KEY"],
                enable_thinking=True, # 启用思考过程演示
            ),
            formatter=DashScopeMultiAgentFormatter(), # 消息格式化器
        )
        
        # 发送身份确认信息
        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("🎮 开始设置三国狼人杀游戏...")
        
        # 1. 获取标准配置的角色列表 (例如 6人局: 2狼, 1预, 1女, 1猎, 1民)
        roles = GameRoles.get_standard_setup(player_count)
        
        # 2. 随机分配三国人物
        characters = random.sample([
            "刘备", "关羽", "张飞", "诸葛亮", "赵云",
            "曹操", "司马懿", "周瑜", "孙权"
        ], player_count)
        
        # 3. 创建玩家并分配阵营
        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)
        
        # 4. 游戏开始公告
        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"🐺 狼人请睁眼,选择今晚要击杀的目标...")
        
        # 狼人私密讨论 (使用 MsgHub 创建私有通信频道)
        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} 的击杀投票无效,随机选择目标")
                    import random
                    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}"))
        
        # 计算最终死亡玩家 (如果被救则为None)
        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)
                
                # 1. 狼人击杀
                killed_player = await self.werewolf_phase(round_num)
                
                # 2. 预言家查验
                await self.seer_phase()
                
                # 3. 女巫行动
                final_killed, poisoned_player = await self.witch_phase(killed_player)
                
                # 4. 处理夜间死亡并公布
                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)
                
                # 5. 检查夜间死亡后是否有阵营获胜
                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():
    """主函数"""
    # 检查环境变量
    load_dotenv()
    api_key = os.getenv("DASHSCOPE_API_KEY") 
    if not api_key: 
        print("❌ 未找到 DASHSCOPE_API_KEY 环境变量,请检查 .env 文件配置") 
        return
    
    print("🎮 欢迎来到三国狼人杀!")
    
    # 创建并运行游戏
    game = ThreeKingdomsWerewolfGame()
    await game.run_game()


if __name__ == "__main__":
    asyncio.run(main())

3. AgentScope框架测试效果

在终端运行 python main.py ,观察到以下输出:

(四)AgentScope的优势与局限性

1. 优势

  • AgentScope以消息驱动为架构核心,将复杂的游戏流程优雅地映射为一系列开发、异步的消息传递事件,从而避免了状态机的僵硬与复杂。
  • 具备强大的结构化输出能力,将游戏规则直接转化为代码层面的约束,极大的提升了系统的可预测性和可预测性。
  • 容错处理机制保证了即使单个智能体出现异常,整体流程也能稳健运行。

2. 局限性

  • 对开发者的技术要求较高,需要开发者掌握异步编程、分布式通信等概念。
  • 对于简单的多智能体对话场景,这种架构可能显得过于复杂,存在"过度工程化"的风险。
  • 作为相对较新的智能体架构,其生态系统和社区资源还有待进一步完善。
相关推荐
倾颜7 小时前
Agent 不一定要自由规划:我给 Tasklist Agent 加了一次受控决策
agent
阿里云云原生8 小时前
可观测性的终局?从“面向数据”到“面向对象”,UModel 如何为 AI Agent 注入认知地图
云原生·agent
LienJack8 小时前
《Re0 Build Harness》第一章 Agent 基础定义:它为什么不是一句 Prompt?
aigc·agent
青云计划8 小时前
Host 组件:从混乱到秩序的多Agent系统总指挥
agent
Puslar9 小时前
Agent系列二:项目架构设计
agent·全栈
PPPHUANG9 小时前
我把 MacBook 的 Touch Bar,改造成了 AI "摸鱼状态灯"
openai·agent·ai编程
SelectDB9 小时前
- 别把懂语义和查事实混为一谈:企业级 Agent 真正缺的是什么?
数据库·数据分析·agent
C咖咖10 小时前
code review agent
agent·code·review
Wu_Dylan12 小时前
造一个 AI Skill 的 Lighthouse:SkillScope 架构设计与工程实践
agent