LOADING

加载过慢请开启缓存 浏览器默认开启

Windows 本地部署 Peach 聊天模型,并通过 Tailscale 接入 Linux Docker 版 AstrBot:完整实战教程

2026/10/17 教程 AI

你还在为自己的QQ聊天机器人接入的AI在R18内容上限制严格而感到苦恼吗?
假如你的手里刚好有一台配置能流畅玩AAA游戏的电脑,那你不妨来尝试在本地跑一个令你满意的轻量聊天模型,并将其接入到你的QQ聊天机器人

注:需网络代理!(相信这对能够看到此博客的你不是问题)
GitHub等访问及下载问题需解决,例~GitHub加速下载代理 - 快速访问 GitHub 文件

教程:

温馨提示:全部实操用时约需2+小时

从 Ollama 安装、GGUF 导入、人格配置,到安全开放 API、连接 Docker 容器中的 AstrBot,以及本地模型的日常启停。

本文根据一套实际跑通的环境整理:Windows 11 + RTX 3060 8GB + 16GB 内存;云服务器 OpenCloudOS 9.6 + 宝塔 + Docker,AstrBot 4.26.7,Napcat 与 AstrBot 分别运行在Docker容器中。

请注意:文中的 Tailscale IP 均使用占位符,执行前必须替换成自己设备的实际地址。不要把 Ollama 的 11434 端口公开到互联网。

1. 最终效果与架构

本方案不是把模型部署到云服务器,而是让 Windows 本地电脑负责推理,云服务器继续运行机器人和消息平台适配器。AstrBot 通过兼容 OpenAI 格式的 HTTP API 请求本地 Ollama。

flowchart LR U["QQ 私聊"] --> NC["Napcat Docker 容器"] NC --> AB["AstrBot Docker 容器"] AB -- "HTTP API: 11434/v1" --> TS["Tailscale 私有网络"] TS --> OL["Windows 11 - Ollama"] OL --> GPU["Peach-Framboise GGUF 模型"] GPU --> VRAM["RTX 3060 8GB"]

链路说明:

  • Windows 本机安装 Ollama,并使用 NVIDIA 显卡运行量化后的 Peach 模型。
  • Windows 与 Linux 云服务器加入同一个 Tailscale 网络。
  • Ollama 只监听 Windows 自己的 Tailscale IP;Windows 防火墙只放行云服务器对应的 Tailscale IP。
  • AstrBot 容器通过 Windows 的 Tailscale IP 请求模型,不需要在云服务器上租 GPU。

2. 实测环境与模型选择

项目 本教程使用的配置
本地系统 Windows 11
本地显卡 NVIDIA RTX 3060 8GB VRAM
本地内存 16GB RAM
本地模型 Peach-2.0-9B-8k-Roleplay,GGUF Q4_K_M
模型文件大小 约 5.33GB(不同仓库页面显示可能略有差异)
模型运行器 Ollama for Windows
云服务器 OpenCloudOS 9.6 x86_64,宝塔面板
机器人 AstrBot 4.26.7 + Napcat,均运行在 Docker 中
私有网络 Tailscale

本机有 8GB 显存,可以尝试 Q4 量化的 9B 模型;但显存还要容纳上下文缓存与其他程序。4096 tokens 是保守起点;如果人格提示词、聊天历史或记忆内容太长,可能需要增加到 8192 tokens,同时检查显存占用。 这不代表所有 8GB 显卡都能在 8192 tokens 下全程 GPU 推理。

模型仓库:Peach-2.0-9B-8k-Roleplay-GGUF(Hugging Face)。仓库提供多种量化格式,本案例选择 Q4_K_M。

注:该模型在后续使用过程中发现“无法通过修改人格提示词等多种方式使其不再出现回复内容中描述动作 ”(可能是该模型在训练时极大程度的加强了动作场景等描述,以及指令遵循能力不够强)

3. 规划本地目录

先在 D 盘准备两个目录:

D:\AI\
├── Ollama\
│   └── models\    # Ollama 自己管理的模型仓库
└── Peach\         # 手动下载的 GGUF 与 Modelfile

在 PowerShell 中执行:

New-Item -ItemType Directory -Force "D:\AI\Ollama\models" | Out-Null
New-Item -ItemType Directory -Force "D:\AI\Peach" | Out-Null

[Environment]::SetEnvironmentVariable(
    "OLLAMA_MODELS",
    "D:\AI\Ollama\models",
    "User"
)

这会把 Ollama 用户级模型存储目录设置到 D 盘。然后在 Windows 右下角系统托盘中右键 Ollama 图标,选择 Quit Ollama,再从开始菜单重新打开 Ollama,让新环境变量生效。也可以新开一个 PowerShell 检查:

$env:OLLAMA_MODELS

