Dify 中级实验(06):变量聚合——如何确定性合并多路分支结果?
Dify 中级实验(06):变量聚合——如何确定性合并多路分支结果?
Dify 实验系列 · 中级 06/20 | 实验编号:DIFY-102-07
上篇:Dify 中级实验(05):并行执行——如何让多路任务同时跑?
1. 实验目的
掌握Variable Aggregator(变量聚合)节点——用确定性方式合并多路分支输出,替代上一课的 LLM 合并。理解三种合并心智:覆盖(后到者赢)、深合并(对象递归合并)、自定义(代码节点兜底)。
适合场景:多源数据拼装用户画像、配置合并、日志/结果收集——需要「精确、可预测、不烧 Token」的合并。
2. 场景设计
并行获取同一个用户的三种数据,聚合后生成用户 360 报告:
- 分支 A 用户画像:
user.name/age/level+metrics.login_count/last_active; - 分支 B 订单数据:
user.total_orders/avg_order_value+metrics.return_rate; - 分支 C 客服交互:
user.ticket_count/satisfaction+metrics.avg_response_time。
聚合后同一个user对象里应包含全部字段(A/B/C 各自贡献一部分)——这正是「深合并」的价值:三个分支的数据结构同构、字段互补,合并后零丢失。
输入:user_name(可选,默认张三);输出:用户 360 报告(身份概览 / 行为画像 / 价值评估 / 服务建议)。
3. 节点拓扑
开始(user_name) ├─ 用户画像数据(Code:object) ├─ 订单数据(Code:object) └─ 客服交互数据(Code:object) ↓ 聚合多源数据(variable-aggregator,output_type: string) ↓ 生成用户 360 报告(LLM,max_tokens 8000)→ 结束4. 关键配置
4.1 三个数据源分支(Code)
每个分支输出一个 object,字段刻意设计成同构互补:
# 分支 A:用户画像defmain(user_name:str)->dict:name=(user_nameor"张三").strip()or"张三"return{"profile_data":{"user":{"name":name,"age":28,"level":"VIP"},"metrics":{"login_count":45,"last_active":"2026-07-21"},}}4.2 聚合节点(核心)
聚合器把三个 object 合并成一个,output_type: string时输出合并后的 JSON 文本:
-data:output_type:stringtitle:聚合多源数据type:variable-aggregatorvariables:--cd_profile# 注意:variables 是 [[节点id, 字段], ...] 嵌套数组-profile_data--cd_orders-orders_data--cd_service-service_dataid:agg_merge⚠️ aggregator 的
variables格式与 LLM 节点不同:LLM 是[{value_selector, variable}]对象数组,aggregator 是嵌套数组[[节点id, 字段], ...],别搞混。
4.3 消费聚合结果的 LLM
长报告输出把max_tokens提到 8000,prompt 加「直接输出正文」约束(DeepSeek 长输出被思考挤空 text 的坑):
model:completion_params:max_tokens:8000temperature:0.7mode:chatname:deepseek-v4-flashprompt_template:-id:p_reportrole:systemtext:|用户全景数据如下(JSON 格式,由用户画像/订单/客服交互三路数据源合并而来): {{#agg_merge.output#}} 请生成一份用户 360 报告,包括: 1. 用户身份概览 2. 行为画像 3. 价值评估 4. 服务建议 直接输出报告正文,不要输出任何解释性文字。reasoning_format:separated5. 运行验证
| 检查项 | 预期 | 实测 |
|---|---|---|
| 合并后的 user 对象 | 含 name/age/level/total_orders/avg_order_value/ticket_count/satisfaction 全部 7 字段 | 与预期一致 |
| 合并后的 metrics 对象 | login_count/last_active/return_rate/avg_response_time 全部保留 | 与预期一致 |
| LLM 报告 | 四段式:身份概览/行为画像/价值评估/服务建议 | 与预期一致 |
对比实验:把聚合策略换成「覆盖」再跑一次——后完成分支会覆盖先完成的同名字段,user对象只剩最后一个分支的字段(数据丢失)。这一对比直观展示「深合并 vs 覆盖」的差异。
6. 采坑点
| 坑 | 现象 | 修复 |
|---|---|---|
| 同名冲突字段被覆盖 | 并行分支完成顺序不确定,「后到者覆盖」不可预测 | 设计阶段约定冲突策略:字段互补(本实验)或代码节点自定义合并加后缀 |
| 把聚合当数值累加 | 期望 total_orders 相加,实际是对象合并 | 合并 ≠ 累加:数值统计(求和/均值)必须走代码节点 |
| aggregator 的 variables 格式写错 | 用 LLM 的[{value_selector, variable}]格式,校验报错 | 用嵌套数组[[节点id, 字段], ...] |
| 长报告输出 text 为空 | DeepSeek 把内容写进思考/解释部分,max_tokens 被挤空 | max_tokens 提到 8000 + prompt 明确「直接输出正文」 |
| 校验脚本在 aggregator 上崩 | check_dsls.py 报AttributeError: 'list' object has no attribute 'get' | 脚本局限(已修),非 DSL 错误——aggregator 格式以本文为准 |
💡 选型心法:要「精确、可预测」→ variable-aggregator;要「理解语义、自由组织」→ LLM 合并。两者不冲突:聚合器负责结构(JSON 全字段保留),LLM 负责叙事(把 JSON 讲成人话)。
7. 实验文档及源码获取
- 实验文档(完整操作步骤):DIFY-07:变量聚合——多路分支结果合并.md
- 源码(可直接导入):dify102_07_多源数据合并器.yml
文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。
下一篇:Dify 中级实验(07):子工作流——如何把公共逻辑做成可复用积木?
