Alice 正在布置首页

把入口和名字,轻轻放好。

alice / 站内 Agent

访客 · 仅公开内容

可使用站长已开放的工具

alice@aliseblu.cn — terminal

ALICE TERMINAL v1.0

命令在这里执行。
自然语言交给 Alice。

输入 查看命令, 重播开屏。想直接说话?

仅接受命令 · 不调用模型

这不是服务器终端。账户与管理操作仍需身份验证;发布、删除需在工作台确认。

跳到正文
图片预览

← 返回博客

博客 技术与思考

Alice 的设计与实现

从网站助手到微信里的持续对话,详细拆解 Alice 的架构、工具执行、公开知识检索、长期记忆与私密整理流程。

Tobey18 分钟阅读更新于 2026.10.10

最初,我只是想在博客里放一个能聊天的小助手。后来发现,比起回答一个孤立的问题,我更希望 Alice 能理解我正在看什么,帮我操作网站,也能接住日常里零散的想法,让这些内容有机会成为记录。

这让问题从“接一个聊天接口”变成了几件相互关联的事情:怎样让一句话变成实际操作,怎样延续很久以前的交流,怎样让聊天整理成文章,又不把私人内容带到公开页面。

目前这版 Alice 使用 DeepSeek Flash 生成回答,但模型只是其中一环。工具、记忆、身份验证、消息状态和发布流程,都需要由模型之外的代码承担。下面沿着一次站内请求和一条微信消息,拆开讲讲她是怎样工作的。

Alice 要解决的三个问题

第一个问题发生在网站里。我在阅读文章时,可能想解释某一段内容,也可能只想切换主题、打开项目或再看一次开屏动画。能用现成操作完成的事情,不应该只收到一句“你可以点击导航栏”。

第二个问题发生在日常交流里。微信里的对话不是一次性提问,我不希望隔了几天再提起一个话题,就必须重新交代全部背景。但模型的上下文有长度限制,保存聊天记录本身并不等于模型随时都能想起它们。

第三个问题是内容整理。日常聊天可以成为记录的来源,却不适合原样公开。我希望 Alice 先整理出一个仅自己可见的版本,再由我决定哪些内容值得留下、哪些内容可以分享。

这三个问题对应三种不同的工作范围:公开网站助手、私人微信对话、经过审核的公开手记。它们属于同一个产品,但不能共用一份不加区分的数据和权限。

整体架构与技术分工

我没有把整套系统塞进一个聊天窗口,而是把阅读页面、站内接口和持续在线的微信服务分开。

部分主要技术在 Alice 中负责什么
公开网站Astro、Markdown、UnoCSS 与普通 CSS阅读内容、呈现助手入口、展示审核后的手记
浏览器交互原生 TypeScript本地指令、操作计划预览、工具确认、阅读上下文
网站服务端Vercel 上的服务端接口验证身份、公开知识检索、模型调用与用量检查
账户与私有数据Supabase站长认证、草稿、私有配置、同步来源和整理记录
微信常驻服务Node.js、SQLite接收文字、保存对话状态、检索记忆、直接调用模型和发送回复

公开网站的主链路比较短:读者发出请求,Alice 查找公开资料或生成受限操作计划,前端再展示结果或执行允许的动作。

私人对话则有自己的持久状态,和网站通过明确接口同步:

公开读者 → 网站 Alice → 公开文章检索/受限站内工具

我 ↔ 微信 ↔ 常驻对话服务 ↔ DeepSeek Flash
                 ↕
          SQLite 原文与记忆索引
                 │ 同步已接收的双方文字与状态
                 ↓
          网站私有对话来源库
                 ↓ 整理
          摘要草稿/私密收件箱
                 ↓ 我审核并确认发布
             公开 Alice 手记

网站还向微信服务提供私有表达配置、绑定授权和用量管理。图中的内容同步不等于公开发布,也不等于同步一完成就已经生成摘要。

