dsh-dream-tavern(梦境酒馆)
运行在 DeepSeek Harness 上的文字游戏/角色扮演宿主,两个并列目的:
- 娱乐:导入手里的 SillyTavern 人物卡,自由游玩或沿剧本推进;
- 研究:作为上下文编排的可测量实验台——「上下文对模型表现的影响」的受控实验载体。
与上游
flizzywine/dsh-tavern的关系:独立实现,不 fork(上游为 AGPL-3.0)。 只借鉴接口、行为与文件格式,不复制其源码片段。许可:MIT(自持)。
与其它酒馆的根本差别:装配单
每一次模型请求都由纯函数 assemble() 产出一份装配单(assembly manifest):每个片段来自哪个槽位、
命中哪个关键词、占多少字节、sha256 是多少、最终 hash 是多少。装配单落盘后从磁盘回读,
用于校验「实际发出的 body 逐字节等于装配单重建的 body」(判据 A1)。
因此上下文编排的每次变化都是一笔可复核的字节账,不是黑箱。这条判据同时是本项目的 研究价值所在:任何「换了编排、模型表现变好」的说法,都必须先过这一关。
工具面
| 工具 | 意图 |
|---|---|
tavern_cards |
卡库清点(只读) |
tavern_card |
读一张卡的字段与原文键集 |
tavern_worldbooks |
世界书清点/按 ST 格式导入(只读源) |
tavern_assemble |
装配一轮并落盘装配单(不调模型),返回字节账与 A1 自检 |
tavern_play |
玩一轮:装配 → 真调模型 → 落盘 → 回报 token/缓存读数 |
tavern_rollback |
原子回退(正文与状态同生共死;无完整快照则响亮拒绝) |
面板(依赖 dsh-panel 宿主,缺席时工具面不受影响):/panel → 「梦境酒馆」。
面板动作:开局 / 玩一轮 / 行动候选 / 后台结算 / 装配单读数 / 回退。
三个 Agent,职责互斥
| Agent | 产出 | 写权限 |
|---|---|---|
正文 Agent(runTurn) |
叙事正文 | 对状态只读 |
候选 Agent(runCandidates) |
N 个行动选项 | 对状态只读,不写历史 |
结算 Agent(runSettlement) |
结构化状态 | 唯一状态写者 |
三者共用同一装配路径(判据 A7 用行为级测试:工具路径与面板路径产出同一 manifest hash)。
配置(cordis.yml)
代码里的默认值一律为空——部署相关的取值全部走配置(宿主公约:插件不得硬编码可调项; 默认值若写死某台机器的目录,公开仓库还会连带泄漏路径)。
| 字段 | 默认 | 说明 |
|---|---|---|
dataDir |
'' |
我们写的一切(会话/装配单/快照)。空 ⇒ 解析为 <DSH_HOME>/dream-tavern-data |
cardDirs |
[] |
只读卡库(ST characters/ 目录)。空 ⇒ 卡库为 0,工具如实报 0 并告警 |
worldbookDirs |
[] |
只读世界书库(ST worlds/ 目录) |
provider / model |
'' |
模型路由。空 ⇒ tavern_play 响亮失败(不猜默认模型) |
maxTokens |
1600 |
单轮输出上限 |
temperature |
0.9 |
采样温度 |
budgetChars |
24000 |
单次请求字符预算(超出按优先级确定性裁剪并记账) |
部署示例(放在 profile patch 里,不要写进源码):
- id: agent-dream-tavern
name: dsh-dream-tavern
config:
dataDir: <你的数据目录>
cardDirs: [<你的 ST characters/ 目录>]
worldbookDirs: [<你的 ST worlds/ 目录>]
provider: <provider>
model: <model>
⚠ 迁移源只读:插件不写回酒馆目录;ST 的
secrets.json一律不读。
验收
node scripts/acceptance.mjs # 一条命令:判据表 + 真实数据读数 + A7 旁路面
node scripts/preset-cost.mjs --preset "E:/alice/tavern/色欲之罪.json" # 迭代预设第一步:块级成本表(不调模型)
npm test # 仅判据测试
全流程透明:一轮到底发了什么、回了什么
每一轮的四类证据都在盘上,且 turns/<轮>.json 把实验条件(卡 / 预设 / 路由 / maxTokens /
temperature / budgetChars)与读数(finishKind / truncated / usage / A1 / 字数)钉在一起
——否则换过参数之后两轮读数不是一回事,失败也事后查不到当时的结束原因。
# 逐轮摊开:条件 → 请求(逐 entry)→ 响应(正文/思维链)→ 读数 → 落点
node scripts/turn-report.mjs --session <会话id> --dataDir <dataDir> [--turn N] [--entries]
# 导出可度量稿:思维链落 drafts/reasoning/ 子目录(同层会被度量器当语料),
# --prose-only 只取 <dream_body> 内的散文(协议包装约占输出 40%,混进去读数不成立),原始另存 drafts/raw/
node scripts/export-draft.mjs --session <会话id> --dataDir <dataDir> --reasoning --prose-only
tavern_play 的返回值同样带 finishKind / reasoningChars / reasoningPath / turnRecordPath
——失败时也带(空正文判失败但思维链仍落盘:它是唯一诊断证据)。
退出码:0 全量通过 | 1 判据失败 | 2 判据通过但真数据不可达(PASS 降级)。
判据:A1 装配单逐字节可重建 | A2 同输入同 hash | A3 原子回退(字节级 + 行为级重放) | A4 缓存命中读数 | A5 ST 卡往返不丢字段且不改卡 | A6 世界书匹配为纯函数 | A7 各路径共用装配器(无旁路)。
A4 实验:只追加 ⇒ 缓存命中率不塌
bash scripts/cache-experiment.sh <会话名> 6 <卡 id> # 需 web 在线且面板宿主已挂载
判据跑之前写死:① 每轮 A1 为真 ② requestChars 单调不减 ③ cacheRead 命中比不塌。
实测(2026-09-22 · 修复「世界书重复注入」后重跑,读数为干净值):
| 轮 | 请求字数 | 新增 tok | 缓存读 tok | 命中比 | A1 |
|---|---|---|---|---|---|
| 2 | 3986 | 219 | 896 | 80.4% | ✓ |
| 4 | 4477 | 425 | 1024 | 70.7% | ✓ |
| 6 | 4935 | 367 | 1408 | 79.3% | ✓ |
| 8 | 5474 | 494 | 1664 | 77.1% | ✓ |
| 10 | 6018 | 497 | 2048 | 80.5% | ✓ |
⇒ 三条判据全过:命中比在 70–81% 波动、无下降趋势(requestChars 与 cacheRead 均单调不减)。
⚠ 命中比按服务端 usage 原样计算(cacheRead / (cacheRead + input)),未核对该 provider 的字段口径。
上下文编排报告(研究线第一件产物)
node scripts/context-report.mjs --data <dataDir> <session>
把一个会话的装配单聚合成「钱花在哪」的账:逐轮总账与增量、来源构成(按片段自身字数)、 槽位构成、世界书触发、增长与裁剪。
实测样例(会话 a4fixed,5 轮,修复重复注入后的干净读数):
| 来源 | 字数 | 占比 |
|---|---|---|
| card | 14220 | 57.2% |
| history | 8885 | 35.8% |
| preset | 1650 | 6.6% |
| input | 75 | 0.3% |
| state | 10 | 0.0% |
⇒ 卡定义一项就占 57.2%——它既是最大的可控自变量,也是预算裁剪时最先该被讨论的对象。
(记账按片段自身 text.length 累加;均分会把 system 块的字节错配到别的来源,
实测曾把 2 字的 state 报成 2655 字。)
已知限制(诚实边界)
- 推理模型的思维链也算进
maxTokens:实测上限 1600 时usage报满 1600,而正文只剩 195 字 就断在句子中间(预算被思维链吃掉)。tavern_play与面板play动作会返回truncated标记, 不静默(判据:outputTokens >= maxTokens)。建议maxTokens ≥ 3000。 - 卡内格式约定会盖过默认预设的格式纪律:卡的世界书若自带输出模板(如
<response><now_plot>), 模型会照它写——这是酒馆语义下正确的行为,但意味着模板类卡不能靠预设纠正格式。 - 面板宿主需调大动作超时:
dsh-panel的actionTimeoutMs默认 10 000 ms,而酒馆一轮 (装配 + 模型调用)常规 10–20 s,卡越大越慢 ⇒ 必须把它提到≥60000,否则长卡的动作会被 宿主判超时(实测:一张 24 条卡内世界书的卡在默认值下返回动作超时(>10000ms))。 ⚠ 且超时 ≠ 撤销:服务端回合仍会跑完并落盘,客户端只看到超时。 这是跨插件契约问题(宿主决定超时、贡献方决定耗时),不是酒馆自身的缺陷。 - ST 生态广度不追:MVU / 正则美化 / 小手机 / 生图 / 多平台(Android)均未实现。
- 世界书语义部分建模:递归控制、冷却/延迟、按角色字段匹配、宏出口、分组评分共 20 种字段
已知未建模——原样留档在
raw里,tavern_worldbooks会逐项报出,不假装支持。 - 结算质量依赖模型:结算 Agent 要求模型输出 JSON,解析失败即响亮失败(不静默吞)。
data.alternate_greetings:目前只取第一个作为回落开场,尚未做多开场切换。- 卡内联变量协议不被解析:部分卡的正文末尾会自带变量更新块(实测见
<VariableEdit>{...}</VariableEdit>)。 这是卡自带的 MVU 风格协议,本插件不消费它——状态走独立的结算 Agent(面板「后台结算」/runSettlement),职责互斥(正文不写状态)。因此这类卡的变量不会自动同步;要支持需显式建模该协议。 - 面板依赖
dsh-panel;未挂载该宿主时没有图形界面(工具面仍可用)。
还没有评论,来写第一条。