Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pi Telegram Agent 用户指南

English guide · 返回项目 README

本指南面向部署和使用者。你不需要先理解内部架构,就能把 1..N 个可配置 AI 群友接入一个 Telegram supergroup,并用 Pi 原生界面观察和操作。

最短路径

  1. 准备群 ID 与 BotFather token,并用 Pi /login/model完成模型登录与默认选择。
  2. 在仓库中运行 bun run pi
  3. 在 Pi 执行 /tg config,完成后等待 all-bots feed 自动打开。

按顺序阅读:

产品边界

  • 一份 deployment = 一个 Telegram supergroup + 1..N bots。
  • 关闭 Pi 不会停止 daemon;Telegram 群才是实际聊天场所,Pi 是本机观察和控制界面。
  • 多群需要隔离工作目录和全部 data/session/process 资源;当前不支持同目录并行多群。
  • telegram.config.ts 是会执行的受信本机代码,不是下载配置的沙箱。
  • tracked 文件不含有效 credential 或真实 deployment persona;旧 Git 历史仍可能包含已移除的 persona。

安装与首次配置

本项目让 1..N 个各有 persona 的 AI bot 住进一个 Telegram supergroup。设计目标是快(daemon 常驻、消息直接路由)、上下文优化(provider prefix cache 让重复上下文不重复计费)、简洁(一套配置、没有中间概念),省成本是这三者的结果。

配置只有一套:telegram.config.ts 放非 secret 设置,.env 放 token 等 secret,模型认证由 Pi 管理。下面的向导会在最后一步替你写好这些文件。

1. 准备本机环境

安装 Bun,然后 clone 仓库并进入项目目录:

git clone <repository-url> pi-extension-telegram-agent
cd pi-extension-telegram-agent
bun run pi

bun run pi 会在项目 Pi CLI 不存在时执行 frozen-lockfile 安装,再启动锁定的 Pi 0.84.1。它不读取相邻的 ../pi 源码。若你需要显式预安装,可运行 bun install --frozen-lockfile

2. 准备 Telegram

对每只 bot:

  1. 在 BotFather 创建 bot 并保存 token。
  2. 使用 BotFather 的 privacy 设置关闭 group privacy,使 bot 能收到普通群消息。
  3. 把 bot 加入目标 supergroup;如果群权限限制发言,为它开放发送消息权限。
  4. 准备 supergroup 的数字 ID。向导接受裸正数、负数或 -100... 形式并统一归一化。

不要把 token 发进群、issue、日志或 Git。每只 bot 必须有自己的 token env key。

3. 准备 Pi 模型

在项目 Pi 会话中:

  1. 运行 /login,完成 Pi 原生 provider 登录。
  2. 运行 /model,选择默认provider与聊天模型。默认 vision 模式下主模型不需要图片输入;如果打算用 media.mode: "context" 让主模型直接看图,所选模型必须支持图片输入,否则daemon启动会以image_input_unsupported失败。即使交互式Pi会话使用其他thinking level,Telegram runtime仍默认reasoning off,除非在telegram.config.ts显式覆盖。

Telegram 项目读取 Pi 合并后的 global/project 模型设置与 Pi auth store,不把模型 credential 复制进本仓库。首次向导默认 tools.search: false,所以不需要先申请 TinyFish key。

4. 运行 /tg config

配置文件缺失或无效时,/tg config 仍会出现在 Pi 帮助和补全中,不需要先启动 daemon。

向导打开输入框前,会先在本地用 Pi catalog/auth 预检并显示 provider/model:thinking;这不会调用模型。随后依次要求:

  1. 中文或 English public persona template;
  2. Telegram supergroup ID;
  3. 本机 bot ID、Telegram 显示名、token env key 名与 BotFather token;
  4. 最终写入确认。

Pi 当前原生 input dialog 没有密码遮罩。BotFather token 输入时可见;请使用私密终端,不要录屏或共享屏幕。向导不会把它写进 notification、进程参数、Pi session 或 provider context。provider 认证留在 Pi 中,这里不会再次询问。

按 Esc 取消任一步都不会留下半份配置。确认后会原子写入:

  • .env:Telegram token,mode 0600,Git ignored;
  • telegram.config.ts:Telegram deployment字段;向导固定刚刚通过预检的Pi provider/model,其余字段省略并继承有界默认(reasoning off、search/run_js/vision关闭、media.mode为默认的"vision"、有界context/cache/retention),mode 0600,Git ignored;
  • personas/<bot-id>.local.md:本机 persona,mode 0600,Git ignored。

5. 确认 ready

向导先用 production loader 验证完整配置,再执行受控 daemon restart。只有命令退出成功并明确报告 daemon ready 时,Pi 才打开 all-bots feed。

如果 Pi 缺少有效默认模型或认证,预检会在任何 dialog 和写入前停止。先用 Pi /login/model 修复,再运行 /tg config

