Files
claw/docs/L3-数据流与Skill体系层.md
2026-03-06 03:36:12 +08:00

1362 lines
58 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 采用 ReActReasoning + 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<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 AOMAccessibility 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 SoMSet-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<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):
```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<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 步完成 │
└─────────────────────────────┘
```
**经验记录格式**
```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 节(记忆与自进化)。*