Dify Custom Tool 配置化实践:用一行提示词接入新报表

作者:袖梨 2026-09-15

报表系统真正难以控制的往往不是单次查询,而是报表种类不断增长后产生的大量重复代码。取数、聚合、表头和格式化流程高度相似,却常被拆成多套实现。要让 Dify Custom Tool 稳定承接新报表,需要先把易变的报表定义从执行逻辑中抽离,再为动态公式和查询性能划定清晰边界。

Dify Custom Tool 设计模式:一行提示词接入一张新报表

系列第 4 篇。第 1 篇解决了"查得快"(2700 次 SQL → 4 次),第 2 篇解决了"答得真"(防幻觉四铁律),这一篇解决更现实的问题:报表数量爆炸时,工具层怎么不跟着爆炸。 全部代码可运行:tools/report_tool/,一张报表就是一个 YAML。

速览(60 秒版)

你将看到一句话结论
事故现场20+ 张报表 = 20+ 套取数代码,每张都要开发、联调、维护
病根把"报表"当程序写(每张一套 SQL + 拼装 + 格式化),而不是当数据描述
解法配置驱动:一张报表 = 一个 YAML;计算列用 formula 声明
兜底新报表上线,AI 侧只改一行提示词,工具侧只加一个 YAML

一、事故现场:报表数量爆炸

真实系统里,报表工具链的核心痛点不是"写 SQL",而是报表永远在增加:日报、周报、月报、值际报、指标统计分析……20+ 张报表,每张的流程都一样——写一段取数 SQL、拼装表头、格式化小数、返回二维数组。每张都要开发、联调、维护。

更糟的是需求永远在变:今天加一个"峰谷差率",明天加一个"条件筛选均值"。每张报表都是硬编码,每次改动都是一次回归风险。开发速度永远追不上报表增加的速度。

二、病根:把报表当程序写

每张报表的 90% 代码其实是相同的:批量取数、聚合计算、格式化输出。真正不同的只有三样——查哪些测点、算哪些指标、表头叫什么

这三样是数据,不是程序。所以正确的设计是把它们从代码里抽出来,变成配置:

把"怎么查"写进代码,把"查什么"写进配置。

三、设计模式拆解

3.1 一张报表 = 一个 YAML

report:
  id: monthly_report
  title: 月度生产报表
  columns:
    - { key: point,       type: point_name, label: "测点" }
    - { key: avg,         type: agg, agg: avg,   label: "月均负荷", unit: "MW" }
    - { key: max,         type: agg, agg: max,   label: "月最高",   unit: "MW" }
    - { key: load_factor, type: calc, formula: "avg / capacity * 100", label: "负荷率", unit: "%" }
  rows:
    - { point: point_001, name: 1号机组, capacity: 1000 }
    - { point: point_003, name: 3号机组, capacity: 660 }

列只有三种类型,覆盖了报表的全部形态:

列类型说明
point_name维度列(测点名)
agg聚合列(avg / max / min / sum / count,白名单)
calc计算列:formula 引用聚合列或行属性,声明即用

新增一张报表 = 复制一个 YAML 改几行,执行器一行代码不用动。测试里专门有一条用例验证"两张不同报表共用同一执行器"。

3.2 计算列:formula 声明 + 白名单校验

派生指标(负荷率、峰谷差率、单位容量发电量)是报表需求变化最频繁的地方。把它们做成 formula 声明后,运营侧加指标不再需要发版。

但 formula 是动态执行的表达式——安全边界必须有。这里的实现走白名单校验:只放行数字、四则运算符、括号、已知变量名;__import__os.system、函数调用一律拒绝(有专门的注入测试用例盯着)。

取舍记录:白名单意味着公式能力有上限(没有 if/嵌套函数)。这十年报表里 95% 的派生指标就是四则运算——为 5% 的复杂需求引入图灵完备的表达式引擎,才是真正的风险。真遇到复杂逻辑,那不是配置的事,是代码的事。

3.3 批量取数打底

配置驱动解决"报表多",批量取数解决"报表慢"——这个在第 1 篇讲透了:无论报表多少列多少测点,取数恒为 1 次查询 + 内存索引。执行器的 [统计] 行每次都输出实际查询次数,性能不是靠感觉,是每次运行都可见。

3.4 统一输出契约

所有报表返回同一种结构:二维数组 + 中文表头 + 两位小数。这个契约带来两个红利:

  1. 前端零适配——新报表上线,前端一行不改
  2. AI 侧零改造——Dify Custom Tool 的返回格式永远一致,模型不需要学新的解析规则

四、"一行提示词接入"是怎么发生的

这是本文标题的答案。配置驱动完成之后,接入一张新报表只剩两个动作:

  1. 工具侧:加一个 YAML(声明列、行、公式)
  2. AI 侧:在 Custom Tool 的提示词规则里加一句"报表清单里新增 XX 报表,参数范围 Y"

第二点正是第 2 篇防幻觉铁律的落地——写规则不写死数据:提示词里不写死任何测点、指标、表头(它们都在 YAML 和 DB 里动态加载),只写路由规则:"用户要报表 → 调本工具 → 报表 id 从清单匹配"。于是新报表对 AI 的全部影响,就是清单里多一行字。

五、实测输出

=== 月度生产报表 ===  (时间范围: 2026-07-01 ~ 2026-07-31)
测点    月均负荷(MW)  月最高(MW)   月最低(MW)   负荷率(%)
1号机组  75.60MW   81.36MW   69.88MW   7.56%
...
[统计] 数据库查询次数: 1 次 | 耗时: 9.2 ms

7 个测试用例覆盖:配置加载、二维数组结构、计算列正确性、多报表共用执行器、注入防护。

六、写在最后

这套模式的本质是一条分工原则:变化频繁的放配置,相对稳定的放代码,安全边界用白名单钉死。

  • 完整实现(执行器 + 2 个示例配置 + 测试):github.com/yang-shenxu… → tools/report_tool/
  • 配套阅读:第 1 篇(性能)、第 2 篇(防幻觉)、第 3 篇(AI 协作治理)

下一篇计划讲演示环境的搭建思路:怎么让招聘方 clone 下来 30 秒跑出和你简历一致的数字。

说明:数据为模拟生成,脱敏处理,仅用于技术演示。

相关文章

精彩推荐