状态:实施中 关联:node-context-ondemand-plan.md(设计草案,本文是其方案 B 的深化与具体化) 适用版本:agent-management backend ≥ 1.6.0 创建日期:2026-08-13
为工作流中的每个节点(通过 LLM Agent)提供一种工具能力,让 LLM 在执行任务时能主动查询:
A→B→C→G、D→E→F→G 这类多前驱链fromVisitUuid 单链表 + 画布边,输出 A→B→C→G、G→C(智能回边)这类访问图status / message / data)logsJson)性能要求:查询要快(亚秒级)和准(不漏不重,按拓扑/时间顺序)。
工作流拓扑: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 } // 回边段
]
node-context-ondemand-plan.md 提出了方案 A/B/C 的高层对比。本方案是方案 B 的深化:
| 关键事实 | 影响 |
|---|---|
工具实际执行在 hermes-agent 项目(不在本仓库) |
不能在本仓库直接注册 LLM 工具 |
| Hermes Bridge 是单向 HTTP,Python 工具无回调 Java 通道 | 需新增"Python 工具回调 Java 查询接口"路径 |
WorkflowContext 不携带图结构、visitUuid、visit 历史 |
需扩展运行时上下文 |
AgenticExecutor 内存只有扁平 currentVars,丢失 visitUuid 关联 |
需在内存中维护结构化 visit 链 |
NodeWorkspace 已有 envelope 解包能力(getScopedDataView 等) |
可直接复用 |
WorkflowScopeResolver 只有前驱可达集,无路径/后继 |
需扩展图遍历能力 |
| 当前 Prompt 注入是扁平 KV,无结构化路径 | 不能仅靠 Prompt 注入满足需求 |
| 用途 | 文件路径 | 关键行号 |
|---|---|---|
| 节点 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 成本 | 低 | 高 | 低 |
| 大工作流可用性 | 好 | 差 | 好 |
| 跨仓库改动 | 是 | 否 | 否 |
| 支持精细查询 | ✓ | ✗ | ✓ |
| 实现复杂度 | 中 | 低 | 中 |
NodeContextQueryService,提供三类查询 API(路径 / 输出 / 中间数据)HermesBridgeClient 调用 /run 时,传入 context_query_endpoint + run_id + current_visit_uuid + auth_tokenworkflow_context 工具,内部走 HTTP 调用上述端点(跨仓库,本仓库仅提供协议文档与 systemPrompt 引导)┌─────────────────────────────────────────────────────────┐
│ 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(复用) │
└─────────────────────────────────────────────────────────┘
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
}
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; // 是否包含回边
}
// 输出查询
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;
}
目标:补齐路径/直接前驱/直接后继查询。
改动:
新增 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 节点)毫秒级单元测试 WorkflowPathResolverTest.java:
验证: 测试通过 + 路径集合断言。
目标:让运行中的节点能"快"查询到结构化访问历史。
改动:
新增 VisitSnapshot.java(POJO,字段见 4.2.1)
新增 InMemoryVisitIndex.java
ConcurrentHashMap<String /*runId*/, ConcurrentLinkedDeque<VisitSnapshot>>register(runId, visitUuid, nodeId, ...):创建空 snapshotappendOutput(runId, visitUuid, Map<String,Object> output)appendLog(runId, visitUuid, ExecutionLog log)getVisits(runId):排序后列表getVisit(runId, visitUuid)getVisitsByNode(runId, nodeId)evict(runId):run 结束后清理(保留 30 分钟兜底)扩展 WorkflowContext.java:新增 transient InMemoryVisitIndex visitIndex(不序列化)
扩展 WorkflowEngine / AgenticExecutor / WorkflowLevelExecutor:
scheduleNode 入口:visitIndex.register(...)streamSink.emit:同步 visitIndex.appendLogvisitIndex.appendOutputrunComplete/runError:延迟 evict(30 分钟兜底)验证: 集成测试覆盖索引内容与 DB 一致;性能测试 1000 visit < 50ms。
NodeContextQueryService目标:三类查询的纯业务逻辑,无 HTTP/LLM 关联。
改动:
新增 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);
新增 NodeOutputView / VisitMetaView / MetaKind(枚举:THINKING/TOOL_CALLS/TODO/ALL)
查询逻辑核心:
visitIndex.getVisits(runId) 非空走内存visitRepo.findByRunIdOrderBySortOrderAsc(...)WorkflowPathResolver.findPathsTo(nodeId, dag, activeEdges)fromVisitUuid 链 + 合并画布前驱路径fromVisitUuid 指向的 nodeId 不在画布前驱中 → 标记为回边验证: 单元测试覆盖三种查询 × DAG/AGENTIC;关键场景(多前驱、回边、失败节点)。
NodeContextQueryController目标:暴露查询能力给 Python 工具。
改动:
新增 NodeContextQueryController.java(路径 /internal/context,不走 /api/v1)
POST /internal/context/queryX-Internal-Token: <token>{ runId, visitUuid, query, nodeId?, field?, iterSeq?, kind?, maxBytes? }{ code: 0, data: <视图> }新增 InternalContextTokenService.java
generateToken(runId, visitUuid, ttlSeconds) → 一次性 token(Caffeine 缓存)validateToken(token, runId, visitUuid)新增 NodeContextQueryProperties.java(配置)
agent-management.context-query.enabled=trueagent-management.context-query.token-ttl=3600agent-management.context-query.max-bytes=8192agent-management.context-query.allowed-hosts=127.0.0.1,hermes-bridge验证: MockMvc 测试覆盖 happy path + 错误码(401/403/404/422);性能 P99 < 100ms(内存)/ < 300ms(DB)。
目标:让 Python 端工具知道"去哪里查、用什么 token"。
改动:
扩展 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": "..."
}
扩展 HermesAgentExecutor.execute(行 67-98):调 Bridge 前生成 token,注入 context_query
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 生成与校验闭环。
WorkflowRunVisitRepository(DB 回退路径)目标:保证 DB 回退也有足够查询能力。
改动:
新增派生查询方法:
Optional<WorkflowRunVisit> findByRunIdAndVisitUuid(Long runId, String visitUuid);
List<WorkflowRunVisit> findByRunIdAndNodeIdOrderBySortOrderAsc(Long runId, String nodeId);
List<WorkflowRunVisit> findByRunIdOrderBySortOrderDesc(Long runId);
新增 WorkflowRunInternalIdResolver(小工具)
runId(8 位短 UUID)解析为 DB 主键 Long id验证: Testcontainers + 真实 PG。
改动:
新增 docs/specs/internal-context-query-spec.md:协议描述、错误码、性能基线、降级开关
降级开关 context-query.enabled=false 时:
context_query跨仓库文档 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| 风险 | 级别 | 缓解 |
|---|---|---|
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 处改动,测试覆盖是关键。
ContextPathGraph)统一表示 DAG + AGENTIC + 回边:一份数据结构覆盖所有场景| 阶段 | 状态 | 完成时间 | 备注 |
|---|---|---|---|
| 1 | 待开始 | - | WorkflowPathResolver |
| 2 | 待开始 | - | InMemoryVisitIndex |
| 3 | 待开始 | - | NodeContextQueryService |
| 4 | 待开始 | - | Controller + Token |
| 5 | 待开始 | - | Bridge 协议 |
| 6 | 待开始 | - | Repository 扩展 |
| 7 | 待开始 | - | 文档 + 降级 |
状态字段值:待开始 / 进行中 / 已完成 / 已阻塞