☰
手把手搭建24小时在线的私人AI助手:ClawdBot完整部署指南
2026/10/11 16:54:58 网站建设 项目流程

先交代一下背景。我自己一直有个困扰:平时有很多零散的问题、临时的想法、读到一半的文章摘要,都希望能随手丢给一个“始终在线”的对话助手,隔天还能接着聊。网页里的对话工具当然能用,但会话一多就乱,换个设备又是另一套,时间久了很难形成真正的个人知识积累。后来我用一个周末的时间,把一套叫 ClawdBot 的轻量级机器人从零搭了起来,挂在旧笔记本上全天候运行,一直用到现在。

这篇文章就是把整个搭建过程完整拆开,按照一个完全零基础的人能照做的标准来写。ClawdBot 本质上是一个基于 Python 的 Web 聊天服务,前端是一个简单的网页对话框,后端接一个大模型对话接口,对话记录存在本地数据库里。你不需要懂前端也不需要懂服务端,只要会执行命令就行。整个过程下来,你会得到一个能 24 小时在线、拥有连续记忆、可以自定义人设的私人 AI 助手。


1. 为什么非要自己搭一个 24/7 的私人 AI 助手

1.1 网页对话工具的四个别扭之处

先说说我为什么放着现成的网页对话不用,非要自己搭。

第一点是会话不连续。网页上的 AI 对话工具虽然也能保存历史,但会话一多,翻找起来非常费力。更麻烦的是,每隔一段时间系统会自动清理旧会话,你想回头找一个两周前的结论,经常找不到。我自己攒了不少“当时觉得有用但没来得及整理”的问答,结果全埋在对话列表里了。

第二点是每次都要重新交代背景。网页对话默认没有长期人设,你今天让它用“简洁专业”的口吻帮你写邮件,明天再开一个新对话,它又把你的偏好忘光了。你需要在每次对话里反复重复“我是做什么的、我希望你怎么回答、不要用语气词”这类信息,浪费大量 token 和时间。

第三点是隐私边界模糊。你输进去的问题,往往带着工作内容、个人计划、生活细节,这些信息放在第三方平台上,我总觉得不踏实。自己搭的机器人,对话记录存在自己的硬盘里,心理边界清楚得多。

第四点是入口不稳定。网页工具依赖浏览器,浏览器依赖电脑电源和网络。你关了电脑,它就不在。真正需要它的时候,比如半夜突然冒出一个想法、周末在书房翻资料想顺手问一句,那个“稳定入口”就找不到了。

1.2 ClawdBot 做了什么取舍

ClawdBot 没有做得很复杂,它的核心能力只有三件事:

  • 提供一个常驻的网页对话窗口,随时打开浏览器就能聊;
  • 把每一条对话记录持久化保存,支持跨会话继续聊;
  • 通过系统提示词设定一个固定的人设,让机器人在所有对话里保持统一的语气和习惯。

至于更高级的插件生态、联网搜索、知识库问答,目前都不在 ClawdBot 的第一版范围内。这样设计是为了降低安装门槛——你只需要管理两个 Python 文件和一个配置文件,出现问题也容易排查。

我实际用下来的感受是,这类“常驻助手”最大的价值不是功能多,而是“永远在”。你在任何时间点打开它,不用重新介绍自己,不用等冷启动,它会基于之前的全部对话继续回答,这种感觉和网页聊天完全不一样。


2. 安装前准备:一台常开设备,一个模型接口密钥

2.1 硬件怎么选

ClawdBot 对硬件要求很低。核心是要有一台能够长时间开机的设备,而不是一台你每天关机带走的办公电脑。

我列一个参考表,方便你根据自己的情况选:

设备类型优点缺点适合人群
旧笔记本自带屏幕、键盘,调试方便功耗相对高,风扇噪音可能偏大手边有闲置笔记本的人
迷你主机功耗低,安静,体积小需要额外购买想认真长期运行的人
云服务器不受本地断电影响,随时随地从外部访问每月有费用,公网安全需要自己把关经常不在家但需要远程用的人
NAS 或软路由本身就是常开设备,复用性强安装环境可能受限,依赖已有设备能力已经有 NAS 的人