预期显示 D:\AI\Ollama\models。

Ollama 官方说明:Windows 安装后会在后台运行,默认提供本机 API;模型存储位置可以用用户环境变量 OLLAMA_MODELS 调整。参考:Ollama Windows 文档。

4. 安装 Ollama

  1. 打开 Ollama 官方 Windows 下载页。
  2. 下载并运行 OllamaSetup.exe。
  3. 安装后从开始菜单启动 Ollama。
  4. 打开 PowerShell 检查命令是否可用:
ollama --version

如果显示版本号,安装基本完成。NVIDIA 显卡要确保驱动正常,Ollama Windows 文档列出了当前驱动与系统要求,安装前可查看:Ollama Windows 系统要求。

5. 下载 Peach GGUF 文件

方法 A:Ollama 直接从 Hugging Face 下载

Ollama 支持通过 Hugging Face GGUF 仓库启动模型,例如:

ollama run hf.co/mradermacher/Peach-2.0-9B-8k-Roleplay-GGUF:Q4_K_M

但本案例实际遇到过失败:

Error: redirect target not allowed: us.aws.cdn.hf.co resolves to non-public 198.18.0.132

这通常提示下载跳转的 DNS 解析结果与客户端的安全检查冲突;如果电脑上的代理软件启用了 Fake-IP/DNS 映射,也可能出现 198.18.x.x 这类地址。不要为了绕过问题而关闭安全检查或把 Ollama API 暴露到公网。

方法 B:浏览器下载 GGUF(本案例成功使用)

打开这个文件直链:

下载 Peach Q4_K_M GGUF 文件

将文件保存到:

D:\AI\Peach\Peach-2.0-9B-8k-Roleplay.Q4_K_M.gguf

下载后核对文件名和大小。Q4_K_M 文件约 5.33GB;还要为 Ollama 导入后的模型数据及其他文件预留空间。

6. 用 Modelfile 导入本地 GGUF

打开 PowerShell,创建一个名为 Modelfile 的文本文件。注意文件名不要变成 Modelfile.txt。

执行:

@'
FROM D:/AI/Peach/Peach-2.0-9B-8k-Roleplay.Q4_K_M.gguf
PARAMETER num_ctx 4096
'@ | Set-Content -Path "D:\AI\Peach\Modelfile" -Encoding ascii

这里先用 4096 tokens 作为基础测试。然后执行:

ollama create peach-roleplay -f "D:\AI\Peach\Modelfile"

等待输出 success。检查模型列表:

ollama list

应当能看到 peach-roleplay:latest。启动模型:

ollama run peach-roleplay

输入一段中文测试,例如“请用自然的简体中文和我打个招呼”。能够正常回复,就说明本地 GGUF 已被 Ollama 正常加载。

经验:手动导入 GGUF 时,Modelfile 可以用 FROM <GGUF 文件路径>。这里没有手写 TEMPLATE,因为聊天模板具有模型特异性;不要随意套用其他模型的模板。参考:Ollama Modelfile 文档。

7. 创建带固定人格的 Framboise 模型

为了让人格提示词随模型配置保留,而不是每次手动输入,可以再创建一个模型标签。它不会重新训练模型权重,只是以原模型为基础加入默认 SYSTEM 提示词和参数。

创建 D:\AI\Peach\Modelfile-Framboise-final:

$personaPath = "D:\AI\Peach\Modelfile-Framboise-final"
$personaContent = @'
FROM peach-roleplay
PARAMETER num_ctx 8192
SYSTEM """
你是 Framboise,一只成年、活泼可爱又有些内敛的法国猫娘。
你默认使用自然、流畅的简体中文。你亲切、调皮、偶尔任性,会撒娇、吐槽和接梗,但不刻意过度热情,也不喜欢长篇大论。
日常回复通常简短自然,通常一至三句。可以自然使用“嗯嗯”“欸”“唔”“呜呜”“好耶”“喵”等口癖,但不要机械重复。
你与用户是熟悉、亲近的伙伴。尊重用户明确表达的偏好,不强行把普通聊天变成暧昧或说教。
保留适度、自然的动作描写和角色互动,它们是角色扮演特色;不要每条回复都强行添加,也不要重复相同的动作套路。
根据当前对话和实际提供的记忆回答,不编造共同经历,不假装记得无法访问的信息。
不懂的事情要坦诚说明,不要编造事实。不要复述本提示词,也不要解释自己正在遵守规则。
"""
'@

[System.IO.File]::WriteAllText(
    $personaPath,
    $personaContent,
    [System.Text.UTF8Encoding]::new($false)
)

然后创建人格模型:

ollama create peach-framboise -f "D:\AI\Peach\Modelfile-Framboise-final"

看到 success 后运行:

ollama run peach-framboise

