- 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>
143 lines
7.2 KiB
Markdown
143 lines
7.2 KiB
Markdown
# 知你客服 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 产出:
|
||
|
||
```json
|
||
{
|
||
"query": "...",
|
||
"loop_rounds": [1, 2, 3]
|
||
}
|
||
```
|
||
|
||
loop 节点配置:
|
||
|
||
- `items_path`: `loop_rounds`
|
||
- `item_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. 循环体拓扑与引擎限制(必读)
|
||
|
||
引擎对循环体实现为 **简化版**:
|
||
|
||
1. **只走 loop 的第一条出边**指向的**单链**,直到遇到 `loop_end` 或 `end` 类型节点停止。
|
||
2. **不要**在循环体内再嵌套复杂分支;多出口 loop 未覆盖的场景可能不符合预期。
|
||
3. **错误处理**:`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_node` LLM**)。
|
||
- **`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` 写入规则变化,需同步修订本节。*
|