我自己用的是旧笔记本,装了 Linux 系统,放在书房的角落里。ClawdBot 平时占用的内存大约 150MB 左右,CPU 在对话之外基本是零负载,对旧机器来说非常轻松。

2.2 系统和 Python 环境

本文以 Linux 环境为例,Windows 和 macOS 也能运行,只是后台服务管理方式略有不同,后面第三章里我会分别说明。

你先确认设备上有 Python 3.10 及以上版本。打开终端执行:

python3 --version

如果输出类似Python 3.10.x,就满足条件。如果没有 Python 或版本太老,先通过系统的包管理工具安装,这一步不同系统命令不同,建议直接搜“你的系统版本 Python 安装”。

接下来创建项目目录和虚拟环境:

mkdir -p ~/clawdbot && cd ~/clawdbot python3 -m venv venv source venv/bin/activate

虚拟环境的作用是把 ClawdBot 依赖的库和系统库隔离,避免以后安装其他 Python 工具时互相干扰。这一步看着简单,但很多新手省掉后,后面会出现各种奇怪报错。

然后安装两个核心依赖:

pip install flask requests

flask负责提供 Web 服务,requests负责向大模型接口发送请求。一个负责“网页对话入口”,一个负责“模型调用”,这两个库就够用了。

2.3 获取大模型接口的访问凭据

ClawdBot 本身不包含模型,它通过调用大模型服务商提供的对话接口来生成回复。你需要准备三样东西:

  • 接口地址(通常是一段 HTTP URL);
  • API 密钥(一串用于身份验证的字符串);
  • 模型名称(服务商定义的模型标识)。

在服务商的开发者后台创建 API 密钥后,建议复制到一个临时文件里保存。注意,密钥相当于“账号密码”,泄露意味着别人可以拿它调用服务并且由你付费。后面我们会把它写进配置文件,但配置文件需要设好权限。

如果你当前还没有任何大模型服务商的账号,先注册一个。目前主流的大模型服务平台基本都提供了兼容的接口格式,设计 ClawdBot 时我特意把接口地址、密钥、模型名称做成配置项,换服务商只需要改config.json,不用改代码。

2.4 网络和端口的基本概念

ClawdBot 启动后会在本机监听一个端口,默认我用8180。浏览器通过“IP 地址加端口号”来访问服务。

本地访问时就访问http://127.0.0.1:8180。如果想让同一局域网内的手机、平板也能访问,ClawdBot 需要监听0.0.0.0,然后用设备的局域网 IP 访问,比如http://192.168.x.x:8180。这一步在配置里会体现,后面详细说。

提示:如果你打算把 ClawdBot 部署到云服务器并对外提供服务,请务必只在云平台的安全组里放行你信任的 IP 地址,不要简单粗暴地把端口对全网开放。API 密钥一旦被拖走,损失的就不只是流量费了。


3. 搭建 ClawdBot 本体:配置、代码与首次启动

3.1 配置文件 config.json

回到刚才创建的~/clawdbot目录,第一步先建配置文件。新建一个config.json文件:

{ "api_base_url": "https://你的接口服务地址", "api_key": "在这里填写你的密钥", "model_name": "clawd-chat", "system_prompt": "你是一位可靠、耐心的私人助理。回答时尽量简洁直接,不说客套话,遇到不确定的信息要坦率说明。", "temperature": 0.7, "max_tokens": 1000, "port": 8180, "host": "127.0.0.1" }

各字段的含义:

字段作用建议值
api_base_url大模型服务商提供的接口根地址替换成你的真实地址
api_key用于鉴权的密钥填真实值,不要加引号
model_name调用的具体模型标识按服务商文档填
system_prompt系统提示词,决定机器人人格和回复风格按你的需求调整
temperature回复随机性,数值越高越自由0.5~0.8 之间比较稳妥
max_tokens单次回复的最大 token 数量日常问答 800~1000 足够
portWeb 服务监听端口避免使用 80 等常被占用的端口
host监听地址,127.0.0.1 仅本机访问,0.0.0.0 支持局域网访问先保持 127.0.0.1

