FastAPI + Dify 实战:5分钟搞定自定义天气查询工具的完整开发流程
最近在折腾AI应用开发,发现一个挺有意思的现象:很多开发者能把大模型玩得很溜,Prompt写得天花乱坠,但一到要把自己的业务系统、内部API接入到AI工作流里,就卡壳了。要么是OpenAPI规范看着头大,要么是跨域问题折腾半天,要么就是部署后AI智能体死活调不通。
如果你也遇到过类似问题,那今天这个实战案例就是为你准备的。我们用一个最经典的场景——天气查询,来演示如何用FastAPI快速构建一个轻量级服务,然后无缝集成到Dify平台,让AI智能体瞬间获得查询真实世界数据的能力。整个过程,从零开始到完全跑通,真的只需要5分钟左右。
这个方案特别适合那些有Python基础,但缺乏AI工程化经验的中级开发者。你不需要是OpenAPI专家,也不需要精通复杂的网络配置,跟着步骤走,就能掌握一套可复用的方法论。无论是内部CRM数据查询、库存系统状态获取,还是像今天要做的天气服务,底层逻辑都是一样的。
1. 为什么选择FastAPI + Dify的组合?
在开始动手之前,我们先聊聊技术选型。市面上能构建API的框架很多,能集成AI的工具也不少,为什么偏偏是FastAPI和Dify?
FastAPI这几年在Python Web框架里算是异军突起,它的核心优势就三个字:快、简、强。快是指性能,基于Starlette和Pydantic,异步支持原生,速度跟Go和Node.js掰手腕也不虚。简是指开发体验,类型提示(Type Hints)用好了,代码几乎自带文档,IDE的自动补全和错误检查能帮你避免很多低级bug。强是指功能,OpenAPI(Swagger)文档自动生成、数据验证、依赖注入,这些现代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,它比传统的venv+pip组合快得多,当然用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(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI( title="Weather Service API", description="一个为Dify智能体提供天气查询数据的模拟服务。", version="1.0.0", openapi_tags=[ {"name": "health", "description": "健康检查端点"}, {"name": "weather", "description": "天气数据查询"}, ] ) # 配置CORS - 允许Dify前端调用 app.add_middleware( CORSMiddleware, allow_origins=["*"], # 生产环境应替换为具体的Dify前端地址,如 ["https://app.dify.ai"] allow_credentials=True, 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(..., ge=0, le=1, 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_model=WeatherResponse, tags=["weather"]) async def get_weather(city: str = Query(..., description="城市名称"), units: str = Query("metric", regex="^(metric|imperial)$")): """ 根据城市名称查询模拟天气数据。 - **city**: 城市名称,支持中英文(不区分大小写) - **units**: 单位,`metric`(摄氏度) 或 `imperial`(华氏度),默认为`metric` """ logger.info(f"Weather query received for city: {city}, units: {units}") city_key = city.strip().lower() if city_key not in MOCK_WEATHER_DATA: logger.warning(f"City not found in mock data: {city}") raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Weather 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( city=city, temperature=round(temperature, 1), humidity=mock_data["humidity"], windspeed=mock_data["windspeed"], units=units, condition=mock_data["condition"] ) return WeatherResponse( ok=True, message=f"Weather data for {city} retrieved successfully.", data=weather_data, timestamp=datetime.utcnow() )代码写完了,用一行命令就能启动服务:
uvicorn app:app --reload --host 0.0.0.0 --port 8088打开浏览器,访问http://localhost:8088/docs,你会看到一个完整的、交互式的API文档页面,这就是FastAPI根据你的代码和类型提示自动生成的OpenAPI文档。试着调用一下/weather?city=beijing接口,看看返回的数据。
| 接口路径 | 方法 | 描述 | 测试用例 |
|---|---|---|---|
/health | GET | 服务健康检查 | curl http://localhost:8088/health |
/weather | GET | 查询城市天气 | curl "http://localhost:8088/weather?city=shanghai&units=metric" |
至此,一个具备完整文档、错误处理、日志和标准化响应的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定制化Schema
Dify对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集成能力,正在成为现代应用开发的标配。