这种拆分的理由很实际:阅读页面应当轻,微信消息接收需要常驻进程,聊天状态需要跨重启保留,而公开内容应当只来自有意发布的版本。模型负责理解与表达,不能代替这些分工。

从自然语言到受控工具

明确指令先用确定性代码处理

像“打开博客”“切换夜间模式”“再看一遍开屏动画”这样的请求,可以先匹配本地指令,不必每次都经过模型。

例如,重播开屏不只是把动画元素重新显示出来。如果当前在文章页,执行器会先回到根目录,再由首页接管重播;离开编辑页面时,仍应尊重原有的未保存内容提醒。

首页的 Terminal 则只接受约定的命令。它不是云服务器终端,也不是浏览器里暴露的一套任意执行接口。命令输入与自然语言对话是两个入口,最后复用同一批明确的站内能力。

模型提出计划而不是直接执行代码

需要模型理解的操作请求,会生成结构化计划。下面是一个用于说明格式的计划示例,并不是真实聊天记录:

{
  "reply": "切换到夜间模式,然后打开博客。",
  "steps": [
    { "tool": "site", "action": "theme-night" },
    { "tool": "site", "action": "blog" }
  ]
}

这个 JSON 表达的是意图,不是授权。程序仍要检查工具是否存在、参数是否合规、当前用户是否有权限,以及页面跳转后后续步骤还能不能执行。

当前计划最多四步,不接受额外字段或未知工具。打开文章时,目标还必须属于本次检索确认存在的来源;跳转页面和打开草稿核对表单必须是最后一步,避免离开页面后还承诺继续执行旧任务。

计划在服务端和浏览器两侧校验,通过后先展示给用户,由用户确认执行。模型即使输出一段 JavaScript,或者声称“已经发布”,也不会因此获得执行能力。

草稿操作单独核对

对站长,Alice 可以进入草稿列表、创建短稿,或提出标题、简介和追加正文的修改。它不是一个自动接管全部文章的编辑器。

读取的原稿留在当前浏览器界面核对,不会在这个流程里整篇交给规划模型;规划使用的是明确选中的草稿编号和请求。保存前还有独立确认,修改时检查版本,避免另一台设备刚保存的内容被旧版本覆盖。

Alice 接口不提供直接发布、删除或任意执行能力。公开发布仍在工作台完成。也就是说,“帮我准备内容”和“把内容交给所有人看”,是两个不同的动作。

公开知识检索怎样辅助阅读

RAG 可以理解为:先从已有资料中找到相关片段,再把这些片段交给模型辅助回答。它不是把网站重新训练进模型,也不意味着每次都需要一套向量数据库。

目前 Alice 采用轻量的词法检索。公开索引只读取本次部署中的已发布博客、随记和项目;草稿、工作台数据和私人聊天不进入这一入口。

检索大致经过以下过程:

  1. 按文章标题和段落切分内容,形成较小的候选片段。
  2. 把中文连续文字拆成相邻双字组合,把英文归一化为词项。
  3. 对标题、简介、标签和正文赋予不同权重,并降低常见词的影响。
  4. 选取相关片段,去重后最多提供三个来源,同时限制最终上下文长度。

如果读者正在看一篇文章,Alice 会利用当前页面作为阅读背景;如果有选中文字,也会先核对这段文字确实存在于对应的公开文章中。一个页面地址只是查找线索,不能变成“按这个地址去抓取任意资料”的权限。

划词也不等于自动发送。它只是准备好引用内容,仍由读者明确提交问题。回答旁边保留来源链接,让读者可以返回原文判断,而不是只相信一段听起来流畅的解释。

这套检索适合当前规模,也便于观察“为什么找到了这一段”。代价是它依赖措辞:同义表达、跨段推理或完全不同的说法,可能检索不到。没有相关结果时,不应凭空拼出本站不存在的来源。

一条微信消息如何被处理

微信侧使用常驻服务轮询接收消息,只让已配对且授权仍有效的发送者进入回复流程。目前处理范围是文字,不下载照片给模型,也不提供语音理解或群聊能力。