设置配置文件权限,防止同机其他用户读取密钥:

chmod 600 config.json

3.2 主程序 app.py

在~/clawdbot下新建一个app.py,内容如下。这段代码我尽量精简,但保留了完整的核心逻辑:

import json import sqlite3 import requests from flask import Flask, request, jsonify, render_template app = Flask(__name__) CONFIG = json.load(open("config.json", encoding="utf-8")) DB_PATH = "history.db" def init_db(): conn = sqlite3.connect(DB_PATH) conn.execute("CREATE TABLE IF NOT EXISTS messages(id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT, content TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP)") conn.commit() conn.close() def save_message(role, content): conn = sqlite3.connect(DB_PATH) conn.execute("INSERT INTO messages(role, content) VALUES(?, ?)", (role, content)) conn.commit() conn.close() def get_recent_messages(limit=10): conn = sqlite3.connect(DB_PATH) rows = conn.execute( "SELECT role, content FROM messages ORDER BY id DESC LIMIT ?", (limit,) ).fetchall() conn.close() rows.reverse() return rows def build_messages(): messages = [{"role": "system", "content": CONFIG["system_prompt"]}] for role, content in get_recent_messages(20): messages.append({"role": role, "content": content}) return messages def call_model(messages): resp = requests.post( CONFIG["api_base_url"].rstrip("/") + "/chat/completions", headers={"Authorization": f"Bearer {CONFIG['api_key']}"}, json={ "model": CONFIG["model_name"], "messages": messages, "temperature": CONFIG["temperature"], "max_tokens": CONFIG["max_tokens"], }, timeout=60, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] @app.route("/", methods=["GET"]) def index(): return render_template("index.html") @app.route("/chat", methods=["POST"]) def chat(): user_input = request.json.get("message", "").strip() if not user_input: return jsonify({"error": "消息不能为空"}), 400 save_message("user", user_input) messages = build_messages() try: reply = call_model(messages) save_message("assistant", reply) return jsonify({"reply": reply}) except Exception as e: return jsonify({"error": str(e)}), 500 if __name__ == "__main__": init_db() app.run(host=CONFIG["host"], port=CONFIG["port"], threaded=True)

这段代码做了几件事:

  • 启动时初始化数据库history.db,用来存历史消息;
  • 每次用户发消息,先保存到数据库,再取出最近的历史记录拼进请求;
  • 把系统提示词、历史记录、当前问题一并发给大模型接口;
  • 收到回复后保存,再返回给网页展示。

注意接口路径我写的是/chat/completions,这是目前大多数大模型服务通用的对话端点格式。如果你的服务商接口不同,改为实际文档里的路径即可。

3.3 前端页面 templates/index.html