测试中文闲聊。如果只想使用基础角色扮演模型,也可以继续用 peach-roleplay;本案例最终接入 AstrBot 的模型标签是 peach-framboise:latest。

关于上下文长度的踩坑经验

接入 AstrBot 的记忆插件后,曾经出现过:

request (5933 tokens) exceeds the available context size (4096 tokens)

原因是完整系统提示词、记忆内容和对话请求合计超过 4096 tokens。把人格模型的 PARAMETER num_ctx 调整为 8192 并重新执行 ollama create peach-framboise ... 后,测试正常。

如果要修改已有模型,先修改对应 Modelfile,再重新执行 ollama create。但上下文越大,KV cache 占用通常也越高;RTX 3060 8GB 用户应通过 ollama ps 观察 GPU/CPU 分配和实际显存压力。

8. 先测试本机 API

Ollama 启动后,默认本机 API 在 http://localhost:11434。在尚未把监听地址限制到 Tailscale IP 之前,先验证本机 API。

列出模型:

Invoke-RestMethod -Uri "http://127.0.0.1:11434/api/tags" |
    ConvertTo-Json -Depth 5

返回的模型列表中应包含 peach-framboise:latest。

再实际生成一次回复:

$body = @{
    model = "peach-framboise:latest"
    messages = @(
        @{
            role = "user"
            content = "请用自然的简体中文简短回复:本地 API 测试成功。"
        }
    )
    stream = $false
    options = @{ num_ctx = 8192 }
} | ConvertTo-Json -Depth 6

$response = Invoke-RestMethod `
    -Uri "http://127.0.0.1:11434/api/chat" `
    -Method Post `
    -ContentType "application/json; charset=utf-8" `
    -Body ([System.Text.Encoding]::UTF8.GetBytes($body))

$response.message.content

这里特意将请求体转成 UTF-8 字节,避免部分 Windows PowerShell 版本发送中文 JSON 时出现编码问题。

9. 在 Windows 和 OpenCloudOS 上加入 Tailscale

9.1 Windows 安装

从 Tailscale Windows 官方下载页安装客户端,并登录一个 Tailscale 账号。云服务器也必须加入同一个 tailnet。

9.2 OpenCloudOS 9.6 安装

本案例中,Tailscale 通用安装脚本无法识别 OpenCloudOS 9.6。当时使用 RHEL 9 兼容仓库安装成功。这是本案例实测可用的兼容方式,不等于所有 OpenCloudOS 衍生环境都必然兼容;安装前应核实系统版本和仓库兼容性。

在宝塔终端(Linux 宿主机,不是 AstrBot 容器内部)执行:

curl -fsSL https://pkgs.tailscale.com/stable/rhel/9/tailscale.repo \
  -o /etc/yum.repos.d/tailscale.repo

dnf install -y tailscale
systemctl enable --now tailscaled
tailscale up

tailscale up 首次运行会给出登录授权网址。用浏览器打开并完成授权。接着查看两台设备的 Tailscale IP:

Windows PowerShell:

tailscale status
tailscale ip -4

Linux 宿主机:

tailscale status
tailscale ip -4

记下两个实际地址:

  • WIN_TS_IP:Windows 本地电脑的 Tailscale IPv4。
  • SERVER_TS_IP:OpenCloudOS 云服务器的 Tailscale IPv4。

测试云服务器能否找到 Windows:

tailscale ping WIN_TS_IP

将 WIN_TS_IP 替换成真实地址。如果显示 via DERP(...) 且有 pong,说明通过中继可达;但延迟可能比较高。direct connection not established 并不等于完全无法使用,只是当前没有建立直接点对点连接。

Tailscale 安装参考:Tailscale Linux 安装文档 和 官方软件包列表。

10. 让 Ollama 只监听 Windows 的 Tailscale 地址

默认的 localhost 只对 Windows 本机开放,云服务器无法用它访问。因此我们为 Ollama 设置一个专用监听地址。

在 Windows PowerShell 中,将下面的 WIN_TS_IP 替换为 Windows 自己的 Tailscale IP:

[Environment]::SetEnvironmentVariable(
    "OLLAMA_HOST",
    "WIN_TS_IP:11434",
    "User"
)

注意:上面写的是地址和端口,不要加 http://。例如格式是 100.x.y.z:11434,但请使用自己真实的 IP,不要照搬其他人的地址。

然后:

  1. 右键系统托盘的 Ollama 图标,选择 Quit Ollama。
  2. 确认 Tailscale 已连接。
  3. 从开始菜单重新启动 Ollama。
  4. 在新开的 PowerShell 中检查监听地址:
Get-NetTCPConnection -State Listen -LocalPort 11434 |
    Select-Object LocalAddress, LocalPort