一条被接受的消息先写入 SQLite,再进行上下文组装和模型调用。收到回答后,先保存回答与发送状态,再尝试通过微信发送。同步到网站和后续整理是另外的步骤。

接收文字 → 核对绑定与去重 → 原文落盘
                                ↓
                    检索记忆与组装上下文
                                ↓
                     预留用量后调用模型
                                ↓
                  保存回答与发送意图 → 微信发送
                                ↓
                       记录发送结果并同步

这里最容易被忽略的是状态。“模型生成了回答”“发出了发送请求”“平台确认发送成功”“网站保存了这轮内容”,不能合并成一个简单的成功标记。平台确认发送成功,也不表示我已经读过这条回复。

消息通过唯一事件标识去重。同一个事件重复到来,不应再次调用模型;如果同一标识对应的内容发生变化,也不能直接当成另一条正常消息。

网络超时更麻烦:请求失败返回,不一定意味着对方完全没有收到。因此发送前要记录发送意图,结果不确定时保留 unknown 状态,不盲目重发;模型调用中断后也不直接假定“没有扣费,再来一次”。

这是一种偏谨慎的取舍:宁可让不确定状态等待核对,也不让重试造成连续回复或重复扣费。它降低重复处理风险,但不是“每条消息绝对只送达一次”的保证。

直连移除了什么

早期让微信回复经过网站转发模型请求,会多经过一段网络路径。现在由云服务器直接请求 DeepSeek Flash,文字同步和摘要不需要先完成,才能继续回复。

但直连不代表网站从系统中消失。网站仍提供绑定授权、私有表达配置和预算租约;常驻服务只使用短期有效的控制信息,控制过期或授权无法确认时会暂停付费处理。

因此,我把这次调整理解为缩短模型调用路径,而不是“微信完全脱离网站运行”。实际等待时间仍受网络、模型生成与消息平台影响,不能仅凭少了一次中转就承诺固定秒数。

持续记忆怎样跨越上下文窗口

持久记忆需要回答两个不同的问题:资料有没有保存,以及这次回答有没有找对资料。前者主要是存储问题,后者是检索与上下文组装问题。

原始对话保存在 SQLite,检索索引和滚动记录是可以从原文派生的辅助数据。进程重启或一次模型请求的上下文变短,不会因此清空原始聊天;修订索引也不需要把过去的交流重新编写一遍。

四类上下文各有用途

最近对话负责眼前的连贯性。程序最多取最近十二轮作为候选,再按实际长度决定哪些完整轮次能放进请求。十二轮是候选上限,不是无论多长都会全部发送。

明确偏好与纠正负责少量相对稳定的信息。程序为能识别的明确表达保留原话、来源和时间;同一主题出现更新、更明确的纠正时,优先采用新的记录。它不是通用的事实理解器,不能保证识别所有隐含表达。

相关旧事负责补上近期窗口之外的内容。当前文字会成为检索线索,通过中文双字组合和英文词项寻找旧原文。对于“那后来呢”这类接话,还可以借用前两轮用户发言中的词项,不必只拿几个缺乏信息的字去搜索。

滚动原文摘录则提供中间距离的补充线索。它来自近期用户实际说过的话,而不是每轮让模型重新编一份越来越长的个人简介。这样可以减少“上次总结推测了一件事,下次又把推测当成事实”的积累。

这四类资料最终会去重,保留有来源的片段。最近对话里已经出现的内容,不必在多个记忆区重复占用空间。

原文优先于熟悉感

历史记录需要带时间。旧聊天里的“今天”“明天”,属于那条消息发生时的语境,不能因为今天重新检索出来,就把过去的安排搬到现在。

另一条规则是区分谁说过什么。Alice 自己生成的句子不能自动升级为用户事实;只有确认发送成功的回答才会作为双方对话的一部分带入,不能把没有发出去的话当成我已经听过的内容。