在~/clawdbot下新建templates目录,创建templates/index.html:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ClawdBot</title> <style> body { font-family: system-ui, sans-serif; max-width: 720px; margin: 40px auto; padding: 0 16px; } #history { border: 1px solid #ddd; border-radius: 8px; min-height: 400px; padding: 16px; margin-bottom: 12px; overflow-y: auto; } .msg { margin-bottom: 12px; } .user { color: #333; font-weight: 600; } .assistant { color: #555; white-space: pre-wrap; } #input { width: 100%; padding: 10px; border: 1px solid #aaa; border-radius: 6px; box-sizing: border-box; } #sendBtn { margin-top: 8px; padding: 8px 20px; border: none; background: #2678f0; color: #fff; border-radius: 6px; cursor: pointer; } </style> </head> <body> <h1>ClawdBot 私人助手</h1> <div id="history"></div> <textarea id="input" rows="2" placeholder="输入你的问题,回车发送"></textarea> <button id="sendBtn">发送</button> <script> const historyEl = document.getElementById('history'); const inputEl = document.getElementById('input'); const sendBtn = document.getElementById('sendBtn'); function appendMessage(role, content) { const div = document.createElement('div'); div.className = 'msg'; div.innerHTML = '<div class="' + role + '">' + (role === 'user' ? '我:' : '助手:') + '</div>' + '<div class="' + role + '">' + content.replace(/\n/g, '<br>') + '</div>'; historyEl.appendChild(div); historyEl.scrollTop = historyEl.scrollHeight; } async function send() { const text = inputEl.value.trim(); if (!text) return; inputEl.value = ''; appendMessage('user', text); const btn = sendBtn; btn.disabled = true; try { const resp = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text }) }); const data = await resp.json(); if (data.reply) { appendMessage('assistant', data.reply); } else { appendMessage('assistant', '出错了:' + data.error); } } catch (err) { appendMessage('assistant', '请求失败:' + err); } finally { btn.disabled = false; } } sendBtn.addEventListener('click', send); inputEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }); </script> </body> </html>

这个页面没有任何引入外部框架,打开就是一个朴素的聊天窗口。历史记录通过 JavaScript 动态添加到页面上。你可以根据自己的审美改样式,核心逻辑不变。

3.4 首次启动与验证

在~/clawdbot目录下,退出虚拟环境重进一次,然后直接运行:

source venv/bin/activate python app.py

看到类似Running on http://127.0.0.1:8180的输出就说明启动成功了。然后打开本机浏览器访问http://127.0.0.1:8180,输入“你好,介绍一下你自己”,如果机器人正常回复,说明整个链路已经打通。

第一次跑通你会遇到几种情况,我提前说一下最常见的一个:请求报 401 或者 403。大部分原因都是 API 密钥填错、接口地址写错、或者模型名称不对。先检查 config 三个字段,再检查网络请求是否真的发出去了。


4. 让对话有记忆:上下文管理的原理与实现

4.1 为什么不能每次只问一句

大模型接口本身是无状态的。你发一条“帮我写一封请假邮件”,它只会根据这条信息回答。如果你下一条说“把理由改得再紧急一些”,它不知道“再紧急一些”指的是哪封邮件。

ClawdBot 的解决办法是:每次请求时,把最近的对话历史一起发给接口。这就好比你每次找同一个助理帮忙,都会先把之前的交流记录摆在桌上,对方才能接着干活。

在代码里,消息列表长这样:

[ {"role": "system", "content": "你是一位可靠、耐心的私人助理。"}, {"role": "user", "content": "帮我写一封请假邮件"}, {"role": "assistant", "content": "好的,以下是草稿……"}, {"role": "user", "content": "把理由改得再紧急一些"} ]

接口看到完整的历史,就能知道最后一条指令的真实意图。ClawdBot 的build_messages()函数就是做这件事:先在消息列表头部插入系统提示词,再按时间顺序追加最近的对话记录,最后把当前问题放到末尾。

4.2 为什么不能无限传下去

很多人会想:那我干脆把所有历史都传给接口,记忆不就最完整了?不行。原因有两个,一个是每次请求都有 token 数量上限,另一个是历史越长,响应越慢、成本越高。

假设平均每条消息 300 个汉字,20 条对话记录大约是 6000 个汉字,按中文一两个汉字算一个 token,大概是 3000 到 6000 token,已经接近很多模型单次请求的一半额度了。你聊得越久,历史膨胀得越快。

我的策略是只取最近 20 轮(user 和 assistant 各算一条)作为上下文。设置一个合理的窗口,既能保持连贯性,又能控制请求体大小。

4.3 滑动窗口与关键信息固定

如果你聊的内容比较重要,希望机器人一直记得某件事——比如“我的工作领域是教育信息化”,你就把它写进系统提示词里,也就是 config.json 的system_prompt字段。这样一来,无论历史怎么滑动,这段信息始终在每次请求的最前面。

如果你想更精细地控制,可以在build_messages()里区分“永久记忆”和“滑动历史”:

