返回博客列表

ECHO_loop:React Agent Loop 的工程化实践

深入探讨 React Agent Loop 在 Minecraft AI 伙伴中的工程化实现,包括 Electron 桌面应用架构、LangChain Agent 调度、Mineflayer 游戏交互和本地记忆系统的设计。

#AI#React Agent#Minecraft#Electron#LangChain#TypeScript

引言

在 AI 伙伴的探索路径中,ECHO_loop 代表了一条纯工程化的思路——采用 React Agent Loop 架构,将 LLM 编排、游戏交互和记忆管理整合到 Electron 桌面应用中。

与 ECHO_feeling 的认知科学架构(七层认知、情感系统)和 ECHO_CLAW 的上下文驱动设计不同,ECHO_loop 的核心是工程实践优先:用成熟的技术栈和清晰的流程来实现智能行为。

为什么选择 React Agent Loop

传统方法的困境

在早期的 Minecraft AI 实现中,我们尝试过两种路径:

  1. 硬编码规则系统:定义大量条件分支,如"看到敌人就攻击"、"血量低就逃跑"。问题在于行为僵化,无法处理意外情况。
  2. 纯 LLM 决策:每一步都让 LLM 决定做什么。问题在于响应速度慢、成本高,且容易陷入循环。

React Agent 的平衡点

React Agent Loop(源自 ReAct 论文)提供了一个折中方案:

观察(Observe) → 思考(Think) → 行动(Act) → 观察...

这个循环的关键在于:

  • 思考步骤可见:LLM 的推理过程显式化,便于调试和干预
  • 行动可中断:每个行动是独立的原子操作,失败后可以调整
  • 上下文累积:每次循环的结果都进入下一轮的输入

系统架构

整体架构图

┌─────────────────────────────────────────────────────────────┐
│                     Electron 桌面应用                        │
├─────────────────────────────────────────────────────────────┤
│  Vue 3 前端                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  对话界面(Chat Panel)                              │   │
│  │  • 用户输入                                          │   │
│  │  • AI 回复显示                                       │   │
│  │  • 行动日志                                          │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  记忆面板(Memory Panel)                            │   │
│  │  • 情景记忆浏览                                      │   │
│  │  • 语义记忆检索                                      │   │
│  │  • 状态统计                                          │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  设置面板(Settings)                                │   │
│  │  • LLM 配置                                          │   │
│  │  • 游戏服务器配置                                    │   │
│  │  • 行为参数                                          │   │
│  └─────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────┤
│                    IPC 通信层                                │
│  • 渲染进程 ↔ 主进程通信                                     │
│  • 事件驱动:onMessage, onAction, onMemoryUpdate             │
├─────────────────────────────────────────────────────────────┤
│                  Node.js 主进程                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │           LangChain Agent (Tool-Calling)             │   │
│  │  • ReAct 循环实现                                    │   │
│  │  • Tool 注册与调用                                   │   │
│  │  • 流式输出处理                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Mineflayer 机器人控制器                  │   │
│  │  • 连接 Minecraft 服务器                             │   │
│  │  • 监听游戏事件                                      │   │
│  │  • 执行游戏操作                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │               本地向量数据库(sql.js)                │   │
│  │  • 情景记忆存储                                      │   │
│  │  • 向量嵌入与检索                                    │   │
│  │  • 落盘加密                                          │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

技术栈选择

组件技术选择原因
运行时Electron跨平台桌面应用,支持本地存储
前端Vue 3响应式界面,组件化开发
Agent 框架LangChain成熟的 Tool-Calling Agent 实现
游戏交互MineflayerMinecraft 协议的 Node.js 实现
本地数据库sql.js纯 JavaScript SQLite,无外部依赖

核心流程实现

ReAct 循环的工程化

LangChain 的 Tool-Calling Agent 本质上就是 ReAct 的工程实现。我们在此基础上做了以下扩展:

// 核心循环结构
interface ReActLoop {
  // 1. 观察:收集游戏状态
  observe(): GameState
 
  // 2. 思考:LLM 推理下一步
  think(state: GameState, context: ConversationHistory): Promise<Action[]>
 
  // 3. 行动:执行具体操作
  act(actions: Action[]): Promise<ActionResult[]>
 
  // 4. 反思:评估结果,更新记忆
  reflect(results: ActionResult[]): void
}

Code Agent 模式

传统 ReAct 的行动是单步操作,但在 Minecraft 这种复杂环境中,单步操作效率太低。我们引入了 Code Agent 模式

// AI 生成的 JavaScript 代码块
const generatedCode = `
// 1. 检查附近是否有木头
const nearbyTrees = bot.findBlocks({
  matching: mcData.blocksByName.log.id,
  maxDistance: 32
});
 
// 2. 移动到最近的树
if (nearbyTrees.length > 0) {
  await bot.pathfinder.goto(
    new GoalBlock(nearbyTrees[0].x, nearbyTrees[0].y, nearbyTrees[0].z)
  );
}
 
// 3. 挖掘木头
await bot.dig(bot.blockAt(nearbyTrees[0]));
`;
 
// 在沙箱中批量执行
await sandbox.execute(generatedCode);

这种模式的优点:

  • 批量操作:一次生成多步操作,减少 LLM 调用次数
  • 上下文连贯:代码块内部保持完整逻辑
  • 可调试:生成的代码可查看、可修改

node:vm 沙箱隔离

Code Agent 的安全是关键问题。我们使用 node:vm 创建隔离沙箱:

import { VM } from 'vm2';
 
const sandbox = new VM({
  timeout: 5000,          // 5秒超时
  sandbox: {
    bot: botProxy,        // 代理对象,限制危险操作
    mcData: mcData,
    console: safeConsole  // 安全的 console,输出到日志
  }
});
 
