Dify 高级实验(06):自动化报告——如何让系统定时自动生成周报?
1. 业务场景
先讲一个我们实际遇到的场景。
一家电商公司的运营部,每周一早上 9 点都要开周会,会前必须有一份上周运营周报——营收、订单、客服满意度、活跃用户,还要算环比、标异常。这份周报一直由数据分析师手动做:从三个后台系统分别导出数据,粘到 Excel 里算环比,再复制进 Word 模板,最后手动发到群里。一套流程下来大半天,而且经常漏算某个指标,或者把上周的数据标成本周。运营总监最常说的一句话是:「周报能不能周一早上我睁眼就能看到?」
我们第一次接这个需求时,第一反应也是「写个 Python 脚本定时跑不就行了」。后来把流程拆开一看才发现——取数、算数、送数,每一段都有自己的坑:三个系统数据格式不一、环比计算会除零、推送要对接各渠道。脚本越写越像一锅粥,而 Dify 工作流把这些环节都做成了可视化节点,改一处、重跑一次,全流程跟着变。
这不是个例。任何「定期要出报表」的场景都是这个模式:运营周报、财务日报、销售月度复盘、SRE 巡检报告——数据来源多、格式固定、周期重复,人工做既慢又容易错,还挤占了分析师真正该做的专题分析。
2. 场景痛点
这个流程的痛点,在数据分析师身上体现得最直接:
- 采集耗时:三个系统分别导出数据,格式还不一样,光对齐口径就要一小时——数据在系统里躺着,取出来却要手动搬。
- 算环比易错:环比公式手写在 Excel 里,分母为 0 时直接报错,或者算出来的数没人核对——周报里的数字错了,周会上的决策就跟着错。
- 异常靠人盯:营收环比下降 15% 这种信号,要人一眼扫出来才写进报告——漏看一次,管理层就晚一周才知道业务出问题了。
- 推送靠手动:报告生成后还要手动复制到群、邮件、文档——忘了发、发错版本都是常有的事,定时推送的需求天然存在。
本质上,周报的痛点不在「写报告」,而在「取数、算数、送数」这三段机械环节——数据、计算、推送都是确定性的,唯独靠人每天重复,这是自动化收益最直接的场景。
3. 方案:为什么是这套自动化报告流水线
Dify 工作流里的「并行数据采集 → 变量聚合 → 指标计算与异常检测 → LLM 分析结论 → 模板转换生成报告 → Webhook 推送」链路,把周报全流程搬上自动化。
选它的理由,我们实际对比过:
- 并行采集 + 变量聚合省时间:销售/客服/用户三路数据互不依赖、同时拉取,多分支输出用变量聚合器确定性合并,替代手写代码拼接——节点编排就把采集并行化,不用自己写并发;
- 指标计算代码化,异常不漏看:环比、满意度、活跃用户的计算和异常检测全部代码化,除零有保护、异常有标记——计算稳定可复现,异常自动进报告;
- 模板转换 + 可开关推送:同一个模板按
output_format输出 Markdown 或 JSON,push_enabled开关控制是否推送企业微信/飞书机器人——格式和渠道都配置化。
这篇文章我们就用它搭一个「运营周报自动生成器」:三路数据并行采集,自动算环比、检异常、生成报告,一键推送到群。
4. 整体架构
链路很清晰:三路并行采集 → 聚合 → 计算检测 → LLM 分析 → 模板出报告 → 推送。变量聚合和模板转换是这个架构的关键——聚合把三路数据确定性合并,模板把同一份数据渲染成多种交付格式。
5. 模块设计
5.1 三路采集(cd_sales / cd_customer / cd_support)
每个采集节点同时输出两份:object
结构数据(sales)给下游代码节点与模板消费,json
字符串(sales_json)给变量聚合器合并:
def main():
return {
"sales": {"current": {"total_revenue": 1285000, "total_orders": 342, "avg_order": 3757},
"previous": {"total_revenue": 1150000, "total_orders": 310, "avg_order": 3709},
"daily": [{"date": "周一", "revenue": 185000, "orders": 48}]},
"sales_json": '{"current": {...}, "previous": {...}}' # json.dumps 序列化
}5.2 变量聚合(va_merge)
variables 是 [[节点id, 字段], ...]
嵌套数组格式,output_type: string:
- data:
output_type: string
title: 变量聚合(合并数据)
type: variable-aggregator
variables:
- [cd_sales, sales_json]
- [cd_customer, customer_json]
- [cd_support, support_json]5.3 指标计算与异常检测(cd_analyze)
环比计算必须除零保护;has_anomaly
展平为字符串(boolean 在变量选择器不可见):
def main(sales, customer, support):
issues = []
sales = sales or {}; customer = customer or {}; support = support or {}
cur = sales.get("current") or {}; prev = sales.get("previous") or {}
prev_rev = prev.get("total_revenue") or 0
rev_change = (cur.get("total_revenue", 0) - prev_rev) / prev_rev * 100 if prev_rev else 0
if rev_change < -10:
issues.append(f"营收环比下降 {abs(rev_change):.1f}%,需关注")
# 客服满意度 < 4.0、活跃用户环比 < -5% 同理检测……
return {"revenue_change_pct": round(rev_change, 1), "anomalies": issues,
"has_anomaly": "true" if issues else "false", "summary": "...", "analysis": {...}}5.4 分析结论 LLM(lm_analysis)
你是一个运营数据分析师。根据以下本周({{#start.report_week#}})运营数据,生成中文分析结论。
核心指标摘要:{{#cd_analyze.summary#}}
环比变化:营收 {{#cd_analyze.revenue_change_pct#}}%,客服工单 {{#cd_analyze.ticket_change_pct#}}%,活跃用户 {{#cd_analyze.user_change_pct#}}%
异常项列表:{{#cd_analyze.anomalies#}}
要求:
1. 分析营收、订单、客服、用户四个维度的环比变化趋势
2. 指出异常项(如有)
3. 给出 2-3 条可执行的行动建议
4. 控制在 300 字以内
5. 直接输出正文,不要输出任何解释性文字
5.5 模板转换(tmpl_report)
模板开头一组 {% set %}
做空值保护,计算处全部 if prev else 0
除零保护;所有输入走 variables 映射,模板内用
{{ var }} 引用:
{% set s = sales if sales else {} %}
{% set cur = s.current if s.current else {} %}
{% set prev = s.previous if s.previous else {} %}
{% set issues = analysis.anomalies if analysis and analysis.anomalies else [] %}
{% set rev_pct = analysis.revenue_change_pct if analysis and analysis.revenue_change_pct is defined else 0 %}
{% if output_format == 'json' %}
{ "report_week": "{{ report_week }}", "revenue": {{ cur.total_revenue | default(0) }},
"anomalies": [{% for issue in issues %}"{{ issue }}"{% if not loop.last %}, {% endif %}{% endfor %}] }
{% else %}
# 运营周报({{ report_week }})
## 核心指标
- 营收:¥{{ cur.total_revenue | default(0) }}(环比 {{ rev_pct }}%)
- 订单:{{ cur.total_orders | default(0) }} 单
## 趋势分析
{{ llm_analysis }}
## 异常预警
{% if issues | length > 0 %}{% for issue in issues %}- ⚠️ {{ issue }}{% endfor %}
{% else %}- 无异常,各项指标正常 ✅{% endif %}
{% endif %}
5.6 推送(cd_push)
push_enabled 为 true
时模拟调用企业微信机器人(真实场景换 http-request 节点,URL
指向机器人 Webhook)。
6. 运行验证
| 输入 | 期望行为 | 实测 |
|---|---|---|
| report_week=上周,output_format=markdown,push_enabled=关 | 输出完整 Markdown 周报(核心指标/趋势分析/异常预警三节) | 与预期一致 |
| output_format=json | 输出 JSON 结构报告(report_week/revenue/anomalies/analysis 字段) | 与预期一致 |
| push_enabled=开 | push_status = 已推送到企业微信机器人(模拟 Webhook 调用成功,HTTP 200) | 与预期一致 |
| 三路数据置空(冒烟测试) | 模板空值保护生效,输出空报告而不是运行报错 | 与预期一致({% set %}
兜底) |
7. 实战坑
| 坑 | 现象 | 修复 |
|---|---|---|
| 模板变量为 null/空时直接计算 | {{ x | length }}、x | map(...) | sum / (x | length)
运行报错(0/0 除零、None 切片),冒烟输入空数据直接崩溃 |
模板开头 {% set %} 兜底默认值
+ 计算处 (sum / length) if (length) > 0 else 0(dify102
全量验证实测) |
长报告 text 返回空字符串 |
DeepSeek 把内容写进思考部分,或思考与输出共享 max_tokens 预算被挤空 | 报告类节点 max_tokens 提到
4000-8000 + prompt
明确「直接输出正文,不要输出任何解释性文字」(dify103_09 lm_format
实测) |
has_anomaly 用 boolean
类型输出 |
下游 LLM 模板
{{#cd_analyze.has_anomaly#}} 取不到值(boolean
变量选择器不可见) |
代码输出
"true" if x else "false" 字符串,模板语义不变(2026-07-31
dify-103 实测) |
模板里直接写 {{#node.field#}}
引用 code 的 object 字段 |
validate_dsl.py 模板正则扫描报错,或跨节点引用不可见字段 | 所有输入走 variables
映射(value_selector),模板内用 {{ var }}
引用(dify102-2 21 实证) |
8. 实验文档及源码获取
- 实验文档(完整操作步骤):DIFY-103-06:自动化报告生成.md
- 源码(可直接导入):dify103_06_自动化报告生成.yml
文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。
- Dify 高级实验(01):智能文档处理——如何把非结构化文档变成结构化数据?
- Dify 高级实验(02):对话式 BI 分析——如何让业务人员用自然语言直接查数?
- Dify 高级实验(03):智能客服——如何让 AI 自动应答并把疑难问题转人工?
- Dify 高级实验(04):RAG 增强问答——如何让知识库问答更准还能多轮追问?
- Dify 高级实验(05):多步推理——如何把复杂问题拆解成子问题逐个击破?
- Dify 高级实验(06):自动化报告——如何让系统定时自动生成周报?
- Dify 高级实验(07):智能审批——如何让机器自动完成流程审批?
- Dify 高级实验(08):智能告警——如何用 AI 替代固定阈值监控?
- Dify 高级实验(09):测试用例生成——如何让 AI 自动产出测试用例?
- Dify 高级实验(10):综合实战——如何从零搭建一个企业级智能客服平台?