SillyTavern酒馆部署报告-20260926.md

酒馆(SillyTavern)部署报告 — DSH 机 192.168.31.76

部署日期:2026-09-26 部署目标机:DSH 机(本机,192.168.31.76,Deepin 25) 结论:已可用。局域网打开 http://192.168.31.76:8000 即可聊天,后端已预接 GPU 机的无审核模型,端到端实测通过。


0. 一句话结果

项目值
访问地址http://192.168.31.76:8000
版本SillyTavern 1.19.0(2026-09-14 发布,commit 7e8663c)
托管方式systemd 单元 sillytavern.service:开机自启 + 崩溃自动拉起(Restart=always)
后端模型GPU 机 http://192.168.31.31:8080/v1 · bucoid-heretic-27b(27B 无审核/heretic,IQ4_XS,64K 上下文)
已配置服务器监听 0.0.0.0、局域网白名单、后端连接、出站代理(GitHub 可达)
实测酒馆自己的后端接口 → GPU 机模型 → 返回中文角色扮演对白 ✅

1. 交付物一览(运维速查)

用途路径 / 地址
网页入口http://192.168.31.76:8000 (局域网);http://127.0.0.1:8000 (本机)
安装目录/home/zyw/SillyTavern(527 MB,含 node_modules 400 MB)
数据目录/home/zyw/SillyTavern/data/default-user/
├ 角色卡/home/zyw/SillyTavern/data/default-user/characters/(自带示例 default_Seraphina.png)
├ 聊天记录/home/zyw/SillyTavern/data/default-user/chats/
└ 全部设置/home/zyw/SillyTavern/data/default-user/settings.json
配置文件/home/zyw/SillyTavern/config.yaml
日志/home/zyw/SillyTavern/sillytavern.log(journalctl -u sillytavern 只有 systemd 自身消息)
systemd 单元/etc/systemd/system/sillytavern.service(本目录留了一份副本)
控制脚本bash ~/Downloads/sillytavern-ctl.sh {start|stop|restart|status|log|test|update|info}
运行 node/usr/bin/node(系统包 v20.15.1,满足 ST 要求 engines.node >= 20,没动 nvm)
出站代理socks5://127.0.0.1:10808(本机 v2ray,由 v2ray.service 托管)
设置备份/home/zyw/SillyTavern/data/default-user/settings.json.bak-preconfigure

2. 部署步骤(可完整复现)

2.1 前提核对

本机已有:Node v22.23.2(nvm)与 v20.15.1(系统包)、git 2.51、v2ray 代理(127.0.0.1:10809 HTTP / 10808 SOCKS5,google 探活 200)、8000 端口空闲、/home 剩余 446 GB。

注:GitHub 与 raw.githubusercontent.com 在本网直连不通(实测 000),克隆与 npm 必须走代理。

2.2 拉代码(走代理)

export https_proxy=http://127.0.0.1:10809 http_proxy=http://127.0.0.1:10809
cd /home/zyw
git clone --depth 1 --branch 1.19.0 https://github.com/SillyTavern/SillyTavern.git SillyTavern

2.3 装依赖(走代理,3 分钟)

cd /home/zyw/SillyTavern
export https_proxy=http://127.0.0.1:10809 http_proxy=http://127.0.0.1:10809
export npm_config_proxy=$http_proxy npm_config_https_proxy=$https_proxy
npm install --no-audit --no-fund        # 829 个包,exit 0

2.4 改 config.yaml(逐项及原因)

改动基于 default/config.yaml 复制而来:

配置项值原因
listentrue默认 false 只监听本机,必须打开才能局域网访问
listenAddress.ipv40.0.0.0监听所有网卡
browserLaunch.enabledfalse服务器无桌面,别去拉浏览器
port8000保持默认(已确认空闲)
whitelistModetrue保留白名单模式(安全兜底)
whitelist加 192.168.31.0/24否则除了 127.0.0.1 局域网全被拒
enableForwardedWhitelistfalse无反向代理,关掉可防 X-Real-IP 伪造绕过白名单
basicAuthModefalse保持免密(只用白名单);要密码见 §6
logging.minLogLevel10=DEBUG 太吵,1=INFO
requestProxy.enabledtrue见下(GitHub 必需)
requestProxy.urlsocks5://127.0.0.1:10808本机 v2ray SOCKS5
requestProxy.bypasslocalhost,127.0.0.1,::1,192.168.31.31,192.168.31.76关键:让局域网 LLM 端点直连

