起点:LLM 推理的核心缺陷
大模型做"未来推演"有一个根本性问题——它会幻觉。
你问它"如果美国封锁霍尔木兹海峡会发生什么",它会给你一个听起来非常合理但完全脱离现实的答案。这是因为 LLM 在回答时混用了训练数据里的"通识知识"和它凭空推断的"情报",你无法分辨哪些是真实存在的,哪些是捏造的。
我的解决方案:先把现实世界的信息搜下来,建成一个闭合的小世界快照,然后只允许推理在这个快照内进行。
这就是 MiniWorld-Evolution 的核心——两层管线,L1 构建闭合世界,L2 在其中演变。
L1:闭合小世界构建
L1 的目标是从真实世界搜索信息,构建一个包含实体和关系的闭合图谱。
假设驱动的三维度搜索(v2 升级)
最初的设计是让 LLM 直接生成扁平的搜索关键词列表——效果很差,因为关键词没有结构,搜索结果噪声大。
v2 改成了假设驱动的三维度框架:
维度 1: 影响因素 → 分析背景+关注点,列出5个以上关键影响力量
维度 2: 关键实体 → 识别8-15个参与实体(human + nature 双类型)
维度 3: 关键问题 → 生成5个以上需要搜索回答的核心问题
基于三个维度,生成带有 target_source 路由的 SearchTask 列表:
"news"→ Tavily 新闻搜索(适合时事、政策动态)"social"→ Bocha 多模态(适合舆论观点、各方讨论)"report"→ Tavily 深度 + Bocha 复合(适合研究报告)
按维度分别聚类采样(自实现 KMeans++,不依赖 scikit-learn),确保每个维度的信息都有代表性结果进入提取阶段。
三级收敛检测
L1 是一个迭代管线,什么时候停是个关键问题。我设计了三级收敛检测:
# Level 1 — 结构完整性(NetworkX)
G = nx.Graph()
# 条件1: 弱连通图(所有实体互相可达)
is_connected = nx.is_connected(G)
# 条件2: 无孤立节点
isolated = [n for n in G.nodes() if G.degree(n) == 0]
# Level 2 — 功能完整性(算法)
# 检查: human/nature 比例、关系密度、实体覆盖关注点
# Level 3 — 语义完整性(LLM 反思)
# 仅 L1+L2 都通过后才执行
# LLM 判断: 快照是否能自洽解释背景+关注点
三级全过才算收敛,否则继续下一轮搜索。这比固定跑几轮要靠谱得多——有时候 2 轮就够,有时候 4 轮才收敛。
能力边界而非具体动作(v2 动作空间升级)
收敛后,L1 为所有 human 类实体生成 agent_prompt 和 action_space。
这里有一个踩过的坑,值得单独说:
v1 的做法是从已搜索到的 evidence 中推导"具体动作",比如:
{"do": ["发动空袭击杀伊朗最高领袖哈梅内伊"]}
问题来了——搜索资料里记录的是截至今天已发生的事。如果搜索结果里已经有"哈梅内伊在空袭中遇害",那这个动作就不应该再出现在动作列表里。结果我们在演变中看到了 Agent 在 Tick 1 和 Tick 3 反复"击杀同一个已死的人"。
v2 的做法是让 L1 定义能力边界,不定义具体动作:
{
"do": {
"enabled": true,
"scope": "对伊朗军事目标实施精确打击",
"influence_targets": ["伊朗", "伊朗石油设施", "霍尔木兹海峡"]
}
}
具体行动由 L2 的 Agent 在每个 Tick 根据实时世界状态自主创造。
L2:闭合小世界演变
L2 的核心是 WorldLLM(世界规则引擎)× AgentRunner(Agent 决策执行器) 协作。
每个 Tick 的流程
Step 1: WorldLLM.assess()
→ 基于全量历史叙事评估当前局势
→ 输出:核心矛盾 + 关键决策实体
Step 2: WorldLLM.plan()(v2 改动:不再调用 LLM)
→ 仅从 state.get_all_status_summary() 获取统一世界快照
→ 所有 Agent 看到相同的客观世界
→ 为每个 Agent 单独准备专属情报(信息不对称分发)
Step 3: AgentRunner × N(并行)
→ 每个 human Agent 都参与,自主决定 act/wait
→ 输入:统一世界快照 + 专属情报 + 历史行动记录
→ 输出:do/decide/say/wait + 具体行动描述 + 推理过程
Step 4: WorldLLM.propagate()
→ 将 Agent 行动按关系图传播
→ 更新实体状态(human + nature)
Step 5: WorldLLM.narrate()
→ 生成本 Tick 叙事摘要
→ 判断是否触发终止条件
信息不对称的设计
这是我觉得最有意思的机制。在真实世界里,不同实体获取信息的能力天差地别:
- 军事力量能拿到情报机构的卫星侦察数据
- 经济组织更擅长获取供应链内部数据
- 政治人物可以拿到外交渠道的非公开信息
Step 2 时,WorldLLM 为每个 Agent 定制一份"专属情报",代表该实体凭借自身能力额外获取的信息——不重复公开事件时间线中已有的内容,只补充该实体视角的独家信息。
所有 Agent 基于相同的公开事件 + 差异化的专属情报做决策,这才是演变结果多样化的根源。
v2 Agent 升级:历史记忆
v2 之前,Agent 决策时只看到当前世界状态,不知道自己之前做了什么。导致了"反复击杀同一人"的问题。
v2 的解决方案:为每个 Agent 构建 action_history,在每次决策时把该 Agent 自己的所有历史行动传进去。Agent 知道"我在 Tick 1 发动了空袭,在 Tick 2 宣布了停火",就不会再去重复执行已无意义的动作。
可视化层
后端 FastAPI + SSE 实时流式推送,前端 Vue 3 + Cytoscape.js 渲染实体关系图。
关键设计原则:零侵入。可视化层完全通过 import 复用引擎代码,不修改任何已有文件。
效果是:
- 构建阶段:能看到实体和关系边一条一条地冒出来
- 演变阶段:每个 Tick 的叙事滚动出现,节点高亮显示受影响的实体

踩过的坑总结
| 问题 | 解法 |
|---|---|
| 搜索结果噪声大 | 假设驱动三维度框架 + 工具路由 + 按维度聚类采样 |
| 不知道何时停止构建 | 三级收敛检测(图论 → 功能覆盖 → LLM 反思) |
| Agent 重复执行已发生的事 | action_space 从"具体动作"改为"能力边界" |
| Agent 没有历史记忆 | 每次决策传入该 Agent 完整的行动历史 |
| 先后执行的 Agent 有信息差 | 所有 Agent 并行决策,基于相同的上一 Tick 快照 |
快速开始
git clone https://github.com/zn-ffmb/MiniWorld-Evolution
pip install -r requirements.txt
# 配置 .env(抄 .env.example)
# 至少需要: LLM API Key + Tavily 或 Bocha 搜索 Key
# 构建世界
python run_build_world.py --background "美伊战争" --focus "石油价格与航运"
# 演变
python run_evolution.py \
--world worlds/xxx.json \
--perturbation "伊朗宣布完全封锁霍尔木兹海峡" \
--max-ticks 10
GitHub:github.com/zn-ffmb/Min…
欢迎 Star ⭐,有问题 / 建议可以开 Issue 讨论。