# dataFlow **Repository Path**: emmmmtang/data-flow ## Basic Information - **Project Name**: dataFlow - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-02 - **Last Updated**: 2026-06-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DataFlow DataFlow 是一个 OpenClaw 插件原型,用来把一次 agent 运行中的 hook payload 转换成可视化数据流图。它关注的问题是: - 当前模型看到了哪些输入; - 工具调用使用了哪些参数; - 工具执行产生了哪些结果; - 新的数据节点是否来自历史数据节点; - 敏感标签如何沿数据流传播。 DataFlow 的归因目标不是替代 PII 检测,而是建立一条清晰的数据流边: ```text sourceNode --transformed_from--> targetNode ``` 然后把历史 source 节点上已经存在的敏感标签传播到 target 节点。 ## 当前能力 - 记录 OpenClaw hook:`llm_input`、`before_tool_call`、`after_tool_call`、`llm_output`、`session_end`。 - 生成本地图结构:event node、data node、data input/output edge、event sequence edge、attribution edge。 - 对数据节点执行 direct PII / 敏感数据检测。 - 对工具参数节点执行数据归因。 - 对工具结果节点自动建立工具流 provenance:同一次工具调用的 `tool_args -> tool_result`。 - 支持三类标签: - `directLabels`:当前节点自身检测出的敏感标签。 - `inheritedLabels`:通过归因从历史节点继承来的标签。 - `effectiveLabels`:最终生效标签,等于 direct + inherited。 - 支持快决策归因和慢思考归因。 - 本地网页渲染 DAG,并在归因虚线上显示归因原因。 - `tool_provenance` 工具流边保留在图中,但默认不显示文字原因,避免页面噪声。 ## 目录结构 ```text . ├── index.ts # OpenClaw 插件入口 ├── openclaw.plugin.json # 插件 manifest 和端口配置 schema ├── package.json ├── src │ ├── attribution.ts # 核心归因算法 │ ├── deepseekAttributionClient.ts # DeepSeek 慢思考归因客户端 │ ├── flowGraphManager.ts # 图节点、边、工具流 provenance │ ├── hookAdapter.ts # OpenClaw hook payload 标准化 │ ├── localKnowledgeSearchTool.ts # mock/demo 用本地知识检索工具 │ ├── mockDev.ts # 本地 demo 场景 │ ├── privacy.ts # direct PII 检测和标签传播 │ ├── runtime.ts # hook 运行时编排 │ ├── server.ts # 本地 viewer HTTP/SSE 服务 │ └── types.ts # 数据结构定义 ├── test │ ├── llmAttribution.mjs # 真实 LLM 归因模块测试 │ └── printDeepSeekApiKey.mjs # API key 脱敏诊断 └── web ├── index.html └── src ├── graphRenderer.ts # SVG 图渲染 ├── main.ts # viewer 拉取 /graph 并刷新 └── style.css ``` ## 数据模型 ### DataNode 每个数据节点包含: ```ts type DataNode = { id: NodeId; kind: "data"; dataType: DataType; title: string; contentPreview: string; rawContent?: unknown; createdAt: string; directLabels: SensitivityLabel[]; inheritedLabels: InheritedSensitivityLabel[]; effectiveLabels: SensitivityLabel[]; attributions: AttributionResult[]; }; ``` 重要的 `dataType`: - `user_query`:当前会话的第一个用户原始请求。 - `user_prompt`:后续用户 prompt。 - `tool_args`:模型准备调用工具时生成的参数。 - `tool_result`:工具执行结果。 - `tool_result_context`:后续 LLM input 中引用的工具历史上下文。 - `system_prompt`、`tool_schema`:会进入图,但默认不做隐私检测。 ### AttributionResult 归因结果结构: ```ts type AttributionResult = { id: string; sourceNodeId: NodeId; targetNodeId: NodeId; relation: "transformed_from"; method: | "exact_copy" | "contains" | "pii_token_overlap" | "keyword_source_coverage" | "char_ngram" | "embedding" | "llm" | "tool_provenance" | "opaque_risk"; confidence: number; evidence: { sourcePreview: string; targetPreview: string; matchedTerms?: string[]; matchedPiiTokens?: string[]; sourceCoverage?: number; targetCoverage?: number; ngramScore?: number; semanticScore?: number; llmScore?: number; candidateRank?: number; reason?: string; }; }; ``` 前端会在 `transformed_from` 虚线边上显示 `method`、`confidence` 和 `reason`。`tool_provenance` 是工具调用的结构化流转关系,默认不显示文字标签。 ## 归因处理顺序 新 DataNode 创建时统一进入 `processNewDataNode`。 工具参数节点的完整流程: 1. 对 `rawContent ?? contentPreview` 做 direct PII / 敏感数据检测。 2. 如果检测到敏感信息,写入 `directLabels`。 3. 更新 `effectiveLabels = directLabels + inheritedLabels`。 4. 对工具参数节点,即使命中 directLabels,也可以继续执行归因,用于补齐图上的数据流边。 5. 归因成功后: - 将 `AttributionResult` 写入 targetNode.attributions; - 在图上创建 `sourceNode --transformed_from--> targetNode`; - 如果 sourceNode 有 `effectiveLabels`,将其转换成 target 的 `inheritedLabels`。 6. 如果归因失败,不继承标签。 工具结果节点的工具流 provenance: 1. `before_tool_call` 会创建一个 event node,并把每个工具参数作为 `tool_args` 数据输入。 2. `after_tool_call` 创建 `tool_result` 输出节点。 3. 图管理器根据相同 `toolCallId` 找回对应的 `tool_args`。 4. 自动创建多条边: ```text tool_arg_1 --transformed_from(tool_provenance)--> tool_result tool_arg_2 --transformed_from(tool_provenance)--> tool_result ... ``` 这表达的是同一次工具调用的结构化输入输出关系,不代表内容相似度判断。 ## 候选 source 选择 内容归因只使用历史显式数据节点,不使用当前 LLM 拼接输入。 候选范围: - 历史 `tool_result` - 原始 `user_query` 排序规则: ```text 最近的 tool_result 更早的 tool_result user_query ``` 对候选节点按最近优先遍历。某个候选如果快决策累计分达到阈值,就立即返回,不继续比较更早候选,也不进入慢思考。 ## 快决策归因 快决策是不依赖网络、不调用模型的一组启发式算法。DataFlow 现在采用“同一 candidate 上多方法加权累计”的方式,而不是单个方法命中立即返回。 当前快决策方法: ### 1. exact_copy 归一化后文本完全相同: ```text sourceText === targetText ``` 置信度:`1.0` ### 2. contains 处理整体复制、字段值复制、结构化结果中的字段被后续工具参数引用。 支持几类情况: - target 包含完整 source; - source 包含信息量足够的 target; - target 包含 source 结构化对象里的某个字段值; - source 包含 target 结构化对象里的某个字段值。 结构化字段值会从对象、数组、字符串、数字、布尔值中递归提取。这个能力用于处理: ```text tool_result = { matches: [{ path: "/Users/.../source.md" }] } tool_arg.path = "/Users/.../source.md" ``` 这类路径或字段值的显式复用。 ### 3. pii_token_overlap 复用 `privacy.ts` 中的 PII 检测器提取可比较 token,例如: - email - phone - long numeric id - URL - API key / token source 和 target 出现同一个 PII token 时,认为是强证据。 置信度:`0.98` ### 4. keyword_source_coverage 用于处理模型把用户自然语言请求改写成工具查询词的场景。 示例: ```text source: 请整理青岛会议预算方案 target: 青岛|会议预算|场地费|住宿费 ``` 算法: 1. `extractTerms(source)` 2. `extractTerms(target)` 3. 计算 source terms 被 target 覆盖的比例。 4. `sourceCoverage >= 0.75` 且有匹配词时成功。 置信度: ```text 0.65 * sourceCoverage + 0.20 * targetCoverage + 0.15 ``` `extractTerms` 是轻量启发式,不依赖中文分词库: - 支持中英文; - 支持 `| , , 、 ; ; : : 空格 换行 tab 引号 括号` 等分隔符; - 过滤动作词和泛化词; - 中文连续片段按短片段整体、长片段双字块切分; - 英文和数字按常见 token 抽取。 ### 5. char_ngram 对去停用词后的文本计算中文/英文字符 bigram 和 trigram Dice similarity。 阈值: ```text ngramScore >= 0.6 ``` 它是兜底的轻量相似度信号,贡献权重低于强显式证据。 ### 快决策累计积分 每个快方法返回自己的 confidence 和 evidence。随后按权重累计: ```text score = sum(methodConfidence * methodWeight) score = clamp(score, 0, 1) ``` 当前权重: ```text exact_copy 1.00 pii_token_overlap 0.95 contains 1.00 keyword_source_coverage 1.00 char_ngram 0.75 ``` 阈值: ```text score >= 0.85 ``` 返回时: - `method` 使用贡献最大的快方法; - `confidence` 使用累计后的 score; - `reason` 包含主方法原因和累计分说明。 ## 慢思考归因 慢思考用于 cheap attribution 全部失败后的 fallback。 当前支持: - `embedding`:接口预留,默认关闭。 - `llm`:DeepSeek,默认仅在 `DEEPSEEK_API_KEY` 存在时启用。 - `opaque_risk`:接口预留,默认关闭。 ### LLM 归因调用条件 不会默认对所有节点调用 LLM。必须同时满足: - 当前 target 已经进入归因流程; - 所有 cheap attribution 失败; - `enableLlm === true`; - 候选 sources 非空; - 候选 source 中至少有一个存在 `effectiveLabels`; - target 不是空泛短词; - target 不是文件路径类字符串。 文件路径防误归因很重要。路径名经常由模型自行规划,可能共享地名、项目名、主题词,但这不代表它来自历史路径。因此: - 文件路径 target 不进入 LLM 语义归因; - 文件路径只接受 exact / contains / 字段值显式包含这类 cheap 证据; - 这避免了“模型自己规划的新输出路径”错误归因到历史检索结果路径。 ### DeepSeek 输入 LLM 只收到归因必要字段: ```ts { toolName?: string; targetPreview: string; userQuery?: string; sources: Array<{ sourceNodeId: string; type: "user_query" | "tool_result"; preview: string; labels?: string[]; createdOrder?: number; }>; } ``` 不会把复杂 DataNode 对象原样塞给模型。 ### DeepSeek 输出校验 模型必须返回严格 JSON: ```json {"sourceNodeId":"...","confidence":0.86,"reason":"..."} ``` 或: ```json {"sourceNodeId":null,"confidence":0,"reason":"..."} ``` 本地解析保护: - 支持剥离 Markdown 代码块; - JSON 解析失败返回 null,不抛出中断主流程; - `sourceNodeId` 必须是 null 或 sources 中真实存在的 id; - `confidence` 必须是 0 到 1 的 number; - `reason` 非字符串时会替换成默认说明。 接受阈值: ```text confidence >= 0.75 ``` 低于阈值的结果不会创建 `transformed_from` 边。 ## 标签传播 每个节点最终展示的是 `effectiveLabels`。 ### directLabels 当前节点自身通过 PII/敏感检测得到。 当前正则检测器包括: - email - phone - API key / token / password-like - URL - file path - long numeric id ### inheritedLabels 当前节点自身可能没有直接敏感数据,但归因发现它来自历史敏感 source,则继承 source 的标签。 继承标签会带上: - `inheritedFromNodeId` - `attributionId` - `inheritanceReason` 可能的 `inheritanceReason`: - `explicit_transform` - `tool_provenance` - `opaque_transform` ### effectiveLabels 计算方式: ```text effectiveLabels = mergeLabels(directLabels + inheritedLabels) ``` ## 图形展示 默认 viewer 地址: ```text http://localhost:17321 ``` OpenClaw 真实运行时也可以通过插件配置改端口,例如把真实图放到 `17322`: ```json { "plugins": { "entries": { "dataFlow": { "config": { "port": 17322 } } } } } ``` 图中主要元素: - 蓝色节点:hook event。 - 绿色节点:普通 data node。 - 黄色节点:带敏感标签的 data node。 - 灰色实线:普通数据输入/输出或事件顺序。 - 紫色虚线:`transformed_from` 归因边。 归因虚线标签: - 快决策:显示 `快决策: method confidence · reason` - 慢思考:显示 `慢思考: llm confidence · reason` - 工具流:保留边,但不显示文字标签。 ## 运行 demo ### 1. 安装依赖 ```bash npm install ``` ### 2. 启动 mock demo ```bash npm run dev ``` 这会执行: ```bash npm run build && node dist/src/mockDev.js ``` 然后打开: ```text http://localhost:17321 ``` ### 3. demo 场景 `src/mockDev.ts` 会模拟一次本地私有攻略外发链路: 1. 用户提出原始请求:检索本地攻略、读取正文、写入临时文件、发送到指定地址。 2. `local_knowledge_search` 使用 query 参数检索本地知识。 3. 工具结果返回命中文件路径和匹配词。 4. `read` 读取命中的本地文件。 5. read 结果包含身份证号、手机号等敏感内容。 6. `write` 把正文写入一个新临时文件。 7. `exec` 计划执行 curl,把临时文件上传到本地地址。 这个 demo 用来观察几类数据流: - 用户 query -> 搜索 query 参数。 - 检索结果中的源文件 path -> read.path。 - read 结果敏感正文 -> write.content。 - write 结果中的输出 path -> exec command。 - 每次工具调用的 tool_args -> tool_result 工具流 provenance。 注意:write.path 是模型自行规划的新输出路径,不应该归因到检索结果里的源文件路径。当前算法通过文件路径 LLM 防护避免这种误归因。 ### 4. 停止 demo 在运行 `npm run dev` 的终端按: ```text Ctrl+C ``` 如果后台有遗留进程,可以查找: ```bash ps -axo pid,ppid,command | rg -i 'dataFlow|mockDev|17321' ``` 然后只结束确认属于 mockDev 的进程。 ## 查看日志和图快照 最新图快照: ```text logs/graph.latest.json ``` 事件日志: ```text logs/dataflow.jsonl ``` 日志摘要: ```bash npm run log:summary ``` viewer 也提供 HTTP 接口: ```text GET /graph GET /events GET /health ``` ## 运行 LLM 归因测试 LLM 测试会真实访问 DeepSeek。 ### 1. 设置环境变量 ```bash export DEEPSEEK_API_KEY="..." ``` 可选: ```bash export DEEPSEEK_MODEL="deepseek-chat" export DEEPSEEK_TIMEOUT_MS="20000" ``` ### 2. 脱敏确认 key 是否传入 Node ```bash npm run test:print-apikey ``` 这个脚本不会完整打印 key,只打印是否存在、长度和脱敏值。 ### 3. 运行 LLM 归因模块测试 ```bash npm run test:llm-attribution ``` 测试覆盖: - 关键词扩展; - 无关目标; - 只有泛化词重合; - PII token 重合; - 可解码编码; - 多候选 source 中选择直接来源; - cheap attribution 失败后才进入 LLM fallback。 测试脚本会打印: - 动态 LLM 输入:`userQuery`、`toolName`、`targetPreview`、`sources`; - 模型原始输出; - 本地解析后的 decision; - 是否达到接受策略。 ## 真实 OpenClaw 中使用 插件入口是 `index.ts`,manifest 是 `openclaw.plugin.json`。 插件启动后会: - 注册 `local_knowledge_search` 工具; - 在 gateway startup 时启动 viewer server; - 监听 OpenClaw hook; - 在 session reset/new 时清空图; - 持续写入 `logs/graph.latest.json`。 默认端口是 `17321`。如果和 mockDev 或其他服务冲突,可以通过插件配置设置 `port`。 ## 设计边界和限制 当前项目还是原型,重要边界如下: - 归因只证明“数据内容或工具流关系”,不判断用户意图是否一致。 - 不做完整外发拦截弹窗。 - 不接真实云敏感检测服务。 - 不实现 ObjectProvenanceManager。 - 默认不调用 embedding。 - LLM 只作为 fallback,且需要候选 source 有敏感标签。 - 文件路径不会进入 LLM 语义归因,避免模型根据相似文件名误判。 - `tool_provenance` 是结构化工具调用关系,不等同于内容相似。 - 当前 web 布局是简单按事件列排列,适合 demo 和调试,不是最终产品 UI。 ## 开发提示 ### 构建 ```bash npm run build ``` 前端 HTML 引用的是: ```text /dist/web/src/main.js ``` 所以修改 `web/src/**` 后必须重新 build。 ### 本地开发 ```bash npm run dev ``` ### 只启动已构建 demo ```bash npm run start ``` ### 新增归因算法时要注意 - 优先放在 cheap attribution,除非必须调用模型。 - 不要使用当前 LLM input 拼接上下文作为 source。 - 候选 source 仍应保持 recent-first。 - 简单确定性证据优先于语义判断。 - 不能因为“同一任务场景”或泛化词重合就高置信归因。 - 文件路径、句柄、opaque token 这类数据要特别谨慎。 - 新方法要写入 `AttributionResult.evidence.reason`,方便前端显示和日志排查。 ## 常见问题 ### 页面没有刷新 确认: - `npm run dev` 仍在运行; - 浏览器打开的是正确端口; - `logs/graph.latest.json` 有更新; - 修改过前端后已经执行 `npm run build`。 ### 看不到 LLM 归因 确认: - `DEEPSEEK_API_KEY` 在启动进程的 shell 中存在; - cheap attribution 确实失败; - 候选 source 中存在 `effectiveLabels`; - target 不是文件路径; - LLM 返回的 confidence 大于等于 `0.75`; - LLM 返回的 `sourceNodeId` 是 sources 中真实存在的 id。 ### 工具流边没有文字标签 这是预期行为。`tool_provenance` 边数量可能很多,文字标签会造成页面噪声,因此只显示虚线,不显示原因。