FastAPI Dify 实战5分钟搞定自定义天气查询工具的完整开发流程最近在折腾AI应用开发发现一个挺有意思的现象很多开发者能把大模型玩得很溜Prompt写得天花乱坠但一到要把自己的业务系统、内部API接入到AI工作流里就卡壳了。要么是OpenAPI规范看着头大要么是跨域问题折腾半天要么就是部署后AI智能体死活调不通。如果你也遇到过类似问题那今天这个实战案例就是为你准备的。我们用一个最经典的场景——天气查询来演示如何用FastAPI快速构建一个轻量级服务然后无缝集成到Dify平台让AI智能体瞬间获得查询真实世界数据的能力。整个过程从零开始到完全跑通真的只需要5分钟左右。这个方案特别适合那些有Python基础但缺乏AI工程化经验的中级开发者。你不需要是OpenAPI专家也不需要精通复杂的网络配置跟着步骤走就能掌握一套可复用的方法论。无论是内部CRM数据查询、库存系统状态获取还是像今天要做的天气服务底层逻辑都是一样的。1. 为什么选择FastAPI Dify的组合在开始动手之前我们先聊聊技术选型。市面上能构建API的框架很多能集成AI的工具也不少为什么偏偏是FastAPI和DifyFastAPI这几年在Python Web框架里算是异军突起它的核心优势就三个字快、简、强。快是指性能基于Starlette和Pydantic异步支持原生速度跟Go和Node.js掰手腕也不虚。简是指开发体验类型提示Type Hints用好了代码几乎自带文档IDE的自动补全和错误检查能帮你避免很多低级bug。强是指功能OpenAPISwagger文档自动生成、数据验证、依赖注入这些现代API该有的它全都有。看看下面这个最简单的例子感受一下它的简洁from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Item(BaseModel): name: str price: float app.post(/items/) async def create_item(item: Item): return {item_name: item.name, item_price: item.price}就这么几行一个带数据验证的POST接口就完成了而且自动生成了交互式API文档。这种开发效率对于快速原型和微服务来说简直是神器。Dify的定位很清晰让AI应用开发变得更简单。它不像有些平台只关注模型训练或部署而是把重点放在了应用编排和工具集成上。你可以把它理解成一个可视化的AI工作流搭建平台通过拖拽节点就能组合出复杂的AI应用逻辑。Dify对自定义工具的支持是其核心亮点之一。它不要求你把代码上传到它的平台而是通过标准的OpenAPI规范来“描述”你的外部服务然后像调用本地函数一样去调用。这种设计有几个关键好处技术栈无关你的服务可以用任何语言、任何框架编写只要符合HTTP和OpenAPI规范。部署独立服务部署在你自己的服务器、容器或云函数上数据安全和运维自主可控。生态集成自定义工具和Dify内置的工具如知识库检索、代码执行在同一个工作流里平等使用无缝协作。把FastAPI和Dify放一起你会发现它们是天作之合FastAPI负责快速、可靠地暴露业务能力Dify负责智能、灵活地编排和调用这些能力。一个管“能做啥”一个管“怎么用”分工明确。提示在选择技术栈时一定要考虑团队的熟悉程度和项目的长期维护成本。FastAPI的学习曲线平缓Dify的界面直观这对于中小团队快速启动AI项目至关重要。2. 5分钟构建一个生产可用的天气查询API理论说再多不如动手。我们现在就来用FastAPI搭建一个天气查询服务。别被“生产可用”吓到我们会从最简单的模拟数据开始然后一步步加入错误处理、日志、配置等生产级要素。2.1 项目初始化与基础依赖首先创建一个新的项目目录并初始化虚拟环境。我习惯用uv它比传统的venvpip组合快得多当然用pipenv或poetry也行。# 创建项目目录 mkdir fastapi-weather-tool cd fastapi-weather-tool # 使用uv初始化虚拟环境和项目如果没有uv可以用 pip install uv 安装 uv init uv add fastapi uvicorn pydantic-settings安装的包不多就三个fastapi: 核心框架。uvicorn: ASGI服务器用于运行FastAPI应用。pydantic-settings: 用于管理配置比直接读环境变量更优雅。接下来创建项目的主文件app.py和配置文件.env。2.2 核心服务代码与OpenAPI自动生成打开app.py我们开始编写服务。第一步是创建FastAPI应用实例并配置CORS跨域资源共享。这是关键一步因为Dify平台和你的API服务很可能不在同一个域名下不配置CORS会导致浏览器安全策略阻止请求。from fastapi import FastAPI, Query, HTTPException, status from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field from typing import Optional import logging from datetime import datetime # 配置日志 logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) app FastAPI( titleWeather Service API, description一个为Dify智能体提供天气查询数据的模拟服务。, version1.0.0, openapi_tags[ {name: health, description: 健康检查端点}, {name: weather, description: 天气数据查询}, ] ) # 配置CORS - 允许Dify前端调用 app.add_middleware( CORSMiddleware, allow_origins[*], # 生产环境应替换为具体的Dify前端地址如 [https://app.dify.ai] allow_credentialsTrue, allow_methods[*], allow_headers[*], )注意看allow_origins[*]这一行。在开发阶段为了方便测试我们允许所有来源。但在生产环境强烈建议将其替换为你的Dify前端实际部署的域名例如[https://your-dify-domain.com]这是基本的安全最佳实践。接下来我们定义数据模型。使用Pydantic模型不仅能自动验证请求和响应数据还能让生成的OpenAPI文档更加清晰。class WeatherRequest(BaseModel): 查询天气的请求参数模型 city: str Field(..., description城市名称例如北京、Shanghai、Tokyo) units: Optional[str] Field( metric, description温度单位体系。metric为公制摄氏度imperial为英制华氏度, regex^(metric|imperial)$ ) class WeatherData(BaseModel): 天气数据详情 city: str temperature: float humidity: float Field(..., ge0, le1, description相对湿度范围0到1) windspeed: float units: str condition: str Field(sunny, description天气状况如 sunny, cloudy, rainy) class WeatherResponse(BaseModel): 标准化的API响应格式 ok: bool message: str data: Optional[WeatherData] None timestamp: datetime定义了模型后就可以编写业务端点了。我们先写一个健康检查端点这是微服务的标配。app.get(/health, tags[health]) async def health_check(): 健康检查端点用于服务探活和监控 logger.info(Health check endpoint called.) return { status: healthy, service: weather-api, timestamp: datetime.utcnow().isoformat() }重头戏是天气查询接口。为了演示我们返回模拟数据。在实际项目中这里应该调用真实的天气API如OpenWeatherMap、和风天气等。# 模拟一些城市的天气数据 MOCK_WEATHER_DATA { beijing: {temp_c: 22.5, temp_f: 72.5, humidity: 0.4, windspeed: 12.1, condition: sunny}, shanghai: {temp_c: 25.8, temp_f: 78.4, humidity: 0.65, windspeed: 8.3, condition: cloudy}, tokyo: {temp_c: 26.1, temp_f: 79.0, humidity: 0.68, windspeed: 3.4, condition: partly cloudy}, new york: {temp_c: 18.3, temp_f: 64.9, humidity: 0.55, windspeed: 15.0, condition: windy}, } app.get(/weather, response_modelWeatherResponse, tags[weather]) async def get_weather(city: str Query(..., description城市名称), units: str Query(metric, regex^(metric|imperial)$)): 根据城市名称查询模拟天气数据。 - **city**: 城市名称支持中英文不区分大小写 - **units**: 单位metric(摄氏度) 或 imperial(华氏度)默认为metric logger.info(fWeather query received for city: {city}, units: {units}) city_key city.strip().lower() if city_key not in MOCK_WEATHER_DATA: logger.warning(fCity not found in mock data: {city}) raise HTTPException( status_codestatus.HTTP_404_NOT_FOUND, detailfWeather data for city {city} is currently unavailable in the mock dataset. ) mock_data MOCK_WEATHER_DATA[city_key] temperature mock_data[temp_c] if units metric else mock_data[temp_f] weather_data WeatherData( citycity, temperatureround(temperature, 1), humiditymock_data[humidity], windspeedmock_data[windspeed], unitsunits, conditionmock_data[condition] ) return WeatherResponse( okTrue, messagefWeather data for {city} retrieved successfully., dataweather_data, timestampdatetime.utcnow() )代码写完了用一行命令就能启动服务uvicorn app:app --reload --host 0.0.0.0 --port 8088打开浏览器访问http://localhost:8088/docs你会看到一个完整的、交互式的API文档页面这就是FastAPI根据你的代码和类型提示自动生成的OpenAPI文档。试着调用一下/weather?citybeijing接口看看返回的数据。接口路径方法描述测试用例/healthGET服务健康检查curl http://localhost:8088/health/weatherGET查询城市天气curl http://localhost:8088/weather?cityshanghaiunitsmetric至此一个具备完整文档、错误处理、日志和标准化响应的API服务就完成了。接下来我们要让它被AI智能体“认识”并调用。3. 编写OpenAPI Schema让Dify“看懂”你的服务Difi的自定义工具功能本质是一个“翻译器”它需要一份标准的“说明书”来理解你的API。这份说明书就是OpenAPI Schema以前叫Swagger。好消息是FastAPI已经为我们生成了绝大部分内容我们只需要做一点微调和适配。3.1 导出与理解FastAPI生成的Schema首先从FastAPI自动生成的文档中导出Schema。访问http://localhost:8088/openapi.json你会看到一个完整的JSON文件。这就是我们需要的OpenAPI Schema。把它保存下来命名为openapi_schema.json。这个JSON文件结构清晰主要包含以下几个部分openapi: 规范版本FastAPI默认生成3.0.0或3.1.0。info: API的基本信息如标题、描述、版本。servers: API服务器的地址列表。这里需要修改把本地地址换成你的服务在公网或内网可访问的地址。paths: 核心部分描述了所有可用的接口路径、方法、参数和响应。components: 可复用的组件定义比如我们上面定义的WeatherResponse、WeatherData模型。3.2 为Dify定制化SchemaDify对Schema的兼容性很好但为了获得最佳体验我们通常需要手动调整几个地方修改servers地址这是最重要的。Dify需要知道去哪里调用你的服务。如果你把服务部署在了云服务器上地址可能是http://your-server-ip:8088或https://api.yourdomain.com。确保operationId唯一且清晰Dify会用这个字段来生成工具的函数名。建议使用类似get_weather这样的蛇形命名。补充description清晰的描述能帮助Dify的LLM更好地理解这个工具是干什么用的从而在合适的场景调用它。考虑超时设置可以为特定操作添加x-timeout-ms扩展字段定义Dify调用该接口时的超时时间。下面是一个针对我们天气服务优化后的Schema示例片段重点关注/weather路径和servers部分{ openapi: 3.1.0, info: { title: Weather Service for Dify Agent, description: 为Dify智能体提供模拟天气查询功能。支持公制/英制单位转换。, version: v1.0.0 }, servers: [ { url: http://your-server-ip:8088, description: 生产环境服务器 } ], paths: { /weather: { get: { operationId: get_weather, summary: 查询指定城市的天气信息, description: 根据城市名称返回模拟的温度、湿度、风速和天气状况。这是一个示例工具实际使用时请接入真实天气数据源。, tags: [weather], parameters: [ { name: city, in: query, required: true, description: 要查询天气的城市名称例如Beijing, Shanghai, Tokyo, New York。不区分大小写。, schema: { type: string, minLength: 1 } }, { name: units, in: query, required: false, description: 温度单位。metric 表示摄氏度(°C)imperial 表示华氏度(°F)。默认值为 metric。, schema: { type: string, enum: [metric, imperial], default: metric } } ], responses: { 200: { description: 成功获取天气数据, content: { application/json: { schema: { $ref: #/components/schemas/WeatherResponse } } } }, 404: { description: 请求的城市不在模拟数据集中 }, 500: { description: 服务器内部错误 } }, x-timeout-ms: 10000 } } }, components: { schemas: { WeatherResponse: { type: object, required: [ok, message, timestamp], properties: { ok: { type: boolean, description: 请求是否成功 }, message: { type: string, description: 响应的描述信息 }, data: { $ref: #/components/schemas/WeatherData }, timestamp: { type: string, format: date-time, description: 响应生成的时间戳 } } }, WeatherData: { type: object, required: [city, temperature, humidity, windspeed, units, condition], properties: { city: { type: string, description: 查询的城市名 }, temperature: { type: number, description: 温度值单位取决于units字段 }, humidity: { type: number, description: 相对湿度范围0到1, minimum: 0, maximum: 1 }, windspeed: { type: number, description: 风速单位米/秒(m/s)或英里/小时(mph) }, units: { type: string, enum: [metric, imperial], description: 使用的度量单位体系 }, condition: { type: string, description: 简化的天气状况描述 } } } } } }注意在将Schema填入Dify之前务必确保servers.url是Dify平台网络可达的地址。如果Dify部署在公有云你的API服务在本地开发机需要做内网穿透如使用ngrok、frp等工具或部署到云服务器。4. 在Dify中创建并配置自定义工具有了完善的OpenAPI Schema在Dify中创建工具就变成了一个简单的表单填写过程。登录你的Dify控制台按照以下步骤操作进入工具管理在左侧导航栏找到“工具”或“Tools”模块点击进入。创建自定义工具点击“创建自定义工具”或“Create Custom Tool”按钮。填写基本信息工具名称起个易懂的名字如weather_query。工具描述用自然语言描述这个工具的功能这有助于LLM理解何时调用它。例如“查询全球主要城市的模拟天气信息包括温度、湿度、风速和天气状况。”粘贴OpenAPI Schema将上一步我们精心调整好的JSON Schema完整地粘贴到“Schema”或“OpenAPI Specification”文本框中。等待解析Dify会自动解析你粘贴的Schema。如果格式正确你会看到它识别出了/weather接口并可能将其列为一个可用的“操作”。配置认证可选如果你的API需要API Key、Bearer Token等认证可以在相应的字段进行配置。我们的示例API是公开的所以跳过。保存并测试点击保存。保存成功后Dify通常会提供一个测试界面。你可以在这里直接输入参数如city: Beijing来调用工具验证返回结果是否符合预期。如果测试成功恭喜你这个工具已经成功注册到Dify平台了。现在任何在Dify上创建的智能体Agent或工作流Workflow都可以像使用内置工具一样使用你的天气查询服务了。5. 在智能体和工作流中实战调用工具创建好了怎么用起来我们分两种最常见的场景来看对话型智能体Agent和可视化工作流Workflow。5.1 在对话型智能体中集成这是最直观的用法。创建一个新的智能体应用在“工具”配置部分勾选我们刚刚创建的weather_query工具。关键在于**系统提示词System Prompt**的编写。你需要告诉LLM它现在拥有了这个新工具并指导它在什么情况下使用。你是一个友好的天气助手可以帮助用户查询城市的天气信息。 你拥有以下工具 - weather_query: 可以查询指定城市的模拟天气数据。当用户询问天气、温度、湿度、风速或出行建议时你应该主动使用这个工具。 使用工具时请遵循以下步骤 1. 确认用户想查询哪个城市。 2. 调用 weather_query 工具并传入 city 参数。 3. 将工具返回的数据温度、湿度、风速、天气状况用自然、友好的语言组织起来回复给用户。 4. 如果用户没有指定单位默认使用摄氏度(°C)。 例如 用户“上海天气怎么样” 你应该调用工具查询上海天气然后回复“上海目前天气晴朗温度25.8°C湿度65%风速8.3米/秒。是个出门的好天气”保存配置后在聊天窗口输入“北京今天热吗”观察智能体是否会自动调用工具并返回格式化的天气信息。这个过程完美展示了LLM如何将自然语言指令转化为对特定API的调用。5.2 在可视化工作流中编排对于更复杂的逻辑工作流模式提供了更强的可控性。我们设计一个简单的“出行建议”工作流开始节点接收用户输入例如“我明天要去东京出差”。LLM节点提取用户语句中的关键信息——城市东京。可以使用Prompt如“请从用户输入中提取城市名。只输出城市名不要其他文字。用户输入{{query}}”工具节点将上一步提取的“东京”作为city参数调用weather_query工具。代码节点可选对返回的原始JSON数据进行加工。例如判断如果temperature 30或condition包含rain则生成特定的建议。def main(weather_data: dict) - str: data weather_data.get(data, {}) suggestion f根据天气数据温度{data.get(temperature)}°C天气{data.get(condition)}。 if data.get(condition) in [rainy, stormy]: suggestion 建议携带雨具。 elif data.get(temperature, 0) 28: suggestion 天气较热建议穿着轻薄衣物。 else: suggestion 天气适宜祝您出行愉快 return suggestion回复节点将代码节点生成的建议或直接工具返回的天气信息组合成最终回复给用户。通过工作流你可以将天气查询与知识库检索、条件判断、多步骤处理等结合起来构建出功能强大的AI应用。这种将自定义工具作为“乐高积木”来搭建复杂应用的能力正是Dify的核心价值。6. 部署、监控与进阶思考本地开发测试一切顺利接下来就要考虑如何让服务稳定可靠地运行了。部署方案传统服务器使用nohup或systemd守护进程。命令示例nohup uvicorn app:app --host 0.0.0.0 --port 8088 Docker容器化推荐编写Dockerfile构建镜像后在任何支持Docker的环境运行。这保证了环境一致性。FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [uvicorn, app:app, --host, 0.0.0.0, --port, 8088]云函数/Serverless如果你追求极致的弹性伸缩和成本优化可以将FastAPI应用部署到云函数如AWS Lambda需要搭配Mangum等适配器或专门的Serverless平台。监控与日志确保应用日志我们之前用logging模块配置的被收集到文件或日志系统中如ELK、Loki。为/health端点配置健康检查方便Kubernetes或负载均衡器探活。考虑在API中集成简单的指标暴露例如使用Prometheus客户端库监控请求量、延迟和错误率。安全加固CORS如前所述在生产环境收紧allow_origins。认证鉴权如果API敏感在FastAPI端添加API Key或JWT认证。Dify的自定义工具配置也支持填写认证信息。输入校验虽然Pydantic做了基础校验但对于city这类参数可以增加更严格的校验如防SQL注入、防路径遍历。速率限制使用slowapi等中间件防止恶意刷接口。走完这个完整的流程你收获的不仅仅是一个天气查询工具。你掌握的是将任意现有业务能力快速AI化的通用模式。无论是查询数据库、调用内部系统、触发自动化脚本还是连接硬件设备都可以遵循“FastAPI暴露接口 - OpenAPI描述 - Dify集成调用”这条路径。我自己的团队就用这个模式把客户管理系统、项目状态看板、甚至服务器监控告警都接入了AI助手效果出奇的好。开发人员不用再写繁琐的对话逻辑产品经理也能直接参与设计AI交互流程。这种低门槛的AI集成能力正在成为现代应用开发的标配。