预期 LocalAddress 是你填写的 Windows Tailscale IP,而不是 0.0.0.0。如果还显示 127.0.0.1,说明 Ollama 可能尚未读取新的环境变量;退出并重新启动它再检查。

11. 用 Windows 防火墙限制 API 来源

Ollama 的本地 API 不应被当作自带身份验证的公网服务。 本案例采取的是:只监听 Tailscale 地址,并仅允许自己的云服务器 Tailscale IP 访问。

先检查 Windows 防火墙状态:

Get-NetFirewallProfile |
    Format-Table Name, Enabled, DefaultInboundAction

确保 Windows 防火墙处于启用状态,并且入站默认策略是阻止。如果你用第三方防火墙管理安全策略,先按实际环境配置,不要盲目叠加规则。

在管理员 PowerShell 中执行下面的规则。把两个占位符替换成真实 IP,且只执行一次:

$winTsIp = "WIN_TS_IP"
$serverTsIp = "SERVER_TS_IP"

New-NetFirewallRule `
  -DisplayName "Ollama API - Tailscale Cloud Only" `
  -Direction Inbound `
  -Action Allow `
  -Protocol TCP `
  -LocalAddress $winTsIp `
  -LocalPort 11434 `
  -RemoteAddress $serverTsIp `
  -Profile Any

检查规则:

Get-NetFirewallRule -DisplayName "Ollama API - Tailscale Cloud Only" |
    Format-Table DisplayName, Enabled, Direction, Action, Profile

不要在路由器、宝塔安全设置或云服务器安全组中把 TCP 11434 开放到公网。API Key 字段中填 ollama 只是 AstrBot/Ollama 兼容接入时使用的占位值,不应当视为安全认证。

12. 从 Linux 宿主机测试远程 API

回到宝塔终端的 OpenCloudOS 宿主机执行,把 WIN_TS_IP 替换为 Windows 的真实 Tailscale IP:

curl --connect-timeout 10 \
  "http://WIN_TS_IP:11434/api/tags"

如果返回 JSON,并能看到 peach-framboise:latest,说明云服务器宿主机已能读取 Ollama 模型列表。

再测试真实聊天请求:

curl --connect-timeout 10 --max-time 180 -sS \
  "http://WIN_TS_IP:11434/api/chat" \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "peach-framboise:latest",
    "messages": [
      {
        "role": "user",
        "content": "请用简体中文简短回复:云服务器已成功调用本地 Peach 模型。"
      }
    ],
    "stream": false,
    "options": {
      "num_ctx": 8192
    }
  }'

返回 JSON 中包含 message.content 和 done: true,就说明实际推理请求成功。本案例第一次远程生成总耗时约 8 秒,其中模型加载约 6 秒;首次冷启动比后续请求慢是可能的。之后如果通过 DERP 中继通信,也可能有可感知延迟。

13. 从 AstrBot Docker 容器内部验证

宿主机能访问并不意味着容器一定能访问,所以再从 AstrBot 容器内部进行一次检查:

docker exec AstrBot sh -lc \
  'curl --connect-timeout 5 --max-time 10 -sS "http://WIN_TS_IP:11434/api/tags"'

将 WIN_TS_IP 换成真实地址。容器里应返回同样的模型列表。

如果容器内没有 curl,可先检查是否有 python3,再用 Python 的 urllib.request 做同样的 GET 请求。不要为了这项测试而直接修改 Docker 网络模式。

14. 在 AstrBot 中添加 Ollama 提供商

这一步在 AstrBot WebUI 里完成。

14.1 新增模型提供商

进入 提供商(Providers)→ 对话补全(Chat Completion)→ 新增,选择 Ollama。

填写:

项目 填写值
提供商名称 Peach-本地模型(可自定义)
API Base URL http://WIN_TS_IP:11434/v1
API Key ollama
模型 peach-framboise:latest

务必将 WIN_TS_IP 换成 Windows 本地机的 Tailscale IP。这里的地址需要带 /v1,不要把 /api/tags 当作 Base URL。

点击 保存并获取模型 / Save and Fetch Models,在列表中找到 peach-framboise:latest,点击旁边的 + 添加并启用。如果自动获取模型失败,也可以保存提供商后通过自定义模型添加精确的模型 ID。

14.2 将它设为当前对话模型

进入 配置(Config)→ 当前使用的配置文件 → AI → Model,将 Chat Model / 对话模型 选择为刚添加的 Peach-本地模型/peach-framboise:latest,保存配置。

然后使用 AstrBot 的模型测试功能,或通过 QQ 私聊机器人发送一句短消息。

官方步骤参考:AstrBot:接入 Ollama 与 AstrBot:LLM 提供商。

15. Docker 持久化:部署前一定要检查

