第十四章 Generative Agents与仿真¶
⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。
构建由AI驱动的虚拟社会——理解Generative Agents的设计与实现
📌 定位说明:本章基于Stanford经典论文"Generative Agents: Interactive Simulacra of Human Behavior"(Park et al., 2023),从理论到代码完整实现一个"Mini赛博小镇"——多个具有记忆、反思和规划能力的AI Agent在虚拟环境中自主生活、交互,展现涌现行为。
📖 本章概览¶
| 主题 | 内容 | 预计学时 |
|---|---|---|
| 14.1 Generative Agents概述 | 论文思想、核心架构、应用场景 | 1小时 |
| 14.2 核心架构 | Agent组件、环境系统、交互系统 | 1小时 |
| 14.3 记忆流(Memory Stream) | 观察记录、重要性评分、三维检索 | 2小时 |
| 14.4 反思机制(Reflection) | 触发条件、高阶认知抽取 | 1.5小时 |
| 14.5 规划系统(Planning) | 日程规划、动态调整、反应式行为 | 1.5小时 |
| 14.6 Agent间交互 | 对话、关系维护、信息传播 | 1.5小时 |
| 14.7 环境系统 | 2D网格世界、位置管理、时间系统 | 1小时 |
| 14.8 完整实现:Mini赛博小镇 | 3-5个Agent模拟一天生活 | 3小时 |
| 14.9 涌现行为分析 | 信息扩散、关系演变、协作涌现 | 1小时 |
| 14.10 扩展方向 | 3D环境、多模态、大规模仿真 | 0.5小时 |
目录¶
- 1. Generative Agents概述
- 2. 核心架构
- 3. 记忆流(Memory Stream)
- 4. 反思机制(Reflection)
- 5. 规划系统(Planning)
- 6. Agent间交互
- 7. 环境系统
- 8. 完整实现:Mini赛博小镇
- 9. 涌现行为分析
- 10. 扩展方向
- 11. 总结与练习
1. Generative Agents概述¶
1.1 Stanford论文核心思想¶
2023年4月,Stanford与Google Research联合发表了"Generative Agents: Interactive Simulacra of Human Behavior"论文,展示了一个由25个AI Agent组成的虚拟小镇——Smallville。
Smallville 小镇:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
25个AI Agent居住在一个虚拟小镇中
它们各自有名字、职业、性格、人际关系
它们自主地:
✦ 起床、做早餐、去上班
✦ 与邻居聊天、交换信息
✦ 参加派对、组织活动
✦ 产生新的人际关系
✦ 形成对他人的看法和记忆
关键发现——涌现行为(Emergent Behavior):
→ 信息在Agent间自发传播(类似人类社区八卦)
→ Agent之间形成新的友谊和合作关系
→ Agent能够协调组织聚会(无需人工设定)
→ Agent表现出与其设定一致的个性特征
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1.2 为什么Generative Agents重要¶
| 维度 | 传统NPC / 规则引擎 | Generative Agents |
|---|---|---|
| 行为生成 | 预编程脚本 | LLM动态生成 |
| 记忆能力 | 无/简单状态机 | 完整记忆流+检索 |
| 社交行为 | 固定对话树 | 自由对话+关系演化 |
| 适应性 | 无法应对意外 | 根据新信息调整行为 |
| 个性表现 | 标签化(友好/敌对) | 连续且一致的性格表达 |
| 可扩展性 | 每个NPC需单独编程 | 统一框架,改变描述即可 |
1.3 应用场景¶
- 游戏NPC:具有真实感的非玩家角色,能记住与玩家的交互
- 社会科学模拟:模拟疫情传播、信息扩散、舆论形成
- 用户体验研究:用AI Agent模拟目标用户的行为和反馈
- 城市规划:模拟居民对设施变化的反应
- 教育训练:生成逼真的训练场景(如消防演练、客服模拟)
2. 核心架构¶
2.1 论文中的Agent架构¶
每个Generative Agent由三个核心组件构成:
┌─────────────────────────────────────┐
│ Generative Agent │
│ │
│ ┌───────────────────────────────┐ │
│ │ Memory Stream │ │ ← 所有经历的完整记录
│ │ (观察/对话/反思/计划) │ │
│ └──────────┬────────────────────┘ │
│ │ 检索相关记忆 │
│ ┌──────────▼────────────────────┐ │
│ │ Reflection │ │ ← 从记忆中提取高层认知
│ │ (What, So What, Now What) │ │
│ └──────────┬────────────────────┘ │
│ │ 指导行为规划 │
│ ┌──────────▼────────────────────┐ │
│ │ Planning │ │ ← 生成和调整日程计划
│ │ (日计划 → 小时计划 → 行动) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
│ 输出行动 ▲ 感知环境
▼ │
┌─────────────────────────────────────┐
│ Environment │
│ (位置 / 物体 / 其他Agent / 时间) │
└─────────────────────────────────────┘
2.2 基础数据模型¶
"""Generative Agents — 基础数据模型"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timedelta
@dataclass
class MemoryRecord:
"""记忆流中的一条记录"""
id: int
timestamp: datetime
content: str # 记忆内容
memory_type: str = "observation" # observation / reflection / plan / dialogue
importance: int = 5 # 重要性评分 1-10
embedding: list[float] | None = None # 向量嵌入,使用联合类型语法(Python 3.10+),等价于Optional[list[float]]
# 元数据
location: str = ""
related_agents: list[str] = field(default_factory=list)
parent_ids: list[int] = field(default_factory=list) # 反思的来源记忆
@dataclass
class AgentProfile:
"""Agent的个人档案"""
name: str
age: int
occupation: str
personality: str # 性格描述
background: str # 背景故事
relationships: dict[str, str] = field(default_factory=dict)
# e.g. {"李明": "大学同学,关系亲密", "王芳": "邻居,偶尔打招呼"}
daily_routine: str = "" # 典型日常作息
@dataclass
class Location:
"""地图上的位置"""
name: str
x: int
y: int
area_type: str = "public" # public / private / work
description: str = ""
occupants: list[str] = field(default_factory=list) # 当前在这里的Agent
@dataclass
class DayPlan:
"""一天的日程"""
date: str
schedule: list[tuple[str, str]] = field(default_factory=list)
# e.g. [("08:00", "在家吃早餐"), ("09:00", "去咖啡店工作"), ...]
current_action: str = ""
current_location: str = ""
3. 记忆流(Memory Stream)¶
记忆流是Generative Agent最核心的创新——它记录Agent所有经历的观察、对话和反思,并通过三维评分进行检索。
3.1 记忆的三维检索公式¶
论文提出通过三个维度的加权组合来检索最相关的记忆:
其中:
- 近因性(Recency):最近的记忆得分更高,使用指数衰减:
其中 \(\gamma_{decay} = 0.99\),\(\tau\) 为时间单位(如1小时),\(t_m\) 为记忆创建时间。
- 重要性(Importance):由LLM在记忆创建时评分(1-10),并归一化:
- 相关性(Relevance):当前情境与记忆的语义相似度(余弦相似度):
论文中的默认权重:\(\alpha = 1.0\),\(\beta = 1.0\),\(\gamma = 1.0\)
3.2 完整实现¶
"""Memory Stream — 记忆流完整实现"""
import math
import numpy as np
from openai import AsyncOpenAI
from datetime import datetime, timedelta
client = AsyncOpenAI()
class MemoryStream:
"""记忆流:存储、评分、检索Agent的所有记忆
注意:当前实现使用内存列表存储记忆,所有读写为同步操作。
生产环境应替换为异步数据库(如 asyncpg / motor)以避免在 async 上下文中阻塞事件循环。
"""
def __init__(
self,
agent_name: str,
alpha: float = 1.0, # 近因性权重
beta: float = 1.0, # 重要性权重
gamma: float = 1.0, # 相关性权重
decay: float = 0.99, # 时间衰减系数
):
self.agent_name = agent_name
self.memories: list[MemoryRecord] = []
self.alpha = alpha
self.beta = beta
self.gamma = gamma
self.decay = decay
self._next_id = 1
# 反思触发:重要性累积阈值
self._importance_accumulator = 0.0
self.reflection_threshold = 50.0
# ────────── 添加记忆 ──────────
async def add_memory(
self,
content: str,
timestamp: datetime,
memory_type: str = "observation",
location: str = "",
related_agents: list[str] | None = None,
parent_ids: list[int] | None = None,
) -> MemoryRecord:
"""添加一条新记忆到记忆流"""
# 1. 评估重要性
importance = await self._rate_importance(content)
# 2. 生成嵌入向量
embedding = await self._get_embedding(content)
# 3. 创建记忆记录
record = MemoryRecord(
id=self._next_id,
timestamp=timestamp,
content=content,
memory_type=memory_type,
importance=importance,
embedding=embedding,
location=location,
related_agents=related_agents or [],
parent_ids=parent_ids or [],
)
self.memories.append(record)
self._next_id += 1
# 4. 累积重要性(用于触发反思)
self._importance_accumulator += importance
return record
# ────────── 检索记忆 ──────────
async def retrieve(
self,
query: str,
current_time: datetime,
top_k: int = 10,
) -> list[MemoryRecord]:
"""三维加权检索最相关的记忆"""
if not self.memories:
return []
query_embedding = await self._get_embedding(query)
scored: list[tuple[float, MemoryRecord]] = [] # 存储(综合得分, 记忆记录)的元组列表
for mem in self.memories:
# 近因性 (指数衰减)
hours_ago = (current_time - mem.timestamp).total_seconds() / 3600
recency = self.decay ** hours_ago
# 重要性 (归一化到0-1)
importance = mem.importance / 10.0
# 相关性 (余弦相似度)
relevance = self._cosine_similarity(query_embedding, mem.embedding)
# 加权总分
score = (
self.alpha * recency
+ self.beta * importance
+ self.gamma * relevance
)
scored.append((score, mem))
# 按总分排序,返回Top-K
scored.sort(key=lambda x: x[0], reverse=True) # 按三维加权总分降序排列
return [mem for _, mem in scored[:top_k]]
# ────────── 重要性评分 ──────────
async def _rate_importance(self, content: str) -> int:
"""让LLM对记忆内容的重要性打分(1-10)"""
response = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
"你是一个记忆重要性评估器。给定一段记忆内容,"
"请评估其对于一个人的日常生活的重要性。\n"
"评分标准:\n"
"1-2: 完全日常(如「刷牙」「走路去厨房」)\n"
"3-4: 一般日常(如「和同事聊了天气」)\n"
"5-6: 有一定意义(如「学到了新技能」「认识了新朋友」)\n"
"7-8: 重要(如「被上司表扬」「发现重要信息」)\n"
"9-10: 非常重要(如「求婚成功」「亲人去世」)\n"
"只输出一个整数,不要任何其他文字。"
)},
{"role": "user", "content": content},
],
temperature=0.1,
max_tokens=5,
)
try:
score = int(response.choices[0].message.content.strip())
return max(1, min(10, score))
except ValueError:
return 5 # 默认中等重要性
# ────────── 嵌入向量 ──────────
async def _get_embedding(self, text: str) -> list[float]:
"""获取文本的嵌入向量"""
response = await client.embeddings.create(
model="text-embedding-3-small",
input=text,
)
return response.data[0].embedding
@staticmethod
def _cosine_similarity(a: list[float] | None, b: list[float] | None) -> float:
"""计算两个向量的余弦相似度"""
if a is None or b is None:
return 0.0
# np.array将list转为numpy数组;np.linalg.norm计算L2范数(向量长度);np.dot计算点积
a_arr = np.array(a)
b_arr = np.array(b)
denom = np.linalg.norm(a_arr) * np.linalg.norm(b_arr)
if denom == 0:
return 0.0
return float(np.dot(a_arr, b_arr) / denom)
# ────────── 辅助方法 ──────────
def should_reflect(self) -> bool:
"""判断是否应该触发反思"""
return self._importance_accumulator >= self.reflection_threshold
def reset_importance_accumulator(self):
self._importance_accumulator = 0.0
def get_recent(self, n: int = 20) -> list[MemoryRecord]:
"""获取最近N条记忆"""
return sorted(self.memories, key=lambda m: m.timestamp, reverse=True)[:n] # sorted不修改原列表,[:n]取前n条
3.3 记忆检索示例¶
async def memory_demo():
"""记忆流使用示例"""
ms = MemoryStream(agent_name="陈思远")
base_time = datetime(2025, 6, 15, 8, 0)
# 添加一天中的记忆
await ms.add_memory(
"起床,感觉今天精神不错",
base_time, memory_type="observation", location="家",
)
await ms.add_memory(
"在咖啡店遇到了老朋友林悦,她告诉我下周六有社区聚会",
base_time + timedelta(hours=2), memory_type="dialogue",
location="星巴克", related_agents=["林悦"],
)
await ms.add_memory(
"上午完成了项目报告的初稿",
base_time + timedelta(hours=3), memory_type="observation",
location="办公室",
)
await ms.add_memory(
"午饭时听说公司可能要裁员",
base_time + timedelta(hours=5), memory_type="observation",
location="食堂",
)
# 检索与"社区活动"相关的记忆
current_time = base_time + timedelta(hours=6)
results = await ms.retrieve("社区活动和聚会", current_time, top_k=3)
print("检索结果:")
for mem in results:
print(f" [{mem.importance}/10] {mem.content}")
4. 反思机制(Reflection)¶
4.1 反思的触发与流程¶
反思是Generative Agent形成高阶认知的核心机制。当Agent积累了足够多的重要记忆后,会触发反思过程:
反思流程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 触发条件: importance_accumulator >= threshold (默认50)
例如: 5条重要性为10的记忆,或10条重要性为5的记忆
2. 提问: 基于最近的记忆,"关于[Agent名字],我们可以得出哪3个高层结论?"
3. 检索: 对每个结论检索相关记忆作为证据
4. 生成: 产生新的反思记忆(更高阶的抽象)
反思本身也存储在记忆流中,可以被后续检索!
示例:
记忆1: "陈思远帮李华搬了家" (重要性: 6)
记忆2: "陈思远带生病的同事去医院" (重要性: 7)
记忆3: "陈思远主动加班帮团队赶项目" (重要性: 5)
→ 反思: "陈思远是一个乐于助人的人,会主动帮助身边的人" (重要性: 8)
这个反思会指导后续行为:当被请求帮忙时,陈思远更可能答应。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4.2 反思实现¶
"""Reflection — 反思机制实现"""
class ReflectionEngine:
"""反思引擎:从记忆中提取高阶认知"""
def __init__(self, memory_stream: MemoryStream):
self.memory = memory_stream
self.llm = AsyncOpenAI()
async def reflect(self, current_time: datetime) -> list[MemoryRecord]:
"""执行一次反思过程,返回生成的反思记忆"""
# Step 1: 获取最近的重要记忆
recent = self.memory.get_recent(n=30)
if len(recent) < 5:
return [] # 记忆不足,无法反思
recent_text = "\n".join(
f"[{m.timestamp.strftime('%H:%M')}] {m.content}" for m in recent[:20]
)
# Step 2: 生成反思问题
questions = await self._generate_reflection_questions(recent_text)
# Step 3: 对每个问题检索相关记忆并生成反思
new_reflections: list[MemoryRecord] = []
for question in questions[:3]: # 最多3个反思
# 检索与该问题相关的记忆
relevant = await self.memory.retrieve(question, current_time, top_k=8)
evidence_text = "\n".join(f"- {m.content}" for m in relevant)
parent_ids = [m.id for m in relevant]
# 生成反思
reflection_content = await self._generate_reflection(
question, evidence_text,
)
# 将反思作为新记忆存入记忆流
record = await self.memory.add_memory(
content=reflection_content,
timestamp=current_time,
memory_type="reflection",
parent_ids=parent_ids,
)
new_reflections.append(record)
# 重置重要性累积器
self.memory.reset_importance_accumulator()
return new_reflections
async def _generate_reflection_questions(self, memories_text: str) -> list[str]:
"""基于最近记忆生成反思问题"""
response = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"基于{self.memory.agent_name}最近的经历,"
"提出3个值得深入思考的高层问题。"
"这些问题应该关注模式、关系和自我认知。"
"每行一个问题,不要编号。"
)},
{"role": "user", "content": f"最近的经历:\n{memories_text}"},
],
temperature=0.7,
max_tokens=200,
)
questions = [
q.strip() for q in response.choices[0].message.content.strip().split("\n")
if q.strip()
]
return questions
async def _generate_reflection(
self,
question: str,
evidence: str,
) -> str:
"""基于证据生成一条反思"""
response = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"你是{self.memory.agent_name}的内心思考。"
"基于过去的经历,对以下问题进行深入反思。"
"输出一句简洁的高层认知(不超过50字)。"
)},
{"role": "user", "content": (
f"问题: {question}\n\n"
f"相关经历:\n{evidence}"
)},
],
temperature=0.5,
max_tokens=100,
)
return response.choices[0].message.content.strip()
5. 规划系统(Planning)¶
5.1 两级规划:粗粒度→细粒度¶
论文中的规划采用分层方式:先生成一天的粗略日程,再细化每个时间段的具体行动。
粗粒度计划(日级别):
08:00 - 起床和吃早餐
09:00 - 去咖啡店工作
12:00 - 午餐
13:00 - 继续工作
17:00 - 下班,去公园散步
18:00 - 回家做晚饭
20:00 - 看书
22:00 - 睡觉
细粒度计划(具体行动):
09:00 - 走到咖啡店(路上5分钟)
09:05 - 到达咖啡店,点一杯美式咖啡
09:10 - 找到靠窗的位置坐下
09:15 - 打开笔记本电脑,开始写代码
...
5.2 规划实现¶
"""Planning System — 规划系统实现"""
import json
from openai import AsyncOpenAI
client = AsyncOpenAI()
class PlanningSystem:
"""Agent规划系统:生成和调整日程"""
def __init__(self, profile: AgentProfile, memory: MemoryStream):
self.profile = profile
self.memory = memory
async def create_daily_plan(
self,
date: str,
current_time: datetime,
) -> DayPlan:
"""创建一天的日程计划"""
# 检索与日程规划相关的记忆(习惯、承诺等)
relevant_memories = await self.memory.retrieve(
f"{self.profile.name}的日常作息和今天的安排",
current_time,
top_k=10,
)
memory_context = "\n".join(f"- {m.content}" for m in relevant_memories)
response = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"你是{self.profile.name},{self.profile.age}岁,"
f"职业是{self.profile.occupation}。\n"
f"性格: {self.profile.personality}\n"
f"背景: {self.profile.background}\n"
f"日常作息: {self.profile.daily_routine}\n\n"
"请为自己制定今天的日程。输出JSON格式:\n"
'{"schedule": [["08:00", "活动描述"], ["09:00", "..."], ...]}'
)},
{"role": "user", "content": (
f"日期: {date}\n"
f"相关记忆:\n{memory_context}\n\n"
"请制定今天的日程(从起床到睡觉)。"
)},
],
response_format={"type": "json_object"},
temperature=0.8,
)
data = json.loads(response.choices[0].message.content)
return DayPlan(
date=date,
schedule=[tuple(item) for item in data["schedule"]],
)
async def react_to_event(
self,
event: str,
current_plan: DayPlan,
current_time: datetime,
) -> tuple[str, DayPlan]:
"""对突发事件做出反应,可能调整计划
Returns:
(reaction, updated_plan): Agent的反应和调整后的计划
"""
# 检索相关记忆
relevant = await self.memory.retrieve(event, current_time, top_k=5)
memory_context = "\n".join(f"- {m.content}" for m in relevant)
remaining_schedule = [
item for item in current_plan.schedule
if item[0] >= current_time.strftime("%H:%M")
]
schedule_text = "\n".join(f"{t}: {a}" for t, a in remaining_schedule)
response = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"你是{self.profile.name}。性格: {self.profile.personality}\n"
"一个突发事件发生了。请决定你的反应和是否调整计划。\n"
"输出JSON: {\"reaction\": \"你的反应描述\", "
"\"adjust_plan\": true/false, "
"\"new_schedule\": [[\"时间\", \"活动\"], ...] 如果adjust_plan为true}"
)},
{"role": "user", "content": (
f"突发事件: {event}\n\n"
f"相关记忆:\n{memory_context}\n\n"
f"当前剩余计划:\n{schedule_text}"
)},
],
response_format={"type": "json_object"},
temperature=0.7,
)
data = json.loads(response.choices[0].message.content)
reaction = data["reaction"]
if data.get("adjust_plan"):
# 保留已执行的计划项,替换剩余部分
executed = [
item for item in current_plan.schedule
if item[0] < current_time.strftime("%H:%M")
]
new_items = [tuple(item) for item in data.get("new_schedule", [])]
current_plan.schedule = executed + new_items
return reaction, current_plan
6. Agent间交互¶
6.1 对话系统¶
当两个Agent在同一位置相遇时,需要决定是否对话、对话内容是什么。
"""Agent Interaction — Agent间对话与交互"""
class DialogueSystem:
"""Agent间对话系统"""
def __init__(self):
self.llm = AsyncOpenAI()
async def should_initiate_conversation(
self,
agent: AgentProfile,
other: AgentProfile,
agent_memory: MemoryStream,
current_time: datetime,
context: str = "",
) -> bool:
"""判断Agent是否应该主动与对方对话"""
# 检索与对方相关的记忆
relevant = await agent_memory.retrieve(
f"与{other.name}的互动", current_time, top_k=5,
)
memory_text = "\n".join(f"- {m.content}" for m in relevant)
relationship = agent.relationships.get(other.name, "不太熟悉")
response = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"你是{agent.name}。你现在遇到了{other.name}。\n"
f"你与{other.name}的关系: {relationship}\n"
f"你的性格: {agent.personality}\n"
f"场景: {context}\n"
"你会主动打招呼吗?只回答 yes 或 no。"
)},
{"role": "user", "content": f"相关记忆:\n{memory_text}"},
],
temperature=0.5,
max_tokens=5,
)
return "yes" in response.choices[0].message.content.lower()
async def generate_dialogue(
self,
agent_a: AgentProfile,
agent_b: AgentProfile,
memory_a: MemoryStream,
memory_b: MemoryStream,
current_time: datetime,
location: str,
max_turns: int = 6,
) -> list[tuple[str, str]]:
"""生成两个Agent之间的对话
Returns:
[(speaker_name, utterance), ...]
"""
# 为双方检索相关记忆
context_a = await memory_a.retrieve(
f"与{agent_b.name}聊天", current_time, top_k=5,
)
context_b = await memory_b.retrieve(
f"与{agent_a.name}聊天", current_time, top_k=5,
)
rel_a = agent_a.relationships.get(agent_b.name, "不太熟悉")
rel_b = agent_b.relationships.get(agent_a.name, "不太熟悉")
dialogue: list[tuple[str, str]] = []
messages = [
{"role": "system", "content": (
f"模拟{agent_a.name}和{agent_b.name}在{location}的对话。\n\n"
f"{agent_a.name}: {agent_a.personality},与{agent_b.name}的关系: {rel_a}\n"
f" 相关记忆: {'; '.join(m.content for m in context_a[:3])}\n\n"
f"{agent_b.name}: {agent_b.personality},与{agent_a.name}的关系: {rel_b}\n"
f" 相关记忆: {'; '.join(m.content for m in context_b[:3])}\n\n"
"对话要自然、符合各自性格。每轮输出格式:\n"
f"{agent_a.name}: [说话内容]\n{agent_b.name}: [说话内容]\n"
"当对话自然结束时,输出 [END]"
)},
]
for turn in range(max_turns // 2):
messages.append({"role": "user", "content": "请继续对话。"})
response = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.8,
max_tokens=200,
)
text = response.choices[0].message.content
messages.append({"role": "assistant", "content": text})
if "[END]" in text:
text = text.replace("[END]", "").strip()
# 解析发言
# 注意:基于换行符和前缀的解析较脆弱,LLM输出格式可能不一致。
# 生产环境建议要求LLM以JSON数组格式输出对话,再用json.loads解析。
for line in text.strip().split("\n"):
line = line.strip()
if not line:
continue
if line.startswith(f"{agent_a.name}:"):
utterance = line[len(agent_a.name) + 1:].strip()
dialogue.append((agent_a.name, utterance))
elif line.startswith(f"{agent_b.name}:"):
utterance = line[len(agent_b.name) + 1:].strip()
dialogue.append((agent_b.name, utterance))
if "[END]" in response.choices[0].message.content:
break
return dialogue
6.2 信息传播模拟¶
async def propagate_information(
dialogue: list[tuple[str, str]],
memory_a: MemoryStream,
memory_b: MemoryStream,
agent_a_name: str,
agent_b_name: str,
current_time: datetime,
location: str,
):
"""将对话记录存入双方的记忆流——信息通过对话在Agent间传播"""
# 将完整对话摘要存入记忆
dialogue_text = "\n".join(f"{name}: {text}" for name, text in dialogue)
summary_a = f"在{location}和{agent_b_name}聊天。" + (
dialogue[-1][1] if dialogue else "" # [-1]取最后一条,[1]取元组第二个元素(对话内容)
)
summary_b = f"在{location}和{agent_a_name}聊天。" + (
dialogue[0][1] if dialogue else ""
)
await memory_a.add_memory(
content=summary_a,
timestamp=current_time,
memory_type="dialogue",
location=location,
related_agents=[agent_b_name],
)
await memory_b.add_memory(
content=summary_b,
timestamp=current_time,
memory_type="dialogue",
location=location,
related_agents=[agent_a_name],
)
7. 环境系统¶
7.1 简化的2D网格世界¶
"""Environment — 2D网格世界"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timedelta
@dataclass
class WorldLocation:
"""世界中的一个位置/区域"""
name: str
x: int
y: int
area_type: str # home / work / shop / park / public
description: str = ""
occupants: set[str] = field(default_factory=set)
class GridWorld:
"""简化的2D网格世界"""
def __init__(self, width: int = 20, height: int = 20):
self.width = width
self.height = height
self.locations: dict[str, WorldLocation] = {}
self.agent_positions: dict[str, str] = {} # agent_name -> location_name
self.time: datetime = datetime(2025, 6, 15, 6, 0) # 模拟开始时间
def add_location(self, loc: WorldLocation):
self.locations[loc.name] = loc
def move_agent(self, agent_name: str, location_name: str):
"""移动Agent到指定位置"""
# 注意:若 location_name 不在 self.locations 中,移动会被静默忽略。
# 生产环境建议添加边界校验,抛出异常或返回 False 以便调用方感知失败。
# 离开旧位置
old_loc = self.agent_positions.get(agent_name)
if old_loc and old_loc in self.locations:
self.locations[old_loc].occupants.discard(agent_name) # discard不会在元素不存在时抛异常,比remove更安全
# 进入新位置
if location_name in self.locations:
self.locations[location_name].occupants.add(agent_name)
self.agent_positions[agent_name] = location_name
def get_agents_at(self, location_name: str) -> set[str]:
"""获取某位置的所有Agent"""
loc = self.locations.get(location_name)
return loc.occupants if loc else set()
def get_nearby_agents(self, agent_name: str) -> set[str]:
"""获取与某Agent在同一位置的其他Agent"""
loc = self.agent_positions.get(agent_name)
if not loc:
return set()
return self.get_agents_at(loc) - {agent_name}
def advance_time(self, minutes: int = 30):
"""推进模拟时间"""
self.time += timedelta(minutes=minutes)
def get_time_str(self) -> str:
return self.time.strftime("%H:%M")
def describe_location(self, location_name: str) -> str:
"""描述一个位置的当前状态"""
loc = self.locations.get(location_name)
if not loc:
return "未知位置"
people = "、".join(loc.occupants) if loc.occupants else "无人" # 用中文顿号连接当前位置上的所有Agent名称
return f"{loc.name}({loc.description})— 当前: {people}"
def get_world_state(self) -> str:
"""获取世界当前状态的文字描述"""
lines = [f"⏰ 时间: {self.get_time_str()}\n"]
for name, loc in self.locations.items():
if loc.occupants:
people = ", ".join(loc.occupants)
lines.append(f"📍 {name}: {people}")
return "\n".join(lines)
def create_smallville() -> GridWorld:
"""创建一个迷你小镇地图"""
world = GridWorld(width=10, height=10)
locations = [
WorldLocation("陈思远的家", 1, 1, "home", "一间温馨的公寓"),
WorldLocation("林悦的家", 3, 1, "home", "装饰精美的小屋"),
WorldLocation("张伟的家", 5, 1, "home", "宽敞的两室公寓"),
WorldLocation("王芳的家", 7, 1, "home", "带花园的房子"),
WorldLocation("星光咖啡店", 4, 4, "shop", "社区里最受欢迎的咖啡店"),
WorldLocation("社区公园", 5, 6, "park", "有长椅和喷泉的小公园"),
WorldLocation("创新工作室", 2, 5, "work", "开放式联合办公空间"),
WorldLocation("社区图书馆", 7, 5, "public", "安静的阅读空间"),
WorldLocation("小镇广场", 4, 8, "public", "居民聚会的中心广场"),
]
for loc in locations:
world.add_location(loc)
return world
7.2 时间系统与事件循环¶
class TimeManager:
"""时间管理器:控制模拟的时间推进"""
def __init__(self, start_time: datetime):
self.current_time = start_time
self.time_step = timedelta(minutes=30) # 每次推进30分钟
def advance(self) -> datetime:
self.current_time += self.time_step
return self.current_time
@property
def hour(self) -> int:
return self.current_time.hour
@property
def is_daytime(self) -> bool:
return 7 <= self.hour <= 22
@property
def period(self) -> str:
h = self.hour
if h < 7:
return "凌晨"
elif h < 9:
return "早晨"
elif h < 12:
return "上午"
elif h < 14:
return "中午"
elif h < 17:
return "下午"
elif h < 19:
return "傍晚"
elif h < 22:
return "晚上"
else:
return "深夜"
8. 完整实现:Mini赛博小镇¶
8.1 依赖安装¶
8.2 完整可运行代码¶
"""
Mini赛博小镇 — Generative Agents 完整实现
==========================================
创建4个具有不同性格的Agent,模拟一天的生活。
依赖:pip install openai numpy
环境变量:OPENAI_API_KEY
"""
from __future__ import annotations
import asyncio
import json
from dataclasses import dataclass, field
from datetime import datetime, timedelta
import numpy as np
from openai import AsyncOpenAI
# ─────────────── 数据模型 ───────────────
@dataclass
class Memory:
id: int
timestamp: datetime
content: str
mem_type: str = "observation" # observation / reflection / dialogue / plan
importance: int = 5
embedding: list[float] | None = None
related_agents: list[str] = field(default_factory=list)
@dataclass
class Agent:
name: str
age: int
occupation: str
personality: str
background: str
relationships: dict[str, str] = field(default_factory=dict)
daily_routine: str = ""
schedule: list[tuple[str, str]] = field(default_factory=list)
current_action: str = "睡觉"
current_location: str = ""
@dataclass
class Place:
name: str
x: int
y: int
description: str
occupants: set[str] = field(default_factory=set)
# ─────────────── 记忆流 ───────────────
class SimpleMemoryStream:
"""简化版记忆流"""
def __init__(self, agent_name: str, llm: AsyncOpenAI):
self.agent_name = agent_name
self.llm = llm
self.memories: list[Memory] = []
self._next_id = 1
self._imp_accum = 0.0
async def add(
self, content: str, timestamp: datetime,
mem_type: str = "observation",
related_agents: list[str] | None = None,
) -> Memory:
importance = await self._rate(content)
embedding = await self._embed(content)
mem = Memory(
id=self._next_id, timestamp=timestamp,
content=content, mem_type=mem_type,
importance=importance, embedding=embedding,
related_agents=related_agents or [],
)
self.memories.append(mem)
self._next_id += 1
self._imp_accum += importance
return mem
async def retrieve(
self, query: str, now: datetime, top_k: int = 8,
) -> list[Memory]:
if not self.memories:
return []
q_emb = await self._embed(query)
scored = []
for m in self.memories:
hours = max((now - m.timestamp).total_seconds() / 3600, 0.01)
recency = 0.99 ** hours
importance = m.importance / 10.0
relevance = self._cos_sim(q_emb, m.embedding)
score = recency + importance + relevance
scored.append((score, m))
scored.sort(key=lambda x: x[0], reverse=True) # 按近因性+重要性+相关性综合得分降序
return [m for _, m in scored[:top_k]]
def should_reflect(self) -> bool:
return self._imp_accum >= 40
def reset_accum(self):
self._imp_accum = 0.0
async def _rate(self, content: str) -> int:
resp = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
"对以下事件的重要性评分(1-10)。1=极日常,10=改变人生。只输出数字。"
)},
{"role": "user", "content": content},
],
temperature=0.1, max_tokens=5,
)
try:
return max(1, min(10, int(resp.choices[0].message.content.strip())))
except ValueError:
return 5
async def _embed(self, text: str) -> list[float]:
resp = await self.llm.embeddings.create(
model="text-embedding-3-small", input=text,
)
return resp.data[0].embedding
@staticmethod
def _cos_sim(a: list[float] | None, b: list[float] | None) -> float:
if a is None or b is None:
return 0.0
a_, b_ = np.array(a), np.array(b)
d = np.linalg.norm(a_) * np.linalg.norm(b_)
return float(np.dot(a_, b_) / d) if d > 0 else 0.0 # 余弦相似度,分母为零时返回0避免除零错误
# ─────────────── 世界环境 ───────────────
class World:
def __init__(self):
self.places: dict[str, Place] = {}
self.agent_loc: dict[str, str] = {}
self.time = datetime(2025, 6, 15, 7, 0)
def add_place(self, p: Place):
self.places[p.name] = p
def move(self, agent: str, place: str):
old = self.agent_loc.get(agent)
if old and old in self.places:
self.places[old].occupants.discard(agent)
if place in self.places:
self.places[place].occupants.add(agent)
self.agent_loc[agent] = place
def colocated(self, agent: str) -> set[str]:
loc = self.agent_loc.get(agent, "")
if loc in self.places:
return self.places[loc].occupants - {agent}
return set()
def advance(self, minutes: int = 60):
self.time += timedelta(minutes=minutes)
def state_str(self) -> str:
lines = [f"⏰ {self.time.strftime('%Y-%m-%d %H:%M')}"]
for name, p in self.places.items():
if p.occupants:
lines.append(f" 📍 {name}: {', '.join(sorted(p.occupants))}")
return "\n".join(lines)
# ─────────────── 模拟引擎 ───────────────
class Simulation:
"""Mini赛博小镇模拟引擎"""
def __init__(self):
self.llm = AsyncOpenAI()
self.world = World()
self.agents: dict[str, Agent] = {}
self.memories: dict[str, SimpleMemoryStream] = {}
self.log: list[str] = []
# ── 初始化 ──
def setup(self):
"""初始化世界和Agent"""
# 创建地点
places = [
Place("陈思远的家", 1, 1, "温馨的公寓"),
Place("林悦的家", 3, 1, "精致的小屋"),
Place("张伟的家", 5, 1, "宽敞的公寓"),
Place("王芳的家", 7, 1, "带花园的房子"),
Place("星光咖啡店", 4, 4, "社区最受欢迎的咖啡店"),
Place("社区公园", 5, 6, "有长椅和喷泉的公园"),
Place("创新工作室", 2, 5, "联合办公空间"),
Place("社区图书馆", 7, 5, "安静的图书馆"),
]
for p in places:
self.world.add_place(p)
# 创建Agent
agents_data = [
Agent(
name="陈思远", age=28, occupation="软件工程师",
personality="内向但热心,喜欢技术,会主动帮助别人",
background="从小城市来到大城市工作,热爱编程,最近在学习AI",
relationships={"林悦": "好朋友,经常一起喝咖啡", "张伟": "同事",
"王芳": "邻居,偶尔打招呼"},
daily_routine="早起,去咖啡店工作,下午去工作室,晚上看书",
),
Agent(
name="林悦", age=26, occupation="平面设计师",
personality="外向活泼,善于社交,喜欢组织活动",
background="艺术学院毕业,做自由职业设计师,是社区活动的积极组织者",
relationships={"陈思远": "好朋友", "张伟": "认识但不太熟",
"王芳": "关系不错,经常聊天"},
daily_routine="晚起,去咖啡店设计,下午逛公园找灵感,喜欢社交",
),
Agent(
name="张伟", age=35, occupation="大学教授",
personality="严谨学术派,但对学生很耐心,偶尔幽默",
background="物理学博士,在本地大学任教,研究量子计算",
relationships={"陈思远": "同事关系", "林悦": "不太熟",
"王芳": "经常去她的花园买花"},
daily_routine="早起锻炼,去图书馆备课研究,下午给学生答疑",
),
Agent(
name="王芳", age=42, occupation="花艺师/社区志愿者",
personality="温暖亲切,是社区的核心人物,喜欢帮助他人",
background="经营一个小花店,同时是社区志愿者委员会主席",
relationships={"陈思远": "邻居,对这个年轻人很照顾",
"林悦": "好朋友,经常合作搞社区活动",
"张伟": "老客户,卖花给他装饰办公室"},
daily_routine="早起打理花园,上午在花店,下午做社区志愿工作",
),
]
for a in agents_data:
self.agents[a.name] = a
self.memories[a.name] = SimpleMemoryStream(a.name, self.llm)
# 所有Agent从自己家开始
self.world.move(a.name, f"{a.name}的家")
# ── 规划 ──
async def plan_day(self, agent: Agent) -> list[tuple[str, str]]:
"""为Agent生成一天的日程"""
recent = await self.memories[agent.name].retrieve(
"今天的安排", self.world.time, top_k=5,
)
mem_ctx = "\n".join(f"- {m.content}" for m in recent) or "暂无"
resp = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"你是{agent.name},{agent.age}岁{agent.occupation}。\n"
f"性格: {agent.personality}\n"
f"日常: {agent.daily_routine}\n"
"为自己规划从07:00到22:00的日程(每小时一项)。\n"
f"可选地点: {', '.join(self.world.places.keys())}\n"
"输出JSON: {{\"schedule\": [[\"07:00\", \"活动\", \"地点\"], ...]}}"
)},
{"role": "user", "content": f"记忆:\n{mem_ctx}"},
],
response_format={"type": "json_object"},
temperature=0.8,
)
data = json.loads(resp.choices[0].message.content)
schedule = []
for item in data.get("schedule", []):
time_str = item[0] if len(item) > 0 else "08:00"
action = item[1] if len(item) > 1 else "休息"
location = item[2] if len(item) > 2 else f"{agent.name}的家"
schedule.append((time_str, f"{action} @ {location}"))
agent.schedule = schedule
return schedule
# ── 执行行动 ──
async def execute_action(
self, agent: Agent, action: str, time_slot: str,
):
"""执行一个Agent的行动"""
# 解析位置
location = f"{agent.name}的家"
if " @ " in action:
parts = action.rsplit(" @ ", 1)
action_desc = parts[0]
location = parts[1]
else:
action_desc = action
# 确保位置存在
if location not in self.world.places:
# 找最近匹配
for place_name in self.world.places:
if location in place_name or place_name in location:
location = place_name
break
else:
location = f"{agent.name}的家"
# 移动Agent
self.world.move(agent.name, location)
agent.current_action = action_desc
agent.current_location = location
# 记录观察
observation = f"{time_slot} - {action_desc}(在{location})"
await self.memories[agent.name].add(
observation, self.world.time, "observation",
)
self._print(f" {agent.name}: {action_desc} 📍{location}")
# ── 社交互动 ──
async def check_interactions(self):
"""检查同一位置的Agent是否互动"""
for name, agent in self.agents.items():
nearby = self.world.colocated(name)
for other_name in nearby:
# 避免重复(只让"名字排序靠前"的一方发起)
if name > other_name:
continue
other = self.agents[other_name]
# 决定是否对话
should_talk = await self._should_talk(agent, other)
if should_talk:
await self._do_conversation(agent, other)
async def _should_talk(self, a: Agent, b: Agent) -> bool:
rel = a.relationships.get(b.name, "不太熟")
resp = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"{a.name}(性格:{a.personality})在{a.current_location}"
f"遇到了{b.name}。关系: {rel}。"
"会主动聊天吗?只回答yes或no"
)},
{"role": "user", "content": "yes or no?"},
],
temperature=0.5, max_tokens=5,
)
return "yes" in resp.choices[0].message.content.lower()
async def _do_conversation(self, a: Agent, b: Agent):
"""执行一次对话"""
mem_a = await self.memories[a.name].retrieve(
f"与{b.name}的关系", self.world.time, top_k=3,
)
mem_b = await self.memories[b.name].retrieve(
f"与{a.name}的关系", self.world.time, top_k=3,
)
resp = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"模拟{a.name}和{b.name}在{a.current_location}的简短对话(3-4轮)。\n"
f"{a.name}: {a.personality}。当前在做: {a.current_action}\n"
f" 记忆: {'; '.join(m.content for m in mem_a[:2])}\n"
f"{b.name}: {b.personality}。当前在做: {b.current_action}\n"
f" 记忆: {'; '.join(m.content for m in mem_b[:2])}\n"
"格式: 姓名: 说话内容"
)},
{"role": "user", "content": "请生成对话。"},
],
temperature=0.8, max_tokens=300,
)
dialogue = resp.choices[0].message.content.strip()
self._print(f"\n 💬 对话 ({a.current_location}):")
for line in dialogue.split("\n"):
if line.strip():
self._print(f" {line.strip()}")
self._print("")
# 存入双方记忆
summary_a = f"在{a.current_location}和{b.name}聊了天"
summary_b = f"在{b.current_location}和{a.name}聊了天"
await self.memories[a.name].add(
summary_a, self.world.time, "dialogue", [b.name],
)
await self.memories[b.name].add(
summary_b, self.world.time, "dialogue", [a.name],
)
# ── 反思 ──
async def maybe_reflect(self, agent: Agent):
"""如果累积重要性超过阈值,进行反思"""
ms = self.memories[agent.name]
if not ms.should_reflect():
return
recent = [m.content for m in ms.memories[-15:]]
resp = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
f"你是{agent.name}。基于最近的经历,写出1-2条深层反思/感悟。"
"每条反思独占一行,简洁有力(20字以内)。"
)},
{"role": "user", "content": "\n".join(f"- {m}" for m in recent)},
],
temperature=0.7, max_tokens=100,
)
reflections = resp.choices[0].message.content.strip().split("\n")
for ref in reflections:
ref = ref.strip().lstrip("- ·•")
if ref:
await ms.add(ref, self.world.time, "reflection")
self._print(f" 💭 {agent.name}反思: {ref}")
ms.reset_accum()
# ── 主循环 ──
async def run(self, hours: int = 16):
"""运行模拟"""
self._print("=" * 60)
self._print("🏘️ Mini赛博小镇 — 模拟开始")
self._print("=" * 60)
# 为所有Agent规划日程
self._print("\n📋 日程规划:")
plan_tasks = [self.plan_day(a) for a in self.agents.values()]
# asyncio.gather并发执行所有协程任务,*解包列表为位置参数,等待全部完成后返回结果列表
await asyncio.gather(*plan_tasks)
for a in self.agents.values():
self._print(f"\n {a.name}的日程:")
for t, act in a.schedule[:5]:
self._print(f" {t} {act}")
if len(a.schedule) > 5:
self._print(f" ... (共{len(a.schedule)}项)")
# 按时间步推进模拟
for step in range(hours):
current_hour = 7 + step
if current_hour > 22:
break
time_str = f"{current_hour:02d}:00"
self.world.time = datetime(2025, 6, 15, current_hour, 0)
self._print(f"\n{'─' * 50}")
self._print(f"⏰ {time_str}")
self._print(f"{'─' * 50}")
# 每个Agent执行当前时间段的行动
for agent in self.agents.values():
# 找到对应时间段的行动
action = "休息"
for t, act in agent.schedule:
if t.startswith(f"{current_hour:02d}"):
action = act
break
await self.execute_action(agent, action, time_str)
# 检查互动
await self.check_interactions()
# 检查是否需要反思
for agent in self.agents.values():
await self.maybe_reflect(agent)
# 打印世界状态
self._print(f"\n{self.world.state_str()}")
self._print(f"\n{'=' * 60}")
self._print("🌙 模拟结束 — 一天结束了")
self._print(f"{'=' * 60}")
# 打印记忆统计
self._print("\n📊 记忆统计:")
for name, ms in self.memories.items():
types = {}
for m in ms.memories:
types[m.mem_type] = types.get(m.mem_type, 0) + 1
self._print(f" {name}: {len(ms.memories)}条记忆 {types}")
def _print(self, msg: str):
print(msg)
self.log.append(msg)
def save_log(self, path: str = "simulation_log.txt"):
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(self.log))
print(f"\n📄 日志已保存至 {path}")
# ─────────────── 运行入口 ───────────────
async def main():
sim = Simulation()
sim.setup()
await sim.run(hours=16) # 模拟16小时(07:00 - 22:00)
sim.save_log()
if __name__ == "__main__":
asyncio.run(main())
8.3 运行效果示例¶
============================================================
🏘️ Mini赛博小镇 — 模拟开始
============================================================
📋 日程规划:
陈思远的日程:
07:00 起床洗漱吃早餐 @ 陈思远的家
08:00 去咖啡店写代码 @ 星光咖啡店
09:00 继续开发AI项目 @ 星光咖啡店
10:00 去工作室开会 @ 创新工作室
11:00 写文档和code review @ 创新工作室
...
──────────────────────────────────────────────
⏰ 08:00
──────────────────────────────────────────────
陈思远: 去咖啡店写代码 📍星光咖啡店
林悦: 在家画设计稿 📍林悦的家
张伟: 晨跑后吃早餐 📍张伟的家
王芳: 浇花打理花园 📍王芳的家
──────────────────────────────────────────────
⏰ 10:00
──────────────────────────────────────────────
陈思远: 去工作室开会 📍创新工作室
林悦: 去咖啡店画设计 📍星光咖啡店
张伟: 去图书馆备课 📍社区图书馆
王芳: 去咖啡店买咖啡 📍星光咖啡店
💬 对话 (星光咖啡店):
林悦: 王芳姐!好久不见,最近忙什么呢?
王芳: 林悦啊!我在筹备下周的社区读书会,你有兴趣参加吗?
林悦: 当然!我可以帮忙设计海报,用什么主题?
王芳: 太好了!主题是"科技与生活",思远可能也感兴趣。
💭 王芳反思: 社区活动需要更多年轻人参与
──────────────────────────────────────────────
⏰ 14:00
──────────────────────────────────────────────
陈思远: 去公园散步放松 📍社区公园
林悦: 去公园找灵感 📍社区公园
...
💬 对话 (社区公园):
陈思远: 林悦!你也来公园了?
林悦: 嗯,找点设计灵感。对了,王芳姐在筹备社区读书会,主题是科技与生活,你要来吗?
陈思远: 听起来不错!什么时候?
林悦: 下周六,我在设计海报呢。
→ 信息传播: "社区读书会"的消息从王芳→林悦→陈思远
============================================================
🌙 模拟结束 — 一天结束了
============================================================
📊 记忆统计:
陈思远: 23条记忆 {'observation': 16, 'dialogue': 5, 'reflection': 2}
林悦: 21条记忆 {'observation': 14, 'dialogue': 5, 'reflection': 2}
张伟: 18条记忆 {'observation': 14, 'dialogue': 2, 'reflection': 2}
王芳: 22条记忆 {'observation': 15, 'dialogue': 5, 'reflection': 2}
9. 涌现行为分析¶
9.1 信息扩散实验¶
我们可以设计实验来观察信息在Agent间的传播:
async def information_diffusion_experiment(sim: Simulation):
"""信息扩散实验: 给一个Agent注入信息,观察传播"""
# 注入信息: 只有王芳知道"下周社区要搞美食节"
await sim.memories["王芳"].add(
"社区要在下周六举办美食节,需要志愿者帮忙",
sim.world.time, "observation",
)
# 运行模拟多个时间步
# 观察信息是否通过对话传播给其他Agent
# 检查: 哪些Agent最终知道了美食节
for name, ms in sim.memories.items():
knows = any("美食节" in m.content for m in ms.memories) # any+生成器:检查记忆中是否包含关键词
print(f" {name}: {'✅ 知道' if knows else '❌ 不知道'}") # f-string内嵌三元表达式
9.2 关系演变观察¶
def analyze_relationships(sim: Simulation):
"""分析模拟过程中Agent间关系的变化"""
print("\n📊 关系分析:")
for name, ms in sim.memories.items():
dialogue_memories = [m for m in ms.memories if m.mem_type == "dialogue"]
# 统计与各Agent的互动次数
interaction_count: dict[str, int] = {}
for m in dialogue_memories:
for other in m.related_agents:
interaction_count[other] = interaction_count.get(other, 0) + 1
print(f"\n {name}的互动频率:")
for other, count in sorted(
interaction_count.items(), key=lambda x: x[1], reverse=True,
):
bar = "█" * count
print(f" {other}: {bar} ({count}次)")
9.3 与论文结果对比¶
| 涌现行为 | 原始论文(25 Agents) | 我们的实现(4 Agents) |
|---|---|---|
| 信息扩散 | 完整的社区八卦链 | 可观察到1-2跳传播 |
| 关系形成 | 新友谊和恋爱关系 | 简单的互动频率变化 |
| 活动组织 | Agent自发组织派对 | 通过对话传播活动信息 |
| 日程协调 | 调整计划以参加活动 | 基础的反应式调整 |
| 记忆一致性 | 能回忆过去的对话 | 通过记忆流实现 |
差距原因及改进思路: 1. Agent数量:25个Agent形成更复杂的社交网络;增加Agent数可观察更丰富的涌现 2. 时间跨度:论文模拟了多天;延长模拟时间可观察关系演变 3. 环境复杂度:论文有更细致的地图和物品系统 4. 行为粒度:论文使用5分钟级别的细粒度行为
10. 扩展方向¶
10.1 更复杂的环境¶
# 思路: 集成pygame或类似库做2D可视化
class VisualWorld(World):
"""带2D可视化的世界"""
def render(self):
"""渲染当前世界状态为图片(使用matplotlib)"""
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams["font.sans-serif"] = ["SimHei"]
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
# 绘制地点
for name, place in self.places.items():
color = {"home": "lightblue", "work": "lightyellow",
"shop": "lightsalmon", "park": "lightgreen",
"public": "lavender"}.get(place.description, "white")
ax.plot(place.x, place.y, "s", markersize=20, color=color)
ax.annotate(name, (place.x, place.y), ha="center", va="bottom")
# 标注在场Agent
for i, agent in enumerate(place.occupants):
ax.annotate(agent, (place.x, place.y - 0.3 - i * 0.2),
ha="center", fontsize=8, color="red")
ax.set_title(f"Mini赛博小镇 — {self.time.strftime('%H:%M')}")
plt.savefig(f"world_{self.time.strftime('%H%M')}.png", dpi=100)
plt.close()
10.2 多模态Agent¶
扩展方向:
1. 视觉感知: Agent能"看到"环境截图,用视觉模型理解场景
2. 语音交互: 使用TTS/STT让Agent"说话"
3. 表情系统: Agent根据情绪状态展示不同表情
4. 物理交互: Agent可以移动物品、打开门等
10.3 大规模仿真优化¶
# 大规模Agent仿真的关键优化
class OptimizedSimulation:
"""优化的大规模仿真"""
# 1. 记忆分层缓存
# - 热记忆: 最近2小时 (内存)
# - 温记忆: 最近1天 (SQLite)
# - 冷记忆: 更早 (向量数据库)
# 2. LLM调用批处理
# - 将多个Agent的请求batch到一次API调用
# - 对低优先级操作使用更小的模型
# 3. 选择性模拟
# - 只详细模拟"有趣"的Agent(正在互动的)
# - 其他Agent使用简化/缓存行为
# 4. 异步并行
# - 无依赖的Agent行动并行执行
# - 使用asyncio.gather加速
pass
11. 总结与练习¶
11.1 本章总结¶
Generative Agents 核心知识点:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✦ 记忆流(Memory Stream)
- 记录所有观察、对话、反思
- 三维检索: recency × importance × relevance
- 数学公式: score = α·recency + β·importance + γ·relevance
✦ 反思(Reflection)
- 重要性累积触发
- 从具体记忆中抽象出高阶认知
- 反思本身也存入记忆流
✦ 规划(Planning)
- 粗粒度→细粒度两级规划
- 基于记忆和环境动态调整
- 突发事件触发反应式行为
✦ 涌现行为(Emergent Behavior)
- 信息在Agent间自发传播
- 社交关系自发形成和演化
- 群体活动的自组织
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
11.2 自查清单¶
| 知识点 | 能否解释? |
|---|---|
| 记忆流三维检索公式 | 能否写出 \(score = \alpha \cdot recency + \beta \cdot importance + \gamma \cdot relevance\) 并解释每一项? |
| 重要性评分 | 能否设计重要性评分的Prompt? |
| 反思触发条件 | 能否解释何时触发反思? |
| 反思的递归性 | 能否解释为什么反思也存入记忆流? |
| 两级规划 | 能否描述粗粒度→细粒度的规划过程? |
| 涌现行为 | 能否举例说明什么是涌现行为? |
11.3 练习题¶
练习1:扩展Agent数量(基础)¶
在Mini赛博小镇中增加2个新Agent(如"赵刚——退休老教师"和"小美——大学生")。观察: - 社交网络如何变化? - 更多Agent是否产生新的涌现行为? - 信息传播速度是否加快?
练习2:实现情感系统(中级)¶
为Agent添加情感状态(快乐、悲伤、焦虑等),并让情感影响: - 行为决策(悲伤时可能不愿出门) - 对话风格(快乐时更外向) - 记忆重要性评分(情感强烈的事件得分更高)
@dataclass
class EmotionalState:
happiness: float = 0.5 # 0-1
energy: float = 0.5 # 0-1
sociability: float = 0.5 # 0-1
anxiety: float = 0.0 # 0-1
def update_from_event(self, event_type: str, intensity: float):
"""根据事件更新情绪"""
...
练习3:实现多天模拟与长期记忆(高级)¶
将模拟扩展到7天: - 实现"睡眠"过程中的记忆巩固(重要记忆强化、琐碎记忆衰减) - 观察Agent间关系在一周内的演变 - 统计信息扩散路径并可视化为图
练习4:对接游戏引擎(进阶)¶
将Agent系统对接到一个简单的2D游戏界面: - 使用pygame创建可视化地图 - Agent在地图上实时移动 - 点击Agent可以查看其记忆和当前想法 - 对话以气泡形式显示
🔗 相关章节¶
- Agent基础与架构 → 01-Agent基础与架构
- 多Agent系统 → 04-多Agent系统与实战
- Agent Memory系统 → 12-Agent Memory系统
- Context Engineering → 08-Context Engineering
- Deep Research Agent → 13-Deep Research Agent
📚 参考资料¶
- Park et al. "Generative Agents: Interactive Simulacra of Human Behavior" (2023) — 核心论文
- Stanford Smallville 项目源码 — GitHub
- Kaiya et al. "Lyfe Agents: Generative agents for low-cost real-time social interactions" (2023)
- Gao et al. "S³: Social-network Simulation System with Large Language Model-Empowered Agents" (2023)
- AgentSims: An Open-Source Sandbox for Large Language Model Evaluation (2023)
- Park et al. "Social Simulacra: Creating Populated Prototypes for Social Computing Systems" (2022)
- Kovarik et al. "Evaluating Generative Agents" (2024) — Agent行为评估框架
导航:上一章 13-Deep Research Agent | 返回目录