OpenViking 狼人杀多 Agent 演示完整指南:一条命令拉起裁判与 6 名 AI 玩家
【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
想在本地用一条命令拉起「1 个裁判 + 6 个 AI 玩家」,看它们在共享群聊里按固定顺序轮流行动、各自记着私密小本子的狼人杀对局吗?OpenViking 的狼人杀 Demo(bot/demo/werewolf/)把多 Agent 群聊协作做成了一个可观察的最小闭环:god 裁判和 6 个玩家都是独立的 channel bot,各自拥有工作目录与GAME.md私有状态文件,整局游戏靠消息路由循环驱动、靠文件系统传递私密状态。
读完这篇指南,你将能够:
- 用一条命令在本地跑通完整对局,并用浏览器按钮驱动开局、连跑、停止;
- 说清楚每条消息如何在 god 与玩家之间串行流转、最终如何落到结算;
- 切换到真人混局模式,顶替一个玩家席位参与游戏;
- 对局结束后核对会话归档、回放状态与排行榜,并让 Agent 通过
/remember逐局积累经验; - 对照自救手册排掉常见故障,并把「裁判 Agent + 文件状态总线 + 路由循环」范式迁移到别的多角色协作场景。
先建立心智模型:三层服务与六个文件
这一节解决「这个 Demo 到底由什么组成」的问题,帮你把后面的启动与排障都挂在同一张地图上。
整套服务分三层,从下到上依次是:
| 层 | 默认地址 | 职责 |
|---|---|---|
| OpenViking 服务 | 127.0.0.1:1933 | 记忆存取与 Agent 能力底座 |
| Vikingbot 网关 | 内嵌于 OpenViking 进程,--with-bot --bot-port 18790暴露 HTTP API | 把消息投递给不同的bot_api类型 channel Agent |
| 狼人杀 UI 服务 | 127.0.0.1:1995 | 由 werewolf_server.py 提供,消费网关接口并驱动整个对局路由 |
Vikingbot 的所有对外接口统一挂在/bot/v1前缀下(见 openapi.py),Demo 真正依赖的只有两个:{vikingbot_url}/bot/v1/health(健康检查)和{vikingbot_url}/bot/v1/chat/channel(向指定 channel 发消息并等待回复,后者由 werewolf_server.py 中的send_to_channel封装调用)。
目录内六个核心文件的分工如下:
| 文件 | 职责 |
|---|---|
| start_werewolf_demo.py | 一键启动:补全配置、准备工作目录、拉起并守护两个服务 |
| werewolf_server.py | 对局服务端:消息路由循环 + FastAPI Web UI 后端 |
| werewolfUI.html | 前端单页:游戏控制、记忆浏览、排行榜、回放 |
| SOUL-god.md | 裁判(god)角色规则,约 300 行完整对局规则 |
| SOUL-player.md | 玩家角色规则与分身份行动指南 |
Cubic_11_1.010_R.ttf/GeistPixel-Square.woff2 | 页面使用的中文 / 像素风格字体 |
一条消息的主链路可以压缩成一句话:你在页面上点按钮 → UI 服务经/bot/v1/chat/channel把消息发给 god → god 的回复按座位号广播给玩家 → 玩家回复被汇总结论再送回 god → 游戏进度写进GAME.md/GAME_RECORD.md,整个对局就靠这个循环推进。
一键启动命令:本地跑起来之前先查三件事
这一节解决「怎么最省事地把它跑起来」,同时把两个最容易翻车的配置坑提前说清。
启动前你只需要三样东西:
python(建议 3.10+,依赖httpx、fastapi、typer、uvicorn、loguru);openviking-server命令可用;- 一份JSON 格式的配置文件(README 示例为
~/.openviking/ov.conf)。
⚠️ 配置文件必须是 JSON:一键脚本里的
load_json_config直接json.load,YAML 会当场报错;werewolf_server.py 的load_config同样只认 JSON。⚠️ README 示例命令总是显式传
--config ~/.openviking/ov.conf,但两个脚本源码里的默认路径都是~/.openviking/ov-multi.conf。要么显式指定路径,要么把配置放到默认位置,别两边都猜。
在bot/demo/werewolf/目录下执行:
python start_werewolf_demo.py --config ~/.openviking/ov.conf不传任何参数时,脚本使用的默认值如下(其中bot与storage是配置文件里最少要有的两级结构;缺storage.workspace时脚本会自动补默认值并回写):
| 参数 | 默认值 | 含义 |
|---|---|---|
| UI 端口 | 1995 | 狼人杀 Web UI 端口 |
| OpenViking host | 127.0.0.1 | OpenViking 服务监听地址 |
| OpenViking port | 1933 | OpenViking 服务端口 |
| Vikingbot URL | http://localhost:18790 | Vikingbot 网关地址 |
| game mode | all_agents | 全 AI 模式 |
| game id | default | 对局 ID |
| config | ~/.openviking/ov-multi.conf | 配置路径(示例命令会显式覆盖) |
| startup timeout | 30.0秒 | 网关健康检查超时时间 |
常用的可选参数组合:
python start_werewolf_demo.py \ --config ~/.openviking/ov.conf \ --ui-port 1995 \ --game-mode all_agents \ --smart-buttons--game-mode:all_agents(全 AI)或human_player(保留一个真人席位human);--smart-buttons:开启前端「智能按钮显示」,按钮按游戏状态动态显隐;--server-host、--server-port、--vikingbot-url、--game-id、--startup-timeout:微调运行环境。
一键脚本在背后做了什么
这一节解决「那条命令背后到底替你干了什么」,共五个动作,每步都先说做了什么、再说为什么。
- 校验资源(
validate_assets):确认SOUL-god.md、SOUL-player.md、werewolf_server.py都在。先检查再动手,避免文件缺失时出现「半启动」状态。 - 注入并回写 channel 配置(
ensure_werewolf_channels):清掉旧的 demo channel,再注入god、player_1~player_6共 7 个bot_apichannel,随后json.dump回写配置文件(缩进 2、ensure_ascii=False)。先清后注是为了防止重复启动时配置里攒出重复 channel。 - 设置沙箱边界(
ensure_sandbox):把bot.sandbox.mode设为per-channel,并将 god 的工作目录限制到{workspace}/bot。裁判只需要读写自己的对局记录,收紧权限后它碰不到其他玩家的文件。 - 补齐存储路径(
resolve_workspace):缺storage.workspace时自动填默认值~/.openviking/data并回写,保证后续所有 Agent 工作目录、对局档案落在同一棵树下。 - 准备 Agent 工作目录(
prepare_workspace):在{workspace}/bot/workspace/下创建bot_api__god、bot_api__player_1…bot_api__player_6七个目录,god 拷入改名为SOUL.md的SOUL-god.md,每个玩家拷入SOUL-player.md——每个 Agent 启动时从自己的工作目录读取「我是谁」。
之后脚本启动openviking-server --with-bot,以 1 秒间隔轮询{vikingbot_url}/bot/v1/health(wait_for_health,默认 30 秒超时),健康检查通过后才启动 UI 服务——这避免了 UI 服务调用网关时网关还没就绪。最后它守护两个子进程:任一退出则终止另一个;Ctrl+C 时先发SIGTERM,5 秒未退再SIGKILL,不留孤儿进程。
启动后打开配置文件可以直接核对注入结果,关键片段长这样(player_2/player_3与player_1同构,player_4/5/6只有ov_tools_enable: false):
{ "bot": { "channels": [ { "type": "bot_api", "id": "god", "enabled": true, "ov_tools_enable": false }, { "type": "bot_api", "id": "player_1", "enabled": true, "profile_user_list": ["player_2", "player_3", "player_4", "player_5", "player_6"], "memory_user": "player_1" } ], "sandbox": { "mode": "per-channel", "restrictWorkspaces": { "bot_api__god": "{workspace}/bot" } } } }设计意图写在配置里:player_1/2/3互相开放画像互看(profile_user_list)并各持独立记忆(memory_user),用来演示 Agent 基于「对其他玩家的画像记忆」做推理;god与player_4/5/6关闭ov_tools_enable,不接触不必要的工具。
手动挡调试:把两个服务拆开跑
这一节解决「我想单独调其中一个服务」的问题。先记住一个前提:手动方式要求配置里已经包含全部 7 个 demo channel——这正是建议你先跑一次一键脚本的原因,channel 注入与回写都是它代劳的。
先起 OpenViking(内嵌 Vikingbot 网关)
openviking-server \ --config ~/.openviking/ov.conf \ --host 127.0.0.1 \ --port 1933 \ --with-bot \ --bot-port 18790--with-bot让 OpenViking 进程同时挂载 Vikingbot 网关,--bot-port决定网关端口。用http://localhost:18790/bot/v1/health确认网关就绪。
再起狼人杀 UI 服务
python werewolf_server.py \ --config ~/.openviking/ov.conf \ --port 1995 \ --game-mode all_agents基于 Typer 定义参数,还支持短参-p/--port、-c/--config、-m/--game-mode、-s/--smart-buttons,以及--vikingbot-url、--game-id。
⚠️ 手动启动时它还会读运行时状态文件
RUNTIME_STATE.json(位于 storage 的bot/workspace/werewolf/下):如果之前以human_player模式跑过,即使命令行写的是all_agents,服务也会自动恢复为human_player。此外服务启动时会加载最近一次会话并复用其 session id,保证重启后历史可衔接。
与一键启动的差异一句话总结:channel 注入、沙箱设置、工作目录准备、健康检查等待、进程守护这五件事,手动模式下全在你自己身上。
页面操作手册:每个按钮背后都是哪个接口
这一节解决「页面上看到的东西分别对应后端什么」,让你排障和二次开发时有据可查。
启动后访问:
| 地址 | 说明 |
|---|---|
http://localhost:1995/ | 主页面,由 werewolfUI.html 渲染 |
http://localhost:1995/test | 测试页,服务端加载同目录可选的test_server.html |
http://localhost:1995/debug | 调试页,加载可选的debug.html |
/test与/debug在对应 HTML 不存在时返回空页面——当前仓库只随附了主页面文件,实战以/为准。
顶部四个导航页各自的数据来源:
| 导航 | 对应接口 | 作用 |
|---|---|---|
| 游戏 | — | 主对局页 |
| 记忆 | GET /api/openviking/tree、GET /api/openviking/file | 读取 storage 下viking/default/agent与viking/default/user两棵目录树并查看文件内容 |
| 排行榜 | GET /api/leaderboard | 累计战绩与胜率曲线 |
| 回放 | /api/conversations、/api/conversation/{session_id}、/api/replay-state/{session_id}、/api/bot-sessions | 按历史会话回放对局 |
游戏页顶部控制按钮与后端 API 的完整映射:
| 页面元素 | 对应接口 | 作用 |
|---|---|---|
| 开始游戏 | POST /api/start | 发送「开始」指令,进入当前局流程 |
| 继续 | POST /api/continue | 暂停态下催促 god 继续本局 |
| 自动N局(旁边输入框填局数) | POST /api/auto-run | 开启/关闭连续跑局;前端以enabled=true, mode=fixed, target_games=N提交,后端另支持mode=infinite无限连跑 |
| 停止游戏 | POST /api/stop | 停止当前路由流程并关闭自动连跑 |
| 初始化游戏 / 重新开始 | POST /api/restart | 强制新建 session 并重新初始化新局 |
前端刷新的三个状态接口:
GET /api/status:返回running、game_mode、waiting_for_human、auto_run_*、completed_games等字段,是智能按钮与连跑展示的数据源;GET /api/messages:完整聊天历史;GET /api/players:逐个读取各玩家GAME.md的「身份」字段生成座位信息。
顶部「模式」下拉框会写入start/restart请求的game_mode字段:all_agents即全 AI;human_player时后端会从玩家列表末尾去掉最后一个 bot(player_6),追加专用 channelhuman(build_channels_for_game_mode),并自动创建{storage}/bot/workspace/human/GAME.md。
⚠️模式只在「开始」或「重启」动作里生效:
apply_game_mode_to_state只在这两个入口被调用,光切下拉框不会改变正在跑的局。⚠️
human_player模式的对局不计入排行榜:save_game_to_leaderboard_from_record对该模式直接返回skipped,理由是真人操作不具备可复现性。
切换为human_player并执行开始/重启后,页面会出现「真实玩家」区域,按钮是否可点取决于后端状态waiting_for_human(god 是否正等待@human的回合):
| 页面元素 | 对应接口 | 作用 |
|---|---|---|
| 只发给 god | POST /api/human/send,target=god | 真人回复作为私密回执单独送回 god,不广播给其他玩家 |
| 发给全员 | POST /api/human/send,target=all | 内容公开广播给所有其他玩家(含 bot),同时写入公开消息历史 |
| 查看 GAME.md | GET /api/human/game-md(POST /api/human/game-md可改写) | 读取/修改真人玩家的私有GAME.md |
这套「私密操作走GAME.md、公开内容才发群」的约定贯穿整个 SOUL 规则:human是正常玩家位,必须像其他玩家一样纳入固定顺序与昼夜流程,但查验结果、用药、刀人目标这类敏感信息只能写入human/GAME.md。
以--smart-buttons启动时,前端轮询GET /api/status并按状态调整按钮:running=true时隐藏「开始/继续」;game_ended=true时显示「重新开始」;waiting_for_human=true时启用真人输入区。该功能默认关闭,不开不影响任何对局逻辑,只是按钮恒定显示。
黑盒拆解:一条消息如何被路由成一整局
这一节解决「按钮点下去之后,后端到底怎么把一局游戏跑完」,先看状态,再看循环,最后看容错。
GameState是全局共享状态 dataclass,核心字段分组如下:
running/router_task:路由循环是否在跑及其 asyncio 任务句柄;channels:当前局参与名单(随模式变化);messages/human_messages:公开消息流与真人私聊流;session_id:每局唯一的会话标识;game_ended、completed_games:结束标记与累计完成局数(供 auto-run 判定);pending_replies、waiting_for_human、human_player_message:真人回合的等待与投递;auto_run_*系列:自动连跑配置与计数。
对局推进遵循「单飞」原则:POST /api/start、/api/continue、/api/restart都会先stop_router_task取消旧循环,再以不同的初始消息启动新循环,避免并发导致流程混乱。三条入口的核心差异只有初始消息:
- 开始:
"开始"; - 继续:
"继续本局游戏"(用于 god 上次回复停留在「等待指令/初始化完成」等状态); - 重新开始:先生成新 session、归档旧会话与回放状态,再由
build_restart_message拼一段带完整玩家名单与各GAME.md路径的建局消息发给 god,要求它初始化新局后等待「开始」指令。
路由主循环message_router_loop是整场对局的引擎,单轮流程:
- 发消息给当前说话者(通常是 god):调用
send_to_channel且need_reply=True,同步等待 Agent 回复; - 记录回复:追加进
messages,并落盘为会话文件; - 解析 @ 提及:
parse_mentions用正则@\s*(\w+)提取 god 回复中所有被点名的玩家 id;「一次只能 @ 一个玩家」由 SOUL 规则约束; - 按座位号广播:
broadcast_to_players把 god 的发言并发发给所有玩家——被@的玩家need_reply=True必须回复,其余玩家need_reply=False只接收,且发送者前缀带座位号(如3号:); - 广播玩家回复:每个有回复的玩家,其发言再以
need_reply=False广播给除自己外的所有玩家,让全员听到本轮发言; - 汇总结论回传 god:
build_message_for_god把各玩家回复拼成「座位号:内容」格式送回 god,进入下一轮,由 god 再决定 @ 谁、是否进入下一阶段; - 保护上限:循环最多 1000 轮(
max_loops),到达即强制停止。
容错机制有三处,专治「LLM 不按剧本走」:
- 无有效 @ 的回推:游戏未结束但 god 没 @ 任何玩家时,系统以
admin_fallback_no_mention身份回推提示「你上个回复没有@任何玩家……继续@一个玩家进行」,最多重试 2 次(god_no_mention_retry_count)后终止循环; - 等待态识别:god 回复命中「初始化完成/等待开始/等待指令/等待继续」等标记(
is_waiting_like_reply)时,判定建局完毕,主动 break 等待下一次开始指令; - 非法提及直接收车:god @ 到不在名单里的 channel 时,循环直接结束,不再空转。
公开域与私密域:保密信息为什么不会泄漏
这一节解决「多 Agent 同局时私密信息怎么隔离」,并把安全设计与测试佐证一并交代。
Agent 之间靠两条通道协作,边界划得很死:
| 通道 | 载体 | 允许出现的内容 |
|---|---|---|
| 公开域 | 群聊消息(messages) | 公开的日夜发言、表态、投票 |
| 私密域 | 各自工作目录里的状态文件 | 查验结果、用药、刀人目标等一切需保密信息 |
各角色的状态文件与写入约束:
| 角色 | 文件 | 内容 |
|---|---|---|
| god | {storage}/bot/workspace/bot_api__god/GAME_RECORD.md | 全局进度表:游戏状态、轮次、玩家身份表、胜负;「游戏状态/游戏结果/游戏时间/玩家状态」均有约定格式 |
| player_N | bot_api__player_N/GAME.md | 身份、夜间技能目标、查验/用药结果等私有信息 |
| human | {storage}/bot/workspace/human/GAME.md | 真人席位状态,模式启用时自动创建,页面可直接编辑 |
硬约束写在两份 SOUL 文件里:SOUL-god.md 要求黑夜与白天所有环节按开局固定的玩家顺序逐个点名、串行推进;第一晚的死亡结果在警长竞选结束前不写入任何玩家文件的「存活状态」,防止提前泄密;并给出 6/9/12 人局身份配置与胜负判定(狼人胜利=所有神职或所有平民出局)。SOUL-player.md 则约束玩家只能基于「群内公开信息 + 裁判明确告知 + 自己GAME.md」行动,严禁上帝视角。
服务端还暴露/data/{path}文件浏览与/api/game-file/{channel_id}/{filename}接口,可在页面直接查看 god/玩家的GAME.md、GAME_RECORD.md原始文件;其中/data/werewolf/GAME_RECORD.md会被特殊映射到 god 的记录文件,方便统一路径查看。所有文件访问都做了路径越界校验,读不到 storage 根目录之外的内容。
因为 UI 服务对公网(0.0.0.0:1995)开放,错误信息与路径也做了收敛,仓库自带的 test_werewolf_server_security.py 佐证了三点:
POST /api/start内部抛ValueError时,接口只返回"Failed to start game",文件系统细节不进响应;- 读会话文件抛内部异常时,
/api/conversation/{session_id}返回通用的"Failed to read conversation"(HTTP 500),堆栈被隐藏; /api/openviking/file收到../../../路径穿越请求时被拒绝(404),越界文件内容不会返回。
对局结算后的三件套:归档、排行榜、记忆沉淀
这一节解决「一局跑完之后,系统留下了什么、为什么值得留」。
结束判定发生在每轮 god 回复之后:is_game_ended_from_record解析GAME_RECORD.md,确认结束必须同时满足——记录显示「游戏结束」、god 已产出最终结论、且 god 不再 @ 任何玩家追问后续。三条都成立后依次执行:
- 归档:god 的最终结算先广播给所有玩家,然后保存会话文件
CONVERSATION_{session_id}.md,再把回放状态归档到REPLAY_STATE_{session_id}.json(快照GAME_RECORD.md文本、解析结果与玩家信息,使回放不依赖仍在变动的 live 文件)。意义在于对局从此可审计、可回放; - 排行榜:按 god 工作区的
GAME_RECORD.md解析胜方与玩家状态,计算积分(胜利 2 分 + 存活 1 分),累计进bot/workspace/werewolf/LEADERBOARD.json;重复 session 自动去重跳过,避免同一局重复计分; - 记忆沉淀:向 god 与每个玩家发送
/remember指令,让各 Agent 把本局经验写进自己的 OpenViking memory。这是与 OpenViking 记忆能力衔接的关键一步,也是跨局水平提升的基础。
三件套全部完成后,才依据 auto-run 配置决定下一步:满足连跑条件则 1.5 秒后自动 restart + start 调度下一局;否则关闭连跑。若开启 auto-run 时当前没有对局在跑,会以 0.1 秒延迟调度第一局;每局真正跑完后completed_games自增,达到目标局数即自动停。
狼人杀 Demo 故障点自救手册
这一节解决「跑起来之后坏了怎么办」,按「现象 → 排查顺序 → 常见根因」组织。 🛠
现象 1:点击「开始/继续」没反应
- 访问
GET /api/status,确认 UI 后端在线; - 浏览器打开
{vikingbot_url}/bot/v1/health,确认 Vikingbot 网关就绪; - 看返回里
running是否为true。
常见根因:OpenViking 没带--with-bot启动,网关不存在,所有对局消息超时;或上一轮路由循环尚未结束,需要先「停止游戏」再操作。
现象 2:真人模式看不到输入区
- 确认顶部模式已选
human_player; - 确认是用该模式执行了开始或重启。
常见根因:模式只在 start/restart 动作里生效(apply_game_mode_to_state),纯切换下拉框不会改变正在跑的局。
现象 3:回放内容不完整
- 检查
CONVERSATION_{session_id}.md与REPLAY_STATE_{session_id}.json是否存在且完整; - 回忆该局是否中途强制停止过。
常见根因:回放依赖会话记录与归档状态文件,中途被停的局缺少权威GAME_RECORD.md快照;建议让一局正常走到结算后再看回放。
现象 4:一局跑太久或疑似卡死
- 随时点「停止游戏」中断路由(取消 router task 并关闭自动连跑);
- 观察 god 是否连续两次没 @ 到有效玩家。
常见根因:god 连续 2 次无效 @ 时循环会自动停止,不会无限循环;max_loops的 1000 轮是硬顶,超过即收车。
现象 5:UI 起来了,但对局消息全部超时
- 确认
openviking-server进程存活、端口1933在监听; - 确认 Vikingbot 端口
18790在监听; - 核对 UI 服务的
--vikingbot-url指向与网关实际地址一致。
常见根因:手动启动时--bot-port与 UI 端--vikingbot-url不匹配,消息发往了不存在的网关。
现象 6:想连续压测对局
- 在「自动N局」输入框填局数后点击,开启 fixed 模式连跑;
- 观察
GET /api/status中auto_run_remaining_games递减到 0。
根因提示:对局之间会自动完成 restart + 建局 + start 的完整衔接,且每局的/remember让 Agent 记忆逐局累积,跨局表现会随之变化——这是特性,不是随机波动。
超越 Demo:四个可迁移的工程范式
这一节解决「不玩狼人杀,这套东西对我还有什么用」。
- 双通道信息隔离:夜间行动只写
GAME.md,群里只回「操作完成」,天然规避了 LLM 上下文里「谁都能看到所有人记忆」的常见泄漏问题。可迁移到多角色客服质检、合规审查等要求敏感信息不跨角色流动的场景。 - 串行协作的双重约束:路由循环的「一次只 @ 一个 + 等回复再广播 + 汇总回传」与 SOUL 的固定顺序规则互为备份,任何一层失守另一层兜底。可迁移到多方谈判模拟、仲裁流程、剧本杀等按轮次推进的多角色协作。
- 文件即状态总线:
GAME_RECORD.md/GAME.md承载对局进度,CONVERSATION_*、REPLAY_STATE_*、LEADERBOARD.json构成可审计的对局档案,任何时刻打开文件就能知道局面。可迁移到需要审计与回放的裁判型多 Agent 流程。 - 跨局经验累积:每局结算后全员
/remember,经验沉淀进 OpenViking memory,排行榜与回放为策略分析提供数据基础,Agent 越打越强。可迁移到需要随使用次数自我改进的长期运行 Agent。
参考路径速查
- Demo 说明文档:bot/demo/werewolf/README.md
- 一键启动脚本:bot/demo/werewolf/start_werewolf_demo.py
- 对局服务与路由引擎:bot/demo/werewolf/werewolf_server.py
- 前端页面:bot/demo/werewolf/werewolfUI.html
- 裁判角色规则:bot/demo/werewolf/SOUL-god.md
- 玩家角色规则:bot/demo/werewolf/SOUL-player.md
- 服务安全测试:bot/tests/test_werewolf_server_security.py
- Vikingbot
/bot/v1路由挂载点:bot/vikingbot/channels/openapi.py
【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考