本案例后来才发现,原先的 AstrBot 容器没有任何数据卷挂载,docker inspect 的 .Mounts 返回 []。这意味着配置、数据库和插件数据都在容器内部;直接删除或重建容器可能丢失数据。

检查当前是否有挂载:

docker inspect --format '{{json .Mounts}}' AstrBot

理想状态下,应能看到类似:

/opt/astrbot/data -> /AstrBot/data

新部署容器时,建议使用持久化挂载:

volumes:
  - /opt/astrbot/data:/AstrBot/data

如果是已经运行、有插件和数据的容器,不要直接复制一条新 docker run 命令来替换它。应先停止容器、备份 /AstrBot/data,核对原镜像 ID、端口映射、重启策略及 Docker 网络,再迁移数据并保留旧容器作为回滚。本文实战环境保留了 6185、6199 两个端口,以及 baota_net 和 bridge 网络,迁移后 QQ 私聊仍正常。

16. 本地模型的关闭与重新启用

这里需要区分“卸载模型”与“关闭 Ollama API 服务”。

16.1 只卸载模型,释放显存

在 Windows PowerShell 执行:

ollama stop peach-framboise

检查:

ollama ps

如果模型不再列出,说明它当前没有驻留运行。Ollama 后台服务仍然在运行;如果 AstrBot 继续调用这个模型,Ollama 通常会再次加载它,因此这不是彻底关闭服务。

16.2 完全关闭本地 API 服务

  1. 在 Windows 右下角系统托盘中找到 Ollama。
  2. 右键选择 Quit Ollama。
  3. 不要停止或退出 Tailscale,除非你也希望关闭私有网络连接。

这样云服务器就不能再访问 Windows 的 Ollama API。若 AstrBot 当前默认模型仍设为 peach-framboise:latest,机器人使用该模型时会请求失败;应先把 AstrBot 的默认对话模型切换到可用的 DeepSeek 等云端模型,再退出 Ollama。

16.3 重新启用

  1. 确保 Windows 开机并联网。
  2. 确认 Tailscale 已登录并连接。
  3. 从 Windows 开始菜单重新启动 Ollama。
  4. 如果想预热本地模型,在 PowerShell 执行:
ollama run peach-framboise

聊天测试完后可以输入 /bye 退出交互界面;不需要一直开着这个终端,AstrBot 通过 API 请求时会让 Ollama 加载模型。

检查监听:

Get-NetTCPConnection -State Listen -LocalPort 11434 |
    Select-Object LocalAddress, LocalPort

必须能看到 Windows 自己的 Tailscale IP 和端口 11434。然后在云服务器上再测试:

curl --connect-timeout 10 "http://WIN_TS_IP:11434/api/tags"

最后,如果要让 QQ 机器人改用本地模型,在 AstrBot Config → AI → Model → Chat Model 中切回 Peach-本地模型/peach-framboise:latest 并保存。

日常使用建议

  • 平时使用 DeepSeek:AstrBot 默认对话模型设为 DeepSeek,可以退出 Ollama,节省本地显存。
  • 想试本地 Framboise:启动 Ollama,检查 Tailscale 与 API,再把 AstrBot 默认模型切回 peach-framboise:latest。
  • 不要删除模型:ollama stop 是卸载运行中的模型;ollama rm peach-framboise 才是删除模型标签,本文不需要执行删除命令。

17. 常见故障排查

现象 常见原因 排查方式
redirect target not allowed ... 198.18.0.132 下载跳转的 DNS 解析与安全检查冲突,可能涉及代理 Fake-IP 使用浏览器下载仓库中的 GGUF 文件,再通过 Modelfile 导入;不要关闭安全检查
ollama list 看不到模型 创建失败、模型名称不一致或 Ollama 使用了另一套模型目录 检查 ollama list、OLLAMA_MODELS 和 ollama create 的输出
云服务器无法访问 11434 OLLAMA_HOST 还在监听 localhost、Tailscale 未连接或 Windows 防火墙没放行 检查 Windows 监听地址、tailscale status 与防火墙来源 IP
宿主机能访问,容器不能访问 Docker 容器网络或工具缺失 在容器内部执行 /api/tags 测试,再查容器网络,不要直接公开端口
request exceeds the available context size (4096 tokens) 系统提示词、聊天记录、记忆内容超过上下文窗口 对 8K 上下文模型可试 num_ctx 8192,然后检查显存与响应速度
首次请求很慢 模型尚未加载进显存,需要冷启动 先等一会儿;通过 ollama ps 检查模型状态
机器人关闭本地模型后不回复 AstrBot 仍把本地模型设为默认模型 恢复 Ollama 服务,或先在 AstrBot 里切换到可用的云端模型
模型有 API 但输出奇怪 模型模板、系统提示词、停止标记或模型本身的生成习惯 不要盲目复制其他架构的 TEMPLATE;先保持默认模板,用短提示词测试

