# L3 — 数据流与 Skill 体系层 **文档版本**: 1.0 **适用项目**: sgClaw (业数融合一平台 AI Agent 底座) **编制日期**: 2026-03-03 **读者**: 高级开发者、Skill 开发者 —— 需要理解 Agent 内部数据流转、编写业务 Skill、集成 LLM、 调优感知层和记忆系统。 --- ## 1. 核心数据流时序 ### 1.1 端到端数据流全景 一次完整的用户任务执行涉及以下数据流转路径。从用户在 Side Panel 输入自然语言指令开始, 到最终任务完成结果返回,数据流经前端 → C++ → Pipe → Rust → LLM → Pipe → C++ → DOM。 ``` 用户输入 结果展示 "导出本月合规报表" "已导出3份报表" │ ▲ ▼ │ ┌─────────────────────────────────────────────────────────────────────────────┐ │ Side Panel (Vue) │ │ AgentControlPanel.vue │ │ ├─ sgFunctionsUI('sgclaw_submit_task', { instruction: "..." }) │ │ └─ 接收 onLogEntry / onTaskCompleted 事件 │ └──────────────┬────────────────────────────────────────────────┬────────────┘ │ FunctionsUI IPC ▲ ExecuteJS push ▼ │ ┌─────────────────────────────────────────────────────────────────────────────┐ │ C++ Browser Process │ │ │ │ SgClawProcessHost │ │ ├─ 将 instruction 封装为 pipe submit_task 消息 │ │ ├─ PipeListener 接收 sgClaw 的 command 消息 │ │ ├─ MAC Whitelist Check 校验 │ │ └─→ CommandRouter → CdpCommandExecutor → 页面 DOM 操作 │ │ │ │ 响应路径: │ │ CommandRouter result → PipeWriter → sgClaw │ │ + AOM Snapshot (操作后的页面状态快照) │ └──────────────┬────────────────────────────────────────────────┬────────────┘ │ STDIO Pipe (JSON Line) ▲ ▼ │ ┌─────────────────────────────────────────────────────────────────────────────┐ │ sgClaw Rust Process │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ Agent Runtime (ReAct Loop) │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ THINK │───→│ ACT │───→│ OBSERVE │──┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ LLM 推理 │ │ 执行工具 │ │ 解析结果 │ │ │ │ │ │ │ 生成计划 │ │ 发送命令 │ │ 更新记忆 │ │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ ▲ │ │ │ │ │ └──── Critic 评估 ◄──────────────────────┘ │ │ │ │ │ OK → 继续循环 │ │ │ │ │ Abort → 终止任务 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ 数据交互: │ │ ├─ BrowserPipeTool ──→ Pipe ──→ Browser (command) │ │ ├─ LLM Provider ──→ HTTP(S) ──→ Claude / GPT / Ollama │ │ ├─ Memory ──→ SQLite (本地文件) │ │ └─ SkillLoader ──→ 文件系统 (技能脚本) │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 1.2 单步数据流详解 Agent 每一步(ReAct loop 的一次迭代)的数据流如下: ``` Step N 开始 │ ├── 1. 构造 LLM 输入 │ ├── system_prompt (模板渲染) │ │ ├── 可用工具描述 (BrowserPipeTool + MCP tools) │ │ ├── 可用技能列表 (SkillLoader.list_skills()) │ │ ├── 安全约束说明 │ │ └── 当前会话 trace_id │ │ │ ├── messages (对话历史) │ │ ├── 短期记忆 (最近 N 条, token 截断) │ │ └── 长期记忆检索结果 (语义相关的历史经验) │ │ │ └── tools (工具定义) │ ├── browser_action: { 参数 JSON Schema } │ └── mcp_tools: [外部工具列表] │ ├── 2. 调用 LLM (THINK) │ ├── → HTTP(S) → Claude API / OpenAI API / Ollama │ ├── ← streaming chunks (thinking + tool_call) │ └── 解析 LLM 输出: │ ├── 纯文本 → 最终回答 (任务完成) │ └── tool_call → 继续执行 │ ├── 3. 执行工具调用 (ACT) │ │ │ ├── [browser_action] BrowserPipeTool.execute() │ │ ├── MAC Policy 校验 (Rust 层) │ │ ├── 构造 pipe command JSON │ │ │ { seq: N, type: "command", action: "click", │ │ │ params: { selector: "#btn" }, │ │ │ security: { expected_domain: "erp.example.com", hmac: "..." } } │ │ ├── 写入 stdout (→ pipe → Browser) │ │ └── 等待 response (← pipe ← Browser) │ │ { seq: N, type: "response", success: true, │ │ data: { clicked: true }, │ │ aom_snapshot: [...] } │ │ │ └── [mcp_tool] McpClientManager.call_tool() │ ├── STDIO → MCP Server │ └── ← tool result JSON │ ├── 4. 处理结果 (OBSERVE) │ ├── 格式化 observation 文本 │ │ ├── 操作成功/失败 │ │ ├── 结果数据摘要 │ │ └── AOM 快照 (页面状态描述) │ │ │ ├── 存入短期记忆 │ └── 追加到 messages 历史 │ ├── 5. 质量评估 (CRITIC) │ ├── 检查操作成功/失败 │ ├── 检查是否重复操作 (死循环检测) │ ├── 检查任务时长 │ └── 更新 Circuit Breaker 状态 │ └── Step N 结束 → Step N+1 或 任务完成 ``` ### 1.3 数据格式在各阶段的变换 ``` 阶段 数据格式 示例 ──────────── ──────────────────────────── ────────────────────────────── 用户输入 自然语言字符串 "导出本月合规报表" │ ▼ LLM 输入 Message[] + ToolDef[] { role: "user", content: "..." } │ ▼ LLM 输出 ToolCall { name: "browser_action", │ arguments: { action: "click", ... } } ▼ Pipe 命令 JSON Line {"seq":1,"type":"command","action":"click",...} │ ▼ C++ 内部 base::Value (Dict) CommandRouter 参数字典 │ ▼ DOM 操作 CDP / JS 执行 document.querySelector('#btn').click() │ ▼ DOM 结果 CDP 返回值 { "result": { "type": "undefined" } } │ ▼ Pipe 响应 JSON Line {"seq":1,"type":"response","success":true,...} │ ▼ Observation 格式化文本 "操作成功:点击了'提交'按钮。\n当前页面..." │ ▼ LLM 下一轮 Message (role: "tool") { role: "tool", content: "操作成功:..." } ``` --- ## 2. Agent 循环详解 ### 2.1 ReAct 循环模型 sgClaw 采用 ReAct(Reasoning + Acting)循环模型,这是当前 AI Agent 领域最成熟的执行范式。 核心思想:让 LLM 交替进行推理(Reasoning)和行动(Acting),每次行动后观察结果, 再决定下一步。 ``` ┌─────────────────────────────────────────────────────────┐ │ ReAct 循环 │ │ │ │ ┌──────────┐ │ │ │ 用户指令 │ │ │ └─────┬────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ │ │ │ │ │ ┌─→│ THINK │────→│ ACT │────→│ OBSERVE │──┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 推理下一步│ │ 调用工具 │ │ 读取结果 │ │ │ │ │ │ 生成计划 │ │ 执行操作 │ │ 理解状态 │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ ┌──────────┐ │ │ │ └─────────│ CRITIC │◄─────────────────────────────┘ │ │ │ │ │ │ │ 质量评估 │ │ │ │ 熔断检查 │ │ │ └────┬─────┘ │ │ │ │ │ OK → 继续 │ │ Abort → 终止 │ │ Done → 返回结果 │ │ │ └─────────────────────────────────────────────────────────┘ ``` ### 2.2 System Prompt 模板 Agent 的行为由 System Prompt 定义。以下是 sgClaw 的 System Prompt 模板结构: ``` 你是 sgClaw,一个运行在 SuperRPA 浏览器中的 AI 助手。你的任务是帮助用户在企业业务系统 中完成自动化操作。 ## 身份与角色 - 你运行在国家电网"业数融合一平台"的 SuperRPA 浏览器中 - 你可以操控浏览器中打开的业务系统页面(ERP、OA、财务、HR 等) - 你的每个操作都会被审计记录,trace_id: {{trace_id}} ## 可用工具 你有一个核心工具 `browser_action`,支持以下操作: {{#each tools}} ### {{this.name}} {{this.description}} 参数: {{this.parameters}} {{/each}} ## 可用技能 以下是预置的业务技能脚本,当任务匹配时优先使用: {{#each skills}} - **{{this.name}}** (v{{this.version}}): {{this.description}} 适用域名: {{this.domains}} {{/each}} ## 安全约束 1. 只能操作以下域名的页面: {{allowed_domains}} 2. 每个 browser_action 调用必须指定 expected_domain 3. 禁止尝试执行 eval、executeJsInPage 等操作 4. 涉及登录、登出、清空存储的操作会请求用户确认 5. 不要尝试读取或猜测用户密码 ## 执行策略 1. 先观察当前页面状态 (getAomSnapshot) 2. 制定执行计划,分步骤完成 3. 每步操作后检查结果 4. 遇到异常时尝试恢复(最多重试 3 次) 5. 任务完成后提供简明的执行摘要 ## 输出格式 - 思考过程用自然语言描述 - 操作通过 tool_call 执行 - 最终结果用中文文本总结 ``` ### 2.3 消息历史管理策略 Agent 的 LLM 调用需要发送对话历史(messages),但上下文窗口有限。 sgClaw 采用分层记忆策略管理消息历史: ``` ┌──────────────────────────────────────────────────────────────┐ │ LLM 上下文窗口 (如 200K tokens) │ │ │ │ ┌─ System Prompt ────────────────────────────────────────┐ │ │ │ ~2000 tokens (固定) │ │ │ │ 工具定义 + 技能列表 + 安全约束 + 执行策略 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ 长期记忆检索 ─────────────────────────────────────────┐ │ │ │ ~1000 tokens (按相关度检索) │ │ │ │ 相似任务的历史经验、成功的操作路径 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ 短期对话记忆 ─────────────────────────────────────────┐ │ │ │ 动态大小 (剩余 token 预算) │ │ │ │ │ │ │ │ ┌─ 最近 N 步完整记录 ──────────────────────────────┐ │ │ │ │ │ Step K: thinking + action + observation │ │ │ │ │ │ Step K+1: thinking + action + observation │ │ │ │ │ │ ... │ │ │ │ │ │ Step N: thinking + action + observation │ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─ 更早步骤的压缩摘要 ─────────────────────────────┐ │ │ │ │ │ "Steps 1-5: 登录 ERP,导航到报表模块, │ │ │ │ │ │ 设置了时间范围为本月" │ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ 当前用户指令 ─────────────────────────────────────────┐ │ │ │ ~100 tokens │ │ │ │ "导出本月合规报表" │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ ``` **Token 预算分配**: | 区域 | Token 预算 | 策略 | |------|-----------|------| | System Prompt | ~2000 | 固定,编译时确定 | | 长期记忆检索 | ~1000 | 按语义相关度排序取 top-3 | | 短期对话记忆 | 剩余空间 | 最近步骤完整保留,早期步骤压缩 | | 用户指令 | ~100 | 原文保留 | | LLM 生成预留 | ~4096 | max_tokens 参数 | **短期记忆截断算法**: ``` truncate_messages(messages, token_budget): // 1. 保留最近 5 步完整记录 recent = messages[-5:] recent_tokens = count_tokens(recent) // 2. 如果最近 5 步已超预算,减少保留数 while recent_tokens > token_budget * 0.8 and len(recent) > 2: recent = recent[-len(recent)+1:] recent_tokens = count_tokens(recent) // 3. 对更早的步骤生成压缩摘要 remaining_budget = token_budget - recent_tokens older = messages[:-len(recent)] if older: summary = compress_steps(older, remaining_budget) return [summary_message] + recent else: return recent ``` ### 2.4 任务生命周期状态机 ``` ┌────────────────────────────┐ │ TaskState Machine │ └────────────────────────────┘ ┌─────────┐ │ Idle │ └────┬────┘ │ submit_task(instruction) ▼ ┌─────────┐ │Planning │ ← LLM 分析指令,生成初步计划 └────┬────┘ │ 计划生成完成 ▼ ┌─────────┐ ┌───→│Executing│◄───┐ │ └────┬────┘ │ │ │ │ │ ┌────┴────┐ │ │ │Step N │ │ │ │executing│ │ │ └────┬────┘ │ │ │ │ │ ┌────┴────┐ │ │ │Waiting │ │ step OK, more to do │ │for resp │ │ │ └────┬────┘ │ │ │ │ │ ┌────┴────┐ │ │ │Observing│────┘ │ └────┬────┘ │ │ │ ┌────┴────┐ │ │Confirm? │ ← human-in-the-loop │ └──┬───┬──┘ │ yes │ │ no (auto-proceed) │ │ │ │ ┌──┴───┴──┐ └────│Evaluate │ └────┬────┘ │ ┌─────────┼─────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌──────┐ ┌────────┐ │Completed│ │Failed│ │Aborted │ │ (成功) │ │(失败) │ │(用户终止)│ └─────────┘ └──────┘ └────────┘ ``` --- ## 3. Skill 体系 ### 3.1 Skill 定义格式 每个 Skill 是一个 JavaScript 文件,包含元数据头和执行函数。 **完整 Skill 文件结构**: ```javascript /** * @skill erp-monthly-report * @version 1.0.0 * @description 从ERP系统导出月度财务报表。支持按部门、科目筛选, * 自动处理分页数据,合并导出为完整报表。 * @domains erp.example.com, erp-test.example.com * @author sgClaw Team * @params { * "type": "object", * "required": ["month"], * "properties": { * "month": { * "type": "string", * "pattern": "^\\d{4}-\\d{2}$", * "description": "报表月份 (如 2026-03)" * }, * "department": { * "type": "string", * "description": "部门名称 (可选, 不填则导出全部)" * }, * "format": { * "type": "string", * "enum": ["xlsx", "csv", "pdf"], * "default": "xlsx", * "description": "导出格式" * } * } * } */ /** * 技能执行入口 * @param {object} params - 输入参数 (符合上方 @params 定义) * @param {function} browserAction - BrowserAction 调用函数 * @returns {object} 执行结果 { success: boolean, data?: any, error?: string } */ async function execute(params, browserAction) { const { month, department, format = 'xlsx' } = params; try { // Step 1: 导航到 ERP 报表页面 await browserAction('navigate', 'https://erp.example.com/report/finance'); await browserAction('waitForSelector', '.report-filter', 5000); // Step 2: 设置筛选条件 await browserAction('click', '#month-picker'); await browserAction('type', '#month-input', month); if (department) { await browserAction('click', '#dept-selector'); await browserAction('type', '#dept-search', department); await browserAction('click', `.dept-option[data-name="${department}"]`); } // Step 3: 选择导出格式 await browserAction('select', '#export-format', format); // Step 4: 点击导出 await browserAction('click', '#export-btn'); // Step 5: 等待导出完成 await browserAction('waitForSelector', '.export-success', 30000); // Step 6: 获取结果信息 const resultText = await browserAction('getText', '.export-result'); return { success: true, data: { message: resultText, month: month, department: department || '全部', format: format } }; } catch (error) { return { success: false, error: `导出报表失败: ${error.message}` }; } } ``` ### 3.2 Skill 仓库结构 ``` sgclaw-skills/ ├── registry.json # 技能清单 (签名 + 哈希索引) ├── builtin/ # 内置技能 (随产品交付) │ ├── erp-monthly-report.js # ERP 月度报表导出 │ ├── erp-anomaly-check.js # ERP 异常交易检查 │ ├── oa-approval.js # OA 审批单处理 │ ├── oa-meeting-schedule.js # OA 会议日程管理 │ ├── finance-compliance.js # 财务合规线索提报 │ ├── finance-reconciliation.js # 财务对账 │ ├── hr-social-insurance.js # 人力社保申报 │ ├── hr-salary-check.js # 薪酬数据核验 │ ├── legal-contract-monitor.js # 合同履约监测 │ └── cross-system-sync.js # 跨系统数据同步 │ ├── custom/ # 用户自定义技能 │ └── (用户创建的 .js 文件) │ └── keys/ └── skill_verify.pub # Ed25519 公钥 (校验签名) ``` **registry.json 格式**: ```json { "version": "1.0", "updated_at": "2026-03-03T00:00:00Z", "skills": [ { "name": "erp-monthly-report", "file": "builtin/erp-monthly-report.js", "version": "1.0.0", "hash": "sha256:a1b2c3d4e5f6...", "signature": "ed25519:x7y8z9...", "enabled": true }, { "name": "oa-approval", "file": "builtin/oa-approval.js", "version": "1.2.0", "hash": "sha256:f6e5d4c3b2a1...", "signature": "ed25519:z9y8x7...", "enabled": true } ] } ``` ### 3.3 Skill 生命周期 ``` ┌─────────────┐ │ 开发阶段 │ │ (JS 编写) │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 签名阶段 │ │ 构建系统 │ │ Ed25519 签名│ │ SHA-256 哈希│ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 发布阶段 │ │ 更新 │ │ registry │ │ .json │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 部署阶段 │ │ 放入 │ │ sgclaw- │ │ skills/ │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 加载阶段 │ │ sgClaw 启动 │ │ 校验签名 │ │ 校验哈希 │ │ 解析元数据 │ │ 注册到 Agent│ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 执行阶段 │ ← Agent ReAct 循环中 LLM 选择使用 │ Agent 调用 │ │ 传入 params │ │ + browser │ │ Action 引用 │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 沉淀阶段 │ ← 执行结果存入 Memory │ 记录经验 │ │ 成功率统计 │ │ 供后续复用 │ └─────────────┘ ``` ### 3.4 从现有 JS 场景代码迁移到 Skill SuperRPA 已有大量 agent-vue 中的 JS 场景代码(49 个文件,208+ 个调用点)。 这些代码可以迁移为 sgClaw Skill,实现复用。 **迁移策略**: | 阶段 | 内容 | 说明 | |------|------|------| | **Phase 1: 直接复用** | 现有 JS 代码通过 BrowserAction API 调用 | sgClaw 发送 pipe 命令 → Browser 执行 → JS SDK 代码无需改动 | | **Phase 2: 技能化封装** | 将高频场景代码提取为 Skill | 添加元数据头、参数 schema、错误处理 | | **Phase 3: LLM 增强** | Skill 作为 Agent 的工具选项 | LLM 根据用户指令自动选择合适的 Skill | **迁移示例**:将 agent-vue 中的 OA 审批代码迁移为 Skill 现有代码(agent-vue 中): ```javascript // agent-vue/src/scenes/oa-approval.js async function approveAll() { await sgBrowser.page.navigate('https://oa.example.com/approval/pending'); await sgBrowser.page.waitForSelector('.approval-list'); const items = await sgBrowser.page.getText('.approval-list .item'); for (const item of items) { await sgBrowser.input.click(`.item[data-id="${item.id}"] .approve-btn`); await sgBrowser.page.waitForSelector('.confirm-dialog'); await sgBrowser.input.click('.confirm-dialog .ok-btn'); } } ``` 迁移后的 Skill: ```javascript /** * @skill oa-approval * @version 1.0.0 * @description OA系统待审批单据批量处理。支持查看待审批列表、 * 批量审批、批量驳回、添加审批意见。 * @domains oa.example.com, oa-test.example.com * @params { * "type": "object", * "required": ["action"], * "properties": { * "action": { "enum": ["list", "approve_all", "reject", "approve_one"] }, * "item_id": { "type": "string", "description": "单据ID (approve_one/reject 时需要)" }, * "opinion": { "type": "string", "description": "审批意见 (可选)" } * } * } */ async function execute(params, browserAction) { const { action, item_id, opinion } = params; // 导航到审批页面 await browserAction('navigate', 'https://oa.example.com/approval/pending'); await browserAction('waitForSelector', '.approval-list', 5000); switch (action) { case 'list': { const text = await browserAction('getText', '.approval-list'); return { success: true, data: { items: text } }; } case 'approve_all': { // 获取所有待审批项 const snapshot = await browserAction('getAomSnapshot', '.approval-list'); let count = 0; // 逐个审批 (带错误处理) for (const item of snapshot) { try { await browserAction('click', `.item[data-id="${item.name}"] .approve-btn`); await browserAction('waitForSelector', '.confirm-dialog', 3000); if (opinion) { await browserAction('type', '.opinion-input', opinion); } await browserAction('click', '.confirm-dialog .ok-btn'); await browserAction('waitForSelector', '.success-toast', 3000); count++; } catch (e) { // 单条失败不影响整体 continue; } } return { success: true, data: { approved: count, total: snapshot.length } }; } // ... 其他 action } } ``` **迁移改动点总结**: | 改动项 | 说明 | |-------|------| | 添加元数据头 | `@skill`, `@version`, `@description`, `@domains`, `@params` | | 函数签名统一 | `async function execute(params, browserAction)` | | API 调用方式 | `sgBrowser.page.navigate(url)` → `browserAction('navigate', url)` | | 错误处理 | 添加 try/catch,返回统一格式 `{ success, data, error }` | | 参数化 | 硬编码值改为 params 输入 | ### 3.5 Skill 执行沙箱 Skill 的 JS 脚本需要在受限环境中执行,防止恶意代码突破安全边界。 **沙箱约束**: | 能力 | 是否允许 | 说明 | |------|---------|------| | `browserAction()` | 允许 | 唯一的外部交互方式,受 MAC 策略约束 | | `console.log/error` | 允许 | 输出到 Agent 日志 | | `JSON.parse/stringify` | 允许 | 数据处理必需 | | `Promise / async-await` | 允许 | 异步控制流必需 | | `setTimeout / setInterval` | 允许 (受限) | 最大延迟 30s,用于等待 | | `fetch / XMLHttpRequest` | **禁止** | 网络请求必须通过 browserAction | | `require / import` | **禁止** | 不允许加载外部模块 | | `process / child_process` | **禁止** | 不允许访问系统进程 | | `fs / path` | **禁止** | 不允许访问文件系统 | | `eval / Function()` | **禁止** | 不允许动态代码执行 | **实现方案**: sgClaw 使用轻量级 JS 引擎(如 Boa 或 embedded V8 via Rusty_V8)运行 Skill 脚本, 在创建执行上下文时仅注入允许的全局对象: ```rust // Skill 沙箱执行 (概念代码) fn execute_skill(script: &str, params: Value, browser_tool: &BrowserPipeTool) -> Result { let mut context = JsContext::new(); // 仅注入允许的全局对象 context.register_global("browserAction", |args| { // 代理到 BrowserPipeTool.execute() browser_tool.execute(args) }); context.register_global("console", ConsoleProxy::new()); context.register_global("JSON", JsonGlobal::new()); context.register_global("Promise", PromiseGlobal::new()); // 禁止所有其他全局对象 // (JsContext 默认不提供 Node.js / Browser API) // 执行技能脚本 context.eval(script)?; // 调用 execute 函数 let result = context.call("execute", &[params, browser_action_ref])?; Ok(result) } ``` --- ## 4. 感知层数据格式 ### 4.1 AOM(Accessibility Object Model)快照 AOM 快照是 Agent 理解页面状态的核心数据源。每次浏览器操作后,响应中附带当前页面的 AOM 快照,供 Agent 的 OBSERVE 阶段使用。 **AOM 快照格式**: ```json { "aom_snapshot": [ { "role": "navigation", "name": "主导航栏", "bounds": [0, 0, 1920, 60], "children": [ { "role": "link", "name": "首页", "bounds": [20, 10, 60, 40] }, { "role": "link", "name": "财务报表", "bounds": [100, 10, 80, 40], "focused": true }, { "role": "link", "name": "系统设置", "bounds": [200, 10, 80, 40] } ] }, { "role": "main", "name": "报表筛选区", "bounds": [0, 60, 1920, 200], "children": [ { "role": "combobox", "name": "月份选择", "value": "2026-03", "bounds": [20, 80, 200, 40], "selector": "#month-picker" }, { "role": "combobox", "name": "部门筛选", "value": "", "bounds": [240, 80, 200, 40], "selector": "#dept-selector" }, { "role": "button", "name": "导出报表", "bounds": [460, 80, 100, 40], "selector": "#export-btn", "disabled": false } ] }, { "role": "table", "name": "报表数据", "bounds": [0, 260, 1920, 600], "row_count": 25, "children": [ { "role": "row", "name": "表头", "children": [ { "role": "columnheader", "name": "科目编号" }, { "role": "columnheader", "name": "科目名称" }, { "role": "columnheader", "name": "借方金额" }, { "role": "columnheader", "name": "贷方金额" } ] } ] } ] } ``` **AOM 快照字段说明**: | 字段 | 类型 | 说明 | |------|------|------| | `role` | string | ARIA 角色 (button, link, textbox, table, etc.) | | `name` | string | 元素的可访问名称 (label text, aria-label, etc.) | | `bounds` | [x, y, w, h] | 元素的视口坐标和尺寸 (px) | | `value` | string | 元素当前值 (input, select 等) | | `selector` | string | CSS 选择器 (供后续操作使用) | | `focused` | boolean | 是否获得焦点 | | `disabled` | boolean | 是否禁用 | | `checked` | boolean | 是否选中 (checkbox, radio) | | `children` | array | 子元素列表 | | `row_count` | number | 表格行数 (仅 table 角色) | **AOM 快照与 LLM 的关系**: AOM 快照被格式化为结构化文本后,作为 observation 的一部分发送给 LLM: ``` 操作结果: 点击"财务报表"链接成功。 当前页面状态: - 导航栏: 首页 | [财务报表] (当前) | 系统设置 - 报表筛选区: - 月份选择: 2026-03 - 部门筛选: (未选择) - [导出报表] 按钮 (可用) - 报表数据: 25行数据已加载 - 表头: 科目编号 | 科目名称 | 借方金额 | 贷方金额 ``` ### 4.2 SoM(Set-of-Mark)标注 在需要视觉辅助时,sgClaw 可以请求带 SoM 标注的页面截图。SoM 在页面截图上为每个 可交互元素叠加数字标签,便于 LLM 通过编号引用元素。 **请求方式**: ```json { "seq": 10, "type": "command", "action": "pageScreenshot", "params": { "full_page": false, "som_overlay": true }, "security": { "expected_domain": "erp.example.com", "hmac": "..." } } ``` **响应**: ```json { "seq": 10, "type": "response", "success": true, "data": { "image_base64": "/9j/4AAQSkZJRg...", "som_labels": [ { "id": 1, "selector": "#month-picker", "name": "月份选择", "bounds": [20, 80, 200, 40] }, { "id": 2, "selector": "#dept-selector", "name": "部门筛选", "bounds": [240, 80, 200, 40] }, { "id": 3, "selector": "#export-btn", "name": "导出报表", "bounds": [460, 80, 100, 40] } ] } } ``` **LLM 使用 SoM 的方式**: LLM 看到截图后可以引用标签编号: ``` 我看到页面上有以下可交互元素: [1] 月份选择 - 当前值 2026-03 [2] 部门筛选 - 未选择 [3] 导出报表按钮 我需要点击 [3] 导出报表按钮来导出数据。 ``` **SoM 使用策略**: | 场景 | 使用方式 | 优先级 | |------|---------|--------| | 正常操作 | AOM 快照(文本) | 默认,token 开销低 | | AOM 不可用或不完整 | SoM 截图 | 兜底方案 | | 复杂视觉布局 | AOM + SoM 结合 | 特殊场景 | | 表格/图表分析 | SoM 截图 | 视觉信息丰富时 | ### 4.3 感知数据选择策略 Agent 在 OBSERVE 阶段根据当前状态自动选择感知方式: ``` OBSERVE(pipe_response): │ ├── 1. 解析 pipe_response.aom_snapshot │ │ │ ├── AOM 不为空且完整度 > 80%? │ │ → 使用 AOM 文本格式作为 observation │ │ │ └── AOM 为空或不完整? │ → 请求 SoM 截图作为 observation │ ├── 2. AOM 完整度判断 │ ├── 页面有可交互元素但 AOM 未列出 → 不完整 │ ├── AOM 元素数为 0 但页面非空白 → 不完整 │ └── 其他情况 → 完整 │ └── 3. 格式化 observation ├── AOM: 结构化文本描述 └── SoM: 截图 (base64) + 标签列表 ``` --- ## 5. LLM 集成 ### 5.1 Provider 适配层 sgClaw 通过 ZeroClaw 的 Provider trait 抽象对接不同的 LLM 服务。 每个 Provider 实现 HTTP 调用、streaming 解析、错误重试等逻辑。 **Provider 统一行为要求**: | 行为 | 要求 | 说明 | |------|------|------| | 超时 | 连接 10s,首 token 30s,总体 120s | 防止挂起 | | 重试 | 最多 3 次,指数退避 (1s, 2s, 4s) | 仅重试 5xx 和网络错误 | | Streaming | 必须支持 | 用于实时日志显示 | | Tool-use | 必须支持 | Agent ReAct 的核心能力 | | Token 计量 | 每次调用后上报 usage | 审计和成本控制 | | 错误分类 | 区分可重试/不可重试错误 | 401/403 不重试 | ### 5.2 Claude Provider 实现要点 ```rust pub struct ClaudeProvider { client: reqwest::Client, api_key: String, model: String, base_url: String, } impl ClaudeProvider { const DEFAULT_BASE_URL: &'static str = "https://api.anthropic.com"; const API_VERSION: &'static str = "2023-06-01"; /// 将 sgClaw 的 Message 格式转换为 Claude API 格式 fn convert_messages(messages: &[Message]) -> Vec { // Message::User → { role: "user", content: [...] } // Message::Assistant → { role: "assistant", content: [...] } // Message::Tool → { role: "user", content: [{ type: "tool_result", ... }] } } /// 将 sgClaw 的 ToolDefinition 转换为 Claude tool 格式 fn convert_tools(tools: &[ToolDefinition]) -> Vec { // ToolDefinition → { name, description, input_schema } } } ``` ### 5.3 输出约束 LLM 的输出必须遵循以下约束: **Tool-use 输出格式** (Claude API): ```json { "role": "assistant", "content": [ { "type": "thinking", "thinking": "用户要导出本月合规报表。我需要先导航到 ERP 系统的报表模块..." }, { "type": "tool_use", "id": "toolu_01xyz", "name": "browser_action", "input": { "action": "navigate", "params": { "url": "https://erp.example.com/report/compliance" }, "expected_domain": "erp.example.com" } } ] } ``` **输出校验规则**: ``` LLM 输出校验: │ ├── 1. 是否为合法 JSON? │ → 否: 请求 LLM 重新生成 (最多 2 次) │ ├── 2. tool_call 格式是否正确? │ ├── name 必须是 "browser_action" 或已注册的 MCP 工具 │ ├── input 必须包含 action 字段 │ └── action 必须在 Action 枚举中 │ ├── 3. 参数是否通过 JSON Schema 校验? │ → 否: 将校验错误信息反馈给 LLM,请求修正 │ ├── 4. 是否包含 expected_domain? │ → 否: 自动从上下文推断 (当前页面域名) │ └── 5. 通过所有校验 → 交给 BrowserPipeTool 执行 ``` ### 5.4 System Prompt 中的工具描述 提供给 LLM 的工具描述需要精确、简洁,帮助 LLM 正确选择操作: ```json { "name": "browser_action", "description": "在浏览器中执行操作。可以点击元素、输入文本、导航页面、获取页面内容等。每次调用需要指定 action 类型和对应参数。", "input_schema": { "type": "object", "required": ["action", "expected_domain"], "properties": { "action": { "type": "string", "enum": ["click", "type", "navigate", "getText", "getHtml", "waitForSelector", "pageScreenshot", "select", "scrollTo", "getAomSnapshot", "storageSet", "storageGet", "zombieSpawn", "zombieKill"], "description": "要执行的操作类型" }, "params": { "type": "object", "description": "操作参数,根据 action 类型不同而不同" }, "expected_domain": { "type": "string", "description": "操作目标页面的域名(安全校验用)" } } } } ``` --- ## 6. 记忆与自进化 ### 6.1 记忆分层架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 记忆系统架构 │ │ │ │ ┌─ L0: 即时记忆 ─────────────────────────────────────────┐ │ │ │ LLM 上下文窗口内的消息历史 │ │ │ │ 生命周期: 单次 LLM 调用 │ │ │ │ 容量: max_tokens (如 4096) │ │ │ │ 用途: 当前步骤的推理依据 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ L1: 短期记忆 (Ring Buffer) ────────────────────────────┐ │ │ │ VecDeque │ │ │ │ 生命周期: 单次任务 (execute_task 调用) │ │ │ │ 容量: 50 条消息 / 8000 tokens │ │ │ │ 用途: 当前任务的完整对话历史 │ │ │ │ 淘汰策略: FIFO,超限时压缩最早的消息为摘要 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ L2: 长期记忆 (SQLite) ─────────────────────────────────┐ │ │ │ 持久化存储 │ │ │ │ 生命周期: 跨任务、跨会话 │ │ │ │ 容量: 磁盘空间限制 (建议 < 100MB) │ │ │ │ 用途: 任务经验、用户偏好、技能执行记录 │ │ │ │ 检索: 语义相似度 (向量) + 关键词匹配 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 6.2 记忆读写流程 **写入流程**(任务执行过程中): ``` Agent 每步操作后: │ ├── 1. 写入短期记忆 (L1) │ ├── 追加 thinking 消息 │ ├── 追加 action 消息 │ └── 追加 observation 消息 │ ├── 2. 检查短期记忆容量 │ ├── 消息数 > 50? → 压缩最早 10 条为摘要 │ └── token 数 > 8000? → 压缩直至 < 8000 │ └── 3. (任务结束时) 写入长期记忆 (L2) ├── 保存任务摘要 (type: TaskResult) ├── 保存成功步骤序列 (type: SkillExperience) └── 更新技能执行统计 (skill_executions 表) ``` **读取流程**(新任务开始时): ``` execute_task(instruction): │ ├── 1. 清空短期记忆 (新任务) │ ├── 2. 从长期记忆检索相关经验 │ ├── 语义搜索: embedding(instruction) vs memory_entries │ ├── 取 top-3 相关条目 │ └── 格式化为 "历史经验" 上下文 │ ├── 3. 检查是否有精确匹配的 Skill 经验 │ ├── 查询 skill_executions 表 │ ├── 找到成功执行记录? │ │ → 作为推荐步骤加入 system_prompt │ └── 未找到? │ → 不影响执行 │ └── 4. 组装初始上下文 ├── system_prompt + 长期记忆检索结果 ├── 用户指令 └── 开始 ReAct 循环 ``` ### 6.3 自进化学习机制 sgClaw 的自进化核心思想:每次成功执行的任务都是一个学习样本。 通过记录和检索这些样本,Agent 在后续遇到相似任务时可以更快、更准确地完成。 **自进化循环**: ``` ┌─────────────────────────────┐ │ 首次执行新任务 │ │ │ │ LLM 从零推理 │ │ 多步试探 │ │ 可能遇到错误和回退 │ │ 平均 8-15 步完成 │ └──────────┬──────────────────┘ │ 成功完成 │ ▼ ┌─────────────────────────────┐ │ 经验沉淀 │ │ │ │ 记录成功步骤序列 │ │ 记录关键决策点 │ │ 计算向量嵌入 │ │ 存入长期记忆 (L2) │ └──────────┬──────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 再次执行相似任务 │ │ │ │ 检索到历史经验 │ │ LLM 参考已有路径 │ │ 跳过试探阶段 │ │ 平均 4-6 步完成 │ └──────────┬──────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 技能提炼 (可选) │ │ │ │ 高频任务 (>5 次相似执行) │ │ 提取为正式 Skill 脚本 │ │ 后续直接调用,1-2 步完成 │ └─────────────────────────────┘ ``` **经验记录格式**: ```json { "id": "exp-2026-03-03-001", "type": "task_result", "content": "任务: 导出本月ERP合规报表\n\n执行路径:\n1. navigate → erp.example.com/report\n2. click → #month-picker → 设置为2026-03\n3. click → #compliance-tab\n4. click → #export-btn\n5. waitForSelector → .export-success\n\n结果: 成功导出,文件名 compliance-2026-03.xlsx", "metadata": { "task_instruction": "导出本月合规报表", "steps": 5, "duration_ms": 12500, "domains_visited": ["erp.example.com"], "skills_used": [], "success": true }, "embedding": [0.12, -0.34, 0.56, ...], "created_at": "2026-03-03T10:30:00Z", "session_id": "trace-abc123" } ``` ### 6.4 向量检索实现 长期记忆的语义检索使用简单的余弦相似度计算。在数据量较小时(< 10000 条), 线性扫描足够快(< 10ms)。 **嵌入向量生成**: ``` 文本 → 嵌入方式: │ ├── 在线模型可用时: │ └── 调用 LLM Provider 的 embedding API │ ├── Claude: 不提供独立 embedding API,使用 summarize + hash │ ├── OpenAI: text-embedding-3-small (1536 维) │ └── Ollama: nomic-embed-text (384 维) │ └── 离线/无 embedding API 时: └── 简单 TF-IDF 向量 + 关键词匹配 ├── 中文分词 (jieba-rs) ├── 计算词频向量 └── 余弦相似度排序 ``` **检索优化策略**: | 策略 | 说明 | 适用场景 | |------|------|---------| | 类型过滤 | 先按 entry_type 过滤,减少扫描量 | 总是使用 | | 时间窗口 | 优先检索最近 30 天的记忆 | 默认策略 | | 域名过滤 | 按当前任务涉及的域名过滤 | 域名已知时 | | 混合排序 | 0.7 * 向量相似度 + 0.3 * 时间衰减 | 综合排序 | --- ## 7. 审计与追溯 ### 7.1 Trace ID 体系 每次 Agent 会话分配唯一的 `trace_id`,贯穿所有模块和组件,用于事后审计追溯。 **trace_id 格式**: ``` sgclaw-{date}-{random} 示例: sgclaw-20260303-a1b2c3d4 ``` **trace_id 传播路径**: ``` SgClawProcessHost::Start() │ 生成 trace_id │ ├─→ init message: { trace_id: "sgclaw-20260303-a1b2c3d4" } │ ├─→ sgClaw Runtime: 所有日志附带 trace_id │ ├─→ LLM 调用记录 │ ├─→ Pipe 命令记录 │ └─→ Memory 条目关联 │ ├─→ PipeListener: 所有 pipe 消息日志附带 trace_id │ └─→ CommandRouter: 操作审计日志附带 trace_id ``` ### 7.2 审计日志格式 **sgClaw 端日志** (输出到 stderr,结构化 JSON): ```json { "timestamp": "2026-03-03T10:23:05.123Z", "level": "INFO", "trace_id": "sgclaw-20260303-a1b2c3d4", "module": "agent::runtime", "event": "step_completed", "data": { "step": 3, "action": "click", "selector": "#export-btn", "domain": "erp.example.com", "success": true, "duration_ms": 245 } } ``` **Browser 端日志** (SuperRPA 已有日志系统): ``` [2026-03-03 10:23:05.123] [sgclaw] [trace:sgclaw-20260303-a1b2c3d4] PIPE_CMD seq=3 action=click selector=#export-btn domain=erp.example.com MAC_CHECK: ALLOW CMD_EXEC: OK (245ms) ``` ### 7.3 日志采集与存储 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 日志采集架构 │ │ │ │ sgClaw (Rust) Browser (C++) │ │ ┌───────────┐ ┌───────────┐ │ │ │ tracing │ │ LOG() │ │ │ │ → stderr │ │ → file │ │ │ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ │ ▼ ▼ │ │ sgclaw.log chrome_debug.log │ │ (JSON Lines) (SuperRPA 格式) │ │ │ │ │ │ └───────────┬───────────────────┘ │ │ │ │ │ ▼ │ │ 审计查询接口 │ │ (按 trace_id 关联检索) │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` **日志保留策略**: | 日志类型 | 保留时长 | 存储位置 | |---------|---------|---------| | sgClaw 操作日志 | 90 天 | 本地文件 + 可选远程上传 | | Pipe 通信记录 | 30 天 | 本地文件 | | LLM 调用记录 | 90 天 | 本地文件 (含 token 用量) | | 长期记忆 (SQLite) | 永久 | 本地数据库 | | 浏览器审计日志 | 按已有策略 | SuperRPA 日志系统 | --- *文档结束。本文档为 sgClaw L3 层数据流与 Skill 体系参考。Skill 开发者应重点关注 第 3 节(Skill 体系)和第 4 节(感知层数据格式),高级开发者应关注第 2 节(Agent 循环) 和第 6 节(记忆与自进化)。*