- Add AI学习助手 agent creation script with all 39 tools, 3-layer KG+RAG memory - Add renshenguo (人参果) feishu bot integration (app_service + ws_handler) - Register renshenguo WS client in main.py startup - Add RENSHENGUO_APP_ID / RENSHENGUO_APP_SECRET / RENSHENGUO_AGENT_ID config - Reorganize docs from root into docs/ subdirectories - Move startup scripts to scripts/startup/ - Various backend optimizations and tool improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.2 KiB
知你客服 15 号 · Loop + 多轮 LLM 工作流设计(可落地版)
本文档说明如何在同一次 API 调用内,用 循环(loop/foreach)节点驱动「多轮」LLM 执行,并与现有 知你主线(Start → Cache → llm-unified → Cache → End)衔接;同时标明引擎行为边界与推荐降级方案。
1. 目标与两种能力层级
| 层级 | 做法 | 适用场景 |
|---|---|---|
| A. 单节点多轮工具 | 在 llm-unified 的 data 中配置 max_tool_iterations(如 28),一次进入 LLM 节点内多轮 tool 调用 |
工具链较长、但不需要按「外部数组」拆成多次独立推理 |
| B. 循环体多轮 LLM | 在图中增加 loop,循环体里放 专用 LLM 子节点,对 items 数组每一项跑一遍模型(可带不同 loop_input) |
需要「子任务列表 / 分步计划 / 固定 K 轮」等显式多段执行 |
15 号线已具备 A;若产品明确要求「画布上多次跑 LLM」,按 B 设计。
2. 与知你主线的衔接(推荐拓扑)
在不破坏原有缓存与记忆的前提下,典型插入方式为:
Start → Cache(读) → [准备循环数组] → Loop → [合并/写缓存] → End
↑ ↑
Transform/Code 循环体:LLM + 可选 Cache
- 准备循环数组:用 Transform 或 Code 节点,在上游输出上增加一个 数组字段(见下节),供 loop 的
items_path读取。 - 合并:loop 的输出是数组;若 End 或下游 Cache 需要单条字符串回复,在 Loop 后增加 Transform,把数组折叠成
reply/right等字段。
不要把原来的
llm-unified同时当作「主答复」和「循环体里唯一 LLM」复用同一套 End 依赖,除非你已经用 Transform 明确产出最终reply。
3. 驱动循环的数据:items_path 与数组形态
引擎从进入 loop 节点时的输入中,用 items_path(默认 items)取数组;若不是 list 会报错。
示例(简单 K 轮)
上游 Transform 产出:
{
"query": "...",
"loop_rounds": [1, 2, 3]
}
loop 节点配置:
items_path:loop_roundsitem_variable:round(则循环体内还有round_index、round_total)
示例(子任务列表)
iteration_plan: [ {"id":"s1","desc":"..."}, ... ],items_path 填 iteration_plan,item_variable 填 step,提示词里用 {{step}} 或 {step} 引用(与现有 LLM 占位符规则一致)。
4. 循环体内:如何把「上一轮结果」带入下一轮
引擎行为(简化理解):
- 每轮会构造
loop_input = { **原输入, item变量, item_index, item_total }。 - 循环体内每执行一个节点,若输出为 dict,会 merge 进下一轮同轮内的
loop_input;非 dict 则写入result键。
因此:
- 同轮内链式节点:后一节点能读到前一节点合并进
loop_input的字段。 - 轮与轮之间:下一轮会重新从原始
input_data展开并覆盖item;同轮内合并的字段不会自动跨轮保留,除非上游 Transform 把「需要跨轮的状态」写进不变的全局字段(例如仍来自 Start/Cache 的memory),或在每轮依赖的数组项里自带「累计状态」。
若业务强依赖「第 N 轮必须读到第 N-1 轮 LLM 全文」,建议:
- 在循环体末尾加 Code/Transform,把本轮产出写入结构化字段,并通过下一轮
item内容(由上游数组生成)携带;或 - 仍用 A 档(单节点 + max_tool_iterations) 在一轮推理里完成链式工具与总结。
5. 循环体拓扑与引擎限制(必读)
引擎对循环体实现为 简化版:
- 只走 loop 的第一条出边指向的单链,直到遇到
loop_end或end类型节点停止。 - 不要在循环体内再嵌套复杂分支;多出口 loop 未覆盖的场景可能不符合预期。
- 错误处理:
error_handling为continue时失败轮次会往结果里塞null;stop则整段失败。
画布上需包含:loop → … → loop_end(或 end)的链,以满足「链尾」语义。
6. node_outputs、End 与「谁产出最终 reply」
主流程在跑完 loop 后,会把 loop 节点本身 的输出写入 node_outputs[loop节点id],值为 output: 每轮结果的数组(每轮取循环体最后一个节点的 output)。
重要:循环体内的 LLM 节点在引擎里会随 loop 被标记为已执行,但不会再走主线程里「每个节点写一次 node_outputs」的路径;因此依赖 _extract_reply_from_llm_node_outputs 等「扫描所有 LLM 节点输出」的逻辑,可能拿不到循环体内的那个 LLM。
推荐做法:
- 把「给用户的一句话」放在 Loop 之后的 Transform 里,从 loop 输出数组 生成
reply/right;或 - 让 End 的前驱只依赖 已合并好的单字段,而不是隐式依赖某个
llm-xxx的node_outputs。
7. 与「同一次 API 多次跑 LLM」的关系
- Loop:同一请求内,对数组长度 N 次调用循环体(若体内含 LLM,即 N 次独立
execute_nodeLLM)。 max_tool_iterations:同一 LLM 节点一次调用 内的 tool 轮数,不是 N 次顶层 LLM。
二者可并存:例如 loop 两轮,每轮 LLM 再 max_tool_iterations=14,但总成本与延迟会明显上升,需在运营侧限制 items 长度与并发策略(若后续引擎支持)。
8. 最小示意(节点意图,非完整 JSON)
code-prepare:输出loop_rounds: [1,2,3](或真实子任务数组)。loop-zhini:items_path=loop_rounds,item_variable=round。- 子链:
llm-subtask(专用 id,提示词含本轮任务与{{round}})→loop_end。 transform-merge:输入为 loop 输出数组 → 拼成最终reply。cache-save/end:只依赖transform-merge。
9. 风险与降级
| 风险 | 应对 |
|---|---|
| 循环体只能单链、多分支不支持 | 业务拆成多段 Transform 或回到 A 档 |
| 跨轮状态不自动传递 | 用数组项携带状态,或单节点长链工具 |
| End 收不到体内 LLM | 必须经 loop 输出或后续 Transform 显式合并 |
| 成本/超时 | 限制 len(items),必要时产品侧改需求为单节点多工具 |
默认推荐:若仅为「多步工具 + 可持续任务汇报」,优先保持 15 号现有 llm-unified + max_tool_iterations + 提示词;仅在产品明确要求「按列表逐条推理」时启用 loop 方案。
10. 与脚本/文档的对应
- 工作流脚本:
backend/scripts/create_zhini_kefu_15.py(当前为单 LLM 主线;若落地 loop,需另存副本或版本化,避免破坏 14/15 默认行为)。 - 能力说明:
知你客服15号能力文档.md(可与本节交叉引用「进阶:Loop 多轮」)。
文档版本:与引擎 workflow_engine.py 中 loop / _execute_loop_body 行为对齐;若引擎升级多链循环或 node_outputs 写入规则变化,需同步修订本节。