如果 Telegram credential 或网络导致 readiness 失败,已验证文件会保留,界面不会声称已连接。按顺序执行:

/tg status-daemon
/tg restart

必要时查看 data/daemon.log 的脱敏诊断。不要为了重试反复覆盖配置。

已有配置

再次运行 /tg config 时可以:

  • 验证现有 deployment;
  • 用 Pi editor 编辑项目根 telegram.config.ts 原文,确认后保留完整 backup;
  • 对默认 source 明确执行“备份并替换”;
  • 取消并保持所有字节不变。

下一步:配置与添加 bot

配置与添加 bot

/tg config 是推荐入口。需要添加 bot 或调整高级项时,编辑 ignored telegram.config.ts,然后执行 bun run restart 或 Pi 中的 /tg restart

文件边界

文件保存什么是否提交
telegram.config.ts群、bot、Pi 模型选择、routing、成本上限、tools
.envTelegram/TinyFish token 与 router secret
personas/*.local.md真实 deployment persona
telegram.config.example.tspublic typed schema example
personas/template.*.mdpublic generic persona templates

.env 使用项目自己的冒号格式,不是 dotenv 的等号格式:

telegram_bot_token: 123456:REPLACE_WITH_BOTFATHER_TOKEN
router_secret: REPLACE_WITH_RANDOM_LOCAL_SECRET

成本优先的单 bot 配置

import { defineConfig } from "./src/config.ts";

export default defineConfig({
  group_peer_id: 1234567890,
  provider: "openai-codex",
  model: "gpt-5.6-luna",
  reasoning_effort: "off",
  cache_retention: "short",
  compaction_model: "openai-codex/gpt-5.6-luna:low",
  context_window: 65_536,
  max_suffix_tokens: 12_000,
  max_message_tokens: 4_096,
  media: {
    mode: "vision", // 默认;改 "context" 时主模型必须支持图片输入
    max_images_per_turn: 4,
    download_concurrency: 2,
  },
  vision: {
    enabled: false,
    foreground_media_limit: 2,
    concurrency: 2,
  },
  telemetry_retention_days: 90,
  raw_update_retention_days: 30,
  message_event_retention_days: 365,
  bots: [{
    id: "friend",
    name: "Mochi",
    token_env: "telegram_bot_token",
    persona_path: "personas/friend.local.md",
    routing_p: 0.1,
    sticker_sets: [],
    tools: { send: true, search: false, run_js: false },
  }],
});

完整注释和高级默认见仓库根的 telegram.config.example.ts。TypeScript config 是受信本机代码;只编辑你自己维护的文件,不执行来源不明的片段。

添加第二或第三只 bot

  1. .env 增加独立 token key。
  2. 从 public template 复制新的 ignored persona。
  3. bots 数组追加对象;id 必须唯一且只含字母、数字、_-
  4. 执行受控 restart,并在 Pi 用 /tg attach <id>/tg status <id> 验证。
{
  id: "helper",
  name: "Nori",
  token_env: "helper_bot_token",
  persona_path: "personas/helper.local.md",
  routing_p: 0,
  tools: { send: true, search: false, run_js: false },
}

routing_p: 0 只关闭普通消息的概率抽样;mention、直接 reply 和配置名称仍是明确触发。所有 bot 的 routing_p 总和必须 <= 1,配置顺序决定确定性概率桶顺序。

每个 bot 的 Telegram poller、agent session、模型选择、state 与 telemetry 都隔离;共享的是一个 Pi model runtime/auth snapshot、目标群与 canonical SQLite history。

Pi 模型与 tools override

公开示例固定了一组成本优先profile:Luna、reasoning off、short cache retention,并用Luna low做compaction。向导会把已经通过Pi预检的provider/model固定进新配置,因此以后改变Pi默认值不会静默改变这个deployment。旧手写配置仍可省略这两个字段兼容继承Pi合并后的默认值;但省略reasoning_effort表示off,不再继承Pi的thinking level。daemon会通过Pi原生resource loader读取用户级已安装provider extension,因此插件提供的模型、能力与费用元数据和交互式Pi一致;项目extension不会进入bot session。单bot可以覆盖到另一个catalog entry;切换provider时必须同时填写provider和model。认证始终来自Pi,不来自本配置或.env

reasoning_effort不仅必须是Pi全局枚举,还必须是所选模型实际支持的档位。Pi SDK本身会把不支持的值静默夹到最近档位;Telegram agent为避免费用、行为与状态显示不一致,会在任何Telegram/provider调用前拒绝启动,并列出requested与supported值。main bot、compaction_model及vision模式下启用的辅助视觉模型执行同一检查。请在Pi /model查看可选档位;例如deepseek-v4-flash只接受offhighmax

摘要只使用配置的 compaction_model,失败不会切换到主模型。provider_retries 是聊天和摘要请求可重试错误的最大额外尝试次数;0 会关闭 Pi 与 adapter 的额外重试。每次请求的 timeout 覆盖 stream 创建与消费全过程;取消或 daemon 关停会终止摘要请求。

自定义OpenAI兼容端点(自建网关、代理等)通过Pi原生的~/.pi/agent/models.json注册,不需要任何项目侧扩展:在providers里声明baseUrlapi: "openai-completions"apiKey(可写成"$ENV_VAR"引用环境变量),并为每个模型显式声明input(如["text","image"])、contextWindowmaxTokens。注册后在Pi /model确认可用,再把provider/model写进本配置;打算用media.mode: "context"时模型声明必须包含image输入,否则daemon启动时fail fast。

以下边界都有默认上限:

  • max_suffix_tokens: 12000max_message_tokens: 4096限制每轮新增的Telegram provider context;
  • 主聊天默认cache_retention: "short",compaction使用配置的廉价task model且关闭provider cache retention;
  • context_window(默认65,536)限制主模型的有效上下文窗口,并把compaction_threshold的上限压到context_window − 16,384
  • media.mode(默认"vision")决定媒体如何到达模型。vision模式:vision.enabled开启后,辅助视觉模型(auxiliary_visual_model)为每个媒体生成文字描述;描述持久化在media.vision列、所有bot共享,以immutable media-update event追加进上下文,主模型读到的是[图片: 描述]占位,因此主模型不需要图片输入;每轮最多处理foreground_media_limit个未缓存媒体,所有bot共用一个最多concurrency个active job的FIFO门。context模式:完全不调用视觉模型,照片与静态sticker直接作为图片进入主模型上下文,视频(含视频sticker与GIF动图)本地抽取1-3张代表帧;视觉描述绝不进入上下文——已持久化的历史描述也不会以 media-update event 注入;主模型必须支持图片输入,否则启动直接报image_input_unsupported;每次模型调用最多附media.max_images_per_turn张图(默认4,每张约1.1K token),超出上限或上下文预算的媒体自动降级为文字占位,下载/抽帧并发由media.download_concurrency(默认2)控制。两种模式下voice/audio/非视频document/TGS动态贴纸都保持文字占位(当前模型API的硬限制),视频抽帧都要求daemon主机PATH中有ffmpegffprobe;缺失时视频只保留文字占位且不占provider token,只给operator安装提示,不影响daemon、聊天、图片或sticker发送;
  • telemetry、raw update、immutable message event默认分别保留90、30、365天。旧event只有在所有已知bot cursor都消费且没有reply obligation引用时才删除。

model、reasoning、cache policy、persona、tools、serializer、media.mode等cache-visible字段变化都会得到新context fingerprint。受控restart会保留旧session文件,但在restore前创建新session,绝不会用新identity恢复旧context。

tools

  • send:允许agent发送本地转换为Telegram message entities的Markdown文字,以及static/animated/video sticker;普通正文保持普通字重;可选reaction(限Telegram固定reaction emoji)落在reply_to消息上表态,不能替代必须的文字回应;
  • search:启用同一个TinyFish工具的有界网页检索与单页读取,需要 .env 中由 tinyfish_key_env 指定的TinyFish key;
  • run_js:启用受限的确定性计算工具;默认关闭,因为模型提供的JavaScript即使经过sandbox仍有残余风险。

search与run_js只有字段显式为true才启用,不存在旧版隐式默认。启用search前把TinyFish credential加入.env(默认key名为tiny_fish_api_key);它与Pi模型认证无关。启用后agent可显式搜索,或在回答确实需要页面内容时读取一个public HTTP(S) URL;不会自动抓取群里的每条链接,也不支持登录态、cookie或private/local地址。

Routing 与管理命令

  • mention > reply > 配置名称 > probability;bot 消息不会触发 bot-to-bot run。
  • routing_p 是普通 human 消息的回应机会,不是最终群发言配额。每条 eligible 消息只生成一个确定性值并至多落入一个累计桶;当总和为 1 时,每条 eligible 消息恰有一个 probability target。
  • sampling_cooldown_ms 只约束 probability 路径;默认 2000,0 表示关闭冷却。
  • probability target busy 或 cooldown 时会直接 skip,不改投另一只 bot;mention/reply/name 走明确触发路径。即使成功开始,persona 仍可选择沉默,发送也可能失败,所以群内公开消息比例无需等于 routing_p
  • telegram_admins 默认空,拒绝 Telegram 群内 compact/set。需要时优先加入你自己的正整数 numeric user ID;不要复制示例占位值。
  • Telegram /set <routing_p|cooldown_ms> <value> 写穿 telegram.config.ts(原子写入 + 全量校验,任何一步失败回滚文件),成功后立即更新内存 effective 值,重启后仍然生效。

只读诊断当前 deployment 用 bun run debug(见运维故障排查)。

多群

一份 deployment 只有一个 group_peer_id。多个群必须使用隔离工作目录及 data/session/DB/pid/socket,不能在同一 checkout 只换配置文件并行运行。

下一步:在 Pi 中聊天和观察

图片上下文超预算时,本次压缩临时缩小原文保留窗口,结束后恢复配置。共享媒体文件由统一引用回收管理。未完成路由交付的 raw update 暂不受 retention 删除;control 消息排除身份永久保存。

本次 cache schema 20 升级会自动创建新上下文 epoch,旧 session 文件保留;无需手工改库。路由按全局 @mention(含 caption)优先于 reply,不取决于 bot 排列顺序。

在 Pi 中聊天和观察

打开 feed

daemon 长期运行;Pi 可以随时打开或关闭:

bun run pi

首次 /tg config ready 后会自动 attach 全局 feed。以后可手工选择:

/tg attach             # 群消息 + 所有 bot 的 LOCAL 事件
/tg attach friend      # 群消息 + friend 的 LOCAL/usage
/tg more               # 加载一页更早历史
/tg detach             # 断开 live socket,保留已显示 transcript

Telegram feed 是一个 TUI-only Pi custom entry。滚动、resize、选择、主题和图片布局由 Pi 原生组件负责;editor 上方的一行统一显示 feed scope、连接与 compose 状态,attached 期间则用 Pi 官方 footer API 显示路径和 Telegram usage/model,隐藏 operator usage 行。消息不会因为展示而进入当前 Pi agent 的 provider context。

/tg 后使用 Tab 或原生选择菜单。bot 参数由当前已验证配置动态补全。

直接发送

attach 后 Pi editor 默认直接发 Telegram:单 bot filter 直接使用该 bot;全局 feed 若有多个 bot,每次提交都会打开 Pi 原生选择框;只有一个 bot 时不弹框。

/tg attach friend       # 直接以 friend 发送
/tg attach              # 多 bot 时每条消息选择身份
/tg compose friend      # 可选:固定为 friend,连续发送不再选择
/tg compose off         # 暂时把 editor 交还 Pi
/tg compose             # 恢复当前 feed scope

feed header 会在 attached 后显示 send as ...choose bot on send,选择与发送时原位更新。取消选择会恢复逐字节相同的原文且不发送。compose 只拦截 interactive editor;RPC 或 extension 输入仍交给 Pi。附件不会被偷偷降级成只发 caption。

明确失败会恢复 editor 原文。如果 ACK 丢失或连接在发送中断开,结果是 unknown:

  1. compose 自动关闭;
  2. 插件不自动重试;
  3. 先检查 Telegram 群;
  4. 只有确认消息不存在时才再次发送。

这条边界防止“远端已成功、本地确认失败”导致重复消息。

状态

/tg status             # 全局 Telegram telemetry
/tg status friend      # lifetime + latest 明细

Pi /tg status 与 Telegram /status 共用统一 telemetry 口径:lifetime 来自 SQLite llm_runs 保留期并包含 compact 调用,详细状态的 current context 直接取 daemon 中对应 Pi session 的实时 used/window/percent,不是最近 run 或历史 prompt 总和。attached footer 保持原有 latest-run 口径和 Pi 原生的路径与 usage/model 信息顺序;compose guidance 留在 feed header,/tg detach后恢复Pi默认footer。

本地事件、stream 与媒体

  • assistant thinking/text/tool partial 会在同一 Pi native card 原位更新,结束后由持久 LOCAL/Telegram event 接替;partial 不写 SQLite。
  • bot 没有调用 send 时的 local assistant text 只在 feed 可见,不会发群。
  • vision模式(默认)下,vision.enabled开启后,辅助视觉模型为照片、sticker和视频生成文字描述,只在真实bot run需要时生成;视频抽最多3张固定代表帧,并在一次vision调用中综合理解;描述按媒体持久化并跨bot共享,以[图片: 描述]进入主模型上下文,UI本身不会额外触发provider。
  • context模式(opt-in)下,主模型直接看到上下文里的图片,没有辅助视觉模型和文字描述——历史 vision 时代持久化的描述同样绝不注入上下文:照片、静态sticker作为图片进入上下文,视频(含视频sticker、GIF动图、video note)抽取1-3张代表帧;每次模型调用最多附media.max_images_per_turn张图,超出上限或上下文预算的媒体降级为文字占位。
  • 两种模式下语音/音频/非视频文件/TGS动态贴纸模型都看不到内容,只有文字占位——这是当前模型API的硬限制。
  • 媒体属于共享群消息:全局与任一单bot feed都会显示对应媒体(vision模式的描述显示在对应图片正下方);单bot filter只限制LOCAL事件与usage。
  • 用户和bot发出的static photo/sticker共用本地展示准备链路;video、animation、video note、video document与video sticker以媒体placeholder显示,vision模式下可获得视觉描述,context模式下其代表帧只进入模型上下文。inline image是否可见仍取决于Pi terminal capability,文字、media label和视觉描述保持可读fallback。

网页搜索与链接读取

为当前bot启用tools.search并配置TinyFish key后,agent可按需调用同一个工具:用query取得最多5条短结果,或读取一条public HTTP(S)链接。群里的链接不会被自动抓取;只有回答需要页面正文时才显式调用。

网页正文先受8,000字符本地护栏约束,再受2,048 provider tokens上限约束,并带有固定“不可信网页内容”边界。页面里的命令不会成为agent指令;登录态、userinfo、localhost、private/link-local地址会在请求前拒绝。事件和日志只保留hostname、字符数和固定结果类别,不保留URL path/query/fragment或正文。

Pi 内 daemon 命令

/tg start
/tg restart
/tg stop
/tg status-daemon

/tg restart 会关闭 compose 和旧 IPC,受控替换整个 deployment。明确 ready 后恢复已有 feed;失败时保留 transcript 并给出诊断。

下一步:日常运维

日常运维

规范命令

bun run start
bun run status
bun run restart
bun run stop
  • start:后台启动并等待pid/socket readiness;配置错误会在任何 bot polling 前失败。
  • restart:串行停止同 deployment 的pid owner与孤儿进程,等PID、pid file与socket释放后只启动一次替代进程。
  • status:验证PID确实属于当前仓库daemon,不只相信pid file。
  • stop:SIGTERM优雅停止所有bot、agent与IPC资源。

日志位于 data/daemon.log。controller只回显有界、credential-redacted末尾;不要把完整 .env 或未经检查的日志贴进 issue。

配置变更

配置不热重载。编辑 telegram.config.ts.env 或 persona 后运行:

bun run restart

已有配置也可在 Pi 运行 /tg config 进行验证或受保护编辑。replace 会留下本机 .bak-<nonce>;确认新 deployment ready 后再按你的备份策略处理,不要在同一任务顺手删除。

数据与备份

默认持久资源在 data/ 与项目本机 session 目录。SQLite 是 canonical history;Telegram 不是历史恢复来源。

备份前:

  1. bun run stop
  2. 确认 bun run status 不再报告 running;
  3. 复制整个 deployment 的配置、persona、data 与 session 资源到访问受控的位置;
  4. 保护 .env 和 private persona,不上传公共 artifact。

不要只复制DB后在同一目录并行启动两份daemon。

Telegram 群内控制

公开只读命令:/help/status

/status 只展示实际接收命令的bot;用/status@bot_username可明确指定。富消息展示 runtime 状态、provider/model/effective reasoning、当前 context/window/%、按1,024 tokens一个红/紫/棕/蓝/绿方块表示的system/tool/摘要/messages/free分段、平均tok/s/send/think耗时、最近主对话请求、SQLite 保留期累计、缓存命中率、延迟/费用、路由与最近一次 compact。方块条单独一行,图例逐项显示在下面。当前context直接读取Pi session;compact后到下一次主请求前显示unknown,不会继续展示旧epoch数值。它与 Pi /tg status 共用统一 telemetry 口径。若 Telegram 在创建消息前明确拒绝富消息方法或格式,daemon 会改发一次独立生成的纯文本版本;超时、限流和服务端错误等结果不确定时不会重发,以免重复回复。

telegram_admins allowlist 才能运行:

/compact
/set <routing_p|cooldown_ms> <value>

命令默认作用于接收消息的 bot,带 @bot_username 后缀时定向到对应 bot。这些命令由确定性 control plane 消费,不进入 persona/provider context。compact 会调用现有辅助摘要模型,可能产生费用;busy bot 不会被 abort。set 写穿 telegram.config.ts,新值重启后仍然生效。

真实验证

默认 bun test 不调用 Telegram/provider;test preload 会机械拒绝一切非 loopback 网络访问。需要真实网络时,脚本强制选择bot:

bun run scripts/smoke-pi.ts --bot friend
bun run scripts/e2e-agent.ts --bot friend
bun run scripts/e2e-compaction.ts --bot friend

这些操作可能产生费用或群消息。运行前先读daemon runbook,记录bot、预期副作用与回滚步骤。

为什么必须隔离工作目录

当前一个工作目录只支持一个群 deployment。这不是临时的UI限制:以下资源都由工作目录拥有,并没有deployment namespace:

  • 单一group_peer_id和SQLite canonical history,包括每只bot的consumed cursor、visible refs与reply obligation;
  • agent session与context epoch;
  • 每只poller的Telegram update offset,以及共享router secret;
  • daemon PID、control lock与Unix socket。

所以,在同一checkout中只换配置文件并行运行不会形成两个deployment。它可能把一个群的history送入另一个群的模型context、用错误offset跳过update,或让两个daemon争抢同一PID/socket。

第二个群应使用独立clone或worktree,并分别保存.env、config、persona和Telegram bot tokens;同时隔离整个data/DB、session、PID/lock/socket与daemon工作目录。不要只复制DB,也不要让两个目录指回同一data路径。

这种边界符合项目的极简原则:用文件系统隔离这个已有、可检查的安全边界,而不是为尚未要求的多租户场景增加namespace、热加载和第二套控制面。完整原则见成本设计概览;技术权威见项目说明

下一步:故障排查

故障排查

先从症状选择安全的下一步;不要删除data、pid或socket来“试一下”。完整进程恢复规则见daemon runbook

bun run pi 无法启动

运行:

bun install --frozen-lockfile
bun run pi --version

预期版本是项目锁定的 Pi 0.84.1。若安装失败,保留错误输出并修复registry/network;不要改成未锁定的全局 Pi 来掩盖问题。

/tg config 不在菜单中

确认你从仓库根运行 bun run pi,并且 package discovery 加载了 .pi/extensions/tg-extension.tsconfig 是静态命令,不依赖已有 config;若完全缺失,优先排查Pi/package加载,而不是手工创建空配置文件。

向导拒绝配置

  • 字段错误:按notification列出的字段修复;值不会回显。
  • 已有文件:选择validate/editor,或明确确认backup-replace;取消不会改字节。
  • Pi model preflight:退出向导,用 Pi /login/model 修复后重试;deployment 文件尚未写入。

配置有效但 daemon 未 ready

/tg status-daemon
/tg restart

再检查 data/daemon.log。常见原因是Telegram token错误、网络不可达、Pi login/default model已变化、model不在Pi catalog、media.mode: "context"下主模型不支持图片输入(image_input_unsupported,用Pi /model换一个支持的模型或回到默认vision模式)或bot未加入目标群。有效配置会保留;不需要重新粘贴token。

daemon starting 很久

配置了sticker sets时首次Telegram catalog拉取可能较慢。vision模式的描述生成只有vision.enabled显式开启后才工作,不属于默认启动路径。运行bun run status并观察脱敏日志。如果child仍alive,controller不会把60秒等待上限误报成ready;只有socket真实可连接才算ready。

修改model、persona、cache policy、tools等cache-visible字段后,日志出现session ready (new)是预期行为。context fingerprint会阻止用新identity恢复旧session;旧文件仍保留用于恢复或审计。

Telegram 401 或没有群消息

  • 401:轮换或修正对应bot token,确认token_env指向正确key,再restart。
  • 普通群消息不可见:在BotFather关闭该bot group privacy,确认bot已加入正确supergroup。
  • bot不能发言:检查Telegram群权限;不需要为了普通读取授予多余管理员权限。

Telegram 409 / duplicate poller

同一个token正在被另一进程长轮询。运行bun run restart;controller会验证并回收当前deployment的真实daemon与孤儿。不要盲目kill pid file中的数字,也不要并发start。

Pi feed 或 compose 断开

  • no connected Telegram feed:先/tg attach [bot],等snapshot连接完成。
  • unknown bot id:使用/tg 补全,或检查配置id。
  • compose unknown outcome:先查群,不自动重试;确认缺失后再发。
  • /tg detach或关闭Pi不会停止daemon;重新/tg attach即可。

图片没有内联显示

用户或bot新发的static photo/sticker会先显示media label,再由daemon后台下载并在同一Pi卡片原位出现;它不依赖routing或任何模型调用。animated/video媒体保留文字placeholder。daemon启动时会把旧绝对cache path按文件名迁到当前data/media,不存在的记录先清空,再只回填仍被当前上下文、未消费event或待回复义务引用的最新100条static display缺口。

成功compaction后,所有当前配置bot都不再引用的本地媒体cache会按有界批次自动删除;旧Pi卡片因此只剩label是预期行为,不代表消息、vision描述或媒体派生文件记录、Telegram file mapping丢失。restart不会为了历史展示把这些文件无条件下载回来。

若新媒体持续只有label,先在脱敏日志中查media_cache_ready/skip/error的固定category与queue数字,再检查文件是否超过1 MiB、是否为支持的静态图片格式、terminal图像能力和当前项目Pi版本。Pi根据当前capability选择Kitty/iTerm2/native fallback;不要手写terminal escape或绕过Pi组件。稳定复现时只记录terminal、tmux状态、媒体种类、固定outcome和“是否有本地path”,不要附带token、绝对path或私人图片本体。

视频没有视觉描述或代表帧

先运行bun run debugvideo_transcoder_unavailable表示主机缺少ffmpegffprobestart/restart/status也会说明它只用于视频抽帧并给出安装建议。两种媒体模式的视频抽帧都需要FFmpeg。这个告警不阻塞daemon,不会发到群里:vision模式下视频在Telegram下载和provider调用前跳过、不占provider token;context模式下视频(含视频sticker与GIF动图)只以文字占位进入上下文。聊天、图片处理和三种sticker发送保持正常。安装同一FFmpeg发行包后restart即可,之前的失败不会永久缓存。video_probe_failedvideo_frame_extraction_failed表示文件无法由本机工具读取;检查是否超过20 MiB和格式支持。日志不会包含文件path、stderr或视频内容。

搜索或网页读取失败

  • 确认对应bot的tools.search为true,.env中存在tinyfish_key_env选中的key,然后受控restart。
  • invalid_url表示目标不是允许的public HTTP(S) URL,或包含userinfo/local/private/link-local地址;不要通过关闭校验来访问内网。
  • *_timeout*_http_**_response_too_largefetch_*是固定类别。它们只影响当前turn;不会后台重试或换URL。
  • 排查时只记录固定category与hostname。不要粘贴API key、signed URL的path/query/fragment或网页正文。

仍无法恢复

收集以下非敏感信息:

  • bun run status 输出;
  • bun run pi --version
  • data/daemon.log中手工复核过的脱敏末尾;
  • 失败命令、bot id、配置是新写还是替换已有文件;
  • 是否使用tmux、Kitty/Ghostty/iTerm2。

不要提交 .env、真实persona、完整群消息、token、API key或未脱敏绝对路径。

成本设计概览

本项目不承诺固定节省百分比。provider价格、群活跃度、persona长度与模型cache行为都会变化;实际效果以/tg status和SQLite telemetry保留窗口为准。

项目的“极简”是最少机制而不是最少保障:优先少一个状态、接口、网络请求和provider-visible byte,同时保留transaction、timeout、脱敏、测试与可观察性。下面七项就是这一哲学在现有系统里的具体实现,不是未来平台功能清单。

1. 确定性 routing 先决定是否调用模型

mention、reply、配置名称和HMAC概率桶都由本地代码判断。普通消息没有命中时不会创建provider run;目标bot busy或处于probability cooldown时也不会改投另一个bot或补抽。

这样减少的是整个无意义调用,而不是在调用后省几个token。权威行为见架构的 Routing 章节

明确 @、回复或点名后的正常 turn 如果没有公开发送,且原消息仍在上下文,最多补答一次;仍未回应会保留待回复状态,不无限重试。已发送、部分发送或结果未知时不会自动重复发送。普通概率沉默不增加调用。

2. Stable provider prefix 复用cache

共享协议位于最前,persona随后,末尾是有界的 sticker 目录,再之后是固定顺序tool schema,让多只bot尽可能共享逐字节相同的prefix。固定目录每行为 s<id>: <emoji> <描述>(描述取持久化 vision 文本,缺失时逐级降级为 s<id>: <emoji>s<id>;set 名与格式不进入模型可见文本),并有条数上限。另一份最多8条的动态候选只取当前context真正可见、且该bot可发送的最近用户sticker,行格式与目录一致。候选在session中独立保存,provider只在最后一批Telegram消息之后看到一次,不会在每个历史消息批次后重复;suffix预算不足时整体省略。三种格式都通过Telegram原始file id发送。

fingerprint覆盖Pi/provider/model/cache policy、protocol、persona、serializer、compaction、extensions与tools。cache-visible内容变化必须升级schema,并在restore前创建新session/epoch;旧session文件保留,但不会用不同identity恢复。UI、telemetry和operator命令不能偷偷改变provider bytes。权威规则见Cache工程

3. 有界 context 只携带需要的信息

Telegram canonical history与immutable event stream保存在SQLite。每只bot用单调cursor消费event,另一组visible refs只描述当前context仍真正包含完整内容的消息。模型每轮只收到token有界、direct address(@mention / reply / 配置名称点名)优先的event batch;日志、raw rich JSON、UI状态和无界工具输出不会进入provider context。

默认新增suffix上限是12,000 tokens,单event上限4,096。这把本地“完整事实来源”和模型“本轮必要上下文”分开。权威数据流见架构数据模型

4. Compaction 在明确边界换epoch

主模型有效context由context_window限定(缺省65,536);上下文到达可配置的触发阈值(缺省32K,最高context_window − 16,384)时生成摘要,保留最近 compaction_keep_recent token 原文(单位是 token:缺省 1 实际只剩摘要;生产推荐 20,000,约 1-2 个完整 turn),然后进入新epoch。失败或空摘要不会伪造epoch;structured details替换visible refs,业务消费cursor永不回退,也不会重放已压缩历史。

compaction使用配置的廉价task model且关闭provider cache retention,因此不是每轮在线优化器。阈值和保留量由配置决定,效果用telemetry验证。权威规则见Cache工程测试状态

5. 媒体按模式进入模型:默认文字描述,可选直接看图

context 模式的图片在 Pi 准备压缩之前计入保留预算,避免“图片很多、文字很少”时不压缩。摘要模型声明支持图片输入时,待丢弃图片按消息顺序一起进入这一次摘要调用;不支持或文件缺失时保留文字,并记录退化计数。图片增加摘要输入 token,不增加逐图识别调用。输入保守估算超出摘要模型窗口会拒绝压缩并保留原上下文,此时应选择窗口足够的摘要模型。

media.mode选择媒体到达模型的方式。默认vision模式:vision.enabled开启后,辅助视觉模型为每个媒体生成一次文字描述——照片与静态sticker直接描述,视频(含视频sticker与GIF动图)抽最多3张代表帧、一次provider调用综合理解;结果按media identity持久化进media.vision列并在所有bot之间复用,以immutable media-update event追加而不是改写旧context,主模型读到的是[图片: 描述]占位,因此任何聊天模型都能用。UI使用缓存结果原位更新,不额外调用模型。opt-in的context模式完全不调用视觉模型:照片与静态sticker直接作为图片进入主模型上下文,视频本地抽取1-3张代表帧;媒体准备的全部成本是Telegram下载与本机转码。每张图片按固定1,100 token计入上下文预算,同时受media.max_images_per_turn(默认4)与上下文预算双重约束,超出的媒体自动降级为文字占位;准备好的图片记录进media.context_files持久化并跨bot复用。主模型必须声明图片输入,否则daemon启动即失败(image_input_unsupported)。两种模式下voice/audio/非视频document/TGS动态贴纸始终是文字占位——这是当前模型API的硬限制,不是可配置项。

用户和bot的static照片/sticker都先落canonical DB并共用一条有界展示缓存;vision模式的video source只在真实vision turn中lazy准备。SQLite只保存cache-relative文件名,deployment移动后不会继续把TUI绑定到旧绝对路径。同一媒体按media identity只准备一次,pruning与context packing共用这同一份事实。两种模式的视频抽帧都需要FFmpeg;缺失时视频在下载前跳过、只留文字占位,不占provider token,也不影响聊天、图片或sticker发送。固定sticker目录仍是system prompt里的固定前缀(含已持久化的描述),不参与每轮图片挂载。

成功compaction后,daemon会删除所有当前配置bot都不再引用的有界批次本地媒体文件;未消费消息与待回复媒体仍保留。消息、视觉描述与media.context_files、sticker short id和Telegram file mapping不会删除,因此以后重新需要时可以下载source,并继续复用已有的vision结果或派生文件。restart也不会把这批无引用历史自动下载回来。

权威流程见架构的媒体链路章节。

6. 网页只按需读取且结果有界

搜索与读取网页复用一个tool,不增加第四项稳定schema。query只返回最多5条短结果;url只有模型明确需要时才发出一次网络请求,正文先受8,000字符本地护栏约束,再受2,048 provider tokens上限约束。群链接不会eager fetch,所以未使用网页能力的turn没有额外网络请求或动态token。

页面正文是不可信数据,URL安全和日志脱敏在确定性代码中完成,不用额外模型调用。单次fetch仍会产生TinyFish请求并把有界正文加入当前动态context,实际成本取决于调用频率与页面长度。

7. UI 与 telemetry 走side channel

Pi native feed、assistant partial、feed status widget、/tg status和Telegram control使用本地IPC/SQLite/control plane。它们可观察运行状态,但不进入persona或主provider context。

因此打开Pi、滚动历史或查看usage不会消耗一次聊天模型调用。权威边界见Pi原生transcript架构Cache工程

如何评估自己的 deployment

  1. /tg status [bot]或Telegram /status统一 telemetry 口径记录runs、当前context/window、prompt miss/read/write、output、reasoning、latency与cost;“lifetime”只表示配置的SQLite保留窗口。 表示 provider 未返回 cache token 细项时的本地严格前缀估算,不证明 provider 实际命中。每个run仍按原始 provider usage 与当时实际provider/model费率固化cost,本地估算不回算费用;切换模型后的累计值会保留旧模型费用并加上新模型费用,订阅provider的值可能只是等价按量估算。
  2. 比较同类活跃期,不把不同provider/persona/群规模混为一组。
  3. 调整compaction阈值时用bun run debugllm_runs遥测的context数据做依据;不要凭感觉改。
  4. 任何prompt/tool/serialization改动先按开发指南的cache流程验证golden和epoch。
  5. 同时比较每个有效公开回复与每个run的成本;沉默或发送失败的run仍产生provider成本。
  6. 设计新能力时先尝试删除一层、一个tool、一次模型调用或一个动态字段;未经明确需求,不把单群deployment扩成多租户系统。