def build_messages(): permanent = [ {"role": "system", "content": CONFIG["system_prompt"]}, {"role": "user", "content": "请记住:我一般工作日上午处理邮件,下午做项目。以后安排任务时考虑这个时间偏好。"}, {"role": "assistant", "content": "好的,我已记住。"}, ] recent = [ {"role": role, "content": content} for role, content in get_recent_messages(20) ] return permanent + recent

这样,永久记忆不会被滑动窗口挤出请求,而近期的对话又能正常引用。这个方法是我实际用了一个月后总结出来的,比单纯把历史长度调大要省 token 得多。

提示:改变上下文策略后,建议先重启服务再验证效果。因为历史是存在数据库里的,调整代码后旧消息会以新的组织方式参与上下文,有时会出现“突然忘了之前说的话”的错觉,其实切换逻辑后相当于换了一次系统提示词。


5. 让它 24/7 在线:systemd 守护进程与开机自启

5.1 为什么不能开着终端挂着

很多人跑到这一步就以为大功告成了,直接在终端里python app.py,窗口挂着就关掉显示器。但这样有两个隐患:

一是终端窗口一旦关闭,Python 进程就可能被终止,助手就掉线了。二是重启电脑后你忘了重新运行,它就一直不在线。

在 Linux 系统下,正规做法是使用 systemd 把 ClawdBot 注册成一个后台服务。systemd 负责在开机时自动启动它,在进程崩溃时自动重启它,还能统一收集日志。这才是“24/7 在线”的正解。

5.2 编写 ClawdBot 服务文件

在终端执行:

sudo nano /etc/systemd/system/clawdbot.service

写入以下内容:

[Unit] Description=ClawdBot AI Assistant After=network-online.target Wants=network-online.target [Service] User=你的用户名 WorkingDirectory=/home/你的用户名/clawdbot ExecStart=/home/你的用户名/clawdbot/venv/bin/python /home/你的用户名/clawdbot/app.py Restart=always RestartSec=5 Environment=PYTHONUNBUFFERED=1 [Install] WantedBy=multi-user.target

逐项说明:

  • User:以哪个系统用户运行服务。不要用 root,万一程序出问题,影响范围会小很多;
  • WorkingDirectory:工作目录,必须是项目目录,程序里的相对路径才能生效;
  • ExecStart:服务启动命令,要指向虚拟环境里的 Python,而不是系统 Python;
  • Restart=always:无论什么原因退出,都尝试自动重启;
  • RestartSec=5:重启前等待 5 秒,避免崩溃后陷入快速重启的循环。

保存文件后,重新加载 systemd 配置并启动服务:

sudo systemctl daemon-reload sudo systemctl enable clawdbot sudo systemctl start clawdbot

enable是设置开机自启,start是立即启动。这一步之后,你重启电脑也不用管它了,ClawdBot 会自动爬起来。

检查运行状态:

sudo systemctl status clawdbot

输出里看到active (running)就说明服务在跑。浏览器再访问一次,功能正常,后台服务也就正式上线了。

5.3 查看日志和手动重启

服务有问题时,第一件事就是看日志:

sudo journalctl -u clawdbot -n 50

-n 50表示查看最近 50 行。日志里会显示 Python 的完整错误堆栈,排查问题比终端里看还方便。

修改完代码后,重启服务的命令统一为:

sudo systemctl restart clawdbot

记住这套命令,后面日常维护只需要这一个动作。

5.4 Windows 和 macOS 的替代方案

Windows 上最简单的做法,是写一个.bat启动脚本,然后把脚本快捷方式放进“启动”文件夹,实现开机自启。但如果是长期挂机,我建议直接使用 Windows 任务计划程序,设置“用户登录时”或“系统启动时”运行脚本,比塞启动文件夹更稳定。

macOS 用 launchd,写一个.plist文件放到~/Library/LaunchAgents:

