node-context-query-tool-plan.md 20 KB

节点前驱上下文查询工具实施方案

状态:实施中 关联: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(内存访问快照)

public class VisitSnapshot {
    String visitUuid;
    String nodeId;
    String nodeType;
    String nodeName;
    int iterSeq;
    String fromVisitUuid;       // AGENTIC 用
    String fromNodeId;          // AGENTIC 用:"⬅ 来自 X#N"
    Map<String, Object> output; // envelope 形式
    List<ExecutionLog> logs;    // 思考/工具/todo...
    long sortOrder;
    long startedAt;
    long endedAt;
    String status;              // RUNNING/SUCCESS/FAILED
}

4.2.2 ContextPathGraph(路径图)

public class ContextPathGraph {
    List<String> visitUuids;          // 所有关键 visitUuid(按 sortOrder)
    List<String> nodeIds;             // 所有关键 nodeId(按访问顺序去重)
    List<ContextPath> paths;          // 多前驱链
    Set<String> backtrackEdgeKeys;    // 哪些 path 段是回边
}

public class ContextPath {
    List<String> sequence;            // 节点序列(如 [A, B, C, G])
    List<String> visitUuids;          // 每跳对应 visitUuid
    boolean hasBacktrack;             // 是否包含回边
}

4.2.3 查询响应 DTO

// 输出查询
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<ToolCallEntry> toolCalls;
    List<TodoEntry> todoList;
    List<ExecutionLog> logs;   // 原始日志(kind=ALL 时返回)
    boolean truncated;
}

五、实施阶段

阶段 1:扩展图遍历能力(无外部依赖)

目标:补齐路径/直接前驱/直接后继查询。

改动:

  1. 新增 WorkflowPathResolver.java(独立工具类)

    • findPathsTo(nodeId, dag, activeEdges) → List<List<String>>:DFS 反向遍历 incomingEdges,遇无入边节点作为路径起点
    • getDirectPredecessors(nodeId, dag, activeEdges) → List<String>:按拓扑排序
    • getDirectSuccessors(nodeId, dag) → List<String>
    • 算法复杂度 O(V+E),工作流规模小(<100 节点)毫秒级
  2. 单元测试 WorkflowPathResolverTest.java:

    • 多前驱 DAG
    • 条件分支激活/未激活
    • 单链路
    • 环(防御性,虽然 DAG 不允许)

验证: 测试通过 + 路径集合断言。

阶段 2:内存 Visit 索引(核心数据结构)

目标:让运行中的节点能"快"查询到结构化访问历史。

改动:

  1. 新增 VisitSnapshot.java(POJO,字段见 4.2.1)

  2. 新增 InMemoryVisitIndex.java

    • 内部:ConcurrentHashMap<String /*runId*/, ConcurrentLinkedDeque<VisitSnapshot>>
    • 方法:
      • register(runId, visitUuid, nodeId, ...):创建空 snapshot
      • appendOutput(runId, visitUuid, Map<String,Object> 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
    • 方法:

      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: <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 新增

    "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. 新增派生查询方法:

    Optional<WorkflowRunVisit> findByRunIdAndVisitUuid(Long runId, String visitUuid);
    List<WorkflowRunVisit> findByRunIdAndNodeIdOrderBySortOrderAsc(Long runId, String nodeId);
    List<WorkflowRunVisit> 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 待开始 - 文档 + 降级

状态字段值:待开始 / 进行中 / 已完成 / 已阻塞