OpenClaw05_回声机制

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() 允许业务逻辑主动清除记录(如用户要求重发)

实际应用场景

  1. IM 机器人 --- 防止网络重试导致的重复消息
  2. Webhook 处理器 --- 避免重复处理相同的推送事件
  3. 消息队列消费 --- 幂等性保障,防止重复消费
  4. 表单提交防重 --- 快速双击提交按钮的保护

这个工具本质上是一个带容量控制的短期记忆缓存,专门用来回答"我最近有没有处理过这个?"这个问题。


相关推荐
jinanwuhuaguo3 小时前
AI工具终极解构:OpenClaw、Coze、Dify、FastGPT、n8n、LangChain、RagFlow、GPTBots.ai 的万言深度剖析
人工智能·学习·重构·新人首发·openclaw
cuguanren3 小时前
MuleRun vs OpenClaw vs 网页服务:云端安全与本地自由的取舍之道
安全·大模型·llm·agent·智能体·openclaw·mulerun
马克Markorg3 小时前
OpenClaw架构学习与思考
大模型·agent·openclaw·小龙虾
主机哥哥4 小时前
OpenClaw到底是什么?可以做什么?怎样去部署?
openclaw·openclaw部署·openclaw安装·openclaw介绍
蓝队云计算5 小时前
蓝队云揭秘:如何利用云服务器高效养殖龙虾OpenClaw?
运维·服务器·人工智能·云服务器·openclaw
bennybi5 小时前
Openclaw 实践笔记
笔记·ai·openclaw
熊猫钓鱼>_>6 小时前
AI语料投毒与信息证伪:当生成式引擎成为攻击向量
人工智能·ai·agent·geo·skills·agent skills·openclaw
坐吃山猪7 小时前
OpenClaw03_第一句聊天拆解
源码·openclaw