<?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> <key>Label</key> <string>com.local.clawdbot</string> <key>ProgramArguments</key> <array> <string>/Users/你的用户名/clawdbot/venv/bin/python</string> <string>/Users/你的用户名/clawdbot/app.py</string> </array> <key>WorkingDirectory</key> <string>/Users/你的用户名/clawdbot</string> <key>KeepAlive</key> <true/> </dict> </plist>

然后用launchctl load加载。这块不同系统的命令差异较大,建议在虚拟机或真实设备上做一次实验,确认重启后能自动拉起即可。


6. 运行一段时间后必然会踩的坑

6.1 请求超时与网络波动

大模型接口的响应时间并不稳定,遇到高峰时段,一次请求可能 20 秒甚至更久。我在代码里设置的timeout=60,基本够用。如果你用的接口偶尔出现超时,可以在调用层加一个简单的重试:

for attempt in range(3): try: resp = requests.post(..., timeout=60) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] except requests.exceptions.Timeout: if attempt == 2: raise time.sleep(2)

但要注意,重试只适合幂等性请求。对话生成这类请求重试可能导致机器人重复回复同一段内容。所以我没把重试做进第一版,宁可让前端直接报错,也不要产生混乱的重复回复。

6.2 上下文爆炸导致成本失控

我前面提到只取最近 20 轮,这是保险做法。如果你把窗口调大到 50 轮,再加上回复本身很长,单次请求消耗的 token 会迅速上升。连续对话一整天,费用可能超出预期。

一个简单的控制办法,在配置里增加一个最大请求轮次限制:

MAX_ROUNDS = 10

然后在build_messages()里用history[-MAX_ROUNDS:]做截取。当你的对话特别长时,它会自动忽略最远的历史。如果你发现 ClawdBot 突然“失忆”,先看看是不是上下文窗口不够,而不是模型本身出问题。

6.3 API 密钥安全

密钥泄暴露是最大的风险。以下几个习惯我强烈建议你有:

  • config.json不要提交到任何代码托管平台,如果要备份,先做加密;
  • 密钥权限设置成只有自己可读,前面说过的chmod 600不要省;
  • 定期在服务商后台更换密钥,一个月或一个季度换一次,成本不高,心理踏实;
  • 把 ClawdBot 所在设备本身设置好登录密码,不要裸奔在局域网里。

6.4 日志和磁盘慢慢变大

history.db会随着对话增加持续变大,但就算每天聊几百条,一年下来也就几十 MB,对现代硬盘来说可以忽略。

倒是 systemd 日志更容易增长,特别是你频繁调试的时候。可以使用:

sudo journalctl --vacuum-size=200M

把日志压缩到 200MB 以内。如果你希望长期自动清理,去查看系统对应的 journald 配置,修改SystemMaxUse那个字段就行。

6.5 局域网访问时不生效

如果你想在手机上用,把 config 里的host改成0.0.0.0,然后重启服务。手机浏览器访问时用的地址是“电脑的局域网 IP 加端口号”,不是127.0.0.1。

查局域网 IP 的命令:

ip addr | grep inet

或者:

ifconfig

看到形如192.168.1.x的地址就是。把手机和电脑连到同一个 WiFi 下,浏览器访问http://192.168.1.x:8180,就能在手机上用同一个 ClawdBot 了。

提示:如果手机访问不了,先检查电脑自带防火墙是否放行了 8180 端口。不同系统防火墙命令不同,但排查思路都一样:先确认服务真的在监听,再确认防火墙允许了该端口,最后确认手机和电脑在同一网段。


运行这段时间,我最大的体会是:ClawdBot 这类自托管助手的真正门槛不在安装,而在“怎么持续引导它成为你的助手”。我的建议是花一点时间把system_prompt写成一段真正贴合自己需求的说明,而不是随手复制一句模板。它语气更像真人、回复更符合我的习惯,用得越久价值越高。另外记得每个月把history.db备份一次,哪怕只是复制到另一块硬盘——你积累的对话记录,才是这个私人助手真正的不可替代资产。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询