ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AIRI Telegram 机器人:基于 Velin 模板的群聊消息读取提示词设计与实现解析

AIRI Telegram 机器人:基于 Velin 模板的群聊消息读取提示词设计与实现解析 AIRI Telegram 机器人基于 Velin 模板的群聊消息读取提示词设计与实现解析【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi导读本文以 AIRI 仓库中 Telegram 机器人集成的提示词模板 action-read-messages.velin.md 为核心讲解该模板如何在 LLM Agent 的循环中注入群聊上下文最近消息、未读消息、向量检索到的相关消息并通过三种结构化 JSON 输出协议驱动机器人做出「忽略 / 参与发言 / 回复指定消息」的决策。读完本文你将掌握该提示词的完整语义、它与源码执行链路的对应关系以及如何配置环境让这条链路真正跑起来。一、模板定位Agent 循环中的「读消息」动作AIRI 的 Telegram 机器人integrations/telegram-bot包并不是简单的「消息 → 回复」规则机器人而是一个自主 Agent它由 LLM 反复「想象下一个动作」再执行动作、把结果写回上下文形成循环。在 类型定义 中读消息被建模为一个动作export interface ReadUnreadMessagesAction { action: read_unread_messages chatId: string }当 LLM 在imagineAnAction见 actions.ts中决定执行read_unread_messages后主循环 index.ts 的dispatchAction会调用readMessage(...)把读到的内容作为动作结果推入chatCtx.actions随后handleLoopStep继续循环让 LLM 基于新读到的内容决定是否发言。而action-read-messages.velin.md正是readMessage在拿到数据后、喂给 LLM 的那一段「现场还原式」指令——它让模型代入「正在用手机 Telegram 刷群聊」的第一人称视角来消化消息。二、Velin 模板机制SFC props 的动态渲染这个文件的后缀是.velin.md它不是普通 Markdown而是一个 Velin 模板SFC Markdown 混合。文件开头是标准 Vue SFC 的script setup块声明了三个可选 propsscript setup langts const props defineProps{ lastMessages?: string unreadHistoryMessages?: string relevantChatMessages?: string }() /script这三个 props 通过{{ props.xxx }}插值注入正文。渲染入口在 prompts/index.tsexport async function actionReadMessages(props: { lastMessages?: string, unreadHistoryMessages?: string, relevantChatMessages?: string }) { return await (velin{ lastMessages?: string, unreadHistoryMessages?: string, relevantChatMessages?: string }(action-read-messages.velin.md, import.meta.url))(props) }而 utils/velin.ts 中的velin()封装了velin-dev/core的renderMarkdownString读取模板文件、传入数据并渲染出最终的字符串提示词。也就是说模板只负责「教模型怎么看待和使用数据」真正的数据装配发生在readMessage实现里两者通过 props 契约解耦。模板在开头还用一句话做了角色设定You choose to read the messages from the group (perhaps you are already engaging the topics in the group). Imaging you are using Telegram app on the mobile phone, and you are reading the messages from the group chat.即「你选择读取群消息想象你在手机 Telegram App 里刷这个群」引导模型以实时聊天的阅读节奏来理解下面的三块数据。三、注入的三块上下文数据及其源码来源模板正文依次呈现三块数据每一块都对应readMessageread-message.ts中的具体装配逻辑。三者缺一不可分别回答「刚才在聊什么」「哪些还没看」「哪些历史消息和当前话题相关」。1. lastMessages最近 30 条历史消息模板中对应的段落是Previous 30 messages (including what you said): {{ props.lastMessages || No messages }}源码通过 findLastNMessages 从chat_messages表按created_at倒序取最近 30 条再反转成正序随后逐条用chatMessageToOneLine压缩成单行文本含Message ID、发送时间、发送者、是否为回复以及回复对象等字段见 common.ts最后以换行符拼接。这一段给模型提供了「最近的对话脉络」注意其中也包含机器人自己说过的话Yourself标识让模型知道自己的立场。2. unreadHistoryMessages本次请求要读的全部未读消息模板对应段落All the messages you requested to read: {{ props.unreadHistoryMessages || No messages }}这部分来自BotContext.unreadMessages这个运行时缓冲区。机器人每收到一条消息都会先入队、经过去重processedIds、记录到数据库再追加到对应 chat 的未读数组上限 100 条见 index.ts。readMessage用telegramMessageToOneLine把每条原始 Telegram 消息含文本、被回复内容、贴纸描述、图片描述等分支见 common.ts转成单行描述。读取完成后state.unreadMessages[action.chatId] []会清空该群的未读缓冲避免重复读取。3. relevantChatMessages向量检索召回的相关历史消息模板对应段落Relevant chat messages may help you recall the memories: {{ props.relevantChatMessages || No relevant messages }}这是整个链路中技术含量最高的一环。readMessage先对每条未读消息调用embed()生成向量配置EMBEDDING_API_BASE_URL/EMBEDDING_API_KEY/EMBEDDING_MODEL带 5 次重试withRetry随后交给 findRelevantMessages相似度计算按EMBEDDING_DIMENSION1536/1024/768选择对应的向量列用cosineDistance求相似度1 - cosine_distance时间衰减引入timeRelevance 1 - (now - created_at) / 86400 / 30越近的消息权重越高综合打分combined_score 1.2 * similarity 0.2 * time_relevance相似度阈值 0.5每轮取前 3 条上下文窗口对每条命中消息再取它前后各 5 条消息contextWindowSize 5组成完整上下文片段回复追溯自动收集片段中is_reply的reply_to_id回表查出被回复的原始消息一并带上去重通过excludeMessageIds排除已经出现在最近 30 条和未读列表中的消息 ID。这些召回片段按时间正序拼成多行文本帮助模型在长期记忆中「回忆」与当前话题相关的旧对话。注意三个插值都做了空值兜底No messages/No relevant messages保证即使数据源为空提示词结构依然完整不会出现空白占位。四、决策协议三种结构化 JSON 输出模板的核心约束是要求模型以结构化的 JSON作为唯一输出形式并且直接给出决策结果不事先声明「我想参与」原文without telling you willing to participate。完整协议如下1. 选择忽略{ messages: [] }模板原文允许模型「直接发送一个带空数组的messages键对象即可忽略」Feel free to ignore by just sending an empty array within a object with key messages。2. 选择参与发言{ messages: [message content] }模型想发言时直接给出要发送的消息数组。数组支持多条且必须messages键存在但数组为空才表示忽略——这个「空数组 忽略」的约定是后续解析端判断的关键。3. 选择回复某条消息{ messages: [message content], reply_to_message_id: 1234567890 }在数组基础上附带被回复消息的 ID 字符串即可让机器人在 Telegram 中以「回复」形式发出第一条消息。模板还特别叮嘱模型不要额外解释、不要预告直接返回 JSON——这与 actions.ts 中「Respond with the action and parameters you choose in JSON only」的整体风格一致是该项目 Agent 协议的一贯设计。五、输出端的解析与执行提示词与实现的闭环模板的协议能否生效取决于下游解析是否严格遵守。这一步由sendMessage中的 parseMayStructuredMessage 完成先用正则^\{((?)*.*\s*)*\}$判断响应是否整体是 JSON 对象若是用best-effort-json-parser容错解析并过滤掉空白字符串消息messages是空数组 → 返回null→ 主流程直接不发送任何消息对应模板「忽略」分支messages非空 → 逐条发送第一条若带reply_to_message_id则用reply_parameters回复到指定消息send-message.ts输出不是合法 JSON 对象 → 降级为「把原文当一条消息发送」。这些分支全部有单测覆盖见 send-message.test.ts包括空数组返回null、多行数组、带reply_to_message_id的对象、缺失messages键时回退原文等 9 个用例。发送前机器人还会调用sendChatAction(typing)模拟打字、按item.length * 50毫秒模拟输入节奏并把待发言内容交给另一个 Velin 模板 message-split-v1.velin.md 进行「人类化拆分」——控制长文本不要被机械切碎保持 12 条连贯消息的自然语感。至此「读消息 → 理解 → 决策 → 自然发言」形成完整闭环。六、运行前提与环境配置要让read_unread_messages链路真正工作需要满足以下前提均来自 telegram-bot README向量数据库使用 PostgreSQL pgvectors 扩展sql/init.sql 开启vectors.pgvector_compatibilitychat_messages表为 1536/1024/768 三档向量列各建了 HNSW 余弦索引schema.tsEmbedding 服务本地ollama start后ollama pull nomic-embed-text并把EMBEDDING_API_BASE_URL指向 Ollama 的 OpenAI 兼容端点EMBEDDING_DIMENSION必须与实际模型维度一致README 示例为768否则recordMessage与findRelevantMessages都会因维度不匹配而抛错LLM 服务配置LLM_API_BASE_URL/LLM_API_KEY/LLM_MODEL/LLM_RESPONSE_LANGUAGE可选LLM_VISION_*用于图片理解、LLM_OLLAMA_DISABLE_THINK关闭思考链启动方式docker compose up -d启动数据库pnpm run -F proj-airi/telegram-bot start启动机器人见 package.json 的start脚本加载.env与可选的.env.local。另外在 actions.ts 中模型输出动作名时还有别名归一化处理read_messages、get_unread_messages、check_messages、get_messages_from_chat等都会统一映射为read_unread_messages降低不同模型对动作命名不一致带来的失败率——这从侧面说明该动作是 Agent 高频决策路径之一。七、总结action-read-messages.velin.md是 AIRI Telegram 机器人「读消息」动作的提示词核心它以 Velin SFC props 的形态接收三块装配好的上下文最近 30 条、未读消息、向量召回的相关消息用第一人称「刷群」视角引导模型消化信息并通过{messages: []}、{messages: [...]}、{messages: [...], reply_to_message_id: ...}三种 JSON 协议把「忽略 / 参与 / 回复」的决策权交给模型。源码侧readMessage负责数据装配、parseMayStructuredMessage负责协议解析、findRelevantMessages负责向量召回测试用例则锁定了协议边界的正确性。对于想为自托管 AI 角色接入 Telegram 群聊、或学习「提示词模板 结构化输出协议 Agent 循环」架构的开发者这份模板与其上下游实现构成了一个完整、可复现的参考样例。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进