引言
在 AI 伙伴的探索路径中,ECHO_loop 代表了一条纯工程化的思路——采用 React Agent Loop 架构,将 LLM 编排、游戏交互和记忆管理整合到 Electron 桌面应用中。
与 ECHO_feeling 的认知科学架构(七层认知、情感系统)和 ECHO_CLAW 的上下文驱动设计不同,ECHO_loop 的核心是工程实践优先:用成熟的技术栈和清晰的流程来实现智能行为。
为什么选择 React Agent Loop
传统方法的困境
在早期的 Minecraft AI 实现中,我们尝试过两种路径:
- 硬编码规则系统:定义大量条件分支,如"看到敌人就攻击"、"血量低就逃跑"。问题在于行为僵化,无法处理意外情况。
- 纯 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 实现 |
| 游戏交互 | Mineflayer | Minecraft 协议的 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?
- 无外部依赖:纯 JavaScript 实现,打包简单
- 跨平台一致:不依赖系统 SQLite 版本
- 内存+落盘:支持纯内存操作,也支持文件持久化
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_feeling | ECHO_loop | ECHO_CLAW |
|---|---|---|---|
| 核心思路 | 认知科学架构 | React Agent 工程化 | 上下文+Skill调用 |
| 决策来源 | 内驱涌现 | LLM推理+规则 | 上下文驱动 |
| 知识管理 | 认知层管理 | 记忆系统 | 记忆查询 |
| 情感系统 | PAD/OCC双引擎 | 无(可扩展) | 无 |
| 行动方式 | 意愿强度驱动 | Tool-Calling | Skill编排 |
| 适合场景 | 深度沉浸体验 | 工程实践 | 通用游戏框架 |
开发经验总结
Electron 打包的坑
-
sql.js 加载问题:sql.js 需要加载 wasm 文件,打包时必须正确处理
// 在 webpack 配置中 new CopyWebpackPlugin({ patterns: [{ from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'sql-wasm.wasm' }] }) -
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 的架构对比实验
相关链接
- GitHub 仓库(opens in a new tab)
- ECHO_feeling 介绍
- ECHO_CLAW 设计
- LangChain 文档(opens in a new tab)
- Mineflayer 文档(opens in a new tab)
最后更新: 2026-05-13