58 KiB
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 文件结构:
/**
* @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 格式:
{
"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 中):
// 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:
/**
* @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 脚本, 在创建执行上下文时仅注入允许的全局对象:
// Skill 沙箱执行 (概念代码)
fn execute_skill(script: &str, params: Value, browser_tool: &BrowserPipeTool) -> Result<Value> {
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 快照格式:
{
"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 通过编号引用元素。
请求方式:
{
"seq": 10,
"type": "command",
"action": "pageScreenshot",
"params": {
"full_page": false,
"som_overlay": true
},
"security": { "expected_domain": "erp.example.com", "hmac": "..." }
}
响应:
{
"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 实现要点
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<ClaudeMessage> {
// 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<ClaudeTool> {
// ToolDefinition → { name, description, input_schema }
}
}
5.3 输出约束
LLM 的输出必须遵循以下约束:
Tool-use 输出格式 (Claude API):
{
"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 正确选择操作:
{
"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<Message> │ │
│ │ 生命周期: 单次任务 (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 步完成 │
└─────────────────────────────┘
经验记录格式:
{
"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):
{
"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 节(记忆与自进化)。