Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
49 KiB
JSON
1 line
49 KiB
JSON
{"repo": "bzhenbot/bytedance-werewolf", "files": {"shared/api-contract.md": {"1b4a1a22a8b74877191f9618beab0fc89a9252a1": {"t": 1786068494.9432654, "n": 23486, "h": "5ce6e73048d37557", "c": "# shared/api-contract.md\n\n前后端接口共享契约。此文件是前后端协同开发的唯一真相源(Single Source of Truth)。\n\n---\n\n## 一、数据结构\n\n### GameState\n\n完整的对局状态,通过 `GET /api/games/{game_id}` 返回。\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| game_id | string | 对局唯一标识 |\n| phase | string | 当前阶段:night / day / speech / discussion / vote / hunter_shoot / judgement / game_over |\n| subphase | string\\|null | 夜晚子阶段:werewolf_action / seer_action / witch_action / guard_action / night_resolve / null |\n| day | integer | 当前天数(从 1 开始) |\n| round | integer | 当前轮次 |\n| status | string | 对局状态:waiting / running / finished |\n| winner | string\\|null | 获胜方:werewolf / villager / null(未结束) |\n| votes | object | 公开投票流向,格式 `{ voter_player_id: target_player_id }`;弃票记录为 `abstain` |\n| vote_results | object | 公开票数聚合,格式 `{ target_player_id: count }` |\n| abstain_count | integer | 当前弃票数;不计入任何玩家得票 |\n| pending_human_action | object\\|null | 若当前轮到 Human 席位行动,返回待提交动作描述(玩家、阶段、可选动作、合法目标) |\n| players | PlayerState[] | 玩家列表 |\n| recent_events | GameEvent[] | 最近事件列表 |\n| debug | DebugInfo\\|null | 调试信息(仅在 debug 模式下返回) |\n\n### PlayerState\n\n单个玩家状态。\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| player_id | string | 玩家唯一标识 |\n| name | string | 玩家名称 |\n| seat | integer | 座位号 |\n| role | string\\|null | 角色(仅 debug 模式返回,public 模式下为 null) |\n| alive | boolean | 是否存活 |\n| player_type | string | `ai` 或 `human` |\n| is_ai | boolean | 是否为 AI 玩家 |\n| status | string | 状态:active / eliminated / spectating |\n| public_claim | string\\|null | 公开声称的角色(玩家发言中声称的身份) |\n\n**信息隔离规则**:\n- `public` 模式:`role` 字段必须为 `null`\n- `player` 模式:仅当前玩家的 `role` 可见\n- `debug` 模式:所有角色可见\n\n### PublicGameState\n\n普通观众主界面使用的公开状态,通过 `GET /api/games/{game_id}/public-state` 返回。前端游戏桌 UI 应优先消费此结构,而不是 debug/raw 状态。\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| game_id | string | 对局唯一标识 |\n| status | string | 对局状态:running / finished |\n| phase | string | 当前阶段;猎人被投票放逐后可进入 `hunter_shoot` |\n| subphase | string\\|null | 夜晚子阶段 |\n| day | integer | 当前天数 |\n| day_count | integer | 当前天数,兼容旧前端字段 |\n| round | integer | 当前轮次 |\n| speed | integer | 播放倍速,默认 1 |\n| is_paused | boolean | 是否暂停 |\n| players | PublicPlayerState[] | 公开玩家列表,固定 12 人局时包含 12 项 |\n| alive_players | string[] | 存活玩家 ID |\n| eliminated_players | string[] | 已出局玩家 ID |\n| current_speaker | string\\|null | 当前发言玩家 |\n| current_actor | string\\|null | 兼容旧前端字段;若存在 `pending_human_action`,通常指向该 Human 玩家 |\n| discussion_topics | object[] | `discussion` 阶段由首轮发言提炼出的 topic 列表(`speaker/target/topic/intent`) |\n| discussion_participants | object[] | `discussion` 阶段的微回合顺序(challenge/defense/rebuttal),每项含 `player_id/target/topic/intent/discussion_type/turn_id/status` |\n| discussion_max_messages | integer | 自由讨论总消息上限(默认 8) |\n| current_action | string\\|null | 当前公开动作描述 |\n| key_event | string\\|null | 最近关键公开事件 |\n| public_events | GameEvent[] | 公开事件 |\n| speech_log | object[] | 当前发言日的公开发言流 |\n| current_speech_day | integer | 当前发言流聚焦的天数,用于避免主屏显示旧日发言 |\n| votes | object | 公开投票流向,格式 `{ voter_player_id: target_player_id }`;弃票记录为 `abstain` |\n| vote_results | object | 公开票数聚合,格式 `{ target_player_id: count }` |\n| vote_result | object\\|null | 当前投票聚合,空时为 null |\n| abstain_count | integer | 当前弃票数;不计入任何玩家得票 |\n| pending_hunter_shooter | string\\|null | `hunter_shoot` 阶段待开枪猎人;public 仅用于展示阶段动作,不暴露隐藏身份 |\n| pending_human_action | object\\|null | Human 席位待行动描述(`player_id/phase/subphase/allowed_actions/legal_targets/day_count`,discussion 额外含 `discussion_target/discussion_type/discussion_intent/discussion_topic/discussion_turn_id`) |\n| revealed_deaths | object[] | 已公开死亡玩家与公开身份 |\n| game_over | boolean | 游戏是否结束 |\n| winner | string\\|null | 获胜方 |\n| summary | object\\|null | 局后战报,未生成时为 null |\n| debug | null | 普通公开状态永远为 null |\n\n### GameSummary\n\n局后战报对象。当前实现为启发式生成,不依赖 LLM。\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| winner | string\\|null | 获胜方:werewolf / good / null |\n| key_turning_points | string[] | 关键转折点 |\n| mvp | object\\|null | MVP 玩家与理由 |\n| scapegoat | object\\|null | 背锅侠玩家与理由 |\n| best_speech | object\\|null | 最佳发言 |\n| decisive_vote | object\\|null | 关键投票 |\n| role_performance | object[] | 玩家角色表现摘要 |\n| timeline_summary | string | 一句话复盘 |\n| share_text | string | 可复制复盘文本 |\n\n### PublicPlayerState\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| id | string | 玩家 ID |\n| player_id | string | 兼容旧前端字段,等于 id |\n| seat | integer | 座位号 |\n| name | string | 座位名,如 P1 |\n| display_name | string | AI 玩家昵称 |\n| avatar_key | string | 头像键 |\n| title | string | 称号 |\n| personality | string | 人格标签 |\n| speech_style | string | 发言风格 |\n| short_bio | string | 简短人设说明 |\n| player_type | string | `ai` 或 `human` |\n| is_alive | boolean | 是否存活 |\n| alive | boolean | 兼容旧前端字段,等于 is_alive |\n| role_revealed | boolean | 身份是否已公开 |\n| public_role | string\\|null | 公开身份;未公开时为 null |\n| role | string\\|null | 兼容旧前端字段;未公开时为 null |\n| status_tags | string[] | 公开状态标签 |\n| suspicion_level | number\\|null | 观众态可见怀疑度,暂无时为 null |\n| vote_target | string\\|null | 当前公开投票目标 |\n| is_current_speaker | boolean | 是否正在发言 |\n| is_ai | boolean | 是否 AI 玩家 |\n| status | string | active / eliminated |\n| public_claim | string\\|null | 公开跳身份声明,暂无时为 null |\n\n**PublicGameState 信息隔离规则**:\n- 未出局且游戏未结束的玩家:`role` 和 `public_role` 必须为 `null`。\n- 已出局玩家可以公开 `public_role`。\n- 不返回 `true_state`、`wolf_team`、`seer_results`、女巫药品状态、夜间刀口、守卫目标、狼人夜间票型等私密字段。\n- Debug/raw 状态只能通过 debug 视角或 debug 接口显式获取,不作为普通观战主数据源。\n\n### GameEvent\n\n游戏事件。\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| event_id | string | 事件唯一标识 |\n| game_id | string | 所属对局 |\n| turn | integer | 轮次 |\n| day | integer | 天数 |\n| phase | string | 发生阶段 |\n| type | string | 事件类型(见下方枚举) |\n| actor_id | string\\|null | 触发者 ID |\n| visibility | string | 可见性:public / private / debug |\n| payload | object | 事件负载(根据 type 不同而变化) |\n| created_at | string | ISO 时间戳 |\n\n**event type 枚举**:\n\n| type | 说明 |\n|------|------|\n| game_created | 游戏创建 |\n| phase_changed | 阶段变化 |\n| speech | 公开发言 |\n| vote | 投票 |\n| vote_result | 投票结算,包含 `vote_counts`、`abstain_count`、`eliminated`、`result_type` |\n| vote_tie | 平票或无有效票提示 |\n| hunter_shoot | 猎人被投票放逐后开枪或放弃开枪 |\n| night_action | 夜晚行动 |\n| night_end | 夜晚结算完成;public 仅包含死亡结果,debug/god 视角可包含 `night_actions` |\n| day_opening | 白天开场摘要,用于说明昨夜结果与当天讨论起点 |\n| vote_rally | 归票摘要,用于投票前收束当天讨论焦点 |\n| wolf_discussion_message | 狼人夜聊单条意见(仅狼人私有或 debug/god 可见) |\n| death | 玩家死亡 |\n| memory_write | Agent Memory 写入 |\n| fallback | Agent 降级/回退事件 |\n| system | 系统事件 |\n\n**visibility 枚举**:\n\n| visibility | 说明 |\n|------|------|\n| public | 所有观众可见 |\n| private | 仅特定玩家可见 |\n| debug | 仅调试模式可见 |\n\n### DebugInfo\n\n调试信息(仅 debug 视角)。\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| fallback_count | integer | Fallback 事件总数 |\n| memory_write_count | integer | Memory 写入次数 |\n| agent_action_count | integer | Agent 行动次数 |\n| warnings | string[] | 警告信息列表 |\n| last_agent_action | object\\|null | 最近一次 Agent 行动详情 |\n\n---\n\n## 二、REST API 接口\n\n### 1. 创建对局\n\n```\nPOST /api/games\n```\n\n**Request Body**:\n\n```json\n{\n \"player_count\": 12,\n \"mode\": \"ai_only\",\n \"roles\": [\"werewolf\", \"seer\", \"witch\", \"villager\", \"villager\", \"villager\"],\n \"agent_ids\": [\"agent_linzhou_001\", \"agent_xuran_002\"],\n \"human_player_ids\": [\"player_1\"],\n \"role_assignment_mode\": \"random\",\n \"allow_duplicates\": false\n}\n```\n\n**Response** (201):\n\n```json\n{\n \"game_id\": \"game_001\",\n \"phase\": \"night\",\n \"day\": 1,\n \"players\": [],\n \"agent_ids\": [\"agent_linzhou_001\", \"agent_xuran_002\"],\n \"player_types\": {\n \"player_1\": \"human\",\n \"player_2\": \"ai\"\n },\n \"human_player_ids\": [\"player_1\"],\n \"agent_selection_warnings\": []\n}\n```\n\n**说明**:当前仅支持 `player_count: 9` 或 `12`;`mode` 字段预留(目前仅 ai_only 模式);`roles` 字段被接收但当前使用默认角色分配(自定义 roles 功能待实现)。`agent_ids` 为空时由后端从 Agent 库自动补齐;数量不足会补齐并在 `agent_selection_warnings` 说明;数量超出、重复或不存在时返回 400。`human_player_ids` 可选,未传时默认全 AI。旧接口 `/api/games/start` 保留兼容并支持同样请求体。\n\n### 1a. Agent 库\n\n```\nGET /api/agents\nGET /api/agents/{agent_id}\n```\n\n`GET /api/agents` 返回可参赛 Agent 列表:\n\n```json\n{\n \"agents\": [\n {\n \"agent_id\": \"agent_linzhou_001\",\n \"name\": \"林舟\",\n \"avatar\": \"🧭\",\n \"persona\": \"冷静分析位\",\n \"description\": \"重视票型和逻辑。\",\n \"speech_style\": \"先结论后证据\",\n \"strategy_tags\": [\"analysis\"],\n \"default_profile\": {},\n \"enabled\": true\n }\n ]\n}\n```\n\n`seat/player_id/agent_id/role` 关系:`seat` 和 `player_id` 只在本局有效;`agent_id` 是跨局身份,profile 与长期 memory 跟随 `agent_id`;`role` 每局由规则随机或配置分配,不与 `agent_id` 固定绑定。\n\n### 2. 获取对局状态\n\n```\nGET /api/games/{game_id}\n```\n\n**Query Parameters**:\n\n| 参数 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| view_mode | string | 否 | 视角模式:public(默认)/ player / debug |\n| viewer_id | string | 否 | 当 view_mode=player 时指定玩家 ID |\n\n**Response** (200):返回 `GameState` 对象。\n\n### 2a. 获取公开观战状态\n\n```\nGET /api/games/{game_id}/public-state\n```\n\n**Response** (200):返回 `PublicGameState` 对象。\n\n**说明**:普通前端游戏桌和观众互动 UI 应使用此接口;该接口不接受 `view_mode`,不会返回隐藏身份、夜间私密行动或 debug/raw 状态。\n\n### 3. 推进一步\n\n```\nPOST /api/games/{game_id}/step\n```\n\n推进游戏一个阶段(night → day/speech → vote → hunter_shoot(仅投票放逐猎人时)→ judgement → next night)。\n\n**Response** (200):返回更新后的 `GameState`。\n\n**说明**:已实现。旧接口 `/api/games/{game_id}/advance` 保留兼容,新代码建议使用标准契约接口 `/step`。\n\n### 3a. 暂停对局\n\n```\nPOST /api/games/{game_id}/pause\n```\n\n**Response** (200):返回更新后的 `PublicGameState`,其中 `is_paused: true`。\n\n### 3b. 继续对局\n\n```\nPOST /api/games/{game_id}/resume\n```\n\n**Response** (200):返回更新后的 `PublicGameState`,其中 `is_paused: false`。\n\n### 3c. 设置倍速\n\n```\nPOST /api/games/{game_id}/speed\n```\n\n**Request Body**:\n\n```json\n{ \"speed\": 4 }\n```\n\n**Response** (200):返回更新后的 `PublicGameState`。\n\n**说明**:`speed` 仅支持 `1 / 2 / 4 / 8`。\n\n### 3d. 重新开局\n\n```\nPOST /api/games/{game_id}/restart\n```\n\n**Response** (200):返回新对局的 `PublicGameState`。新对局保留原人数,默认 `speed: 1`、`is_paused: false`。\n\n### 3e. Human 席位提交行动\n\n```\nPOST /api/games/{game_id}/human/action\n```\n\n**Request Body**:\n\n```json\n{\n \"player_id\": \"player_1\",\n \"action_type\": \"vote\",\n \"target_id\": \"player_3\",\n \"message\": \"我先投给 P3\",\n \"discussion_type\": \"challenge\",\n \"intent\": \"question\",\n \"topic\": \"P3 在主发言中未给出明确投票意图\",\n \"discussion_turn_id\": \"day1_topic1_challenge_1\"\n}\n```\n\n**Response** (200):\n\n```json\n{\n \"success\": true,\n \"game_id\": \"game_001\",\n \"phase\": \"vote\",\n \"player_id\": \"player_1\",\n \"action\": \"vote\",\n \"target\": \"player_3\",\n \"pending_human_action\": null\n}\n```\n\n**说明**:仅 `player_type=human` 可调用。后端会按当前阶段校验合法动作:\n- `night`:狼人 `kill/pass`,预言家 `check/pass`,女巫 `heal/poison/pass`,守卫 `guard/pass`\n- `speech`:`speak/pass`\n- `discussion`:`discuss/speak/pass`(推荐 `discuss`,并携带 `intent/topic/discussion_turn_id`)\n- `vote`:`vote/pass`\n- `hunter_shoot`:`shoot/pass`\n\n### 4. 获取事件流\n\n```\nGET /api/games/{game_id}/events\n```\n\n**Query Parameters**:\n\n| 参数 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| visibility | string | 否 | 过滤可见性:public / private / debug;默认 public |\n| limit | integer | 否 | 返回条数限制,默认 50 |\n\n**Response** (200):\n\n```json\n{\n \"game_id\": \"game_001\",\n \"events\": [ /* GameEvent[] */ ]\n}\n```\n\n**当前状态**:已实现 — 见 `backend/src/api/app.py` 中的 `get_game_events` 路由。\n\n**信息隔离**:未显式传入 `visibility=debug` 时,该接口只返回 public-safe 事件;`memory_write`、`fallback`、`role_assignment`、`night_action` 等审计/私有事件不会进入默认事件流,非 debug 视角会递归移除 `role`、`private_state`、`night_actions` 等敏感 payload 字段。\n\n### 5. 获取 Debug 信息\n\n```\nGET /api/games/{game_id}/debug\n```\n\n**Response** (200):返回 `DebugInfo` 对象。\n\n**当前状态**:已实现 — 见 `backend/src/api/app.py` 中的 `get_game_debug` 路由。\n\n### 6. 获取局后战报\n\n```\nGET /api/games/{game_id}/summary\n```\n\n**Response** (200):\n\n```json\n{\n \"game_id\": \"game_001\",\n \"summary\": {\n \"winner\": \"good\",\n \"key_turning_points\": [\"投票阶段淘汰了 P1 林舟\"],\n \"mvp\": { \"player_id\": \"player_5\", \"name\": \"P5 沈星\", \"reason\": \"...\" },\n \"scapegoat\": null,\n \"best_speech\": null,\n \"decisive_vote\": null,\n \"role_performance\": [],\n \"timeline_summary\": \"本局进行到第 2 天,好人阵营。\",\n \"share_text\": \"AI狼人杀复盘...\"\n }\n}\n```\n\n**说明**:普通 `PublicGameState.summary` 仅在 `game_over: true` 或已经能判定胜方时返回;未结束时为 `null`。\n\n---\n\n## 三、WebSocket 接口\n\n### 连接\n\n```\nws://localhost:8000/ws/games/{game_id}\n```\n\n### 客户端 → 服务端消息\n\n```json\n{ \"action\": \"subscribe\", \"view_mode\": \"public\", \"viewer_id\": null }\n{ \"action\": \"get_state\" }\n```\n\n### 服务端 → 客户端消息\n\n```json\n{\n \"event_type\": \"state_update | game_start | phase_change | night_action | speech | discussion | vote | game_end | initial_state | wolf_discussion\",\n \"game_id\": \"game_001\",\n \"data\": { /* 根据 event_type 不同的负载 */ },\n \"timestamp\": 1716934800.123\n}\n```\n\n常用事件 payload:\n\n| event_type | payload 字段 |\n|------|------|\n| phase_change | `phase`、`subphase`、`previous_subphase`、`message` |\n| discussion | `player_id`、`content`、`target`、`discussion_type`、`intent`、`topic`、`discussion_turn_id` |\n| vote | `voter`、`target`、`is_abstain`、`vote_results`、`abstain_count` |\n| night_end | `eliminated`、`phase`、`day_count`、`message`;debug/god 可额外包含 `night_actions` |\n| system/day_opening | `message`、`event_kind: \"day_opening\"`、`day_count`、`eliminated` |\n| system/vote_rally | `message`、`event_kind: \"vote_rally\"`、`day_count` |\n\n`night_actions` 调试负载字段:\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| kill_target | string\\|null | 狼人最终刀口 |\n| heal_target | string\\|null | 女巫解药目标 |\n| healed_target | string\\|null | 实际被解药救下的刀口,仅存在时返回 |\n| poison_target | string\\|null | 女巫毒药目标 |\n| guard_target | string\\|null | 守卫守护目标 |\n| seer_target | string\\|null | 预言家查验目标 |\n| wolf_votes | object | 狼人夜间刀人投票,仅 debug/god 可见 |\n\n---\n\n## 四、迁移说明\n\n当前后端实际接口与契约定义的差异:\n\n| 契约接口 | 实际接口 | 差异 | 优先级 |\n|------|------|------|------|\n| POST /api/games | POST /api/games | 已实现,路由名一致 | 仅支持 9/12 人,roles/mode 待扩展 |\n| POST /api/games/{id}/step | POST /api/games/{id}/step | 已实现,路由名一致,返回 GameState | - |\n| POST /api/games/{id}/human/action | POST /api/games/{id}/human/action | 已实现;仅 Human 席位可调用 | 最小人机混战闭环 |\n| GET /api/games/{id}/events | GET /api/games/{id}/events | 已实现,路由名一致 | - |\n| GET /api/games/{id}/debug | GET /api/games/{id}/debug | 已实现,路由名一致 | - |\n\n迁移策略:所有契约接口路由已实现。旧接口(`/start`、`/advance`)保留向后兼容。`roles`/`mode` 等高级参数待后续阶段支持。\n\n---\n\n## 五、History API(比赛交付新增)\n\n### `GET /api/history/games`\n\n查询参数:`limit`、`offset`、`status`、`winner`、`include_test_data`、`real_llm_only`。\n\n`status=completed` 会在后端兼容映射为内部完成态 `finished`,用于前端首屏和 History 默认查询最近真实 LLM 完成局。\n\n默认行为:\n- `include_test_data=false`:隐藏测试对局、异常测试局和测试 Agent 对局。\n- `real_llm_only=false`:返回真实/FakeLLM 对局;前端 History 主视图默认传 `real_llm_only=true`,优先展示真实 LLM 对局。\n\n返回:\n\n```json\n{\n \"games\": [\n {\n \"game_id\": \"abc123\",\n \"status\": \"finished\",\n \"player_count\": 12,\n \"winner\": \"good\",\n \"win_reason\": \"所有狼人已被消灭\",\n \"current_day\": 3,\n \"current_phase\": \"game_over\",\n \"started_at\": \"2026-05-26T10:00:00\",\n \"ended_at\": \"2026-05-26T10:05:00\",\n \"event_count\": 120,\n \"config\": {\n \"use_real_llm\": true,\n \"model_name\": \"deepseek-v4-flash\",\n \"agent_ids\": [\"agent_linzhou_001\"]\n }\n }\n ],\n \"limit\": 50,\n \"offset\": 0,\n \"include_test_data\": false,\n \"real_llm_only\": true\n}\n```\n\n### `GET /api/history/games/{game_id}`\n\n返回数据库中的 game metadata、summary,以及当前进程仍保留时的 players 调试信息。响应包含 `game_agent_assignments`,用于记录本局 `seat/player_id/agent_id/display_name` 绑定;仅 `visibility=debug` 时返回 `assigned_role`。\n\n### `GET /api/history/games/{game_id}/events`\n\n查询参数:`visibility=public|player|debug`、`player_id`、`phase`、`event_type`、`limit`、`offset`。\n\n隔离规则:\n\n- `public` 仅返回 `visibility=public` 事件。\n- `player` 返回 public + 该 `player_id` 可见的 player/private 事件。\n- `debug` 返回完整事件。\n\n### `GET /api/history/games/{game_id}/decisions`\n\n查询参数:`visibility=public|player|debug`、`agent_id`、`phase`、`action_source`、`limit`、`offset`。\n\n返回 `agent_decision` 事件,payload 包含 `policy_suggestion`、`llm_raw_output`、`final_action`、`action_source`、`repair_reason`、`fallback_reason`、`fallback_used`、`memory_ids_used`。\n\n信息隔离:默认 `visibility=public`,返回空决策集;只有显式 `visibility=debug` 时返回完整 Agent 决策链。前端 History 页必须随当前视角传入该参数,不能在 public/player 视角主动请求 debug 决策。\n\n## 六、Memory API(比赛交付新增)\n\n### `GET /api/memory/agents`\n\n查询参数:`include_test_data`,默认 `false`。\n\n返回 Agent 记忆统计,并附带“每个 Agent 10 局真实 LLM memory”覆盖度。默认只展示当前启用 Agent Registry(`preset_20`)中的 Agent,并隐藏 `agent_api_test_*`、`agent_memory_test_*` 等测试/历史遗留 Agent;勾选测试数据后才展示数据库里的完整列表。\n\n```json\n{\n \"agents\": [\n {\n \"agent_id\": \"agent_linzhou_001\",\n \"memory_count\": 12,\n \"last_updated_at\": \"2026-06-02T20:00:00\",\n \"self_lesson_count\": 4,\n \"role_lesson_count\": 6,\n \"opponent_style_count\": 2,\n \"real_llm_game_count\": 3,\n \"target_real_llm_games\": 10,\n \"remaining_real_llm_games\": 7,\n \"real_llm_memory_ready\": false\n }\n ],\n \"include_test_data\": false,\n \"real_llm_memory_target_games\": 10,\n \"real_llm_memory_ready_count\": 0,\n \"real_llm_memory_total_agents\": 20\n}\n```\n\n### `GET /api/memory/real-llm-preflight`\n\n只读接口,不触发真实 LLM 调用。用于在执行“每个 Agent 10 局真实 LLM memory”前展示固定计划、当前覆盖缺口和安全确认要求。\n\n固定标准:`preset_20`、12 人局、`target_agent_games=10`、`max_days=10`、`max_workers=1`。在 20 个 Agent / 12 人局下,系统需要计划 `17` 局,才能保证每个 Agent 至少入场 10 局。\n\n```json\n{\n \"configured\": true,\n \"use_real_llm\": true,\n \"agent_pool\": \"preset_20\",\n \"agent_roster_size\": 20,\n \"player_count\": 12,\n \"target_agent_games\": 10,\n \"required_games\": 17,\n \"max_days\": 10,\n \"max_workers\": 1,\n \"schedule_strategy\": \"balanced\",\n \"min_scheduled_agent_games\": 10,\n \"max_scheduled_agent_games\": 11,\n \"agent_memory_target_met_by_schedule\": true,\n \"current_min_real_llm_games\": 0,\n \"current_max_real_llm_games\": 0,\n \"ready_agent_count\": 0,\n \"pending_agent_count\": 20,\n \"total_remaining_agent_games\": 200,\n \"external_api_call_required\": true,\n \"safety_confirmation_required\": true,\n \"no_auto_execute\": true,\n \"command\": \"cd backend && python3 scripts/run_batch_games.py --use-real-llm --confirm-real-llm-cost-and-writes --agent-pool preset_20 --target-agent-games 10 --max-workers 1 --max-days 10\",\n \"llm_status\": {\n \"configured\": true,\n \"mode\": \"real\",\n \"model_name\": \"deepseek-v4-flash\",\n \"total_tokens\": 0,\n \"estimated_cost\": 0\n },\n \"agent_statuses\": [\n {\n \"agent_id\": \"agent_linzhou_001\",\n \"name\": \"林舟\",\n \"current_real_llm_games\": 0,\n \"target_real_llm_games\": 10,\n \"remaining_real_llm_games\": 10,\n \"scheduled_games\": 10,\n \"real_llm_memory_ready\": false\n }\n ]\n}\n```\n\n### `GET /api/memory/agents/{agent_id}`\n\n查询参数:`memory_type`、`role`、`limit`、`offset`。\n\n返回:`memory_id`、`memory_type`、`source_role`、`content`、`confidence`、`importance`、`usage_count`、`last_used_at`、`metadata`。\n\n### `GET /api/memory/agents/{agent_id}/usage`\n\n返回该 Agent 的 memory usage:`memory_id`、`game_id`、`decision_event_id`、`usage_type`、`score`、`created_at`。\n\n## 七、LLM 与 Reports API(真实 LLM 展示新增)\n\n### `GET /api/llm/status`\n\n不触发外部 API 调用,仅返回当前运行时配置和累计统计。\n\n```json\n{\n \"configured\": true,\n \"mode\": \"real\",\n \"model_name\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"llm_call_count\": 12,\n \"total_tokens\": 34000,\n \"estimated_cost\": 0.0123,\n \"average_latency_ms\": 2500\n}\n```\n\n### `GET /api/reports`\n\n查询参数:`real_llm_only`、`demo_standard_only`、`limit`。\n\n默认 `real_llm_only=true` 且 `demo_standard_only=true`,只返回 `backend/reports/batch_*_metrics.json` 中 `experiment_config.use_real_llm=true` 且符合标准 Demo 证据的报告:`game_count>=3`、`success_count>=3`、`failure_count=0`、`max_days=10`、`max_workers=1`。前端 Reports 页可切换显示 FakeLLM / Offline 报告。\n\n返回中的 `reports` 会按 `generated_at` 倒序;当历史报告只保留紧凑时间戳(如 `20260603_165249`)时,后端会同时返回:\n- `generated_at`:归一化后的 ISO 时间,供前端排序和展示。\n- `generated_at_raw`:原始报告时间戳,供审计或回溯文件名。\n\n返回:\n\n```json\n{\n \"reports\": [\n {\n \"report_id\": \"batch_20260602_200138\",\n \"generated_at\": \"2026-06-02T20:01:38\",\n \"generated_at_raw\": \"20260602_200138\",\n \"use_real_llm\": true,\n \"model_name\": \"deepseek-v4-flash\",\n \"game_count\": 3,\n \"success_count\": 3,\n \"failure_count\": 0,\n \"completed_game_count\": 3,\n \"max_days\": 10,\n \"max_workers\": 1,\n \"target_agent_games\": 0,\n \"min_scheduled_agent_games\": 1,\n \"agent_memory_target_met_by_schedule\": null,\n \"demo_standard\": true,\n \"win_metrics\": { \"avg_days\": 2.3 },\n \"agent_quality\": { \"fallback_rate\": 0.01 },\n \"memory_metrics\": { \"memory_usage_count\": 25 },\n \"governance_metrics\": {\n \"reviewed_games\": 3,\n \"private_info_leak_count\": 0,\n \"info_leak_check_passed\": true,\n \"private_info_leak_examples\": []\n },\n \"real_llm_runtime\": {\n \"use_real_llm\": true,\n \"api_key_configured\": true,\n \"ready_for_real_llm\": true,\n \"model_name\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"fail_fast_enforced\": true\n },\n \"event_metrics\": { \"discussion_count\": 9 },\n \"cost_metrics\": {\n \"total_prompt_tokens\": 80000,\n \"total_completion_tokens\": 40000,\n \"total_tokens\": 120000,\n \"llm_call_count\": 180,\n \"estimated_cost\": 0.08,\n \"avg_llm_latency\": 2300\n },\n \"total_prompt_tokens\": 80000,\n \"total_completion_tokens\": 40000,\n \"total_tokens\": 120000,\n \"llm_call_count\": 180,\n \"estimated_cost\": 0.08,\n \"avg_llm_latency\": 2300,\n \"fallback_count\": 1,\n \"invalid_action_count\": 0,\n \"memory_usage_count\": 25,\n \"info_leak_check_passed\": true,\n \"has_markdown\": true,\n \"has_leaderboard\": true,\n \"top_agents\": []\n }\n ],\n \"real_llm_only\": true,\n \"demo_standard_only\": true,\n \"real_count\": 1,\n \"real_demo_count\": 1,\n \"offline_count\": 10,\n \"llm_status\": { \"configured\": true, \"mode\": \"real\" },\n \"excellence_status\": {\n \"generated_at\": \"2026-06-04T13:40:00\",\n \"ci\": {\n \"mode\": \"ci\",\n \"score\": 100,\n \"level\": \"outstanding\",\n \"minimum_score\": 90,\n \"passed\": true,\n \"failed_count\": 0,\n \"failed_checks\": [],\n \"summary\": \"通过\",\n \"headline\": \"ci 100.00/90.00 · 0 阻塞\"\n },\n \"acceptance\": {\n \"mode\": \"acceptance\",\n \"score\": 95,\n \"level\": \"outstanding\",\n \"minimum_score\": 95,\n \"passed\": false,\n \"failed_count\": 2,\n \"failed_checks\": [\n {\n \"check_id\": \"real_llm_demo_artifact\",\n \"detail\": \"latest real report runtime attestation missing\"\n },\n {\n \"check_id\": \"real_llm_memory_artifact\",\n \"detail\": \"info_leak_check_passed!=true (3 leaks)\"\n }\n ],\n \"summary\": \"未通过\",\n \"headline\": \"acceptance 95.00/95.00 · 2 项阻塞\"\n }\n }\n}\n```\n\n`governance_metrics.private_info_leak_examples` 在 `private_info_leak_count>0` 时返回最多若干条样本,每条包含:\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| game_id | string | 泄露样本所属对局 |\n| review_path | string | 生成样本的 agent review 文件路径 |\n| player_id | string | 触发样本的玩家 |\n| role | string | 该玩家角色 |\n| day | integer | 发生天数 |\n| phase | string | 发生阶段 |\n| detail | string | 命中的泄露规则或说明 |\n| evidence | string | 对应 public_message 片段 |\n\n前端 Reports 页必须把 `info_leak_check_passed=false` 显示为需复核,并在样本存在时展示样本证据。旧报告可能只有 `private_info_leak_count` 而没有 examples,此时只能展示计数。\n\n`real_llm_runtime` 用于真实 LLM artifact 证据链,不包含密钥值,只表示生成报告时真实 LLM 预检是否通过:\n- `api_key_configured=true` — 进程可读取 `LLM_API_KEY` / `OPENAI_API_KEY` / `DEEPSEEK_API_KEY` 之一。\n- `ready_for_real_llm=true` — `use_real_llm=true` 且真实 LLM fail-fast 预检已通过。\n- `fail_fast_enforced=true` — 缺少 API Key 时会拒绝运行,不允许静默降级为 FakeLLM。\n\n`excellence_status` 是 `/api/reports` 的统一验收摘要来源,供 Live 首页与 Reports 首页共用,禁止前端自行拼装“验收通过”结论:\n- `ci`:代码级门禁摘要,只反映测试、构建、静态规则和文档契约。\n- `acceptance`:答辩证据门禁摘要,会直接复用 `scripts/check_excellence.py --mode acceptance` 的真实规则。\n- `failed_checks[]`:每项至少包含 `check_id` 和 `detail`,前端应直接展示阻塞原因,不得将失败状态包装为 `100/100` 或“已就绪”。\n\n用于扩展“每个 Agent 10 局真实 LLM memory”的固定命令:\n\n```bash\ncd backend && python3 scripts/run_batch_games.py \\\n --use-real-llm \\\n --confirm-real-llm-cost-and-writes \\\n --agent-pool preset_20 \\\n --target-agent-games 10 \\\n --max-workers 1 \\\n --max-days 10\n```\n\n该命令会自动把 `num_games` 从默认值提升到满足 20 个 preset Agent、12 人局、每个 Agent 至少入场 10 局的最小局数,即 17 局。真实 LLM 批量运行会调用外部 API 并产生费用,必须在人工确认后执行。\n"}}, "README.md": {"1b4a1a22a8b74877191f9618beab0fc89a9252a1": {"t": 1786068586.393084, "n": 11705, "h": "1f4a0819f56c0e37", "c": "# 🐺 狼人杀 Multi-Agent Arena\n\n一个基于多智能体协作和对抗的完整狼人杀游戏系统。\n\n---\n\n## 📋 项目简介\n\n本项目是一个完整的 AI 狼人杀游戏系统,具有以下特性:\n\n- **LLM-first Multi-Agent Harness**: 采用统一的 Harness 架构而非硬编码角色 Agent\n- **Observation 信息隔离**: 严格的信息过滤机制,确保不同视角看到不同的游戏状态\n- **PlayerAgent 统一调度**: 所有角色使用相同的智能体框架\n- **ActionParser 结构化行动**: 解析 LLM 输出为有效的游戏动作\n- **RuleEngine 裁决**: 验证动作合法性并执行游戏规则\n- **LLM 日志与复盘**: 完整记录 LLM 输入输出,自动生成复盘报告\n- **前端观战**: 支持三种视角(公众/玩家/调试)实时观看游戏\n\n---\n\n## 🏗️ 核心创新点\n\n### 1. Harness 架构而非硬编码角色 Agent\n\n本项目采用统一的 PlayerAgent 框架,而非为狼人、预言家、村民等每个角色硬编码独立 Agent。这种设计的优势:\n\n- 统一的推理接口:所有角色使用相同的决策框架\n- 可扩展性:易于添加新角色或修改现有角色行为\n- 模块化:各组件解耦,便于独立开发和测试\n- 可观测性:统一的日志和追踪机制\n\n### 2. 信息隔离机制\n\n通过 InformationFilter 实现严格的信息隔离:\n\n- **公众视角 (public)**: 无隐藏信息,适合观众观看\n- **玩家视角 (player)**: 只能看到特定玩家看到的信息\n- **调试视角 (debug)**: 上帝视角,查看所有隐藏信息,包括角色身份\n\n### 3. LLM 完整集成\n\n- 支持 OpenAI、DeepSeek 等多种 LLM\n- FakeLLM 降级模式,无需 API Key 即可演示\n- Token 计数和费用估算\n- LLM 调用历史记录和统计\n\n---\n\n## 📊 系统架构\n\n```mermaid\ngraph TB\n subgraph Frontend[\"前端 (React)\"]\n GH[GameHeader<br/>游戏头部]\n PP[PlayerPanel<br/>玩家面板]\n GET[GameEventTimeline<br/>事件时间线]\n LSP[LLMStatsPanel<br/>LLM统计面板]\n end\n\n subgraph APILayer[\"API 层\"]\n WS[WebSocket Server<br/>多视角推送]\n REST[REST API<br/>游戏控制]\n end\n\n subgraph GameEngine[\"GameEngine 核心\"]\n PM[Phase Manager<br/>阶段管理]\n RM[Role Manager<br/>角色管理]\n IF[Information Filter<br/>信息过滤]\n RE[Rule Engine<br/>规则引擎]\n end\n\n subgraph AgentSystem[\"Agent 系统\"]\n PA[PlayerAgent<br/>统一玩家智能体]\n PB[PromptBuilder<br/>提示词构建]\n AP[ActionParser<br/>动作解析]\n LLM[LLM Integration<br/>真实/Fake LLM]\n end\n\n subgraph LoggingReview[\"日志与复盘\"]\n GL[GameLogger<br/>游戏日志]\n GR[GameReviewer<br/>复盘生成]\n ME[Metrics<br/>指标统计]\n end\n\n Frontend -->|WebSocket/REST| APILayer\n APILayer -->|GameState|GameEngine\n GameEngine -->|Observation|AgentSystem\n AgentSystem -->|Actions|GameEngine\n GameEngine -->|Game Events|LoggingReview\n```\n\n---\n\n## 🚀 快速开始\n\n### 前置要求\n\n- Python 3.9+\n- Node.js 16+\n- npm 或 yarn\n\n### 一键安装 & 启动\n\n```bash\n# 安装所有依赖\nmake install\n\n# 启动后端(终端 1)\nmake backend\n\n# 启动前端(终端 2)\nmake frontend\n```\n\n### 后端启动(手动)\n\n```bash\ncd backend\n\n# 安装依赖\npip install -r requirements.txt\n\n# 复制环境变量配置\ncp .env.example .env\n# 编辑 .env 配置你的 LLM API Key(可选)\n```\n\n### FakeLLM 演示(无需配置)\n\n无需任何 API Key 配置,直接使用模拟的 LLM 响应:\n\n```bash\n# 运行单局游戏\npython3 scripts/run_single_game.py --max-days 3\n```\n\n### 真实 LLM 配置\n\n如需接入真实的 LLM,编辑 `.env` 文件:\n\n```bash\n# 通用配置(推荐)\nLLM_API_KEY=your-api-key\nLLM_BASE_URL=https://api.deepseek.com/v1\nLLM_MODEL=deepseek-chat\n\n# 或使用 OpenAI 配置\nOPENAI_API_KEY=your-openai-key\n\n# 或使用 DeepSeek 配置\nDEEPSEEK_API_KEY=your-deepseek-key\n```\n\n### 运行真实 LLM 游戏\n\n```bash\n# 使用真实 LLM 运行单局游戏\npython3 scripts/run_single_game.py --use-real-llm --max-days 10\n```\n\n### 前端启动\n\n```bash\ncd frontend\n\n# 安装依赖\nnpm install\n\n# 构建生产版本\nnpm run build\n\n# 或使用开发模式\nnpm start\n```\n\n---\n\n## 📚 使用指南\n\n### 单局游戏运行\n\n```bash\ncd backend\n\n# FakeLLM 模式(默认)\npython3 scripts/run_single_game.py --max-days 3\n\n# 真实 LLM 模式\npython3 scripts/run_single_game.py --use-real-llm --max-days 10\n```\n\n### 批量游戏运行\n\n```bash\n# FakeLLM 批量\npython3 scripts/run_batch_games.py --num-games 5 --max-days 5\n\n# 真实 LLM 批量\npython3 scripts/run_batch_games.py --use-real-llm --agent-pool preset_20 --num-games 3 --max-workers 1 --max-days 10\n```\n\n### 复盘报告生成\n\n```bash\n# 为最新游戏生成复盘\npython3 scripts/generate_review.py --latest\n\n# 或指定游戏 ID\npython3 scripts/generate_review.py --game-id <game-id>\n```\n\n复盘报告会生成在 `backend/reports/` 目录下,包含 JSON 和 Markdown 两种格式。\n\n### 测试命令\n\n```bash\n# 运行所有测试\ncd backend\npython3 -m pytest tests/ -v\n```\n\n---\n\n## 📁 项目结构\n\n```\nbytesdance-werewolf/\n├── AGENTS.md # 所有 coding agent 行为规范(硬规则)\n├── CLAUDE.md # Claude Code 工作指南\n├── Makefile # 本地开发快捷命令\n├── shared/\n│ └── api-contract.md # 前后端 API 共享契约(唯一真相源)\n├── .github/\n│ ├── workflows/ci.yml # CI 自动检查(测试+构建)\n│ └── pull_request_template.md\n├── backend/\n│ ├── src/\n│ │ ├── agents/ # 智能体实现\n│ │ │ ├── player_agent.py # 统一玩家智能体\n│ │ │ ├── prompt_builder.py # 提示词构建\n│ │ │ ├── action_parser.py # 动作解析\n│ │ │ └── llm_integration.py # LLM 集成\n│ │ ├── api/ # API 和 WebSocket\n│ │ │ └── app.py # FastAPI 应用\n│ │ ├── communication/ # 通信和信息控制\n│ │ │ ├── information_filter.py # 信息过滤\n│ │ │ └── message_bus.py # 消息总线\n│ │ ├── game/ # 游戏核心\n│ │ │ ├── game_engine.py # 游戏引擎\n│ │ │ ├── phase_manager.py # 阶段管理\n│ │ │ ├── role_manager.py # 角色管理\n│ │ │ └── models.py # 数据模型\n│ │ ├── evaluation/ # 评估和复盘\n│ │ │ ├── reviewer.py # 复盘生成\n│ │ │ └── metrics.py # 指标统计\n│ │ └── logs/ # 日志系统\n│ │ └── game_logger.py # 游戏日志\n│ ├── prompts/ # Prompt 配置\n│ ├── tests/ # 后端测试用例\n│ ├── scripts/ # 实用脚本\n│ ├── requirements.txt\n│ ├── .env.example\n│ └── .gitignore\n├── frontend/\n│ ├── src/\n│ │ ├── components/ # React 组件\n│ │ ├── hooks/ # 自定义 Hooks\n│ │ │ └── useGameState.js\n│ │ └── App.js\n│ ├── package.json\n│ ├── .env.example\n│ └── ...\n├── docs/ # 文档\n├── docker-compose.yml # Docker 部署(可选)\n└── README.md\n```\n\n---\n\n## 🔒 安全说明\n\n### API Key 安全\n\n- **请勿提交 `.env` 文件**: 项目包含 `.gitignore` 规则,防止提交包含敏感信息的配置文件\n- **API Key 不被记录**: LLM 集成代码确保 API Key 不会被记录到日志文件中\n- **环境变量优先级**: 支持通用配置 `LLM_API_KEY`,也支持特定提供商配置\n\n### 信息隔离安全\n\n- 敏感信息如角色身份在不同视角下会被正确隐藏\n- 公众视角和玩家视角不会泄露隐藏信息\n- 调试视角仅供开发和调试使用\n\n---\n\n## ✅ 当前已验证结果\n\n| 项目 | 状态 | 说明 |\n|------|------|------|\n| 后端测试 | ✅ 290 passed, 1 skipped | `cd backend && python3 -m pytest -q` |\n| FakeLLM 单局 | ✅ 完成 | `python3 scripts/run_single_game.py --use-fake-llm --max-days 2` |\n| FakeLLM 批量 | ✅ 完成 | `python3 scripts/run_batch_games.py --num-games 1 --max-workers 1 --max-days 2` |\n| 真实 LLM 批量 | ✅ 通过 | `python3 scripts/run_batch_games.py --use-real-llm --num-games 1 --max-workers 1 --max-days 2`,样本 `482fe406` |\n| 真实 LLM 单局 | ✅ 通过 | `python3 scripts/run_single_game.py --use-real-llm --max-days 2`,样本 `7fa8622e` |\n| LLM Token 统计 | ✅ 完成 | 完整的 Token 和费用统计 |\n| 复盘报告 | ✅ 完成 | JSON + Markdown 格式 |\n| 前端测试/构建 | ✅ 通过 | `npm test -- --watchAll=false`、`npm run build` |\n| 信息隔离 | ✅ 通过 | public History API 不返回 private/debug 事件 |\n\n---\n\n## 🔗 前后端联调\n\n### 快速启动\n\n```bash\n# 方式1:使用 Makefile(需要两个终端)\n# 终端 1\nmake backend\n\n# 终端 2\nmake frontend\n\n# 方式2:手动启动\n# 终端 1\ncd backend && python3 -m src.api.app\n\n# 终端 2\ncd frontend && npm start\n```\n\n### 联调说明\n\n- 后端运行在 `http://localhost:8000`,提供 REST API + WebSocket\n- 前端运行在 `http://localhost:3000`,通过 `REACT_APP_API_BASE_URL` 环境变量连接后端\n- 前端默认连接真实后端(`REACT_APP_USE_MOCK=false`)\n- Mock 模式需显式开启(`REACT_APP_USE_MOCK=true`),UI 中会显示黄色警告条\n\n### 环境变量\n\n**后端**:复制 `backend/.env.example` 为 `backend/.env`\n**前端**:复制 `frontend/.env.example` 为 `frontend/.env`\n\n前端核心变量:\n- `REACT_APP_API_BASE_URL` — 后端地址,默认 `http://localhost:8000`\n- `REACT_APP_USE_MOCK` — Mock 模式开关,默认 `false`\n\n---\n\n## 📋 API 契约\n\n前后端接口契约定义在:**[shared/api-contract.md](shared/api-contract.md)**\n\n此文件是前后端协同开发的**唯一真相源**,定义了:\n- 数据结构(`GameState`、`PlayerState`、`GameEvent`、`DebugInfo`)\n- REST API 接口(创建对局、获取状态、推进阶段、事件流、Debug)\n- WebSocket 通信协议\n- 信息隔离规则\n\n⚠️ **重要**:任何 API 变更必须同步更新此文件。\n\n---\n\n## 🌿 分支与 PR 流程\n\n### 分支规则\n\n- `main` 分支保护,**禁止直接提交**\n- 所有变更通过 feature/fix 分支 + PR 合并\n- 分支命名:`feature/<描述>`、`fix/<描述>`、`cleanup/<描述>`\n\n### PR 流程\n\n1. 创建分支 → 提交代码\n2. 推送并创建 PR(使用 PR 模板)\n3. CI 自动运行(后端测试 + 前端构建)\n4. Code Review 通过后合并\n\n### 提交前检查\n\n```bash\n# 后端测试\nmake test\n\n# 前端构建\nmake build\n```\n\n---\n\n## ✅ 最小验收路径\n\n```text\n1. 启动 backend:make backend(或 cd backend && python3 -m src.api.app)\n2. 启动 frontend:make frontend(或 cd frontend && npm start)\n3. 打开浏览器 http://localhost:3000\n4. 点击 \"开始游戏\" 创建一局游戏\n5. 页面显示玩家列表(右侧面板)\n6. 点击推进阶段按钮\n7. phase/day/round 状态更新\n8. timeline 出现新事件\n9. 切换到 debug 视角查看角色和统计信息\n```\n\n---\n\n## 📖 更多文档\n\n- [演示指南](docs/DEMO_GUIDE.md) - 完整的 5 分钟/10 分钟演示脚本\n- [架构详解](docs/ARCHITECTURE.md) - 系统架构和设计原理\n- [验收检查清单](docs/ACCEPTANCE_CHECKLIST.md) - 项目验收标准\n- [真实 LLM 配置](docs/REAL_LLM_SETUP.md) - 真实 LLM 接入指南\n- [API 文档](docs/API_DOC.md) - REST API 文档\n- [开发指南](docs/DEVELOPMENT.md) - 开发者指南\n\n---\n\n## 🎯 后续扩展方向\n\n### P0 - 近期优化\n\n- Prompt 调优实验\n- 多局真实 LLM 对比分析\n- 前端 UI 体验优化\n\n### P1 - 中期扩展\n\n- Prompt A/B 测试框架\n- 多模型对比(OpenAI vs DeepSeek vs ...)\n- Leaderboard 系统\n\n### P2 - 长期愿景\n\n- 自进化机制(从复盘学习优化策略)\n- 多人对战模式(人类 vs AI)\n- 更多角色扩展(白痴等)\n- WebSocket 实时游戏平台\n\n---\n\n## 📝 许可证\n\n本项目仅供学习和研究使用。\n\n---\n\n## 🤝 致谢\n\n感谢所有为项目做出贡献的开发者!\n\n---\n\n## 比赛交付级持久化闭环\n\n### 数据库\n\n项目默认使用 SQLite 保存对局全过程与 Agent Memory:\n\n- 默认地址:`sqlite:///data/werewolf_arena.db`\n- 默认文件:`data/werewolf_arena.db`\n- 可通过环境变量覆盖:`DATABASE_URL`\n\n初始化数据库:\n\n```bash\ncd backend\npython3 -m src.db.init_db\n```\n\n核心表:\n\n- `games`:每局游戏 metadata、状态、胜方、摘要。\n- `game_events`:发言、投票、夜间行动、Agent 决策、repair/fallback、memory 事件。\n- `agent_memories`:跨局长期记忆,替代仅依赖 JSON 文件的存储方式。\n- `agent_memory_usage`:记录 memory 何时被检索、注入 prompt 或局后更新。\n\n### 运行一局并保存历史\n\n```bash\ncd backend\npython3 scripts/run_single_game.py --use-fake-llm\n```\n\n运行时 `GameLogger` 会同步写入 JSON 日志和 SQLite。数据库写入失败不会中断游戏,但会写入后端 warning 日志。\n\n### 查看历史对局与 Memory\n\n启动后端:\n\n```bash\ncd backend\nuvicorn src.api.app:app --reload\n```\n\n启动前端:\n\n```bash\ncd frontend\nnpm start\n```\n\n前端顶部导航包含:\n\n- `Live Game`:实时观战。\n- `History`:历史对局列表、单局时间线、Agent 决策记录。\n- `Agent Memory`:Agent 长期记忆与 usage timeline。\n- `Reports / Evaluation`:批量评测入口说明。\n\n### Agent 库与开局选择\n\n- 后端 Agent 库定义在 `backend/src/agents/registry/default_agents.json`,运行时由 `backend/src/agents/registry/agent_registry.py` 提供 `GET /api/agents` 与 `GET /api/agents/{agent_id}`。\n- 前端 `Live Game` 开局入口会展示 Agent 名称、persona、描述和策略标签;开始游戏时向 `POST /api/games` 传入 `agent_ids`。\n- `seat` / `player_id` 只代表本局座位与玩家;`agent_id` 代表跨局智能体;`role` 仍由本局规则随机分配;长期 profile 与 memory 始终按 `agent_id` 读取和写入。\n- History detail 的 `game_agent_assignments` 记录每局 `seat/player_id/agent_id/display_name` 绑定;public 视角不返回真实角色,debug 视角才返回 `assigned_role`。\n\n### History API\n\n- `GET /api/agents`\n- `GET /api/agents/{agent_id}`\n- `GET /api/history/games`\n- `GET /api/history/games/{game_id}`\n- `GET /api/history/games/{game_id}/events`\n- `GET /api/history/games/{game_id}/decisions`\n\n`events` 默认 `visibility=public`,不会返回 private/debug 事件;`visibility=debug` 用于答辩展示完整链路。\n\n### 投票与猎人规则\n\n- 投票阶段支持弃票:`target=null`、`target=\"abstain\"`、`pass` 或 `弃票` 都会被规范化为弃票。\n- 弃票写入 `vote` 事件,`payload.is_abstain=true`,并计入 `vote_result.abstain_count`;弃票不计入任何玩家得票。\n- 投票结算支持全部弃票、唯一最高票、平票、弃票与有效票混合;死亡玩家不能投票,不能投给死亡或不存在玩家。\n- 猎人仅在“被投票放逐”后进入 `hunter_shoot` 阶段,可选择射击一名存活玩家或不开枪。\n- 当前项目规则不支持猎人夜间死亡或女巫毒杀后开枪;该限制保留为显式规则边界。\n- 前端 `Live Game` 的投票面板提供“全员弃票”,History 时间线可读展示弃票、投票结算与猎人开枪/不开枪事件。\n\n### Memory API\n\n- `GET /api/memory/agents`\n- `GET /api/memory/agents/{agent_id}`\n- `GET /api/memory/agents/{agent_id}/usage`\n\n旧 JSON memory 可导入数据库:\n\n```bash\ncd backend\npython3 scripts/import_memory_json_to_db.py --memory-dir data/memory\n```\n\n### 批量评测\n\n```bash\ncd backend\npython3 scripts/run_batch_games.py --agent-pool preset_20 --num-games 50 --max-workers 2 --max-days 5 --seed 20260530\n```\n\n批量运行会生成:\n\n- `reports/batch_{timestamp}_metrics.json`\n- `reports/batch_{timestamp}_report.md`\n\n报告包含胜率、平均天数、fallback/repair/action_source 分布、memory usage、数据库记录数、token 统计,以及 `vote_count`、`abstain_count`、`vote_result_count`、`vote_tie_count`、`hunter_shoot_count`。\n\n### 比赛演示流程\n\n1. `cd backend && python3 -m src.db.init_db`\n2. `cd backend && uvicorn src.api.app:app --reload`\n3. `cd frontend && npm start`\n4. 在 `Live Game` 创建并推进对局。\n5. 切换到 `History` 查看时间线与决策链路。\n6. 切换到 `Agent Memory` 查看长期记忆和 prompt 注入记录。\n7. 运行 `python3 scripts/run_batch_games.py --agent-pool preset_20 --num-games 50 --max-workers 2 --max-days 5 --seed 20260530` 生成 20 预设 Agent 训练评测报告。\n\n\n## 自由讨论阶段\n- 白天发言后、投票前新增 `DISCUSSION` 阶段。\n- 讨论事件会写入日志、数据库与 History 时间线。\n- 真实 LLM 与 FakeLLM 都支持该阶段。\n"}}}} |