用 Dify 给工程监理公司做报告自动化:一次从"能跑"到"能用"的完整折腾记录
【背景】
客户是一家中型工程监理公司,每年承接 30-80 个项目,每个项目开工前都要出具一份可行性研究报告------三四万字,八个章节,涵盖市场分析、技术方案、投资估算、财务评价、风险矩阵、监理意见。写一份快则三天慢则两周,一年下来工程师有相当一部分时间消耗在这件事上。
为什么这件事不是"prompt 一下就完事":因为可研报告的核心价值在于数字的一致性和规范的准确性。第五章写的投资 3500 万,第六章的 IRR 计算基数必须也是 3500 万;引用的规范编号必须是现行有效版本,不能是已废止的旧标;财务模型对住宅项目和市政项目的计算逻辑完全不同。这些约束靠单次 prompt 根本压不住,必须用工程化的方式来解决。
【目标】
做一个基于 Dify 的自动化工作流,工程师填一张表单,5 分钟内输出一份 25000 字以上的完整报告初稿,财务数据自动计算、前后一致,结构符合住建部可研编制规程,总监审阅后可直接作为工作底稿使用。
判断"做成了"的指标:
- 单次生成时间 ≤ 5 分钟
- 报告字数 ≥ 25000 字(8 章全部有实质内容)
- 财务数字前后章节完全一致
- 总工程师评价"框架可用,核数据后能报审"
【难点】
难点 1:财务数据的生成方式
最容易想到的做法是让 LLM 直接生成财务数字,但这条路基本走不通。LLM 算数不稳定,更严重的是它会"编"数字------第五章写建筑工程费 2.2 亿,第六章可能自己悄悄用了 1.9 亿做 IRR 测算,前后不一致,但表面上看不出来。可研报告偏偏是一个对内部一致性要求极高的文体,这种隐性错误比显性错误更危险,因为总监不一定每次都会交叉核查两章之间的数字。
难点 2:8 个章节的质量方差太大
8 个章节的生成难度差异悬殊。第一章项目概况基本是填空题,给足输入 qwen-plus 就能写得很好;第三章市场与需求分析需要构建逻辑链条、引用行业数据、做预测模型,对模型的推理能力要求高得多。如果所有章节用同一个模型、同一套 prompt 策略,要么贵的章节浪费成本,要么难的章节质量崩。这个分层配置的工作量比想象中大。
难点 3:长文本生成的稳定性
8 个章节并行调用,每次运行消耗 token(大模型处理文本的计量单位)在 25000-35000 之间,调用时长在 3-8 分钟。在这个时间窗口里,任何一个节点的 API 超时、网络抖动或模型侧的偶发故障都会导致整个流程失败。失败的代价不是报错,而是前面 7 章已经烧掉的 token 全部作废。如何在不影响速度的前提下让系统具备基本的容错能力,是个需要仔细设计的问题。
【过程】
决策 1:财务数字用 Code 节点算,不让 LLM 碰
最开始我走了一段弯路。第一版的设计是让 Ch5(投资估算)节点自己算财务数字,然后把结果传给 Ch6(财务评价)引用。逻辑上说得通,但实际运行发现两个问题:一是 Ch5 的 LLM 偶尔会"创造"一些没有依据的数字,比如自己发明一个"其他隐性费用";二是即使 Ch5 算对了,Ch6 引用时也会悄悄做"二次调整",改掉一两个数字,但不告诉你它改了。
后来彻底重设计:在 LLM 节点之前插入一个 Python Code 节点(Dify 允许在工作流中运行 Python 代码),里面写死财务计算逻辑。按住建部标准,住宅项目按"销售型"逻辑:总销售收入 = 总投资 × (1 + 毛利率),IRR 用简化公式估算,回收期基于销售周期计算;市政项目按"运营型"逻辑:年收入 = 总投资 × 运营收益率,NPV 按 20 年运营期折现。
算出来的所有财务指标打包成一个 summary 字符串,注入到每个需要引用财务数据的章节 prompt 里。Ch5 和 Ch6 的 LLM 只负责"把这些数字写成专业的报告语言",不负责"生成"数字。
效果是财务数字在整份报告里完全一致,这个问题从此关闭。
决策 2:模型分层,不同章节用不同配置
跑了几次测试之后,我按章节难度做了分层:
- qwen-plus + temperature 0.2-0.3:Ch1(项目概况)、Ch5(投资估算)。这两章主要是"把给定信息写成规范文体",创造性低、准确性要求高,低温度 + 轻量模型足够。
- qwen-plus + temperature 0.5:Ch2(建设必要性)、Ch7(风险分析)。这两章需要一定的论证逻辑,但结构相对固定,中等配置。
- qwen-max + temperature 0.4:Ch3(市场分析)、Ch4(技术方案)、Ch6(财务评价)、Ch8(监理意见)。这几章要求推理深度和专业表达同时在线,用旗舰模型。
temperature 是控制模型输出随机性的参数,0 代表每次输出几乎相同、非常保守,1 代表更有创造性但也更不稳定。财务类章节我用低 temperature,分析类章节用中等 temperature,这个判断基本是对的。
分层之后,整体成本比全用 qwen-max 降了约 40%,同时质量没有明显下降。
踩坑 1:第三章集体精神失控
这是整个项目里最戏剧性的一次失败。
有一次测试,报告前两章输出正常,第三章打开------满屏句号。不是比喻,是真的连续数百行"。",大概占了五六页。其他章节正常,就第三章在那里默默地输出标点符号。
这是 LLM 在推理过程中陷入某种退化循环的典型表现。直接原因是这一章的 prompt 结构太松散------我只说"请写第三章市场与需求分析",没有给出明确的小节划分、字数要求和内容框架。模型不知道怎么组织这么大块的内容,然后......崩了。
修复方案:把第三章的 prompt 重写,明确要求输出 5 个小节(市场环境、供给现状、需求预测、风险机会、结论),每节 800-1000 字,必须包含数据表格。同时在 prompt 末尾加了一段强约束,本质上是把一个开放式问题变成了一个有严格格式约束的填空题。改完之后这个问题再没出现过。
踩坑 2:SSL 错误吃掉了大量调试成本
项目初期我用的是 Dify Cloud(云端版本),通过 VPN 访问。这带来了一个隐蔽的问题:调用阿里云 DashScope(qwen 系列模型的 API 服务)时,请求路径变成了"本机 → VPN 境外节点 → 绕回国内阿里云",SSL 握手在这条路径上极不稳定。
错误长这样:
vbnet
SSLError: HTTPSConnectionPool(host='dashscope.aliyuncs.com', port=443):
Max retries exceeded (Caused by SSLEOFError(8,
'[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol'))
最头疼的不是失败本身,而是失败的时机------通常是在 8 个章节跑完 6 个之后,第 7 个 SSL 超时,整个流程中止。前面 6 章烧掉的 token 全废,重跑再烧一次。
短期解法是把每个 LLM 节点的"失败重试"开到 5 次、间隔 5 秒,用时间换稳定性。但这只是打补丁。
根本解法是把 Dify 切换到本地 Docker 自部署,国内直连 DashScope,SSL 问题彻底消失,同时响应速度也提升了不少。
这是一个典型的"网络拓扑问题伪装成模型问题"的坑。如果没有意识到 VPN 是变量,可能会花很多时间去调模型参数或者 prompt,完全调错方向。
决策 3:合并节点加质量检查,让系统自己报告哪章坏了
8 章生成完毕后,有一个合并节点(Code 节点)把所有章节拼成完整报告。我在这里加了一个轻量的质量检查逻辑:
- 检测每章字数是否 < 500 字(过短说明生成失败)
- 检测是否存在某个字符连续重复 15 次以上(句号事件的检测规则)
- 检测标点符号占比是否 > 35%(异常重复的变体)
如果检测到异常,status 字段会明确标出是哪一章出了问题,方便定位重跑。
没有这个检测之前,Dify 会把"满屏句号的第三章"标记为 SUCCESS,因为节点确实有输出,只是输出的是垃圾。加了检测之后,这种情况会被标记为 PARTIAL SUCCESS,并在 status 里写明"第三章标点占比异常,疑似生成失败"。
【现状】
目前系统已经在本地 Docker 环境稳定运行,跑了十几次完整测试,没有再出现 SSL 中断或章节质量崩溃的问题。单次生成时间约 4-6 分钟,报告字数稳定在 30000-35000 字,财务数字前后一致,总工程师看完说"框架可用,核数据后能报审"。
还没解决的:
- 规范引用准确性问题。LLM 引用的部分规范编号是真实的,但也有小概率引用一个"看起来存在但实际上不存在"的文件编号。解决方案是接入一个监理规范文档库,让系统在生成前先检索真实文档,再基于检索结果写作------这个功能下一步在做,预计能把准确率从现在的 ~70% 提升到 95% 以上。
- Word 格式输出。目前输出是 Markdown,工程师还需要手动清理格式才能交付。计划做一个自动转换脚本。
【思考】
做完这个项目,我想说一个可能有点反直觉的判断:
在工程化 AI 应用里,最应该警惕的不是 LLM 输出质量差,而是它输出的"置信感"太强。
这个系统出现最多的不是明显的错误------LLM 不会告诉你"我不知道怎么算这个",它会给你一个看起来完全正确、格式规范、数字漂亮的结果,但里面悄悄埋着一个逻辑错误或者一个前后不一致的数字。
住宅项目的财务模型用错成运营型的时候,IRR 从合理的 12% 变成了 7.7%,报告里的财务评价章节照样写得头头是道,还分析了为什么 IRR 低于行业基准但仍然具备可行性。如果不是我自己对这个数字有判断,完全可能被那段分析说服。
这种"自洽的错误"在传统软件里几乎不会出现------程序要么算对,要么崩溃。但 LLM 有一种独特的能力,它能把错误的结论写得极其令人信服。
对应的工程实践结论是:所有需要保证一致性和准确性的数字,都不应该让 LLM 生成,应该用确定性代码计算,然后把结果"喂"给 LLM 去表达。 LLM 的职责是"把正确的数字写成好读的文字",而不是"自己决定数字是什么"。
这个边界划清楚了,整个系统的可靠性会好很多。
这个结论可能看起来很 obvious,但我见过太多人一开始的设计是让 LLM 又算又写,然后花大量时间在 prompt 里反复强调"数字要准确"------这个方向基本是死路。
有做过类似项目的欢迎讨论,特别是规范引用的准确性问题,我现在的思路不一定对。