⚠️ requestProxy 为什么必须开:酒馆的扩展安装、角色卡在线下载、以及若干后端都走 GitHub / raw.githubusercontent.com,本网直连全不通(实测 raw.githubusercontent.com → 000)。开了代理后实测 GitHub 200。 ⚠️ bypass 为什么必须写具体 IP:ST 内部用的是 proxy-from-env,它的 no_proxy 不支持 CIDR(实测 192.168.31.0/24 不生效、192.168.31.31 生效)。以后往局域网加新设备(另一台 GPU 机、NAS 等),必须把它的 IP 补进 bypass,否则请求会被塞进代理而失败。

2.5 systemd 托管

[Unit]
Description=SillyTavern 酒馆 (AI 角色扮演前端, 端口 8000)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=zyw
Group=zyw
WorkingDirectory=/home/zyw/SillyTavern
Environment=NODE_ENV=production
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# 关键:不要在这里设置 HTTP(S)_PROXY —— 酒馆需直连局域网 LLM (192.168.31.31:8080/1234)
Environment=NO_PROXY=localhost,127.0.0.1,192.168.31.0/24
ExecStart=/usr/bin/node /home/zyw/SillyTavern/server.js
Restart=always
RestartSec=5
TimeoutStopSec=15
LimitNOFILE=65535
StandardOutput=append:/home/zyw/SillyTavern/sillytavern.log
StandardError=append:/home/zyw/SillyTavern/sillytavern.log

[Install]
WantedBy=multi-user.target

把上面内容存成 ~/Downloads/sillytavern.service(必须是完整非空文件,0 字节会被 systemd 当成 masked),然后:

sudo cp ~/Downloads/sillytavern.service /etc/systemd/system/sillytavern.service
sudo chown root:root /etc/systemd/system/sillytavern.service
sudo chmod 644 /etc/systemd/system/sillytavern.service
sudo systemctl daemon-reload
sudo systemctl enable --now sillytavern.service

用 execStart=/usr/bin/node(系统 v20)而不是 nvm 的 v22 绝对路径:nvm 路径带版本号,升级 node 就失效;v20.15.1 已满足 ST 的 >= 20 要求,实测运行正常。

2.6 预置后端连接(让酒馆开箱即用)

ST 的连接配置存在 data/default-user/settings.json 的 oai_settings 里。停止服务后改写:

s['main_api'] = 'openai'
o['chat_completion_source'] = 'custom'
o['custom_url']   = 'http://192.168.31.31:8080/v1'   # ⚠️ 不要带 /chat/completions,ST 会自动拼
o['custom_model'] = 'bucoid-heretic-27b'
o['openai_max_context'] = 65536      # 与 GPU 机 n_ctx 对齐
o['openai_max_tokens']  = 4096   # 该模型 --reasoning-budget -1,思考链常吃 400~600 token
o['max_context_unlocked'] = True
o['show_thoughts'] = True            # 该模型是思考型,显示思维链

实测酒馆 UI 会加载到的配置(POST /api/settings/get 回读):

main_api                = openai
chat_completion_source  = custom
custom_url              = http://192.168.31.31:8080/v1
custom_model            = bucoid-heretic-27b
openai_max_context      = 65536
openai_max_tokens       = 4096
show_thoughts           = True

3. 验证结果(实测证据)

验证项方法结果
版本GET /versionSillyTavern:1.19.0 / commit 7e8663c / isLatest:true ✅
本机访问curl 127.0.0.1:8000HTTP 200(734 KB 主页面) ✅
局域网访问curl 192.168.31.76:8000HTTP 200 ✅
白名单data/access.log记录到 192.168.31.76 被放行 ✅
端到端(3 次)调酒馆自己的 /api/backends/chat-completions/generate见下 ✅
出站走向用 ST 的 request-proxy.js + 它用的 node-fetch 实测GitHub raw 200 / GitHub API 200(直连均为 000);GPU LLM 200、本机 200(直连未被拐) ✅
崩溃自愈kill -9 主进程10 秒内自动拉起(新 PID,探活 200,NRestarts=2) ✅
配置持久化回读 settings.json预置项未被覆盖 ✅

端到端实测返回(原文摘录,说明角色扮演+中文正常):

提示模型回复
系统:你是一家酒馆的老板娘,说话爽快热情,回答简短。用户:老板娘,来杯招牌酒!「好嘞!招牌酒马上来,坐稳,别烫着!」
系统:你在酒馆里扮演一位沉默寡言的剑客,只说一句话。用户:客官,打哪儿来?「死人堆里。」
用户:用一句话证明你在正常工作。「我能够理解你的要求并生成连贯的中文回复,这说明我在正常工作。」

(该模型带思维链,reasoning_content 正常返回,酒馆会显示为「Thoughts」。)


4. 本次踩到的 6 个坑(都已在部署中处理)

  1. 空文件 = masked:我第一次用 cat <<EOF | echo pw | sudo tee 单元文件 写单元,管道顺序错导致写出 0 字节文件,systemd 把空单元视为 masked,报 Unit file ... is masked 而不是语法错误。→ 正确写法是先 write 到临时文件再 sudo cp。
  2. pkill -f 'node server.js' 会自杀:排查脚本里用了这个模式,结果匹配到自己的 ssh/命令行走而自杀(本机 AGENTS.md 早有同类记录)。→ 一律用 ss -ltnp 取 PID 再 kill。
  3. proxy-from-env 不支持 CIDR(见 §2.4)。
  4. custom_url 不能带 /chat/completions:ST 源码 src/endpoints/backends/chat-completions.js:2628 会自己拼 ${apiUrl}/chat/completions,写成完整端点会变成 .../chat/completions/chat/completions。只填到 /v1。
  5. ST 的 requestProxy 只对 node-fetch + 全局 agent 生效:它替换的是 http.globalAgent/https.globalAgent,对 Node 原生 fetch(undici) 无效。好在 chat-completions.js 等关键路径确实 import fetch from 'node-fetch',所以代理实际生效(已实测 GitHub 200)。换/加功能时若发现某处不走代理,先确认它用的是不是 node-fetch。
  6. 首次启动慢:ST 首次启动要用 webpack 编译前端库(首次约 1~2 分钟,之后缓存 1.2 秒),我的探活窗口一开始设太短,误判成「没起来」。→ 控制脚本里等待窗口统一给到 60 秒。

5. 日常使用

5.1 开始聊天

  1. 浏览器打开 http://192.168.31.76:8000
  2. 后端已经配好(OpenAI 兼容 → http://192.168.31.31:8080/v1 → bucoid-heretic-27b),直接选角色卡开聊即可。
  3. 若 GPU 机模型没在跑(/v1/models 无响应):登录 GPU 机执行 bash ~/switch-llm.sh(默认 ridge-long;无审核档用 bash ~/switch-llm.sh ko-long),然后 bash ~/Downloads/sillytavern-ctl.sh test 复验。

5.2 换后端 / 换模型

5.3 角色卡 / 扩展

5.4 控制脚本

bash ~/Downloads/sillytavern-ctl.sh status    # 单元/端口/本机+局域网探活/后端模型
bash ~/Downloads/sillytavern-ctl.sh test      # 端到端自检(真发一次请求)
bash ~/Downloads/sillytavern-ctl.sh restart
bash ~/Downloads/sillytavern-ctl.sh log 80    # 看日志
bash ~/Downloads/sillytavern-ctl.sh update    # 拉最新稳定版 tag + 装依赖 + 重启(走代理)
bash ~/Downloads/sillytavern-ctl.sh info

6. 已知限制与待办


7. 回滚

sudo systemctl disable --now sillytavern.service
sudo rm /etc/systemd/system/sillytavern.service && sudo systemctl daemon-reload
mv /home/zyw/SillyTavern ~/SillyTavern.bak      # 或直接 rm -rf
# config.yaml 的 requestProxy 若要一起还原:把 enabled 改回 false

回滚不影响本机其他服务(下载中心 8899、相册 8900、DSH GUI 3080、迅雷 2345、v2ray)。


8. 附:本次新增/改动的文件清单

文件说明
/home/zyw/SillyTavern/酒馆 1.19.0 全量(git 仓库,detached HEAD 于 tag 1.19.0)
/home/zyw/SillyTavern/config.yaml由 default/config.yaml 复制后按 §2.4 修改
/home/zyw/SillyTavern/data/default-user/settings.json预置后端连接(§2.6)
/home/zyw/SillyTavern/data/default-user/settings.json.bak-preconfigure改动前备份
/home/zyw/SillyTavern/proxy-selftest.mjs出站走向自检脚本(用 ST 自己的 request-proxy 模块)
/etc/systemd/system/sillytavern.servicesystemd 单元(本目录有副本)
/home/zyw/Downloads/sillytavern-ctl.sh控制脚本(start/stop/restart/status/log/test/update/info)
本报告dl-hub/55-SillyTavern酒馆部署/

9. 故障记录:Jinja Exception: System message must be at the beginning.

现象:部署完成后在酒馆里正常聊天,发消息即报错 Chat Completion API —— While executing CallExpression at line 106 ... raise_exception('System message must be at the beginning.')

9.1 根因(已实测确证,非猜测)

GPU 机该模型的 Jinja 聊天模板里有一段硬检查(/props 取回的 chat_template 第 104-107 行):

{%- for message in messages %}
    {%- if message.role == "system" %}
        {%- if not loop.first %}
            {{- raise_exception('System message must be at the beginning.') }}
        {%- endif %}

即:只允许第 0 条是 system,任何位置出现第二条 system 就直接 500。这是 Qwen3.x 系模板的已知通病 (见 llama.cpp #27367、 unsloth/Qwen3.8-27B-GGUF 讨论)。

而酒馆默认 squash_system_messages = false(不合并连续同角色消息)——于是「主提示 / 角色描述 / 角色性格 / 场景 / 世界书前后 / 对话示例」 被当成多条连续的 system 逐条发送,第 2 条就触发异常。这解释了为什么我用 [system, user] 两段式 curl 测试能通过、而酒馆里必错。

实测对照表(直接打 GPU 机 /v1/chat/completions):

消息形态结果
1 条 system + user✅ 通过
2 条连续 system + user(酒馆默认真实形态)❌ 被拒(line 106)
system + user + 尾部 system(越狱块默认角色形态)❌ 被拒(line 106)
squash 合并后 + 尾部指令改 user✅ 通过

附带确认:默认的 Post-History Instructions(jailbreak) 与 nsfw 两个块内容为空,所以本次不是它们触发的; 但它们的默认角色是 system 且注入在对话历史末尾,一旦填内容就会以同样方式炸掉,因此一并预防性改掉。

9.2 已实施的修复(酒馆侧,安全可逆)

改 data/default-user/settings.json 的 oai_settings(改动前备份为 settings.json.bak-before-squashfix):

键改前改后作用
squash_system_messagesfalsetrue真凶修复:把连续 system 合并成一条
prompts[].role(jailbreak)systemuser预防:尾部注入不再是 system
prompts[].role(nsfw)systemuser同上

对应 UI 位置:Advanced Formatting(高级格式化) → Squash system messages 勾选框;以及 Prompt Manager 里每个提示块左侧的 A / U / S 角色按钮。

9.3 验证

9.4 当时的剩余隐患(已于同日用 GPU 机侧方案根治,见 §10)

酒馆侧只能做到「合并连续 system + 把尾部块改成 user」;模板的限制本身只放宽到「system 只能在开头」, 所以任何"注入到对话中间"的 system 消息(下面这些正是酒馆的常用功能)仍会 500:

要彻底根治得改 GPU 机侧(见 §10)。


10. GPU 机侧根治:--chat-template-file(✅ 已执行并验证)

该模型模板的硬检查位于 GPU 机单元 llama-bucoid-heretic.service 加载的 bucoid_3.0_mtp.gguf 内嵌模板中。 llama-server 支持 --chat-template-file(已确认该构建支持),可在不改模型文件的前提下覆盖模板:

  1. 用 /props 取回原模板存成 chat_template-fixed.jinja;
  2. 把第 105-107 行的 raise_exception 换成正常渲染一段内联 system 块:
    {%- if message.role == "system" %}
        {%- if not loop.first %}
            {{- '<|im_start|>system\n' + content + '<|im_end|>' + '\n' }}
        {%- endif %}

(首条 system 由模板前面的分支渲染,故仍保留 not loop.first 守卫,不会重复输出。)

  1. 单元里加 --chat-template-file /home/zyw/models/bucoid-heretic-27b/chat_template-fixed.jinja;
  2. systemctl daemon-reload && systemctl restart llama-bucoid-heretic(模型重新加载约 20~40s)。

收益:世界书 at-depth、Author's Note、任意 system 注入全部可用,酒馆不需要任何 role 上的将就, 其他客户端(Open WebUI 等)一并受益。 代价/风险:需重启 GPU 机正在跑的 14GB 模型服务;补丁只改 1 行、可秒回滚(保留原单元与模板备份)。

10.1 执行记录(2026-09-26,用户确认后执行)

步骤操作结果
1从 /props 取回原模板存为 chat_template-orig.jinja170 行
2只替换第 106 行 raise_exception → 渲染内联 system 块,存 chat_template-fixed.jinjadiff 仅 1 行不同;md5 7bb56f72782f5d02c9445a53e399cf95
3传到 GPU 机 /home/zyw/models/bucoid-heretic-27b/chat_template-fixed.jinja远端 md5 一致
4备份单元 → llama-bucoid-heretic.service.bak-20260926-pre-template已备份
5在 ExecStart 末尾追加 --chat-template-file /home/zyw/models/bucoid-heretic-27b/chat_template-fixed.jinja与备份 diff 仅该 1 行
6daemon-reload + restart/health 5s 内 200;ps 确认进程已带该 flag

10.2 验证结果(改前全部 500 → 改后全部通过)

消息形态改前改后
2 条连续 system + user❌ 500✅ 「哟,来啦!今儿想喝啥?」
system + user + 尾部 system❌ 500✅ 「来了,要哪种?」
system + user + assistant + 中间 system(世界书 at-depth 形态)❌ 500✅ 「本店招牌是桂花酿。要一壶?」← 正确用上了注入的世界书内容
常规 1 条 system(回归测试)✅✅ 「(把杯碟往桌上一放)酒?有。」

说明:该模型的 MTP/推理配置未改动,只换了模板来源,因此速度与显存不受影响(依旧 64K + KV4 + MTP)。

10.3 副作用与回滚

10.4 酒馆侧是否还需要那两项设置?

--chat-template-file 生效后,酒馆的 squash_system_messages 与 jailbreak/nsfw → user 不再是必须的, 但建议保留:① 合并连续 system 能少发冗余 token,无副作用;② 若把酒馆指向 GPU 机上别的模型 (switch-llm.sh 切 ridge / ko-ridge 等),那些模型可能同样是严格模板,保留这两项等于多一层保险。

10.5 配套调优:openai_max_tokens 2048 → 4096

联调时发现回复正文为空:该模型 --reasoning-budget -1(思考不设上限),单次思考常吃 400~600 token, 600 的预算会被思考链吃光、正文一字不出。这与备忘里 KO-Ridge 的已知问题同源。 已将酒馆 openai_max_tokens 调至 4096(实测 2000/4096 均能正常出正文,且质量良好、正确使用世界书)。

下载此文件