Human-in-the-Loop (HITL) 设计文档-学习笔记

Human-in-the-Loop (HITL) 设计文档

SpringAI Agent Engine — 设计方案、原理、实现规格与 Demo 图鉴(合并版)
版本:与当前代码库同步(2026-06)
适用范围:backend/ + frontend/ 中的 HITL 子系统

文档结构

章节 内容
§1–2 背景、目标、总体架构
§3–8 sessionId/requestId、数据模型、三通道触发、沙箱续跑、Guard
§9–12 前端设计、REST API、持久化、Agent 线程模型
§13 Demo 1–9 图鉴(含运行截图)
§14–18 配置、源码索引、演进、测试、术语
附录 A 设计原理与状态机

HITL-设计方案与原理.md 已合并至本文附录 A,该文件仅保留跳转说明。


1. 背景与目标

1.1 问题

Agent 执行中需用户确认危险操作、选择方案、补充信息或批量审批。若 LLM 仅在文本里提问,前端无法渲染结构化面板,回答也无法可靠回传给正在运行的 Agent 线程。

1.2 设计目标

目标 说明
结构化输入 confirm / single_choice / multi_choice / freeform_question / batch_confirm
执行挂起 HITL 点阻塞直至用户提交或超时
一问一答绑定 每面板唯一 requestId
会话隔离 sessionId 隔离 pending
多通道触发 ask_user、嵌入 JSON/DSML、沙箱 marker 统一收敛
沙箱续跑 marker 确认后 env 重跑脚本
沙箱防绕过 HitlSandboxGuard
端外快捷审批 Demo 9:消息通知 + 短链 + 处理进度

1.3 非目标

  • WebSocket(使用 SSE + REST)
  • 同 session 并行多个 HITL(串行)
  • 服务端完整对话存储(history 客户端携带)
  • 多租户鉴权

2. 总体架构

React ChatPage / HitlLinkPage
    │ SSE (input_request)          │ REST POST /api/hitl/respond
    ▼                              ▼
AgentService ──► HumanInputService (registerPending / awaitInput / respond)
    ├── AskUserSkillHandler
    └── CodeExecutionSkillHandler + HitlSandboxGuard
            └── HitlLinkService (Demo 9 短链)

2.1 传输层

方向 协议 端点
服务端 → 客户端 SSE POST /api/chat/stream
HITL 回答 REST POST /api/hitl/respond
pending REST GET /api/hitl/pending/{sessionId}
Demo 8 REST POST /api/hitl/demo/large-batch
Demo 9 REST POST /api/hitl/demo/notify-confirm
短链 REST GET/POST /api/hitl/link/{token}
短链页 SPA /h/{token}

3. 核心标识

3.1 sessionId

一次对话生命周期 ID。索引 pending[sessionId]、快照文件、沙箱日志。客户端从 SSE 写入 state 并在后续 chat/hitl 请求复用。

3.2 requestId

单次 HITL「问」的 UUID。SSE input_requestPOST respond 必须一致。CompletableFuture 按 requestId 唤醒。

3.3 history

客户端携带 history[];用户提交后追加 hitl_feedback 消息,保证 LLM 跨轮理解决策。


4. 数据模型

4.1 InputRequest

requestId, sessionId, prompt, interactionType, options, questions, suggestions, defaultTimeoutMs, source, meta

4.2 InputResponse

decision, values, freeText, cancelled, timedOut

4.3 ChatStreamEvent(HITL 相关)