// 执行前验证
function validateCode(code: string): boolean {
  // 禁止访问 fs、process、require
  const forbidden = ['require', 'process', 'fs', 'child_process'];
  for (const keyword of forbidden) {
    if (code.includes(keyword)) return false;
  }
  return true;
}

记忆系统设计

三层记忆架构

┌─────────────────────────────────────────┐
│          Working Memory                  │
│  当前任务上下文,LLM 输入的一部分          │
│  • 最近对话(压缩后)                     │
│  • 当前任务状态                           │
│  • 临时变量                               │
├─────────────────────────────────────────┤
│          Episodic Memory                 │
│  具体事件记录,持久化存储                  │
│  • 游戏事件日志                           │
│  • 对话历史                               │
│  • 任务完成记录                           │
├─────────────────────────────────────────┤
│          Semantic Memory                 │
│  抽象知识,向量嵌入检索                    │
│  • 游戏知识(合成配方、物品属性)          │
│  • 学习到的规律                           │
│  • 玩家偏好                               │
└─────────────────────────────────────────┘

sql.js 本地存储

为什么选择 sql.js 而不是 SQLite native binding?

  1. 无外部依赖:纯 JavaScript 实现,打包简单
  2. 跨平台一致:不依赖系统 SQLite 版本
  3. 内存+落盘:支持纯内存操作,也支持文件持久化
import { Database } from 'sql.js';
 
// 初始化数据库
const db = new Database();
 
// 创建表
db.run(`
  CREATE TABLE IF NOT EXISTS episodic_memory (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp INTEGER NOT NULL,
    event_type TEXT NOT NULL,
    content TEXT NOT NULL,
    embedding BLOB
  )
`);
 
// 落盘加密
function saveToDisk(db: Database, password: string): void {
  const data = db.export();
  const encrypted = encrypt(data, password);
  fs.writeFileSync('memory.db.enc', encrypted);
}

向量检索

对于语义记忆,我们需要向量检索能力。由于 sql.js 不支持向量扩展,我们采用简化方案:

interface SemanticEntry {
  id: number
  content: string
  embedding: number[]  // 1536维 OpenAI embedding
}
 
// 简单的余弦相似度检索
function searchSimilar(
  entries: SemanticEntry[],
  queryEmbedding: number[],
  topK: number = 5
): SemanticEntry[] {
  const scored = entries.map(entry => ({
    entry,
    score: cosineSimilarity(entry.embedding, queryEmbedding)
  }));
 
  return scored
    .sort((a, b) => b.score - a.score)
    .slice(0, topK)
    .map(s => s.entry);
}

Mineflayer 游戏交互

事件监听机制

// 监听游戏事件
bot.on('chat', (username, message) => {
  if (username === bot.username) return;
 
  // 触发 Agent 响应
  agent.handleChatMessage(username, message);
});
 
bot.on('spawn', () => {
  agent.handleSpawn();
});
 
bot.on('health', () => {
  agent.handleHealthChange(bot.health, bot.food);
});
 
bot.on('death', () => {
  agent.handleDeath();
});

操作封装

将 Mineflayer 操作封装为 Tool,供 LangChain Agent 调用:

const tools = [
  new Tool({
    name: 'move_to',
    description: '移动到指定位置',
    parameters: {
      type: 'object',
      properties: {
        x: { type: 'number' },
        y: { type: 'number' },
        z: { type: 'number' }
      }
    },
    execute: async ({ x, y, z }) => {
      await bot.pathfinder.goto(new GoalBlock(x, y, z));
      return { success: true, position: bot.position };
    }
  }),
 
  new Tool({
    name: 'dig_block',
    description: '挖掘指定方块',
    parameters: {
      type: 'object',
      properties: {
        position: { type: 'object' }
      }
    },
    execute: async ({ position }) => {
      const block = bot.blockAt(position);
      await bot.dig(block);
      return { success: true, blockType: block.name };
    }
  })
];

与其他 ECHO 路径的对比

特性ECHO_feelingECHO_loopECHO_CLAW
核心思路认知科学架构React Agent 工程化上下文+Skill调用
决策来源内驱涌现LLM推理+规则上下文驱动
知识管理认知层管理记忆系统记忆查询
情感系统PAD/OCC双引擎无(可扩展)
行动方式意愿强度驱动Tool-CallingSkill编排
适合场景深度沉浸体验工程实践通用游戏框架

开发经验总结

Electron 打包的坑

  1. sql.js 加载问题:sql.js 需要加载 wasm 文件,打包时必须正确处理

    // 在 webpack 配置中
    new CopyWebpackPlugin({
      patterns: [{ from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'sql-wasm.wasm' }]
    })
  2. Mineflayer 连接问题:开发模式和生产模式的网络权限不同

LangChain 流式输出

流式输出对用户体验至关重要:

// 使用 LangChain 的流式回调
const agent = new ToolCallingAgent({
  llm,
  tools,
  stream: true
});
 
for await (const chunk of agent.stream(input)) {
  // 实时发送到前端
  ipcRenderer.send('agent-stream', chunk);
}

当前状态与展望

已完成

  • ✅ Electron + Vue 3 框架搭建
  • ✅ LangChain Agent 基础实现
  • ✅ Mineflayer 连接与操作封装
  • ✅ sql.js 本地记忆存储
  • ✅ Code Agent 模式与沙箱

进行中

  • 🔄 记忆压缩算法优化
  • 🔄 长期记忆向量检索
  • 🔄 行动日志可视化

未来计划

  • 🔜 多 Agent 协作(多个 bot 同时运行)
  • 🔜 学习模块(从游戏 Wiki 自动学习)
  • 🔜 与 ECHO_feeling/ECHO_CLAW 的架构对比实验

相关链接


最后更新: 2026-05-13