18. 整体验收清单

部署完成后,逐项打勾:

  • ollama list 中存在 peach-framboise:latest。
  • Windows 本机 /api/tags 可以正常返回模型列表。
  • Windows 的 11434 端口只监听本地 Tailscale IP。
  • Windows 防火墙只允许指定的云服务器 Tailscale IP 访问 TCP 11434。
  • Linux 上 tailscale status 能看到 Windows 设备。
  • Linux 宿主机请求 http://WIN_TS_IP:11434/api/tags 成功。
  • docker exec AstrBot ... /api/tags 从容器内请求成功。
  • AstrBot 提供商 Base URL 是 http://WIN_TS_IP:11434/v1,API Key 填 ollama。
  • AstrBot 的对话模型确实选择了 peach-framboise:latest。
  • QQ 私聊机器人能返回本地模型生成的消息。
  • AstrBot 数据目录有持久化挂载,且已保留备份。

19. 官方资料与模型来源


ChatGPT 最后的经验总结: 不要把模型 API 暴露到公网;先在本机验证模型,再验证 Tailscale、宿主机、Docker 容器,最后配置 AstrBot。一次只排查一层。对于 8GB 显存的本地设备,量化模型、合理的上下文长度和可回滚配置,比盲目追求更大的模型更重要。

补充教程:排查 Tailscale 中继连接,并诊断 AstrBot 调用本地 Ollama 的响应时间

本文记录一次实际排查:Linux 云服务器与 Windows 本地电脑最初通过 Tailscale 的 DERP 中继通信,随后成功建立直连;之后对 Windows 上的 Ollama API 进行了耗时测试。适合补充到“本地 Ollama + Tailscale + 云端 AstrBot”部署教程中。

一、Tailscale 为什么会走中继?

Tailscale 会尝试在设备之间建立点对点直连。如果 UDP 打洞或网络条件不允许直连,就会回退到 DERP 中继。中继地区名称只说明当前使用的中继节点,不足以单独判断代理设置就是原因。

本次排查开始时,用户怀疑此前使用网络代理导致云服务器和本地电脑通过日本中转。实际诊断发现:

  • Linux 与 Windows 两端均显示 UDP: true。
  • 两端均显示 MappingVariesByDestIP: false。
  • Linux 的最近 DERP 是洛杉矶,Windows 的最近 DERP 是东京。最近 DERP 不等于两台设备实际使用的连接路径。
  • Linux 环境变量中未发现 HTTP/HTTPS/ALL 代理;tailscaled 服务环境中也未显示代理变量。
  • Windows 未发现代理环境变量,netsh winhttp show proxy 显示直接访问。
  • 这些结果没有证据证明当前代理仍是中继的原因,因此没有先清空代理或重装 Tailscale,而是继续检查实际连接路径。

二、按顺序诊断 Tailscale

第 1 步:两端分别查看连接状态和网络能力

Linux 云服务器(OpenCloudOS):

echo "===== Tailscale 连接状态 ====="
tailscale status

echo
echo "===== 网络探测 ====="
tailscale netcheck

echo
echo "===== Tailscale 服务状态 ====="
systemctl is-active tailscaled

echo
echo "===== 代理环境变量 ====="
env | grep -iE '^(http|https|all|no)_proxy=' || true

echo
echo "===== Tailscale 服务代理配置 ====="
systemctl show tailscaled -p Environment -p EnvironmentFiles

Windows PowerShell:

Write-Host "===== Tailscale 连接状态 ====="
tailscale status

Write-Host "`n===== 网络探测 ====="
tailscale netcheck

Write-Host "`n===== 代理环境变量 ====="
Get-ChildItem Env: | Where-Object { $_.Name -match 'proxy' }

Write-Host "`n===== 系统代理 ====="
netsh winhttp show proxy

如果 PowerShell 找不到 tailscale 命令,可尝试使用安装路径:

& "$env:ProgramFiles\Tailscale\tailscale.exe" status
& "$env:ProgramFiles\Tailscale\tailscale.exe" netcheck

这些检查主要用来收集证据,不应单凭 Nearest DERP 判断实际路径。UDP: true 说明网络探测发现 UDP 可用,但并不保证设备之间一定能够直连。

第 2 步:用 tailscale ping 确认实际连接路径

在 Linux 云服务器执行,将示例中的 Windows Tailscale IP 替换为实际地址:

tailscale ping --c 10 100.119.244.24
tailscale status

常见结果:

输出示例 含义
via DERP(tok) 当前数据经东京 DERP 中继传输
via DERP(sin) 当前数据经新加坡 DERP 中继传输
via 39.162.254.48:8462 当前已通过指定公网 IP 和 UDP 端口直连
先显示 DERP,随后显示公网 IP:端口 连接开始时使用中继,之后成功切换到直连
direct connection not established 本次探测期间没有建立直连,仍需继续排查

