← 返回文章列表

Dify 中级实验(03):模板转换实战——如何用零 Token 完成文本加工?

1. 业务场景

先讲一个我们实际遇到的场景。

一家电商公司的运营团队,每天要给不同角色出「评论汇总」:给管理层的是摘要,给产品组的是分析报告,给商家的是一封邮件通知。底层数据都一样——数据库/API 返回的一堆评价 JSON,但输出格式有三种,每天人工复制粘贴、调整排版,动作重复且毫无技术含量。

我们第一次接这类需求时,第一反应也是「丢给 LLM 生成,反正模型会写」。真正动手才发现——LLM 生成的格式十次有三次走样,字段偶尔丢,还得反复调 prompt 盯着。后来翻 Dify 的节点列表才发现:平台原生的 Template Transform(模板转换) 就是干这个的——基于 Jinja2 的文本加工,不消耗 LLM Token,格式永远可控。

这不是个例。任何「数据是结构化的、输出格式是固定的、内容不需要理解语义」的文本加工场景都是这个模式:日报/周报自动生成、邮件与通知模板、订单确认信、报表摘要……格式确定、规则明确,就该用模板。

2. 场景痛点

这个流程的痛点,在运营身上体现得最直接:

本质上,规则明确的文本组装用 LLM 是杀鸡用牛刀——钱和时间都花在了 LLM 不该干的活上。模板负责结构,LLM 负责内容,各干各的才不浪费。

3. 方案:为什么是模板转换

选模板转换的理由,我们实际对比过:

这篇文章我们就用它搭一个「电商评论汇总器」:一份评价 JSON,按 summary/report/email 三种格式类型输出对应文本。

4. 整体架构

graph TD start["开始:product_name / reviews / format_type"] parse["解析评价 JSON:Code 校验 + 结构化"] gen_time["生成采集时间:Code 当前时间"] fmt{"格式类型分支:IF-ELSE、3 分支"} tpl_sum["摘要模板"] end_sum["结束"] tpl_rep["报告模板"] end_rep["结束"] tpl_mail["邮件模板"] end_mail["结束"] start --> parse --> gen_time --> fmt fmt -- "summary" --> tpl_sum --> end_sum fmt -- "report" --> tpl_rep --> end_rep fmt -- "email" --> tpl_mail --> end_mail

链路很清晰:入口收数据 → 解析结构化 → 按格式类型走对应模板。纯 Jinja2 渲染,全程零 Token——只有数据准备(解析评价 JSON、生成采集时间)用了两个代码节点。

5. 模块设计

5.1 输入解析(Code)

reviews 是 JSON 字符串,先解析成结构化数据(同时做空值兜底):

def main(reviews: str) -> dict:

    import json

    try:

        data = json.loads(reviews or "[]")

    except Exception:

        data = []

    if isinstance(data, dict):

        data = data.get("data", [])

    return {"reviews": data, "count": len(data)}

5.2 模板转换节点(核心)

摘要模板——空值保护 + 平均分计算 + 评分分布:

{% set rr = reviews if reviews else [] %}

【{{ product_name }}】用户评价摘要(共 {{ rr | length }} 条)

{% set total_rating = rr | map(attribute='rating') | sum %}

{% set avg_rating = (total_rating / rr | length) | round(1) if rr | length > 0 else 0 %}

平均评分:{{ avg_rating }} / 5

{% for r in rr %}

- {{ r.user }}({{ r.rating }}星):{{ r.content }}

{% endfor %}

报告模板——评分分布统计 + 按评分排序:

{% set rr = reviews if reviews else [] %}

# {{ product_name }} 用户评价分析报告

## 概览

- 评价总数:{{ rr | length }}

- 数据采集时间:{{ report_time }}

## 评分分布

{% set ratings = [5,4,3,2,1] %}

{% for r in ratings %}

{% set count = rr | selectattr('rating', 'equalto', r) | list | length %}

{{ r }} 星:{{ count }} 条

{% endfor %}

5.3 采集时间(Code)

⚠️ Jinja2 模板里没有 now() 函数——Dify 模板环境不提供时间函数。要输出时间,用上游代码节点生成再传参:

def main() -> dict:

    import datetime

    return {"report_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M")}

6. 运行验证

实测三种格式输出:

验证要点:输出内容完全由模板渲染,节点执行耗时毫秒级、Token 消耗为 0——对比 LLM 生成同格式文本的耗时与成本。

7. 实战坑

现象 修复
模板用 now() 运行报错(模板环境无时间函数) 上游 Code 节点生成时间传参
空 reviews 直接遍历 无数据时模板报错/输出异常 {% set rr = reviews if reviews else [] %} 空值保护
平均分除零 reviews 为空时 1/0 报错 if rr | length > 0 else 0 保护
模板转换节点被 LLM 替代 每次请求烧 Token、耗时长 格式确定场景一律用模板(零成本)

💡 选型心法:规则明确的文本组装 → 模板转换(零 Token);需要理解语义的加工 → LLM。同一个工作流里两者配合:模板负责结构,LLM 负责内容。

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

本系列 · Dify 实验 · 中级
  1. Dify 中级实验(01):参数提取器实战——如何从自然语言中提取结构化数据?
  2. Dify 中级实验(02):问题分类器——智能路由引擎如何四路分发?
  3. Dify 中级实验(03):模板转换实战——如何用零 Token 完成文本加工?
  4. Dify 中级实验(04):迭代进阶——如何批量处理数据并守住性能边界?
  5. Dify 中级实验(05):并行执行——如何让多路任务同时跑?
  6. Dify 中级实验(06):变量聚合——如何确定性合并多路分支结果?
  7. Dify 中级实验(07):子工作流——如何把公共逻辑做成可复用积木?
  8. Dify 中级实验(08):代码节点进阶——如何用标准库处理文件与数据?
  9. Dify 中级实验(09):HTTP 节点进阶——如何搞定认证、分页与错误重试?
  10. Dify 中级实验(10):知识库深度调优——如何科学评估检索质量?
  11. Dify 中级实验(11):高级 RAG 流水线——如何搭建多路检索与精排?
  12. Dify 中级实验(12):Agent 深度配置——如何让智能体自主调用工具?
  13. Dify 中级实验(13):多 Agent 协作——如何编排多个智能体分工干活?
  14. Dify 中级实验(14):对话变量与状态管理——如何让工作流记住多轮对话的状态?
  15. Dify 中级实验(15):条件分支高阶策略——多条件路由如何避免分支爆炸?
  16. Dify 中级实验(16):错误处理与降级——工作流如何有尊严地失败?
  17. Dify 中级实验(17):调试监控与性能优化——响应慢和 Token 超支如何定位?
  18. Dify 中级实验(18):插件开发入门——如何把工作流变成 Agent 可调用的工具?
  19. Dify 中级实验(19):综合实战——如何把 19 个实验串成一条生产级流水线?
  20. Dify 中级实验(20):综合实战——自动化报告生成流水线如何从数据到周报一步到位?

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过