input_request | input_resolved | input_timeout | tool_result(含 _hitl:true


5. 三通道触发

通道 source 恢复方式
ask_user 工具 skill tool_result JSON
嵌入 JSON/DSML llm_structured / llm_dsml UserMessage 合成
沙箱 HITL_REQUEST sandbox_marker env 重跑 + 输出合并

6. HumanInputService

registerPending(req) → futures[requestId] = new CompletableFuture<>()
awaitInput(req)        → future.get(timeoutMs)  // Agent worker 阻塞
respond(resp)        → future.complete(resp)    // 仅首次有效

registerPending 必须在 SSE 推送之前调用,避免用户秒点 404。


7. 多会话一致性

层次 机制
运行时 requestId 精确绑定
会话 sessionId 隔离 pending
语义 history + hitl_feedback

防重复:hitlCompletedRef(前端)、hitlResolvedInTurn(后端 ThreadLocal)。


8. 沙箱续跑与 Guard

环境变量: HITL_PHASE=resume, HITL_DECISION, HITL_VALUES, HITL_FREE_TEXT

Phase 分支(推荐): 首次 print marker;resume 分支执行业务。

HitlSandboxGuard: 拒绝伪造 env、无 phase 分支的 marker 脚本、首次路径 premature「已发送」输出。


9. 前端设计

模块 职责
ChatPage.jsx SSE、Demo 1–9、pending 面板、通知卡片
HumanInputPanel.jsx 五种 UI;可选 respondFn(短链页)
HitlLinkPage.jsx /h/{token} 审批 + 时间线
BatchLargePanel.jsx Demo 8 分页大批量
hitl.js API、normalize、15s 超时、listPending 恢复

pending 恢复: hitlLoading 约 2s 后自动 listPending(sessionId)


10. REST API 规格

POST /api/hitl/respond

Body: { sessionId, requestId, decision, values, freeText, cancelled, timedOut }
200: { status: "accepted" } · 404: pending 不存在

GET /api/hitl/pending/

返回 List<InputRequest>

POST /api/hitl/demo/large-batch · /demo/notify-confirm

Demo 8/9 预设载荷,见 §13

HitlLinkView:含 inputRequest, notification, status, steps[]


11. 持久化

FileSnapshotStore./data/hitl-snapshots/{sessionId}.json
重启后 future 丢失;需 listPending + reload 策略(部分未实现)。


12. Agent 执行模型

HTTP 线程返回 Flux;agent-{sessionId} worker 执行 runConversation,HITL 阻塞在 worker 上。
hitlEnabled=false 时不注册 ask_user、忽略沙箱 marker。


13. Demo 图鉴(1–9)

Cursor 预览:§13 截图使用 ![alt](data:image/...;base64,...) 格式(勿用 HTML <img>,长 base64 会被当纯文本)。图片放在表格外独立块中。源 PNG:docs/images/hitl/

预览测试:

Demo 9 处理完成

13.0 Demo 机制分类

类型 Demo 触发方式 依赖 LLM 面板位置
A. LLM 驱动 1–7 Chat 发送 DEMO_SCRIPT[n] → SSE input_request Chat · HumanInputPanel
B. 后端预设 8 POST /api/hitl/demo/large-batch Chat · BatchLargePanel
C. 消息 + 短链 9 POST /api/hitl/demo/notify-confirm Chat + /h/{token}
flowchart LR subgraph A["A · LLM 驱动 Demo 1-7"] U1[用户点 Demo] --> SSE[SSE input_request] SSE --> P1[HumanInputPanel] P1 --> R1[POST /api/hitl/respond] R1 --> AG[Agent 继续] end subgraph B["B · 后端预设 Demo 8"] U2[Demo 8 按钮] --> API8[POST demo/large-batch] API8 --> BL[BatchLargePanel] end subgraph C["C · 短链 Demo 9"] U3[Demo 9 按钮] --> API9[POST demo/notify-confirm] API9 --> N[通知卡片] API9 --> LINK[/h/token] LINK --> TL[处理时间线] end

13.1 Demo 1 — 单选(single_choice)

入口 Demo 1 - 单选 · DEMO_SCRIPT[0]
通道 ask_user 工具
类型 single_choice
验证 标准 function calling → 挂起 → tool_result 续跑

流程:

sequenceDiagram participant U as 用户 participant FE as ChatPage participant AG as Agent participant H as HumanInputService U->>FE: 发送 Demo 1 脚本 FE->>AG: POST /api/chat/stream AG->>H: registerPending + input_request FE->>U: 单选面板 U->>FE: 点击「快速排序」 FE->>H: POST respond H->>AG: 唤醒 → tool_result AG->>FE: 生成排序代码

运行截图(界面示意 · Demo 1–7 依赖 LLM,实机面板随模型响应变化)

① 用户输入

Demo1 输入

② 选项面板

Demo1 面板

③ 提交结果

Demo1 结果

操作: Chat 页 HITL 开启 → 点 Demo 1 - 单选 → 在 amber 边框面板中点击一项(点击即提交)→ 观察 hitl_feedback 收据与 Agent 继续输出。


13.2 Demo 2 — 沙箱多选(multi_choice)

入口 Demo 2 - 沙箱多选 · DEMO_SCRIPT[1]
通道 沙箱 HITL_REQUEST::...::END::
类型 multi_choice
验证 HITL_PHASE=resume + HITL_VALUES 续跑

运行截图

① 多选面板

Demo2 面板

② 沙箱续跑输出

Demo2 结果

脚本要点: 首次 phase 打印 marker;resume phase 读取 HITL_VALUES.selected 并打印「已启用模块」。


13.3 Demo 3 — 批量确认(batch_confirm)

入口 Demo 3 - 批量确认 · DEMO_SCRIPT[2]
通道 ask_user
类型 batch_confirm(5 个临时文件)
values {id}approved / rejected

运行截图

① 批量面板

Demo3 面板

② 提交收据

Demo3 结果

UI: 支持「全部同意 / 全部拒绝」与逐项切换。


13.4 Demo 4 — 自由问答(freeform_question)

入口 Demo 4 - 自由问答 · DEMO_SCRIPT[3]
通道 ask_user(或 DSML 降级)
类型 freeform_question
特性 suggestions chips 快速填充

运行截图

① 问答面板(输入 + chips)

Demo4 面板


13.5 Demo 5 — 沙箱确认(confirm)

入口 Demo 5 - 沙箱确认 · DEMO_SCRIPT[4]
通道 沙箱 marker
类型 confirm
场景 磁盘扫描 → 用户确认 → 执行清理

运行截图

① 确认面板

Demo5 面板

② 清理执行输出

Demo5 结果


13.6 Demo 6 — 沙箱防绕过(HitlSandboxGuard)

入口 Demo 6 - 防绕过 · DEMO_SCRIPT[5]
通道 沙箱 marker + Guard 静态校验
验证 未确认前不得输出「已发送」;伪造 env 拒绝执行

运行截图

① 发邮件前 confirm

Demo6 面板

② Guard 拦截违规脚本

Demo6 Guard

三层防护: Prompt · HitlSandboxGuard · awaitInput 运行时阻塞。


13.7 Demo 7 — 批量改值(editable 行)

入口 Demo 7 - 批量改值 · DEMO_SCRIPT[6]
类型 batch_confirm 混合 confirm + editable
values {id} + {id}__value
{
  "discount-vip": "approved",
  "discount-vip__value": "90",
  "budget-cap": "approved",
  "budget-cap__value": "800"
}

运行截图

① 可改值批量面板

Demo7 面板


13.8 Demo 8 — 200 行 × 5 字段(BatchLargePanel)

入口 Demo 8 - 200行(REST,不经过 LLM)
API POST /api/hitl/demo/large-batch
后端 HitlDemoPresets.largeBatch200() + HitlBatchExpand
values {id} + {id}__{fieldKey}(最多 1200 键)

问题: LLM 无法可靠输出 200 行 questions[]
方案: 服务端展开;前端行数 ≥ 50 启用 BatchLargePanel(分页 25 行、搜索、5 列编辑)。

实机运行效果

Demo8 实机

界面示意

分页表格示意

Demo8 示意

面板裁剪(实机)

Demo8 实机


13.9 Demo 9 — 消息通知 + 短链接

入口 Demo 9 - 通知 / Demo 9 - 失败
API POST /api/hitl/demo/notify-confirm
短链 GET/POST /api/hitl/link/{token} · 页面 /h/{token}
组件 HitlLinkService · HitlLinkPage

处理步骤(模拟): decision → validate → deploy → verify

sequenceDiagram participant Chat as ChatPage participant API as HumanInputController participant Link as HitlLinkService participant Page as HitlLinkPage Chat->>API: POST /demo/notify-confirm API-->>Chat: 通知 + token + inputRequest Page->>API: GET /link/{token} Page->>API: POST /link/{token}/respond Link->>Link: 异步 processing Page->>Page: 轮询步骤时间线

实机运行效果(推荐)

① Chat 页:消息通知 + 确认面板

Demo9 Chat

② 短链页 /h/{token}:待审批

Demo9 pending

③ 短链页:用户同意后 · 处理完成(四步时间线)

Demo9 success

上图:通知卡片 →「处理完成」→ 收到确认 / 校验 / 部署 / 验证 全部打勾。

④ 失败路径

Demo9 fail

界面示意(补充)

通知卡片示意

通知卡片示意

处理中示意

处理中示意

失败跳转示意

失败跳转示意

双通道: Chat POST /api/hitl/respond 与短链 respond 等价;onExternalRespond 同步启动进度流水线。
限制: token 仅存内存,backend 重启后短链失效。


14. 配置项

hitl:
  snapshot-dir: ${HITL_SNAPSHOT_DIR:./data/hitl-snapshots}
  default-timeout-ms: ${HITL_DEFAULT_TIMEOUT_MS:60000}

15. 关键源码索引

模块 路径
AgentService backend/.../service/AgentService.java
HumanInputService backend/.../service/HumanInputService.java
HumanInputController backend/.../controller/HumanInputController.java
HitlLinkService backend/.../service/hitl/HitlLinkService.java
HitlDemoPresets backend/.../service/hitl/HitlDemoPresets.java
HitlSandboxGuard backend/.../service/hitl/HitlSandboxGuard.java
HitlBatchExpand backend/.../service/hitl/HitlBatchExpand.java
ChatPage frontend/src/pages/ChatPage.jsx
HitlLinkPage frontend/src/pages/HitlLinkPage.jsx
HumanInputPanel frontend/src/components/HumanInputPanel.jsx
BatchLargePanel frontend/src/components/BatchLargePanel.jsx
hitl.js frontend/src/lib/hitl.js
Demo 截图 docs/images/hitl/(与 docs/assets/hitl/ 同步)
截图脚本 docs/scripts/capture-hitl-screenshots.mjs

16. 已知限制与演进

  1. respond 不校验 sessionId 归属
  2. 无服务端 conversation store
  3. 快照 reload 未接入启动
  4. sessionId 未 localStorage 持久化
  5. 短链 token 仅内存(Demo 9)
优先级
P0 localStorage sessionId + mount 时 listPending
P1 respond 校验 sessionId + requestId
P2 HitlLink token 持久化 + TTL

17. 测试覆盖

HumanInputServiceTest, HitlRequestParserTest, HitlSandboxGuardTest, HitlBatchExpandTest, HitlLinkServiceTest, CodeExecutionSkillHandlerHitlTest


18. 术语表

术语 定义
HITL 人机协同挂起点
pending 已创建未响应的 InputRequest
Phase 续跑 HITL_PHASE 区分首次/恢复执行
HitlSandboxGuard 沙箱脚本静态防绕过校验

posted on 2026-06-14 09:02  Gary Zhang  阅读(0)  评论(0)    收藏  举报

导航