# 节点前驱上下文查询工具实施方案 > 状态:实施中 > 关联:[node-context-ondemand-plan.md](./node-context-ondemand-plan.md)(设计草案,本文是其方案 B 的深化与具体化) > 适用版本:agent-management backend ≥ 1.6.0 > 创建日期:2026-08-13 ## 一、需求 为工作流中的每个节点(通过 LLM Agent)提供一种工具能力,让 LLM 在执行任务时能主动查询: 1. **前驱路径图**:当前节点之前所有可达前驱的运行轨迹 - DAG 模式:基于画布边 + 激活分支,输出 `A→B→C→G`、`D→E→F→G` 这类多前驱链 - AGENTIC 模式:基于 `fromVisitUuid` 单链表 + 画布边,输出 `A→B→C→G`、`G→C`(智能回边)这类访问图 2. **前驱输出变量**:每个前驱节点的 envelope 输出(含 `status` / `message` / `data`) 3. **前驱中间数据**:每个前驱的思考过程、工具调用记录、待办清单(来自 `logsJson`) **性能要求**:查询要快(亚秒级)和准(不漏不重,按拓扑/时间顺序)。 ### 1.1 路径图示例 ``` 工作流拓扑:A→B→C→G、D→E→F→G G 节点查询 path: paths: [ { sequence: [A, B, C, G] }, { sequence: [D, E, F, G] } ] 若 AGENTIC 模式下从 G 智能回边到 C,C 第二次访问时查询 path: paths: [ { sequence: [A, B, C, G], hasBacktrack: false }, { sequence: [D, E, F, G], hasBacktrack: false }, { sequence: [G, C], hasBacktrack: true } // 回边段 ] ``` ### 1.2 与已有计划的关系 `node-context-ondemand-plan.md` 提出了方案 A/B/C 的高层对比。本方案是**方案 B 的深化**: - 补齐了 Hermes Bridge 单向 HTTP 的协议扩展方案 - 补齐了"路径图"概念(不只是节点集合,而是访问轨迹) - 补齐了内存索引、token 鉴权、性能基线等实施细节 - 不涉及方案 A(envelope 加 trace 字段),保持 envelope 严格规范 --- ## 二、现状核心约束 | 关键事实 | 影响 | |---|---| | 工具实际执行在 `hermes-agent` 项目(不在本仓库) | 不能在本仓库直接注册 LLM 工具 | | Hermes Bridge 是单向 HTTP,Python 工具无回调 Java 通道 | 需新增"Python 工具回调 Java 查询接口"路径 | | `WorkflowContext` 不携带图结构、visitUuid、visit 历史 | 需扩展运行时上下文 | | `AgenticExecutor` 内存只有扁平 `currentVars`,丢失 visitUuid 关联 | 需在内存中维护结构化 visit 链 | | `NodeWorkspace` 已有 envelope 解包能力(`getScopedDataView` 等) | 可直接复用 | | `WorkflowScopeResolver` 只有前驱可达集,无路径/后继 | 需扩展图遍历能力 | | 当前 Prompt 注入是扁平 KV,无结构化路径 | 不能仅靠 Prompt 注入满足需求 | ### 2.1 关键代码位置 | 用途 | 文件路径 | 关键行号 | |---|---|---| | 节点 executor 接口 | `backend/src/main/java/com/agent/management/engine/NodeExecutor.java` | 24 | | 节点 workspace | `backend/src/main/java/com/agent/management/engine/NodeWorkspace.java` | 19-31 | | workspace 构建器(DAG) | `backend/src/main/java/com/agent/management/engine/NodeWorkspaceBuilder.java` | 50-82 | | workspace 构建器(AGENTIC) | `backend/src/main/java/com/agent/management/engine/AgenticExecutor.java` | 311-332 | | 图遍历工具(待扩展) | `backend/src/main/java/com/agent/management/engine/WorkflowScopeResolver.java` | 14-60 | | DAG 结构 | `backend/src/main/java/com/agent/management/engine/DagResolver.java` | 31-42 | | Visit 表实体 | `backend/src/main/java/com/agent/management/model/entity/WorkflowRunVisit.java` | 25-108 | | Visit Repository | `backend/src/main/java/com/agent/management/repository/WorkflowRunVisitRepository.java` | 15-22 | | Prompt 注入入口 | `backend/src/main/java/com/agent/management/engine/ContextPromptHelper.java` | 33-105 | | Hermes Bridge 客户端 | `backend/src/main/java/com/agent/management/engine/hermes/HermesBridgeClient.java` | 137-229 | | Hermes Bridge Python 端 | `backend/hermes-bridge/hermes_bridge.py` | 289-499 | | Hermes Agent 执行器 | `backend/src/main/java/com/agent/management/engine/executor/HermesAgentExecutor.java` | 67-98 | | NodeExecutionResult | `backend/src/main/java/com/agent/management/engine/NodeExecutionResult.java` | 14-27 | --- ## 三、方案选型 | 维度 | 方案 A:Python 工具 + HTTP 回调 | 方案 B:Prompt 预注入 | **方案 C(选定):Java 端工具网关** | |---|---|---|---| | 工具实现位置 | hermes-agent 项目 | 本仓库 Java 端 | 本仓库 Java 端 + Bridge 协议扩展 | | LLM 何时拿到 | 按需查询 | 一次性全量塞 Prompt | 按需查询 | | Token 成本 | 低 | 高 | 低 | | 大工作流可用性 | 好 | 差 | 好 | | 跨仓库改动 | 是 | 否 | 否 | | 支持精细查询 | ✓ | ✗ | ✓ | | 实现复杂度 | 中 | 低 | 中 | ### 3.1 选定方案 C 的核心思路 1. **Java 端**:新增 `NodeContextQueryService`,提供三类查询 API(路径 / 输出 / 中间数据) 2. **Bridge 协议扩展**:`HermesBridgeClient` 调用 `/run` 时,传入 `context_query_endpoint` + `run_id` + `current_visit_uuid` + `auth_token` 3. **Python 端(hermes-agent)**:实现 `workflow_context` 工具,内部走 HTTP 调用上述端点(**跨仓库,本仓库仅提供协议文档与 systemPrompt 引导**) 4. **降级路径**:若 hermes-agent 暂未集成,Java 侧查询服务、HTTP 接口、内存索引仍有独立价值(前端可用、调试可用) ### 3.2 为什么不是方案 B(Prompt 预注入) - 用户明确"通过工具调用获取" - 大工作流全量塞 Prompt token 浪费严重 - 无法精细控制查询字段 ### 3.3 为什么不是方案 A(Python 工具直接实现) - 需改 hermes-agent 仓库,跨仓库协调成本高 - Python 工具回调 Java 需重新搭建认证、超时、错误处理 - 本仓库已掌握所有数据,没必要绕一圈 --- ## 四、总体设计 ### 4.1 三层架构 ``` ┌─────────────────────────────────────────────────────────┐ │ LLM(在 hermes-agent 内) │ │ workflow_context(query="path"|"output"|"meta", │ │ nodeId="llm_1", field="summary") │ └──────────────────┬──────────────────────────────────────┘ │ HTTP(带 auth_token) ▼ ┌─────────────────────────────────────────────────────────┐ │ Java:NodeContextQueryController(新增,内部接口) │ │ POST /internal/context/query │ └──────────────────┬──────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ Java:NodeContextQueryService(新增,核心服务) │ │ - queryPath / queryOutput / queryMeta │ └──────────────────┬──────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ 既有能力 + 扩展 │ │ - WorkflowPathResolver(新增) │ │ - InMemoryVisitIndex(新增) │ │ - WorkflowRunVisitRepository(扩展) │ │ - NodeWorkspace + DagResolver(复用) │ └─────────────────────────────────────────────────────────┘ ``` ### 4.2 数据模型 #### 4.2.1 `VisitSnapshot`(内存访问快照) ```java public class VisitSnapshot { String visitUuid; String nodeId; String nodeType; String nodeName; int iterSeq; String fromVisitUuid; // AGENTIC 用 String fromNodeId; // AGENTIC 用:"⬅ 来自 X#N" Map output; // envelope 形式 List logs; // 思考/工具/todo... long sortOrder; long startedAt; long endedAt; String status; // RUNNING/SUCCESS/FAILED } ``` #### 4.2.2 `ContextPathGraph`(路径图) ```java public class ContextPathGraph { List visitUuids; // 所有关键 visitUuid(按 sortOrder) List nodeIds; // 所有关键 nodeId(按访问顺序去重) List paths; // 多前驱链 Set backtrackEdgeKeys; // 哪些 path 段是回边 } public class ContextPath { List sequence; // 节点序列(如 [A, B, C, G]) List visitUuids; // 每跳对应 visitUuid boolean hasBacktrack; // 是否包含回边 } ``` #### 4.2.3 查询响应 DTO ```java // 输出查询 public class NodeOutputView { String nodeId; Integer iterSeq; String visitUuid; Integer status; // envelope status String message; // envelope message Object data; // envelope data(或单个 field 值) String requestedField; // 若按 field 查询 boolean truncated; } // 中间数据查询 public class VisitMetaView { String nodeId; Integer iterSeq; String visitUuid; String thinking; // 拼接后的思考文本 List toolCalls; List todoList; List logs; // 原始日志(kind=ALL 时返回) boolean truncated; } ``` --- ## 五、实施阶段 ### 阶段 1:扩展图遍历能力(无外部依赖) **目标**:补齐路径/直接前驱/直接后继查询。 **改动:** 1. 新增 `WorkflowPathResolver.java`(独立工具类) - `findPathsTo(nodeId, dag, activeEdges)` → `List>`:DFS 反向遍历 incomingEdges,遇无入边节点作为路径起点 - `getDirectPredecessors(nodeId, dag, activeEdges)` → `List`:按拓扑排序 - `getDirectSuccessors(nodeId, dag)` → `List` - 算法复杂度 `O(V+E)`,工作流规模小(<100 节点)毫秒级 2. 单元测试 `WorkflowPathResolverTest.java`: - 多前驱 DAG - 条件分支激活/未激活 - 单链路 - 环(防御性,虽然 DAG 不允许) **验证:** 测试通过 + 路径集合断言。 ### 阶段 2:内存 Visit 索引(核心数据结构) **目标**:让运行中的节点能"快"查询到结构化访问历史。 **改动:** 1. 新增 `VisitSnapshot.java`(POJO,字段见 4.2.1) 2. 新增 `InMemoryVisitIndex.java` - 内部:`ConcurrentHashMap>` - 方法: - `register(runId, visitUuid, nodeId, ...)`:创建空 snapshot - `appendOutput(runId, visitUuid, Map output)` - `appendLog(runId, visitUuid, ExecutionLog log)` - `getVisits(runId)`:排序后列表 - `getVisit(runId, visitUuid)` - `getVisitsByNode(runId, nodeId)` - `evict(runId)`:run 结束后清理(保留 30 分钟兜底) 3. 扩展 `WorkflowContext.java`:新增 `transient InMemoryVisitIndex visitIndex`(不序列化) 4. 扩展 `WorkflowEngine` / `AgenticExecutor` / `WorkflowLevelExecutor`: - `scheduleNode` 入口:`visitIndex.register(...)` - `streamSink.emit`:同步 `visitIndex.appendLog` - 节点执行完:`visitIndex.appendOutput` - `runComplete/runError`:延迟 evict(30 分钟兜底) **验证:** 集成测试覆盖索引内容与 DB 一致;性能测试 1000 visit < 50ms。 ### 阶段 3:核心查询服务 `NodeContextQueryService` **目标**:三类查询的纯业务逻辑,无 HTTP/LLM 关联。 **改动:** 1. 新增 `NodeContextQueryService.java`(Spring `@Service`) - 依赖:`WorkflowRunVisitRepository` / `InMemoryVisitIndex` / `WorkflowPathResolver` / `DagResolver` - 方法: ```java ContextPathGraph queryPath(String runId, String currentVisitUuid); NodeOutputView queryOutput(String runId, String nodeId, String field, Integer iterSeq); VisitMetaView queryMeta(String runId, String visitUuid, String nodeId, MetaKind kind, Integer iterSeq); ``` 2. 新增 `NodeOutputView` / `VisitMetaView` / `MetaKind`(枚举:THINKING/TOOL_CALLS/TODO/ALL) 3. 查询逻辑核心: - **优先内存**:`visitIndex.getVisits(runId)` 非空走内存 - **回退 DB**:内存为空走 `visitRepo.findByRunIdOrderBySortOrderAsc(...)` - **路径计算**: - DAG:`WorkflowPathResolver.findPathsTo(nodeId, dag, activeEdges)` - AGENTIC:从当前 visit 反向 BFS `fromVisitUuid` 链 + 合并画布前驱路径 - **回边检测**:`fromVisitUuid` 指向的 nodeId 不在画布前驱中 → 标记为回边 **验证:** 单元测试覆盖三种查询 × DAG/AGENTIC;关键场景(多前驱、回边、失败节点)。 ### 阶段 4:内部 HTTP 接口 `NodeContextQueryController` **目标**:暴露查询能力给 Python 工具。 **改动:** 1. 新增 `NodeContextQueryController.java`(路径 `/internal/context`,**不走** `/api/v1`) - `POST /internal/context/query` - Header:`X-Internal-Token: ` - Body:`{ runId, visitUuid, query, nodeId?, field?, iterSeq?, kind?, maxBytes? }` - 响应:`{ code: 0, data: <视图> }` 2. 新增 `InternalContextTokenService.java` - `generateToken(runId, visitUuid, ttlSeconds)` → 一次性 token(Caffeine 缓存) - `validateToken(token, runId, visitUuid)` - 限制只有当前运行中的 visit 才能查自己 run 的数据 3. 新增 `NodeContextQueryProperties.java`(配置) - `agent-management.context-query.enabled=true` - `agent-management.context-query.token-ttl=3600` - `agent-management.context-query.max-bytes=8192` - `agent-management.context-query.allowed-hosts=127.0.0.1,hermes-bridge` **验证:** MockMvc 测试覆盖 happy path + 错误码(401/403/404/422);性能 P99 < 100ms(内存)/ < 300ms(DB)。 ### 阶段 5:Bridge 协议扩展 **目标**:让 Python 端工具知道"去哪里查、用什么 token"。 **改动:** 1. 扩展 `HermesBridgeClient.run`(行 137-229):HTTP body 新增 ```json "context_query": { "enabled": true, "endpoint": "http://127.0.0.1:8080/internal/context/query", "token": "...", "run_id": "abc12345", "current_visit_uuid": "..." } ``` 2. 扩展 `HermesAgentExecutor.execute`(行 67-98):调 Bridge 前生成 token,注入 `context_query` 3. systemPrompt 注入(通过 `ContextPromptHelper.merge` 追加):工具用法说明 ``` === 工作流上下文查询工具 === workflow_context(query="path") → 前驱路径图 workflow_context(query="output", nodeId=..) → 某前驱的输出 workflow_context(query="meta", nodeId=.., kind="THINKING") → 思考过程 ``` **验证:** 集成测试 mock Bridge,断言 body 含 `context_query`;token 生成与校验闭环。 ### 阶段 6:扩展 `WorkflowRunVisitRepository`(DB 回退路径) **目标**:保证 DB 回退也有足够查询能力。 **改动:** 1. 新增派生查询方法: ```java Optional findByRunIdAndVisitUuid(Long runId, String visitUuid); List findByRunIdAndNodeIdOrderBySortOrderAsc(Long runId, String nodeId); List findByRunIdOrderBySortOrderDesc(Long runId); ``` 2. 新增 `WorkflowRunInternalIdResolver`(小工具) - 把前端可见的 `runId`(8 位短 UUID)解析为 DB 主键 `Long id` - Caffeine 缓存 TTL 5 分钟 **验证:** Testcontainers + 真实 PG。 ### 阶段 7:文档与降级策略 **改动:** 1. 新增 `docs/specs/internal-context-query-spec.md`:协议描述、错误码、性能基线、降级开关 2. 降级开关 `context-query.enabled=false` 时: - Bridge 不传 `context_query` - systemPrompt 不注入工具说明 - 内部 HTTP 接口返回 503 3. 跨仓库文档 `docs/reference/hermes-bridge-reference.md`: - `workflow_context` 工具的 Python 实现指引 - 协议字段说明 --- ## 六、依赖关系 ``` 阶段 1(图遍历)─┐ ├─→ 阶段 3(查询服务)─→ 阶段 4(HTTP 接口)─→ 阶段 5(Bridge 协议) 阶段 2(内存索引)┘ │ ↓ 阶段 6(Repository 扩展)─→ 阶段 3(查询服务回退路径) 阶段 7(文档) ``` 可并行:阶段 1 + 阶段 2 + 阶段 6(彼此独立)。 --- ## 七、性能基线 | 查询类型 | 数据源 | 目标 P99 | 备注 | |---|---|---|---| | path(10 节点小图) | 内存 | < 5ms | DFS 遍历 | | path(100 节点大图) | 内存 | < 30ms | 路径数指数增长需限流 | | output(单节点) | 内存 | < 5ms | Map 查询 | | output(单节点) | DB 回退 | < 100ms | 索引命中 | | meta(THINKING,1KB) | 内存 | < 5ms | List 过滤 | | meta(ALL,50KB) | 内存 | < 20ms | 需做 maxBytes 截断 | **保护策略**: - 单查询响应超过 `maxBytes`(默认 8KB)时截断,返回 `truncated: true` - 单 run 内 LLM 调用查询次数超过 50 次时打 warn 日志 --- ## 八、风险与缓解 | 风险 | 级别 | 缓解 | |---|---|---| | hermes-agent 不实现 `workflow_context` 工具 | 高 | 阶段 1-6 即便 Python 不配合也有独立价值;阶段 5 提供 Prompt 引导,Python 侧改造成本极低 | | 内存索引与 DB 不一致(JVM 崩溃) | 中 | run 启动时若 DB 有未结束的 visit 且内存无索引,自动从 DB 重建 | | LLM 滥用查询接口 | 中 | 单 run 查询次数限流(默认 50)+ maxBytes 截断 | | 内部接口被外部访问 | 中 | 内部 token + 主机白名单 + 不走 `/api/v1` 公开路径 | | 大工作流 path 数指数爆炸 | 低 | 限制最大路径数(默认 100),超过聚合为"前驱节点集合" | | AGENTIC 模式 `fromVisitUuid` 链很长 | 低 | 反向 BFS 限制最大深度(默认 50) | --- ## 九、估计复杂度 | 阶段 | 内容 | 工时 | |---|---|---| | 1 | 图遍历扩展 + 测试 | 4-6h | | 2 | 内存索引 + 接入引擎 + 测试 | 8-10h | | 3 | 查询服务 + DTO + 测试 | 8-10h | | 4 | HTTP Controller + Token + 测试 | 4-6h | | 5 | Bridge 协议扩展 + Prompt 注入 | 3-4h | | 6 | Repository 派生方法 + 测试 | 2-3h | | 7 | 文档 + 降级开关 | 2-3h | | **合计** | | **31-42h(约 4-5 个工作日)** | **复杂度评估:中高**。单个模块不复杂,但涉及 7 处改动,测试覆盖是关键。 --- ## 十、关键设计决策 1. **采用方案 C(Java 端工具网关)**:复用现有 Java 能力,不依赖跨仓库改动 2. **优先内存索引,DB 仅作回退**:满足"快"的要求;AGENTIC 运行中查询不阻塞 3. **路径图(`ContextPathGraph`)统一表示 DAG + AGENTIC + 回边**:一份数据结构覆盖所有场景 4. **内部接口 + 一次性 token**:安全且简单,不污染外部 API 鉴权体系 5. **Prompt 主动告知工具用法**:LLM 不会主动猜,必须在 systemPrompt 中明示 6. **降级开关 + 文档**:出问题可快速回滚;为 hermes-agent 集成留足空间 --- ## 十一、实施进度跟踪 | 阶段 | 状态 | 完成时间 | 备注 | |---|---|---|---| | 1 | 待开始 | - | WorkflowPathResolver | | 2 | 待开始 | - | InMemoryVisitIndex | | 3 | 待开始 | - | NodeContextQueryService | | 4 | 待开始 | - | Controller + Token | | 5 | 待开始 | - | Bridge 协议 | | 6 | 待开始 | - | Repository 扩展 | | 7 | 待开始 | - | 文档 + 降级 | > 状态字段值:待开始 / 进行中 / 已完成 / 已阻塞