ITMO AI Dungeon Master 静态分析:模块设计与工作原理
对象:ITMO-Agentic-AI/ai-dungeon-master · 方法:纯静态分析
1. 总体架构
LangGraph + LangChain 的 8 智能体 D&D DM 系统。README 宣称 8 个智能体共享统一 GameState(叙述态/世界态/玩家数据)。静态阅读后确认:共享状态确为单一 GameState(src/core/types.py:276),但「8 智能体实时协作」与实际控制流并不一致——见下。
main.py run_game_loop() 终端交互:读玩家动作(437)、显示DM旁白(730)
│ initialize_world() / execute_turn()
▼
OrchestratorService (services/orchestrator_service.py:30)
│ 8个Agent实例 + context_hub + knowledge_graph + gameplay_executor (81-97)
│
├─ Phase1 初始化(build_pipeline 注册节点, 126-145; 边, 243-248)
│ story_architect → lore_builder → world_engine
│ → player_creator → initial_dm → END
│
└─ Phase2 回合:LangGraph 图已注册(137-145, 251-270) 但【被绕过】
实际走 GameplayExecutor.execute_turn() 的 7 步循环 (execute_turn:491→530)
GameplayExecutor(services/gameplay_executor.py:34)每回合 7 步:
step1 生成动作(207) → step2 规则判定+d20骰(243) → step3 世界更新(313)
→ step4 DM旁白(353) → step5 导演节奏(412) → step6 事件记忆(448)
→ step7 场景切换判定(482)
8 个 Agent 目录(src/agents/*/graph.py)各自 build_graph 只是单节点占位图(如 story_architect/graph.py:18),实际被编排器以方法调用方式复用,而非嵌套子图执行。
2. 核心工作流:一轮 DM 回合
真实回合(main.py:667-727)不经过 orchestrator 的 Phase2 LangGraph 图,而是:
main.py:676get_user_action()读终端输入,按关键词分类 action_type(attack/move/investigate/social/magic/interact,main.py:472-500)。main.py:696置state["response_type"]="action",main.py:711调orchestrator_service.execute_turn(state)。orchestrator_service.py:530转调GameplayExecutor.execute_turn,7 步流水:- step2(
gameplay_executor.py:243)在代码内完成判定:_get_dc_for_intent(534-553)按意图取 DC(attack=12/spell=13/move=8…),_generate_roll_for_intent(591)掷 d20+属性调整值,_calculate_damage(555)算伤害。注意:传入的action_resolver/judge参数在此步从未被调用。 - step4(353)调
dm.narrate_outcome_with_tokens(dungeon_master/graph.py:238)产出旁白+行动建议;step5(412)调director.direct_scene。 - step6(448)把结果写入
SessionMemory(gameplay_phase.py:125双段记忆)。
- step2(
- 返回
(game_state, gameplay_state)二元组,main.py:711解包。
结论:真正起作用的智能体在回合内主要是 DM + Director;Action Resolver/Judge 在 GameplayExecutor 路径中被跳过,仅在未被调用的 LangGraph Phase2 图里存在。
3. 模块逐一分析
- StoryArchitectAgent(
story_architect/graph.py):plan_narrative(24-124) 用 STORYTELLER 框架提示词,结构化输出CampaignBlueprint(SVO 情节节点 + 初始 NarrativeEntity),并对 relationships 做「字符串→单元素列表」清洗(98-111),写回narrative。 - LoreBuilderAgent(
lore_builder/graph.py):build_lore(36-106) 产出WorldBible(regions/cultures/history/key_npcs,14-22);answer_question(108-166) 用世界圣经回答玩家提问(写入messages)。 - WorldEngineAgent(
world_engine/graph.py):instantiate_world(102-240) 把 Region 拆成 3 个LocationNode并放置 NPC;update_world(243-305) 用 LLM 推进时间/天气;update_world_gameplay(308-333) 是空占位(只有pass)。 - PlayerProxyAgent(
player_proxy/graph.py):run_initialization(329-373) 用asyncio.gather并行生成角色(注释说明放弃 LangGraphSend以免死循环),create_single_character(137-214) 结构化生成角色卡;simulate_action(250-326) 让首个玩家角色自主行动;update_players(217-247) 按stat_changes改 HP(占位)。 - DungeonMasterAgent(
dungeon_master/graph.py):narrate_initial(43-151)、narrate_outcome_with_tokens(238-372) 是核心旁白(prompt 里嵌入骰值/DC/伤害,要求末尾输出action_suggestionsJSON);plan_response(415-466) 关键词分类 action/question/exit 供路由。 - ActionResolverAgent(
action_resolver/graph.py):resolve_action(22-85) 用 LLM 结构化输出ActionOutcome并回写 HP/位置——LLM 判定,非规则引擎。 - JudgeAgent / rule_judge(
rule_judge/graph.py):evaluate_turn(28-159) 是唯一带代码化规则的模块:正则抽取法术名/武器名调 D&D 5e API 校验(49-103),硬编码「战士/游荡者不能施法」(105-112),再让 LLM 出JudgeVerdict。但仅在 Phase2 图路径中被引用。 - DirectorAgent(
director/graph.py):direct_scene(22-57) 产出DirectorDirectives(narrative_focus/tension_adjustment),并回写narrative_tension。
4. GameState 数据模型(src/core/types.py)
GameState(276-299) 是 TypedDict:user_prompt / setting / narrative / world / players / actions / combat / rules_context / emergence_metrics / director_directives / messages / metadata / current_action / last_outcome / last_verdict / response_type / __end__ / action_suggestions。两个 reducer:players 用 operator.add(并行角色自动追加)、messages 用 add_messages(281, 288)。
关键子模型:NarrativeState(173) 含 Storyline(PlotNode+SVOEvent);WorldState(231) 含静态 lore(regions/cultures/history/important_npcs)与动态 simulation(locations/active_npcs/global_time);Player(125) 含 DnDCharacterStats(47);另有 CombatState(166)、RulesContext(249)、EmergenceMetrics(255)、JudgeVerdict(215)。
5. 与 Z.R.I.C 功能对照
| 功能 | 本项目实现情况(代码依据) |
|---|---|
| 剧本解析 | 未实现。无 campaign.json/剧本文件;剧情由 StoryArchitect 运行时 LLM 生成(story_architect/graph.py:93)。 |
| 推演分支 | 部分:行动→结构化 ActionOutcomeToken(gameplay_phase.py:61)+ d20 判定,但无多分支候选/副作用树。 |
| 自动判定 | 部分:DC 表+d20+伤害公式代码化(gameplay_executor.py:534-633);另一套 LLM 判定(action_resolver/graph.py:66)。 |
| 世界状态 | 已实现:WorldState.locations/active_npcs/global_time(types.py:231)。 |
| NPC 情绪记忆 | 未实现/未读到。ActiveNPC 仅 status/current_activity(types.py:199),无情绪轴/记忆。 |
| 地图 | 部分:LocationNode.connected_ids 拓扑(types.py:187),但 connected_ids 由 LLM 填字符串且未做可达性校验。 |
| 触发器 | 未实现。无 trigger 判定;场景切换仅靠 PacingMetrics.should_transition_scene(gameplay_phase.py:185)。 |
| 时间线 | 未实现。仅 global_time 小时计数(types.py:243)。 |
| 记忆系统 | 部分:SessionMemory 双段(近期 20 条 + campaign_chronicle,gameplay_phase.py:125)+ 内存 KnowledgeGraphService(knowledge_graph_service.py:22),但 KG 未被任何 Agent 填充(lore_builder/graph.py:101 注明「由 orchestrator 处理」却无调用)。 |
| 本地模型 | 已实现:model_service.py:87 支持 Ollama(ChatOllama),.env.example:23 默认 qwen:7b;亦支持任意 OpenAI 兼容端点。 |
| 许可 | MIT(README:224-225 声明),但仓库内未读到 LICENSE 文件(glob 无 LICENSE*)。 |
6. 设计评价
优点(有代码依据)
- 类型化状态清晰:
GameState用TypedDict+ Pydantic 子模型,reducer 语义明确(types.py:276-299)。 - 结构化输出稳健:
get_structured_output带 JSON 提取 + 指数退避重试 + 错误诊断(structured_output.py:28)。 - 本地模型友好:Ollama/OpenAI 兼容双通道(
model_service.py:44),对应 README 宣称。 - 有意的记忆/知识图谱分层设计(
gameplay_phase.py:125、knowledge_graph_service.py),方向正确。
缺陷(有代码依据)
- 图实不符:README 与 orchestrator 注释描述的 Phase2 LangGraph 多智能体图(
orchestrator_service.py:136-145)在实际回合被GameplayExecutor绕过(orchestrator_service.py:530),导致 Action Resolver/Judge/WorldEngine 在回合中几乎不执行;两套并行实现造成严重不一致。 - 大量占位/死代码:
world_engine.update_world_gameplay只有pass(world_engine/graph.py:326-329);_step3_update_world的world_engine/lore_builder参数未使用(gameplay_executor.py:313);vector_store_service三个方法全空(vector_store_service.py:5-12);search_tools两工具返回「not yet implemented」(search_tools.py:5-13)。 - 字段漂移:
WorldState未声明weather,但 world_engine 读写之(world_engine/graph.py:235-299);data_tools.py:17用player.current_hp/max_hp/inventory,而Player实为stats.current_hit_points(types.py:125);world_repo._default_world_state引用了WorldState不存在的字段(world_repo.py:18-25)。 - 规则判定薄弱:核心回合判定是「意图→固定 DC→d20」的极简近似(
gameplay_executor.py:534),非 5e 规则;真正的 API 校验在rule_judge却不在实际回路中。 - 入口未接线:
chainlit_app.py仅 1 行 docstring;每个langgraph.json引用./graph.py:graph,但各 graph.py 均无模块级graph导出(grep 无匹配),make dev-langgraph独立调试配置疑似无法启动(未运行验证)。
7. 结论
这是一个学术 demo 级的架构草图:状态模型、8 智能体划分、结构化输出、双段记忆/知识图谱的方向设计完整且可读,但工程成熟度低——真正运行的回合是一条简化的 7 步流水(DM + Director + 代码骰子),README 与 orchestrator 注释所描绘的「8 智能体 LangGraph 协作」图并未成为实际控制流。规则判定以关键词分类 + 固定 DC 近似为主,D&D 5e 规则仅有一处(未接入回路的 Judge)API 校验。若作为多智能体拓扑/GameState 设计的参考可取其「统一状态 + 单智能体结构化输出」思路;作为可玩的 DM 系统则功能大量占位、字段多处漂移,需大幅补全。
8. 关键文件索引
- 入口/游戏循环:
main.py(run_game_loop514,get_user_action437) - 编排:
src/services/orchestrator_service.py(图构建 101, 回合 491) - 回合执行:
src/services/gameplay_executor.py(7 步 81-205, 判定 534-633) - 状态模型:
src/core/types.py(GameState 276) - 8 智能体:
src/agents/*/graph.py - 记忆/知识图谱:
src/core/gameplay_phase.py、src/services/knowledge_graph_service.py - 模型接入:
src/services/model_service.py、src/core/config.py