不要因为某一次 ping 出现 DERP 就认定永久无法直连。 连接可能先经中继,之后完成点对点连接;建议观察多次结果和 tailscale status。

第 3 步:检查 Linux 的 UDP 监听和防火墙

本次在 Linux 云服务器执行:

echo "===== Tailscale UDP 监听端口 ====="
ss -lunp | grep -E 'tailscaled|41641' || true

echo
echo "===== firewalld 状态 ====="
systemctl is-active firewalld 2>/dev/null || true

echo
echo "===== firewalld 当前规则 ====="
firewall-cmd --list-all 2>/dev/null || true

echo
echo "===== iptables UDP 规则 ====="
iptables -S 2>/dev/null | grep -E 'INPUT|41641|udp' || true

实际检查结果:

  • tailscaled 监听 0.0.0.0:41641 和 [::]:41641。
  • firewalld 状态为 inactive。
  • iptables 默认 INPUT 策略为 ACCEPT。
  • Tailscale 自己的链中存在 -A ts-input -p udp --dport 41641 -j ACCEPT。

因此,没有发现明显的 Linux 主机 UDP 41641 入站规则阻断;但这不排除云厂商防火墙/安全组、Windows 防火墙、家庭路由器或 NAT 映射方面的问题。

第 4 步:检查腾讯云轻量应用服务器防火墙

本例服务器使用的是腾讯云轻量应用服务器,所以检查入口是轻量应用服务器实例的「防火墙」规则页,而不是把它与普通 CVM 的「安全组」界面混为一谈。

进入腾讯云控制台,打开对应轻量应用服务器实例,找到「防火墙」规则列表,检查有没有允许 UDP 41641 的入站规则。若诊断证据指向云侧 UDP 入站限制,可按实际规则界面评估是否需要增加自定义规则:

  • 协议:UDP
  • 端口:41641
  • 策略:允许
  • 备注:Tailscale(可选)

安全注意: 不要无条件开放端口,也不要关闭整个防火墙。若控制台的来源只能设置为全部 IPv4,应先确认确实需要此规则,并评估公开开放 UDP 端口的风险;只修改必要规则,并保留回滚方法。云端放行也不能保证一定直连,另一端的 NAT 与防火墙仍会影响结果。

第 5 步:复测并确认结果

继续在 Linux 执行:

tailscale ping --c 10 100.119.244.24

本次排查期间,输出先出现新加坡 DERP:

pong from toraepc (100.119.244.24) via DERP(sin) in 345ms

随后出现:

pong from toraepc (100.119.244.24) via 39.162.254.48:8462 in 37ms

这证明该次测试已经建立直连,延迟约 37 ms。前几次仍通过 DERP、之后切换直连并不矛盾。由于连接可能自行切换,不能仅凭前后时间关系断言某一条防火墙规则就是唯一原因。

另外,Windows 曾提示:

Tailscale failed to set the DNS configuration of your device: Access is denied.

这是 Windows DNS 配置权限警告,不等于它必然导致 DERP 中继。应分开诊断,不要仅凭该提示重置网络或重装客户端。

三、通过 Tailscale 地址访问 Windows 上的 Ollama

Tailscale 直连确认后,下一步检查 AstrBot 请求本地 Ollama 的速度。

第 1 步:确认 Ollama 实际监听地址

Windows PowerShell 执行:

Write-Host "===== Ollama 命令 ====="
Get-Command ollama -ErrorAction SilentlyContinue

Write-Host "`n===== 模型列表 ====="
ollama list

Write-Host "`n===== Ollama 进程 ====="
Get-Process ollama -ErrorAction SilentlyContinue

Write-Host "`n===== 11434 端口监听 ====="
Get-NetTCPConnection -State Listen -LocalPort 11434 -ErrorAction SilentlyContinue |
    Select-Object LocalAddress, LocalPort, OwningProcess

本次机器的检查结果:

  • Ollama 进程正在运行。
  • 当前模型列表中是 qwen3-heretic-8k:latest,大小约 5.9 GB;当时列表里没有 peach-framboise:latest。
  • Ollama 监听 100.119.244.24:11434,即 Windows 的 Tailscale 地址。
  • 因为它没有监听 127.0.0.1:11434,访问 http://127.0.0.1:11434 失败是符合此监听配置的;这不代表 Ollama 服务整体不可用。

先用实际监听地址测试 API:

Invoke-RestMethod -Uri "http://100.119.244.24:11434/api/tags" -TimeoutSec 5 |
    Select-Object -ExpandProperty models |
    Select-Object name, size