纠正也有范围。对于程序能识别的同一主题,更新的明确说法优先;没有命中规则的细节,仍然需要原文和模型理解。临时抱怨不应该轻易沉淀成长期性格标签。

上下文按完整记录装配

当前私聊请求对最终序列化内容设置了 56,000 字节上限。这里是字节预算,不是模型的 token 窗口大小,也不是“可以无限聊天”的容量承诺。

打包器优先保留当前消息与近期完整对话,再加入相关原文、明确偏好和滚动摘录。它会检查 JSON 实际序列化后的大小,因为转义符也占空间。

超过预算时,移除的是整条旧记录,而不是把 JSON 从中间截断,或把一个纠正只保留前半句。这次没有进入模型的旧原文仍在存储中,没有因为上下文裁剪而删除。

存储本身也有容量保护,达到限制会停下来,而不是静默丢掉旧聊天。持久化不能代替独立备份,更不能保证任何故障下都不丢数据。

所以,持续记忆的目标是让有依据的回忆更容易发生,不是制造“她永远记得一切”的错觉。换一种说法可能检索失败,复杂经历可能需要我补充;记不起来就承认不确定,比编出一段共同经历更可靠。

表达风格与私人记忆如何分开

一个能记住内容的助手,也可能说话像固定模板。反过来,只有风格提示词、没有真实上下文,也容易显得热情却空泛。

我把表达配置与事实资料分开:前者决定说话方式,后者提供回答依据。私人表达配置保存在网站的站长区域,由已授权的微信回复和私密整理流程复用,同一份构建逻辑再根据“回复”或“整理”补充不同的任务要求。

这样,同一个 Alice 从聊天转到整理时,可以保持表达上的连续性,却不需要把聊天语气硬塞进每一篇记录。整理仍然要遵守来源、结构和事实要求,不能为了写得动人而增加没有发生的情节。

我也发现,自然对话不需要每句话都用问题收尾。表达一个判断、回应一个具体细节,或者让一句话停在那里,都可以是完整的回应。表达特点应当服务于交流,而不是变成每次重复的口头禅。

公开网站的 Alice 不读取这份私人人格或微信记忆。她的公开短会话保留在网页应用内存里,进入私有管理流程或身份变化后会清理相关上下文;这与微信侧的持久对话不是同一个存储层。

同一个角色,可以有连续的私人表达,也可以在公开场合只依据公开资料工作。角色一致性不是把所有上下文混在一起的理由。

从聊天来源到审核后的手记

同步文字、生成摘要、发布文章,是三次不同的状态变化。把文字保存到网站,只说明它进入了私有来源库,不等于已经整理,更不等于愿意公开。

整理任务接收有来源的文字,区分双方发言和发送状态,要求把原文明示的事实与 Alice 的理解分开。生成结果还要通过结构检查,之后才保存为私密收件箱中的记录。

这里不会把摘要模型当成另一个可以调用网站工具的执行器。它的任务是整理限定的输入,不是修改其他文章,更不能因为聊天原文里出现一句“直接发出去”,就获得发布权限。

手动选择文字与定时整理共用这条私密边界。定时任务按配置时间和上海时区领取未整理来源,并为任务去重;当前每批最多五十轮、总计 18 KB,因此它是有范围的整理,不是保证每天的所有聊天都会被一次完整总结。过长来源需要保留待处理,不能悄悄截断后声称已经整理完毕。

接下来由我检查内容、修改措辞和理解,再决定是否公开。通过审核的版本进入独立的 Alice 手记,不混进博客、随记和项目,也不携带完整聊天或内部来源标识。

发布的是经过选择的版本,而不是让公开页面实时读取私密数据库。这使“可以整理”和“可以公开”保持为两种不同权限。

权限与用量控制为什么放在模型之外

提示词可以告诉 Alice 应该怎样回应,却不能作为身份系统。聊天里说“我是站长”,或让模型输出一个管理员角色,都不能改变服务器验证的结果。

站长身份来自登录验证和管理表检查;草稿接口还会再次检查权限,并通过当前用户的数据库访问策略执行。公开知识库则从加载入口就排除私有资料,而不是先把所有数据发给模型,再要求它不要泄露。

