为什么写这篇 / 适合谁看
我在 Dify 里想让大模型「查天气」时,卡了两个问题:一是插件到底和 Function Calling 是什么关系,二是本地写好的接口,云端 Dify 根本访问不到 localhost。
如果你也想给 Dify 挂一个自己的工具(查天气只是例子,换成查库存、查订单、调任意内部接口都一样),但不想先租服务器,这篇就是给你的。技术栈:FastAPI + localtunnel + Dify,30 分钟能跑通一个能用的自定义插件。
一、先理清概念:插件和 Function Calling 是什么关系
一句话:在 Dify 里,工具(插件)就是 Function Calling 的实现形式。
大模型本身只会「说话」,要让它能查天气、查数据库、调外部接口,就得给它挂上工具,由模型自己决定何时调用、传什么参数。
整体路径很简单:
写一个 HTTP 接口 → 用 OpenAPI Schema 描述给 Dify → 模型按需调用
自定义接口开发的 6 个步骤:
| 步骤 | 操作 | 说明 |
|---|---|---|
| Step 1 | 脚本开发 | 本地用 Python 实现接口逻辑 |
| Step 2 | 运行脚本 | 后台跑起 API 服务 |
| Step 3 | 创建工具 | 在 Dify 工具中创建自定义工具 |
| Step 4 | Schema 配置 | 配置 OpenAPI Schema |
| Step 5 | 测试 | 输入参数测试功能 |
| Step 6 | 保存 | 在 Agent 中应用插件 |
二、FastAPI 服务搭建
2.1 安装依赖
bash
# 创建虚拟环境(推荐)
python -m venv weather-env
source weather-env/bin/activate # Linux/Mac
# weather-env\Scripts\activate # Windows
pip install fastapi uvicorn requests
2.2 完整代码(main.py)
城市编码直接硬编码,不依赖外部文件,方便一份文件跑起来:
python
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
import requests
app = FastAPI()
# 身份验证令牌(个人使用,随便设一个自己知道的)
VALID_TOKEN = "my-secret-token"
# 城市编码(按需补充)
CITY_CODES = {
"北京": "101010100",
"上海": "101020100",
"广州": "101280101",
"深圳": "101280601",
"杭州": "101210101",
"成都": "101270101",
# ......可继续补充
}
class WeatherRequest(BaseModel):
location: str
@app.post("/weather")
def get_current_weather(request: Request, body: WeatherRequest):
# 1. 验证身份
auth_header = request.headers.get("Authorization")
if auth_header != f"Bearer {VALID_TOKEN}":
raise HTTPException(status_code=403, detail="Invalid Authorization header")
location = body.location
# 2. 查找城市编码
city_code = CITY_CODES.get(location)
if not city_code:
return {
"status": "error",
"message": f"暂不支持 {location},目前支持:{','.join(CITY_CODES.keys())}"
}
# 3. 调用天气 API
url = f"http://t.weather.itboy.net/api/weather/city/{city_code}"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
except Exception as e:
return {"status": "error", "message": f"天气服务请求失败: {str(e)}"}
# 4. 解析并返回自然语言
try:
forecast = data["data"]["forecast"][0]
weather_type = forecast["type"]
high = forecast["high"].replace("高温 ", "")
low = forecast["low"].replace("低温 ", "")
return f"{location}今天是{weather_type},温度{high}/{low}"
except (KeyError, IndexError) as e:
return {"status": "error", "message": f"天气数据解析失败: {str(e)}"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8081)
2.3 启动服务
bash
python main.py
跑起来应看到:Uvicorn running on http://0.0.0.0:8081
三、公网穿透:让云端 Dify 能访问你的本机
⚠️ 这是最容易卡住的一步。 Dify(尤其云端版)访问不到你本机的 localhost,必须给它一个公网可达的 URL。localtunnel 就是把本地端口临时映射到公网的小工具。
bash
# 全局安装
npm install -g localtunnel
# 新开一个终端窗口
lt --port 8081
输出形如:your url is: https://random-name-123.loca.lt,把这个 URL 复制下来。
❌ 别关这个终端:窗口一关,链接立刻失效。
四、发到 Dify 前先自测
用 curl(或 Postman)先确认接口通,别等配到 Dify 再排错:
bash
curl -X POST https://your-url.loca.lt/weather \
-H "Authorization: Bearer my-secret-token" \
-H "Content-Type: application/json" \
-d '{"location": "杭州"}'
五、Dify 插件配置
5.1 OpenAPI 3.1.0 Schema
json
{
"openapi": "3.1.0",
"info": {
"title": "天气查询API",
"description": "查询中国城市当前天气信息",
"version": "v1.0.0"
},
"servers": [
{ "url": "https://your-url.loca.lt" }
],
"paths": {
"/weather": {
"post": {
"summary": "查询城市天气",
"security": [ { "BearerAuth": [] } ],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称",
"example": "北京"
}
},
"required": ["location"]
}
}
}
},
"responses": {
"200": { "description": "成功获取天气信息" }
}
}
}
},
"components": {
"schemas": {},
"securitySchemes": {
"BearerAuth": { "type": "http", "scheme": "bearer" }
}
}
}
5.2 创建步骤
- 登录 Dify 控制台,进入「工具」→「自定义」
- 把上面的 JSON 完整粘进 Schema 编辑器
- ⚠️ 把
servers.url换成你自己的 localtunnel URL(这一步最容易忘,忘了就一直调不通) - 鉴权方式选「请求头」→ 类型「Bearer」→ Value 填你的
VALID_TOKEN - 在测试区输入
{"location": "深圳"},能返回天气就成了 - 点「发布」,选择要启用该插件的应用
六、在 Agent 里用起来
在应用的提示词里加一句引导,模型才知道什么时候调:
你可以使用天气查询工具帮用户获取天气。当用户询问天气时,调用天气插件。
然后在「工具」里启用这个插件。对话示例:
用户:北京今天天气怎么样? Agent:北京今天是多云,温度 32℃/22℃
七、我踩过的 5 个坑(最值得记的排查表)
| 现象 | 原因 | 解决 |
|---|---|---|
| ❌ Reached maximum retries | Dify 访问不到 localhost | 必须用公网 URL,不能填 localhost |
❌ {"detail":"Not Found"} |
路由路径不对 | 确认代码里是 @app.post("/weather") |
| ❌ 403 Forbidden | token 不匹配 | Dify 里填的 Bearer 值要和 VALID_TOKEN 一致 |
| ❌ 连接超时 | localtunnel 断了 | 重启 lt --port 8081,并更新 Dify 里的 URL |
| ❌ 城市未找到 | 城市不在 CITY_CODES |
在字典里补上该城市编码 |
八、从临时到永久
localtunnel 适合验证阶段,缺点是链接可能 24 小时失效、终端得一直开着。验证跑通后,把 FastAPI 服务部署到免费云(如 Render)拿一个永久 URL,再把 Dify Schema 里的 servers.url 换成它即可,代码一行不用改。
一句话总结
Dify 自定义插件本质就是:把一个 HTTP 接口用 OpenAPI Schema 描述给大模型,模型按需 Function Calling 调用。 本地 FastAPI 写逻辑、localtunnel 暴露公网、Dify 配 Schema + Bearer 鉴权,三步跑通;验证可行后再迁云端做永久部署。
参考:Dify 官方文档