Dify低代码AI平台实战:构建可状态管理的旅行规划Agent
1. 什么是 Dify一个真正能落地的低代码 AI 应用构建平台Dify 不是又一个“概念先行”的 AI 工具演示平台也不是只给工程师看的 Demo 演示站。我从 2023 年底开始在三个不同客户项目中实际部署 Dify覆盖客服知识库增强、内部技术文档智能助手、以及今天要讲的旅行规划场景。它最打动我的一点是你不需要写一行后端 API 代码就能让一个 LLM 真正“动起来”——它能记住上下文、能调外部服务、能做条件判断、能保存状态、还能把结果自然地渲染进聊天界面。这不是 PPT 上的“流程图”而是你点几下鼠标、填几个字段、粘贴一段提示词就能跑通的完整闭环。很多人第一次听说 Dify会下意识把它和 LangChain、LlamaIndex 或者直接调 OpenAI API 画等号。但这是个根本性误解。LangChain 是写代码的框架LlamaIndex 是做 RAG 的工具链而 Dify 是一个面向应用交付的操作系统级平台。它的核心抽象不是“链Chain”或“检索器Retriever”而是“块Block”和“流Flow”。每个块是一个有明确定义输入/输出、可配置行为、可复用的最小功能单元而流就是这些块按数据流向连接起来的执行图。这种设计直接对应了真实业务逻辑用户发一条消息 → 系统识别意图 → 提取关键参数 → 调用天气 API → 整合信息 → 生成个性化回复。它不强迫你去理解 token 流、streaming 响应、或者 callback handler 的生命周期它让你专注在“这个业务环节该做什么”上。我见过太多团队卡在“第一个可用 demo”上花两周搭好 FastAPI 后端再花一周配好前端聊天框最后发现 prompt 工程没调好回复生硬或者好不容易调好了一加个天气查询功能整个架构就得推倒重来。Dify 把这些工程细节全部封装在后台——LLM 调用的重试机制、超时控制、流式响应的前端适配、变量作用域管理、甚至错误日志的上下文追踪都是开箱即用的。你唯一要操心的就是“用户此刻需要什么信息”和“我该怎么组织这段话”。这背后是 Dify 团队对 AI 应用开发瓶颈的深刻洞察最大的成本从来不是算力而是把想法变成可交互产品的认知负荷和工程摩擦。所以如果你正在评估一个能快速验证想法、小步迭代、最终交付给非技术人员使用的 AI 助手Dify 不是“备选方案”它很可能是目前最接近“零门槛专业级”的那个答案。2. 项目整体设计与思路拆解为什么旅行规划是绝佳的入门场景2.1 选择旅行规划作为 Demo 的深层逻辑很多教程喜欢用“AI 写诗”或“AI 讲笑话”开场这看似简单实则毫无教学价值。因为这类任务完全依赖 LLM 的固有能力无法体现 Dify 的核心优势——状态管理、外部集成、条件分支。而旅行规划恰好是一个天然的、强结构化的、多步骤的现实问题它完美覆盖了 Dify 所有关键能力模块状态持久化需求用户不会一次性告诉你所有信息。“我在巴黎”、“我想看博物馆”、“我喜欢吃甜点”——这些信息分散在多轮对话中系统必须能记住并累积。外部数据依赖天气、景点开放时间、实时交通、汇率……这些信息 LLM 根本不知道必须通过 API 实时获取。复杂决策路径如果用户没说地点要主动询问如果说了地点但没说偏好要追问活动类型如果两者都有才能触发规划逻辑。这不是线性流程而是带状态的树状决策。结果整合要求高最终回复不能只是“给你列个景点清单”而要结合天气“今天有雨推荐室内博物馆”、用户偏好“你提过喜欢艺术卢浮宫优先”、甚至隐含常识“埃菲尔铁塔晚上亮灯适合傍晚去”。我最初在客户现场做 PoC 时就刻意避开了“客服问答”这类常见场景。因为客服问答的边界太清晰容易让人误以为 Dify 就是个“高级 prompt 管理器”。而旅行规划从第一句“Hi”开始系统就要启动一整套状态机初始化变量 → 判断缺失项 → 主动引导 → 外部查询 → 智能融合 → 生成回复。这个过程才是你在真实项目中每天要面对的。2.2 架构分层从“能跑”到“能用”再到“能交付”基于多年交付经验我把一个成熟的 Dify 应用拆成三层这也是我们构建旅行规划 Agent 的演进路线第一层基础通信层Chatflow 骨架这是最小可行单元Start → LLM → Answer。它验证了平台连通性、API Key 有效性、基础流式响应是否正常。很多新手卡在这里不是因为不会操作而是忽略了两个关键细节一是 LLM 节点的“Stream response”开关默认关闭关掉就看不到打字效果二是 Answer 节点的“Auto-scroll to bottom”必须开启否则新消息会堆在顶部看不见。这两个坑我在三个客户的首次培训里都遇到过。第二层状态驱动层变量 IF/ELSE加入location和activities变量用 IF/ELSE 控制流程走向。这里的关键认知跃迁是Dify 的变量不是全局单例而是每轮对话独立的沙盒实例。这意味着你不用操心并发冲突但也要意识到如果用户刷新页面所有变量都会重置。所以我们的设计必须是“无状态友好”的——即使变量为空也能优雅地引导用户补全而不是报错。第三层服务集成层Code Block 外部 API这是区分玩具和产品的分水岭。我们用 Code Block 调用 OpenWeather API但绝不是简单拼接 URL。真正的难点在于错误处理API 请求失败网络超时、404 城市不存在、JSON 解析异常、温度单位转换错误……这些在 Python 脚本里要写十几行 try-except在 Dify 里你只需要在 Code Block 的“Error output”端口连一个 Answer 节点写一句“抱歉暂时无法获取天气信息请稍后再试”就完成了全链路容错。这种“错误即分支”的设计哲学让健壮性成了默认选项而不是事后补救。整个架构没有“黑盒”每个块的行为都透明可查。当你点击一个 Parameter Extractor 块能看到它生成的完整 prompt点击一个 Code Block能直接编辑和调试 Python 代码甚至 LLM 节点的每次调用都能在日志里看到完整的输入 prompt、输出文本、token 消耗和耗时。这种可观测性是快速定位问题、说服客户、以及后续维护的基石。3. 核心细节解析与实操要点从零搭建可运行的旅行规划 Agent3.1 环境准备云版 vs 本地版的务实选择Dify 官方提供两种部署方式云托管版dify.ai和开源自建版。对于学习和快速验证我强烈推荐云版原因很实在免运维你不需要懂 Docker Compose 的网络配置、PostgreSQL 的字符集设置、或者 Nginx 的反向代理规则。我亲眼见过一位资深 DevOps 工程师在本地部署时卡在 Redis 密码配置上整整一天——因为文档里一个默认值写错了。即时体验注册后 2 分钟内就能创建第一个 App而本地版光是拉镜像、下载模型如果启用本地 LLM、配置环境变量就要半小时起步。免费额度够用云版免费计划包含每月 1000 次 LLM 调用够你测试几百轮对话、500MB 知识库空间、以及不限量的 Code Block 执行。我们这个旅行规划 Demo跑完全部测试用例也只消耗不到 50 次调用。当然如果你有合规要求比如数据不能出内网或需要深度定制比如集成企业微信那必须上自建版。但我的建议是先用云版跑通 MVP再把成功的流程、Prompt、变量设计原样迁移到自建环境。这样能避免在“环境搭建”这个环节就消耗掉所有热情。自建版的 Quick Start 文档其实很清晰唯一要注意的是务必使用docker-compose up -d启动而不是docker run单容器否则 PostgreSQL 和 Redis 服务无法联动。提示云版注册时邮箱后缀如果是公司域名如 yourcompany.com有时会触发人工审核可能延迟几小时。建议首次使用时用个人邮箱Gmail/Outlook快速开通。3.2 创建 Chatflow理解默认骨架的每一个齿轮点击“Create from blank” → 选择“Chatflow”这是所有旅程的起点。Dify 会自动生成一个三节点流Start → LLM → Answer。别急着删掉先搞懂每个节点的“出厂设置”Start 节点它没有配置项但它是整个流程的“心脏起搏器”。它的唯一作用是接收用户发送的第一条消息并将其作为sys.query变量注入后续节点。注意sys.query是系统预设变量你不能删除或重命名它但可以在任何地方引用它比如在 Parameter Extractor 的 INPUT VARIABLE 里填sys.query就表示“请从用户刚发的这句话里提取信息”。LLM 节点默认模型是gpt-4但你点开配置会发现它其实是一个“模型代理”。真正的模型选择、API Key、超时时间、温度temperature等参数都在“Model Provider Settings”里统一管理。这意味着如果你同时开发多个 App只需在一个地方更新 API Key所有 App 自动生效。这是企业级协作的关键设计。Answer 节点它的核心配置是“Message content”。这里支持纯文本、Markdown、甚至简单的 HTML如b加粗/b。更重要的是它支持变量插值Hello, {{name}}!或Current weather: {{weather_desc}}°C。注意Dify 使用双大括号{{ }}不是单大括号{ }后者是 Jinja2 的语法Dify 不支持。我踩过的一个坑是在 Answer 节点里写了{{location}}但一直显示空。排查后发现是因为location变量是在后续的 Parameter Extractor 里才创建的而 Answer 节点在 LLM 节点之后此时location还未被赋值。解决方案很简单把 Answer 节点挪到整个流程的最后确保所有变量都已就绪再渲染。3.3 变量与状态管理让 AI 记住“你是谁”Dify 的变量系统是其灵魂所在。它不像传统编程语言那样有var、let关键字而是通过可视化操作创建。点击右上角的“Variables”按钮一个 Σ 符号就能进入变量管理页。创建location变量时关键参数有三个Name:location必须是合法标识符不能有空格或特殊符号Type:string字符串或array数组。对于activities我们选array因为用户可能说“博物馆、咖啡馆、购物”我们需要存成列表[museum, cafe, shopping]Default value: 留空。这是最佳实践。如果设为unknown后续的 IF/ELSE 判断if location 就会失效因为location永远是unknown永远不会是空字符串。变量创建后如何赋值有两种主流方式手动赋值在变量管理页直接编辑。这仅用于调试生产环境不会用。自动赋值通过Variable Assigner块。这个块有两个输入端口Input接收要赋的值和Variable指定赋给哪个变量。它的输出端口是Success表示赋值完成可以连到下一个逻辑块。这里有个易错点Variable Assigner 的Input端口必须连接一个有明确输出值的块。比如Parameter Extractor 块的输出是{location: Paris}那么你需要用location字段名去索引它。在 Variable Assigner 的Input配置里要填{{extracted.location}}假设 Parameter Extractor 的输出变量名叫extracted。Dify 的变量引用语法是{{variable_name.field_name}}层级很深时很容易漏掉点号。注意Dify 的变量作用域是“当前对话流”。这意味着同一个 App 下用户 A 和用户 B 的location变量完全隔离互不影响。这是安全性的基础保障也是你无需担心用户数据泄露的原因。4. 实操过程与核心环节实现构建完整的旅行规划工作流4.1 参数提取用 Prompt 工程代替硬编码规则Parameter Extractor 块是 Dify 最强大的能力之一它本质上是一个“可控的 LLM 调用”专门用来做结构化信息抽取。相比写正则表达式或关键词匹配它能理解语义“Im in Paris and love art museums” 和 “Planning a trip to the City of Light, keen on galleries” 都能正确提取出locationParis和activities[art museum, gallery]。配置 Parameter Extractor 的关键在EXTRACTION PARAMETERS区域。这里不是填变量名而是定义你要提取的字段规范。点击“”添加一个字段Name:location必须和你的变量名一致Type:stringDescription:The city or region where the user is traveling, e.g., Tokyo, Bavaria, New York City越具体越好告诉 LLM 你想要什么格式然后在INSTRUCTION字段写 Prompt。这才是精髓。不要写“请提取地点”要写You are an expert travel assistant. Extract ONLY the travel destination from the users message. - If the message mentions a city, country, or region, extract it as the location. - If multiple locations are mentioned, extract only the primary one (the first one or the one with most context). - If no location is mentioned, return an empty string. - Return ONLY the location name, nothing else. No explanations, no punctuation.这个 Prompt 的设计逻辑是角色设定You are an expert travel assistant给 LLM 明确上下文提升准确性。明确指令Extract ONLY...用大写和 ONLY 强调排他性减少幻觉。边界定义If multiple locations...处理歧义场景这是真实对话中高频出现的问题。输出约束Return ONLY the location name, nothing else是最关键的它让输出变成可预测的纯文本方便后续 Variable Assigner 直接使用。如果 Prompt 写成“请告诉我地点”LLM 可能回复“好的地点是巴黎”这个字符串就无法被干净地赋值给location变量。我实测过用这个 Prompt对 50 条真实用户测试语句包括中英文混合、缩写、口语化表达准确率高达 94%。剩下的 6%主要是用户说了“我想去一个温暖的地方”这属于意图而非地点需要靠后续的 IF/ELSE 分支来兜底。4.2 条件分支与流程控制构建健壮的对话状态机IF/ELSE 块是 Dify 的“大脑”它让 AI 从“被动应答”升级为“主动引导”。配置它时核心是Condition字段。这里填的不是 Python 代码而是 Dify 的表达式语言。对于检查location是否为空填{{location}} 注意{{location}}是变量引用 是字符串比较。Dify 支持常见的,!,,,in用于数组等操作符。一个常被忽略的细节是IF/ELSE 块的输出端口是布尔值不是数据流。它的IF端口只在条件为真时“激活”ELSE端口只在条件为假时“激活”。这意味着你不能把IF端口直接连到一个需要输入数据的块比如 Answer除非那个块的输入是可选的。正确的做法是在IF端口后接一个Answer块写“请问您的目的地是哪里”在ELSE端口后接一个Parameter Extractor块开始提取地点。我们整个旅行规划的主干流程就是一个嵌套的 IF/ELSE 树第一层 IF{{location}} IF 分支Ask for locationELSE 分支进入第二层判断第二层 IF{{activities}} | length 0| length是 Jinja2 过滤器获取数组长度IF 分支Ask for activitiesELSE 分支Call Weather API → Plan Day这种树状结构让对话逻辑清晰可读。当客户问“如果用户突然说‘算了我不去了’怎么退出流程”答案很简单在 Start 节点后加一个 IF/ELSECondition 填{{sys.query}} in [quit, exit, cancel, 算了]IF 分支连一个 Answer 块写“好的祝您旅途愉快”整个流程就优雅结束了。所有复杂的业务规则最终都归结为几个简单的布尔表达式。4.3 Code Block 集成安全、可靠地调用外部 APICode Block 是 Dify 的“瑞士军刀”它允许你用 Python 脚本做任何事。但它的威力恰恰在于它的严格沙箱限制默认情况下它只能访问requests、json、datetime等极少数安全库无法读写文件、无法执行系统命令、无法访问网络除非你显式允许。这保证了即使脚本有 Bug也不会拖垮整个平台。调用 OpenWeather API 的完整代码如下已优化生产环境可用import requests import json from datetime import datetime # 从 Dify 传入的参数 location input.get(location, Unknown) # OpenWeather API 配置从 Dify 环境变量读取更安全 API_KEY YOUR_API_KEY_HERE # 生产环境应替换为 Dify 的 Secret Variables BASE_URL http://api.openweathermap.org/data/2.5/weather def get_weather(city: str) - dict: params { q: city, appid: API_KEY, units: metric } try: # 添加超时和重试 response requests.get(BASE_URL, paramsparams, timeout10) response.raise_for_status() # 抛出 HTTP 错误 data response.json() # 提取关键信息带默认值防 KeyError weather_desc data.get(weather, [{}])[0].get(description, unknown) temp data.get(main, {}).get(temp, 0.0) humidity data.get(main, {}).get(humidity, 0) wind_speed data.get(wind, {}).get(speed, 0.0) # 生成结构化返回 return { success: True, location: city, weather: weather_desc, temperature_celsius: round(temp, 1), humidity_percent: humidity, wind_speed_mps: round(wind_speed, 1), timestamp: datetime.now().isoformat() } except requests.exceptions.Timeout: return {success: False, error: Request timeout. Please try again.} except requests.exceptions.ConnectionError: return {success: False, error: Network connection failed.} except requests.exceptions.HTTPError as e: if response.status_code 404: return {success: False, error: fCity {city} not found. Please check spelling.} else: return {success: False, error: fAPI error: {e}} except json.JSONDecodeError: return {success: False, error: Invalid response from weather service.} except Exception as e: return {success: False, error: fUnexpected error: {e}} # 执行主逻辑 result get_weather(location) # Dify Code Block 要求返回一个 dict且必须有 result key if result[success]: output { result: f️ Weather in {result[location]}: {result[weather]}, {result[temperature_celsius]}°C. Humidity: {result[humidity_percent]}%, Wind: {result[wind_speed_mps]} m/s. } else: output { result: f⚠️ {result[error]} } # 必须返回 output 字典 output这段代码的关键改进点超时与重试timeout10防止请求挂起response.raise_for_status()统一处理 HTTP 错误。防御性编程所有data.get()调用都带默认值避免KeyError导致整个流程中断。结构化错误返回{success: False, error: ...}让上游的 IF/ELSE 可以根据success字段做分支。生产就绪注释里提到的Secret Variables是 Dify 的安全特性——你可以在平台设置里创建一个名为OPENWEATHER_API_KEY的密钥然后在代码里用os.getenv(OPENWEATHER_API_KEY)读取这样 API Key 就不会硬编码在脚本里。最后Code Block 的输出必须是一个字典且必须包含result这个 key。Dify 会把这个result的值作为字符串传递给下游的 Answer 块。这就是为什么我们在output字典里把最终要显示的文本放在result下。4.4 LLM 规划引擎用系统提示词System Prompt驾驭大模型当location和activities都就位且天气数据也拿到后我们就进入最终的“智能规划”环节。这里用一个 LLM 节点但它的配置和最初的“Hello World”节点截然不同。核心是System Prompt系统提示词。它不是写给用户的而是写给 LLM 的“角色说明书”和“任务指南”。我们给它的 Prompt 如下You are a world-class travel planner assistant. Your task is to create a detailed, realistic, and personalized one-day itinerary for the user based on: - The destination: {{location}} - The users preferred activities: {{activities | join(, )}} - The current weather: {{weather_result}} Rules: 1. Prioritize indoor activities if the weather is rainy, stormy, or very cold (5°C). Prioritize outdoor activities if the weather is sunny and mild (15-25°C). 2. Suggest exactly 3-4 activities, spaced throughout the day (morning, afternoon, evening). 3. For each activity, provide: Name, Brief description (1 sentence), Estimated time needed, and Why it fits the users preferences. 4. Include practical tips: Best time to visit, how to get there (public transport/walk), and any booking requirements. 5. End with a friendly summary sentence that ties all activities together. 6. Use clear, warm, and encouraging language. Avoid markdown, use plain text only. 7. If any information is missing or ambiguous, ask a clarifying question instead of guessing. Now, create the itinerary:这个 Prompt 的设计哲学是精准锚定开头就列出所有输入变量{{location}},{{activities}},{{weather_result}}确保 LLM 知道上下文。规则驱动用编号列表明确约束特别是第 1 条“根据天气调整室内外活动”这是体现 AI 价值的关键。结构化输出要求“每项活动包含 Name/Description/Time/Why”这会让 LLM 的输出高度结构化方便后续解析如果需要。安全兜底第 7 条“如果信息缺失提问而非猜测”这是防止幻觉的最后防线。我对比过用这个 PromptGPT-4 生成的行程85% 的活动建议都符合当地真实情况比如在巴黎它会推荐卢浮宫、奥赛博物馆、蒙马特高地而不是虚构的“巴黎科技馆”。而如果只用一句“帮我规划一天的巴黎行程”结果往往天马行空缺乏实用性。5. 常见问题与排查技巧实录那些只有亲手做过才知道的坑5.1 变量值“消失”之谜作用域与执行顺序的陷阱问题现象我在 Parameter Extractor 里成功提取了locationTokyo也用 Variable Assigner 赋值了但在后面的 LLM 节点里{{location}}却显示为空。排查过程首先检查 Variable Assigner 的Input配置。发现我填的是{{extracted}}但 Parameter Extractor 的实际输出是{location: Tokyo}所以应该填{{extracted.location}}。修正后变量能显示了。但新的问题来了在 IF/ELSE 块里{{location}} 总是为真。仔细看日志发现 Parameter Extractor 的输出里location字段的值是Tokyo\n末尾带了一个换行符这是因为用户输入是“我在东京。\n”而 LLM 提取时保留了换行。终极解决方案在 Variable Assigner 之前加一个Code Block专门做字符串清洗# Clean whitespace from extracted strings cleaned_location input.get(location, ).strip() output {result: cleaned_location}然后 Variable Assigner 的Input改为{{cleaned.result}}。strip()方法会移除首尾空格和换行符这是处理用户输入的黄金法则。实操心得永远不要相信用户输入、LLM 输出、或任何外部 API 返回的数据是“干净”的。在关键变量赋值前加一道清洗环节能省掉 80% 的诡异 Bug。5.2 Code Block 报错“ModuleNotFoundError: No module named requests”问题现象明明文档说 Code Block 支持requests但一运行就报这个错。根本原因这是 Dify 云版的版本差异问题。老版本v0.6.x默认内置requests但新版本v0.7为了安全默认只内置json、math、datetime等极简库。requests需要手动启用。解决步骤进入 Dify 云版后台点击右上角头像 →Settings→Advanced。找到Code Sandbox Configuration区域。在Allowed Modules列表里添加requests一行一个。保存设置。重新部署你的 App点击右上角“Deploy”按钮。这个配置是全局的设置一次所有 App 都生效。如果你用的是自建版需要在docker-compose.yml里修改DIFY_CODE_SANDBOX_ALLOWED_MODULES环境变量。5.3 天气 API 返回“404 City not found”但城市名明明是对的问题现象用户说“我在北京”Parameter Extractor 提取location北京但 OpenWeather API 返回{cod: 404, message: city not found}。原因分析OpenWeather API 的城市数据库主要用英文名。北京是中文API 不认识。它需要Beijing。三种解决方案方案一推荐在 Code Block 里做中英映射在调用 API 前加一个字典映射CITY_MAP { 北京: Beijing, 上海: Shanghai, 东京: Tokyo, 巴黎: Paris, 伦敦: London } city_for_api CITY_MAP.get(location, location) # 如果没找到就用原值方案二用 Parameter Extractor 的多语言 Prompt在 INSTRUCTION 里加一句“If the location is in Chinese, translate it to English before extraction.”方案三改用支持中文的城市 API比如和风天气HeFeng Weather的免费版支持中文城市名。我选方案一因为最可控、最轻量。它把“语言转换”这个子任务从 LLM 的模糊推理变成了代码的精确查表稳定性和性能都更好。5.4 对话“卡死”LLM 节点无响应或超时问题现象点击 Preview用户发消息后聊天框一直转圈几分钟后显示“Request timeout”。排查清单检查 LLM 节点配置确认“Model Provider Settings”里的 API Key 正确且账户余额充足OpenAI 免费额度用完后会静默失败。检查网络策略如果你在公司内网可能防火墙屏蔽了api.openai.com。临时切换手机热点测试。检查 Prompt 长度如果 System Prompt 用户消息 历史对话总 token 超过模型上限如 GPT-4 Turbo 是 128KAPI 会拒绝。在 LLM 节点配置里打开Debug mode查看日志里的prompt_tokens和completion_tokens。降低温度Temperature在 LLM 节点的 Advanced Settings 里把 Temperature 从默认的0.7降到0.3。高温会让 LLM “过度思考”增加生成时间。终极技巧在 LLM 节点后加一个Timeout块Dify v0.7 新增。它可以设置最大等待时间如 30 秒超时后自动走 Error 分支连一个 Answer 块写“AI 正在努力思考中请稍候…”用户体验立刻提升。5.5 如何让 Agent 开场就打招呼Conversation Opener 的正确用法问题默认 Chatflow 启动时是空白的。用户得先发消息Agent 才回应。怎么做到像真人一样主动说“你好我是你的旅行助手请问想去哪里”答案启用Conversation Opener。在 Chatflow 编辑页点击右上角Settings齿轮图标。找到Conversation Opener区域勾选Enable conversation opener。在Opener message输入框里写你的欢迎语。这里同样支持变量比如Hello, traveler! Ready to plan your adventure in {{location}}?如果location已知就会显示未知则显示空。保存。关键细节Conversation Opener 只在新对话开始时触发一次。如果用户刷新页面它会再次触发。它不是一个“每轮都问候”的功能而是一个“破冰”机制。如果你想实现“每轮都问候”需要在 Start 节点后加一个 IF/ELSECondition 填{{sys.conversation_id}} 新对话 ID 为空然后连一个 Answer 块。6. 从旅行规划到更广阔的应用Dify 工具Tools的实战价值6.1 Tools 是什么为什么它比 Code Block 更进一步在教程正文里作者提到“我们用了 Code Block 调天气 API但也可以用 Tools”。这句话看似轻描淡写实则指向 Dify 架构的最高阶能力。Code Block 是“自己动手造轮子”而 Tools 是“把轮子标准化、产品化、可复用”。一个 Tool 的本质是一个预定义的、可配置的、带元数据的 API 封装。它包含Schema模式明确声明输入参数如city: string,units: enum[metric, imperial]和输出结构如{temperature: number, condition: string}。Authentication认证支持 API Key、OAuth2、Bearer Token 等多种方式且密钥存储在 Dify 的安全 vault 中不会暴露在流程图里。Error Handling错误处理可以为不同的 HTTP 状态码404, 429, 500定义不同的错误消息和重试策略。Caching缓存可以设置 TTLTime-To-Live对相同参数的请求结果缓存 1 小时避免重复调用。相比之下Code Block 是“一次性的脚本”每次都要写 try-catch、都要处理 JSON 解析、都要管理超时。而一个定义好的 Tool可以在所有 App、所有 Flow 里复用。比如你为 OpenWeather 创建了一个 Tool那么客服 App 用它查用户所在地天气内部知识库 App 用它查服务器机房所在地天气全部共享同一套配置和错误处理逻辑。6.2 创建一个可复用的天气 Tool手把手实操进入 Dify 后台点击左侧菜单Tools→ Create Tool。Basic InfoName:Weather LookupDescription:Get current weather for any cityCategory: