部署日期:2026-09-26 部署目标机:DSH 机(本机,192.168.31.76,Deepin 25) 结论:已可用。局域网打开 http://192.168.31.76:8000 即可聊天,后端已预接 GPU 机的无审核模型,端到端实测通过。
| 项目 | 值 |
|---|---|
| 访问地址 | 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 机模型 → 返回中文角色扮演对白 ✅ |
| 用途 | 路径 / 地址 |
|---|---|
| 网页入口 | 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 |
本机已有: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 必须走代理。
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
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
config.yaml(逐项及原因)改动基于 default/config.yaml 复制而来:
| 配置项 | 值 | 原因 |
|---|---|---|
listen | true | 默认 false 只监听本机,必须打开才能局域网访问 |
listenAddress.ipv4 | 0.0.0.0 | 监听所有网卡 |
browserLaunch.enabled | false | 服务器无桌面,别去拉浏览器 |
port | 8000 | 保持默认(已确认空闲) |
whitelistMode | true | 保留白名单模式(安全兜底) |
whitelist | 加 192.168.31.0/24 | 否则除了 127.0.0.1 局域网全被拒 |
enableForwardedWhitelist | false | 无反向代理,关掉可防 X-Real-IP 伪造绕过白名单 |
basicAuthMode | false | 保持免密(只用白名单);要密码见 §6 |
logging.minLogLevel | 1 | 0=DEBUG 太吵,1=INFO |
requestProxy.enabled | true | 见下(GitHub 必需) |
requestProxy.url | socks5://127.0.0.1:10808 | 本机 v2ray SOCKS5 |
requestProxy.bypass | localhost,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,否则请求会被塞进代理而失败。
[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要求,实测运行正常。
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
| 验证项 | 方法 | 结果 |
|---|---|---|
| 版本 | GET /version | SillyTavern:1.19.0 / commit 7e8663c / isLatest:true ✅ |
| 本机访问 | curl 127.0.0.1:8000 | HTTP 200(734 KB 主页面) ✅ |
| 局域网访问 | curl 192.168.31.76:8000 | HTTP 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」。)
cat <<EOF | echo pw | sudo tee 单元文件 写单元,管道顺序错导致写出 0 字节文件,systemd 把空单元视为 masked,报 Unit file ... is masked 而不是语法错误。→ 正确写法是先 write 到临时文件再 sudo cp。pkill -f 'node server.js' 会自杀:排查脚本里用了这个模式,结果匹配到自己的 ssh/命令行走而自杀(本机 AGENTS.md 早有同类记录)。→ 一律用 ss -ltnp 取 PID 再 kill。proxy-from-env 不支持 CIDR(见 §2.4)。custom_url 不能带 /chat/completions:ST 源码 src/endpoints/backends/chat-completions.js:2628 会自己拼 ${apiUrl}/chat/completions,写成完整端点会变成 .../chat/completions/chat/completions。只填到 /v1。requestProxy 只对 node-fetch + 全局 agent 生效:它替换的是 http.globalAgent/https.globalAgent,对 Node 原生 fetch(undici) 无效。好在 chat-completions.js 等关键路径确实 import fetch from 'node-fetch',所以代理实际生效(已实测 GitHub 200)。换/加功能时若发现某处不走代理,先确认它用的是不是 node-fetch。http://192.168.31.31:8080/v1 → bucoid-heretic-27b),直接选角色卡开聊即可。/v1/models 无响应):登录 GPU 机执行 bash ~/switch-llm.sh(默认 ridge-long;无审核档用 bash ~/switch-llm.sh ko-long),然后 bash ~/Downloads/sillytavern-ctl.sh test 复验。Custom (OpenAI-compatible) → 改 URL / 模型名 → 连接。模型下拉可从 /v1/models 自动拉取。http://192.168.31.31:1234/v1,需先在 GPU 机启 llmster。settings.json(改前先 stop)。config.yaml 的 whitelistImportDomains 白名单内有 cdn.discordapp.com、files.catbox.moe、raw.githubusercontent.com 等;其余域名要先加白名单。注意 chub.ai / janitorai 直连返回 403(Cloudflare 拦 curl UA,浏览器里可能正常)。extensions.autoUpdate: true,装第三方扩展(如酒馆助手/JS-Slider-Runner)走 GitHub —— 代理已配好,可正常克隆。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
config.yaml 里把 basicAuthMode: true 并设 basicAuthUser.{username,password},然后 restart。llama-* / LM Studio 单元都是 disabled,酒馆要能聊天需先在 GPU 机手动启动。~/Downloads/v2ray-setup/refresh-sub.sh。settings.json 里 firstRun: true 保留默认,首次打开会看到酒馆的引导弹窗(一次性)。192.168.31.76:8000 之外若要外网访问需自行做反向代理(届时记得开 hostWhitelist 并配 HTTPS)。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)。
| 文件 | 说明 |
|---|---|
/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.service | systemd 单元(本目录有副本) |
/home/zyw/Downloads/sillytavern-ctl.sh | 控制脚本(start/stop/restart/status/log/test/update/info) |
| 本报告 | dl-hub/55-SillyTavern酒馆部署/ |
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.')
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且注入在对话历史末尾,一旦填内容就会以同样方式炸掉,因此一并预防性改掉。
改 data/default-user/settings.json 的 oai_settings(改动前备份为 settings.json.bak-before-squashfix):
| 键 | 改前 | 改后 | 作用 |
|---|---|---|---|
squash_system_messages | false | true | 真凶修复:把连续 system 合并成一条 |
prompts[].role(jailbreak) | system | user | 预防:尾部注入不再是 system |
prompts[].role(nsfw) | system | user | 同上 |
对应 UI 位置:Advanced Formatting(高级格式化) → Squash system messages 勾选框;以及 Prompt Manager 里每个提示块左侧的 A / U / S 角色按钮。
squash_system_messages = true、nsfw/jailbreak role = user ✅(笑着把两只杯都斟满,酒香顺着夜风飘过来) 陪就陪!不过咱可说好,今晚你喝高兴了,我管你喝到尽兴,不许在店里撒酒疯啊。来,碰一个!
酒馆侧只能做到「合并连续 system + 把尾部块改成 user」;模板的限制本身只放宽到「system 只能在开头」, 所以任何"注入到对话中间"的 system 消息(下面这些正是酒馆的常用功能)仍会 500:
public/scripts/world-info.js:3439 写死默认 entry.role ?? SYSTEM, 即每条 at-depth 世界书条目默认就是 system → 用世界书前要把条目角色改成 User(世界书编辑器里有角色图标)。要彻底根治得改 GPU 机侧(见 §10)。
--chat-template-file(✅ 已执行并验证)该模型模板的硬检查位于 GPU 机单元 llama-bucoid-heretic.service 加载的 bucoid_3.0_mtp.gguf 内嵌模板中。 llama-server 支持 --chat-template-file(已确认该构建支持),可在不改模型文件的前提下覆盖模板:
/props 取回原模板存成 chat_template-fixed.jinja;raise_exception 换成正常渲染一段内联 system 块: {%- if message.role == "system" %}
{%- if not loop.first %}
{{- '<|im_start|>system\n' + content + '<|im_end|>' + '\n' }}
{%- endif %}
(首条 system 由模板前面的分支渲染,故仍保留 not loop.first 守卫,不会重复输出。)
--chat-template-file /home/zyw/models/bucoid-heretic-27b/chat_template-fixed.jinja;systemctl daemon-reload && systemctl restart llama-bucoid-heretic(模型重新加载约 20~40s)。收益:世界书 at-depth、Author's Note、任意 system 注入全部可用,酒馆不需要任何 role 上的将就, 其他客户端(Open WebUI 等)一并受益。 代价/风险:需重启 GPU 机正在跑的 14GB 模型服务;补丁只改 1 行、可秒回滚(保留原单元与模板备份)。
| 步骤 | 操作 | 结果 |
|---|---|---|
| 1 | 从 /props 取回原模板存为 chat_template-orig.jinja | 170 行 |
| 2 | 只替换第 106 行 raise_exception → 渲染内联 system 块,存 chat_template-fixed.jinja | diff 仅 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 行 |
| 6 | daemon-reload + restart | /health 5s 内 200;ps 确认进程已带该 flag |
| 消息形态 | 改前 | 改后 |
|---|---|---|
| 2 条连续 system + user | ❌ 500 | ✅ 「哟,来啦!今儿想喝啥?」 |
| system + user + 尾部 system | ❌ 500 | ✅ 「来了,要哪种?」 |
| system + user + assistant + 中间 system(世界书 at-depth 形态) | ❌ 500 | ✅ 「本店招牌是桂花酿。要一壶?」← 正确用上了注入的世界书内容 |
| 常规 1 条 system(回归测试) | ✅ | ✅ 「(把杯碟往桌上一放)酒?有。」 |
说明:该模型的 MTP/推理配置未改动,只换了模板来源,因此速度与显存不受影响(依旧 64K + KV4 + MTP)。
--reasoning-budget -1 等参数未动。Restart=no,重启需手动;改模板后必须 daemon-reload,否则 flag 不生效。sudo cp /etc/systemd/system/llama-bucoid-heretic.service.bak-20260926-pre-template \
/etc/systemd/system/llama-bucoid-heretic.service
sudo systemctl daemon-reload && sudo systemctl restart llama-bucoid-heretic--chat-template-file 生效后,酒馆的 squash_system_messages 与 jailbreak/nsfw → user 不再是必须的, 但建议保留:① 合并连续 system 能少发冗余 token,无副作用;② 若把酒馆指向 GPU 机上别的模型 (switch-llm.sh 切 ridge / ko-ridge 等),那些模型可能同样是严格模板,保留这两项等于多一层保险。
openai_max_tokens 2048 → 4096联调时发现回复正文为空:该模型 --reasoning-budget -1(思考不设上限),单次思考常吃 400~600 token, 600 的预算会被思考链吃光、正文一字不出。这与备忘里 KO-Ridge 的已知问题同源。 已将酒馆 openai_max_tokens 调至 4096(实测 2000/4096 均能正常出正文,且质量良好、正确使用世界书)。