Files
aiagent/docs/zhini-kefu/知你客服15号_循环工作流设计.md
renjianbo eabf90c496 feat: add AI学习助手 agent (KG+RAG ideal) and renshenguo feishu bot
- 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>
2026-05-06 01:37:13 +08:00

143 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 知你客服 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` 写入规则变化,需同步修订本节。*