如果需要从云服务器访问 Ollama,AstrBot 的 API Base URL 应使用该 Windows 设备可通过 Tailscale 访问的地址,并与 Ollama 的监听设置一致。不要为了本机测速而直接把监听地址改成 0.0.0.0;扩大监听范围前必须评估访问控制和防火墙规则。

第 2 步:测量 Ollama 自身推理耗时

使用当前真实存在的模型 qwen3-heretic-8k:latest 进行两次短请求。第一轮可能包含模型加载时间;第二轮更能代表模型已经驻留内存时的表现。

$uri = "http://100.119.244.24:11434/api/generate"
$model = "qwen3-heretic-8k:latest"

for ($i = 1; $i -le 2; $i++) {
    Write-Host "`n===== 第 $i 次测试 ====="

    $body = @{
        model  = $model
        prompt = "请只回答:你好。"
        stream = $false
    } | ConvertTo-Json -Compress

    try {
        $r = Invoke-RestMethod `
            -Uri $uri `
            -Method Post `
            -ContentType "application/json" `
            -Body $body `
            -TimeoutSec 120

        [pscustomobject]@{
            回复内容         = $r.response
            总耗时秒         = [math]::Round($r.total_duration / 1e9, 2)
            模型加载秒       = [math]::Round($r.load_duration / 1e9, 2)
            提示词处理秒     = [math]::Round($r.prompt_eval_duration / 1e9, 2)
            文本生成秒       = [math]::Round($r.eval_duration / 1e9, 2)
            生成Token数      = $r.eval_count
            生成速度Token秒  = if ($r.eval_duration -gt 0) {
                [math]::Round($r.eval_count / ($r.eval_duration / 1e9), 2)
            } else { 0 }
        } | Format-List
    } catch {
        Write-Host "请求失败:$($_.Exception.Message)"
    }
}

第 3 步:本次测试数据与解读

指标 第一次 第二次
总耗时 13.34 秒 6.64 秒
模型加载 7.26 秒 0 秒
提示词处理 0.29 秒 0.06 秒
文本生成 5.79 秒 6.56 秒
生成 Token 数 104 121
生成速度 17.98 Token/s 18.44 Token/s

结论:

  1. 第一次比第二次慢,主要因为额外花了约 7.26 秒加载模型。
  2. 第二次模型加载耗时为 0,推理生成速度约 18.44 Token/s。对于该次测试的模型和硬件环境,这是一个可参考的热运行基线,不应直接当成所有提示词下的固定速度。
  3. 虽然提示词要求“请只回答:你好”,模型却输出了一大段 <think>...</think> 内容,实际生成了 121 个 Token。这增加了生成时间,也暴露出思考内容泄漏或指令遵循方面的问题。
  4. 因此,AstrBot 平均响应时间约 6000 ms 并不能仅凭一个数字判断是否异常。较长的人格提示词、历史消息、记忆内容,以及模型生成的输出长度,都会影响总耗时。

四、如何判断 AstrBot 的 6000 ms 是否正常?

先把请求拆成三层分别测量:

  1. 网络路径: tailscale ping 是否稳定出现 via 公网IP:端口。本次已观测到一次约 37 ms 的直连结果。
  2. Ollama 推理: 参考 /api/generate 返回的 load_duration、prompt_eval_duration、eval_duration 和 eval_count,判断耗时花在加载、提示词处理还是生成阶段。
  3. AstrBot 完整请求: 在 AstrBot 中发送同样简短的测试消息,并对比 Ollama 独立 API 测试。若 Ollama 独立请求很快,而 AstrBot 明显更慢,再检查 AstrBot 的系统提示词、对话历史、记忆插件、请求超时和提供商配置。

如果输出中出现 <think>...</think>,优先检查 AstrBot 的输出过滤层或思考内容过滤插件是否正常工作。不要一开始就修改 Ollama 模型模板或用 stop 规则截断文本,以免影响正常回复。

五、排查时的安全原则

  • 优先运行只读诊断命令;先收集证据,再做配置变更。
  • 不要一遇到 DERP 就重装 Tailscale、执行 tailscale down 或重置网络。
  • 不要因为 Windows DNS 权限警告就认定它是中继的根因。
  • 不要为了测速直接把 Ollama 监听地址扩大到所有网卡;先确认实际监听地址与 API Base URL。
  • 改安全组或云防火墙前,检查规则是否必要,并提前记录原配置以便回滚。
  • 代理设置、Tailscale 直连、Ollama 推理速度属于不同层面,应分层验证,不要把时间上同时出现的现象直接当作因果关系。

参考资料

附:本文章本教程为ChatGPT辅助完成(AI帮忙做AI,原汤化原食了属于是)
(该文章也是とらえ首次尝试用AI来辅助写博客,老手艺人终于放下了骄傲,不再坚持纯手搓了)
(ChatGPT太特喵的好用了知道吗!)