同样,检索到的文章和历史对话属于引用资料,不是高优先级指令。即使资料中混入“忽略规则”之类的文字,也不应该改变工具白名单、身份或发布流程。模型层面的约束需要与数据隔离、参数校验和执行权限一起工作,不能单靠一句“请保密”。

用量管理也在发出请求之前发生。系统先按输入与输出上限预留费用,允许后才调用模型,取得有效用量再结算。遇到超时或未知结果,不立即释放预留额或自动重试,因为供应商可能已经处理了请求。

公开访客有共享的日额度,站长私聊不设金额硬上限,但仍有请求频率、单次输出和异常用量保护。这样可以把“正常使用的自由”和“无限重复调用的风险”分开,而不要求模型自己决定什么时候该停止花钱。

性能与可靠性如何检查

对于博客,助手不应该成为每次打开文章的负担。初始页面只加载轻量入口,第一次打开 Alice 时才动态导入完整控制器;同一次加载会复用,而不是连点几次就启动多套逻辑。

页面切换同样需要清理。旧页面等待中的请求、事件监听和账户订阅不能继续影响新页面,迟到的回答也不应该突然盖到已经离开的文章上。初始脚本体积和按需加载边界都有构建检查,但静态体积检查不等于真实网络延迟测试。

测试重点不是只问模型“你好”能否得到回答,而是验证容易出错的边界:

  • 草稿和未知页面不能进入公开检索,未授权用户不能读取或保存私有内容。
  • 未知工具、额外参数、重复步骤和不存在的文章地址不能通过计划校验。
  • 旧记录不能借用未来消息,新纠正不能被上下文裁剪成相反意思。
  • 重复平台事件和中断后的不确定结果不能触发盲目重发。
  • 进入私有流程或切换身份后,旧公开会话不能继续混用。
  • 摘要保存不能绕过人工审核直接变成公开手记。

这些检查验证的是程序规则。它们不能证明每一次回复都自然,也不能替代真实消息、定时任务和恢复流程的验收。对话质量还需要持续使用中的反馈;能运行和用起来合适,是两件事。

当前范围与源码阅读顺序

这版没有把所有可能的能力都接上。微信目前只处理文字;记忆依赖规则与关键词,不是向量语义记忆;摘要按批次整理,不保证一次覆盖全部历史;网站助手也不能任意运行代码或自动发布内容。

项目中有受限的只读 GitHub MCP 实现,但仓库默认配置并未启用。它不是公开 RAG 的组成部分,更不能据此说 Alice 已经自由接入所有外部工具。是否连接一个服务,与代码里是否存在连接实现,需要分别说明。

如果想继续读实现,可以从 博客源码仓库 按下面的顺序看:

  1. src/lib/alice/action-catalog.ts 与 plan.ts:有哪些动作,计划怎样校验。
  2. src/lib/alice/knowledge.ts 与 retrieval.ts:公开内容从哪里来,如何检索。
  3. src/scripts/alice-loader.ts 与 alice.ts:按需加载、交互确认和页面切换。
  4. scripts/alice-bridge/direct.mjs:一条微信消息从接收到同步的处理过程。
  5. scripts/alice-bridge/direct-state.mjs 与 direct-model.mjs:原文存储、记忆检索和上下文装配。
  6. src/lib/alice-direct/、src/lib/alice-digest/ 与 src/lib/alice-records/:私密整理和公开版本的边界。

做完这些,我对 Agent 的理解更具体了:模型提供理解与表达,工具让意图成为操作,记忆让信息跨越一次请求,而状态、权限和确认让这些能力能够可靠地配合。

现在我更想先把这版用起来,不再为了“像 Agent”继续堆功能。让 Alice 帮助阅读、延续交流、整理值得保留的片段,网站继续承载我自己的写作。她不需要假装无所不知;有依据地回应,把重要的选择留给我,就已经很有意义。