OpenClaw05_回声机制
针对中文版本openClaw进行源码阅读,当前项目针对【回声机制防重】进行解读
文章目录

1-参考地址
2-知识整理
- 1)OpenClaw源码-回声机制防重-源码
- 2)OpenClaw源码-回声机制防重-理解
3-动手实操
1-回声机制防重
造这些词的人真的都是些神人,本来写一个会话防重大家都能理解,又造一个新词[回声EchoTracker]
TypeScript
export type EchoTracker = {
// 记录文本并添加到去重集合,用于回声检测
// text: 要记录的文本内容,可为空字符串或 undefined
// opts: 包含附加配置的选项对象
rememberText: (
text: string | undefined,
opts: {
// 可选的合并后消息体内容
combinedBody?: string;
// 可选的与合并消息体关联的会话键
combinedBodySessionKey?: string;
// 是否记录详细的调试日志
logVerboseMessage?: boolean;
},
) => void;
// 判断给定的键是否存在于最近发送的记录中
has: (key: string) => boolean;
// 从最近发送的记录中移除指定键
forget: (key: string) => void;
// 根据提供的参数构建唯一标识符
buildCombinedKey: (params: { sessionKey: string; combinedBody: string }) => string;
};
// 工厂函数:创建并初始化一个回声追踪器实例
// params: 配置参数对象
export function createEchoTracker(params: {
// 最大允许的条目数量,默认为 100
maxItems?: number;
// 可选的日志输出回调函数,用于详细信息的记录
logVerbose?: (msg: string) => void;
}): EchoTracker {
// 使用 Set 数据结构存储最近发送的文本键值,自动去重且保持顺序插入
const recentlySent = new Set<string>();
// 计算实际有效的最大容量,确保至少为 1
const maxItems = Math.max(1, params.maxItems ?? 100);
// 内部辅助函数:构建唯一的组合键字符串
// p: 包含会话键和合并后的消息内容的参数对象
const buildCombinedKey = (p: { sessionKey: string; combinedBody: string }) =>
// 拼接为格式:combined:会话键:合并后的内容
`combined:${p.sessionKey}:${p.combinedBody}`;
// 内部辅助函数:修剪集合大小,移除旧数据以符合容量限制
const trim = () => {
// 当集合中的元素数量超过最大限制时,循环移除元素
while (recentlySent.size > maxItems) {
// 获取迭代器中第一个元素的值(即最早添加的元素)
const firstKey = recentlySent.values().next().value as string | undefined;
// 如果没有找到键,则中断循环
if (!firstKey) break;
// 从集合中删除该键
recentlySent.delete(firstKey);
}
};
// 实现 rememberText 方法的具体逻辑
const rememberText: EchoTracker["rememberText"] = (text, opts) => {
// 如果文本为空,直接跳过记录
if (!text) return;
// 将基础文本添加到记录集合中
recentlySent.add(text);
// 如果同时提供了合并后的消息体和对应的会话键,则额外添加组合键
if (opts.combinedBody && opts.combinedBodySessionKey) {
recentlySent.add(
// 调用内部函数构建组合键并加入集合
buildCombinedKey({
sessionKey: opts.combinedBodySessionKey,
combinedBody: opts.combinedBody,
}),
);
}
// 如果开启了详细日志模式,则输出当前添加到检测集的信息
if (opts.logVerboseMessage) {
params.logVerbose?.(
// 输出日志:已添加到回声检测集(当前大小)+ 文本前 50 个字符
`Added to echo detection set (size now: ${recentlySent.size}): ${text.substring(0, 50)}...`,
);
}
// 执行修剪逻辑,确保内存占用受控
trim();
};
// 返回实现的 EchoTracker 接口对象
return {
// 注册记忆文本的方法
rememberText,
// 检查键存在性的方法
has: (key) => recentlySent.has(key),
// 移除指定键的方法
forget: (key) => {
recentlySent.delete(key);
},
// 暴露构建组合键的能力
buildCombinedKey,
};
}
2-回声机制防重-理解
场景:防止机器人重复发送相同消息
假设你开发了一个 AI 客服机器人,用户问"今天天气怎么样",机器人回复了答案。如果用户快速点击了两次发送按钮 ,或者网络抖动导致重试 ,机器人可能会重复发送完全相同的回复,造成糟糕的用户体验。
EchoTracker 就是用来**检测并防止这种"回声"(重复发送)**的工具。
代码示例演示
typescript
import { createEchoTracker } from './echoTracker';
// 1. 创建追踪器:最多记 50 条,带调试日志
const tracker = createEchoTracker({
maxItems: 50,
logVerbose: (msg) => console.log(`[EchoTracker] ${msg}`)
});
// ========== 模拟机器人发送消息 ==========
// 场景:机器人要发送一条回复
const replyText = "今天北京晴天,气温15-22度,适合外出!";
const sessionId = "user_123_session"; // 当前会话ID
// 2. 发送前检查:这条消息最近发过吗?
if (tracker.has(replyText)) {
console.log("⚠️ 检测到重复内容,跳过发送");
} else {
// 3. 记录这条消息(表示"我要发了")
tracker.rememberText(replyText, {
combinedBody: replyText, // 完整消息内容
combinedBodySessionKey: sessionId, // 关联到具体会话
logVerboseMessage: true // 记录详细日志
});
// 4. 实际发送消息...
sendToUser(replyText);
console.log("✅ 消息已发送并记录");
}
// ========== 模拟重复触发 ==========
// 5. 假设用户快速双击,再次触发相同逻辑
if (tracker.has(replyText)) {
console.log("⚠️ 检测到重复内容,跳过发送"); // ← 这次会走进这里!
} else {
tracker.rememberText(replyText, { ... });
sendToUser(replyText);
}
// ========== 其他功能演示 ==========
// 6. 使用组合键检测(用于更复杂的场景)
const complexKey = tracker.buildCombinedKey({
sessionKey: "user_123",
combinedBody: "订单查询: #8848"
});
console.log(complexKey); // "combined:user_123:订单查询: #8848"
// 7. 手动遗忘某条记录(比如用户要求"重新发送"时)
tracker.forget(replyText); // 移除记录,允许再次发送
控制台输出
text
[EchoTracker] Added to echo detection set (size now: 1): 今天北京晴天,气温15-22度,适合外出!...
✅ 消息已发送并记录
⚠️ 检测到重复内容,跳过发送
combined:user_123:订单查询: #8848
核心机制图解
用户点击发送 ──┐
│
▼
┌───────────────┐
│ 检查 has() │◄──────── 查 Set 中是否存在
│ 是否重复? │
└───────┬───────┘
│
┌───────┴───────┐
▼ ▼
是(重复) 否(新消息)
│ │
▼ ▼
跳过发送 rememberText() ──► 加入 Set
调用 trim() 维护容量
可选:输出调试日志
关键设计亮点
| 特性 | 说明 |
|---|---|
| 自动去重 | Set 数据结构天然去重,同一文本只存一份 |
| LRU 淘汰 | 超过 maxItems 时,自动删除最早的记录(不是随机删) |
| 双键记录 | 同时记录原始文本 + combined:会话ID:内容 组合键,支持多维度查重 |
| 调试友好 | logVerbose 回调让开发者能看到追踪器内部状态 |
| 手动干预 | forget() 允许业务逻辑主动清除记录(如用户要求重发) |
实际应用场景
- IM 机器人 --- 防止网络重试导致的重复消息
- Webhook 处理器 --- 避免重复处理相同的推送事件
- 消息队列消费 --- 幂等性保障,防止重复消费
- 表单提交防重 --- 快速双击提交按钮的保护
这个工具本质上是一个带容量控制的短期记忆缓存,专门用来回答"我最近有没有处理过这个?"这个问题。