Skip to content
DAILY QUOTE

“ ”

什么是Function Calling

2023年6月13日 OpenAI 公布了 Function Call(函数调用) 功能,该功能指的是在语言模型中集成外部功能或API的调用能力,这意味着模型可以在生成文本的过程中调用外部函数或服务,获取额外的数据或执行特定的任务

Function Call 应用基本流程(简化):

Function Call 功能

Function Call 可以解决大模型什么问题:

  • 信息实时性:大模型训练的数据集无法包含最新的信息,如最新的新闻、实时股价等。通过Function Call,模型可以实时获取最新数据,提供更加时效的服务。
  • 数据局限性:模型训练数据虽多但有限,无法覆盖所有领域,如医学、法律等领域的专业咨询,Function Call允许模型调用外部数据库或API,获取特定领域的详细信息。
  • 功能扩展性:大模型虽然功能强大,但不可能内置所有可能需要的功能。通过Function Call,可以轻松扩展模型能力,如调用外部工具进行复杂计算、数据分析等。

Function Call 工作原理

Function Calling 并非指大语言模型LLM直接在自身沙盒内执行代码,而是一种结构化输出能力与协议。 它允许开发者向 LLM 声明一组本地或外部的函数签名,LLM 凭借其自然语言理解能力,自主判断是否需要调用这些函数来响应用户请求,并提取出符合规范的 JSON 参数包,交由宿主应用完成实际执行。

当没有函数调用(funciton-call)时候,我们调用GPT构建AI应用的模式非常简单。 主要步骤:

  1. 用户(Client)发请求给我们的服务(Chat Server)
  2. 我们的服务(Chat Server)给GPT提示词
  3. 重复执行

当有函数调用(funciton-call)时候,我们调用GPT构建AI应用的模式比之前要复杂一些。 主要步骤:

  1. 用户(Client)发请求prompt以及functions给我们的服务(Chat Server)

  2. GPT模型根据用户的prompt,判断是用普通文本还是函数调用的格式响应我们的服务(Chat Server)

  3. 如果是函数调用格式,那么Chat Server就会执行这个函数,并且将结果返回给GPT

  4. 然后模型使用提供的数据,用连贯的文本响应。返回

注意:大模型的 Function call 不会调用函数,仅返回函数的参数。开发者利用模型输出的参数在应用中调用函数。

Dify如何应用Function Calling

插件是一个工具集,包含一个或多个工具 每个工具 = 一个可调用的API

核心机制不变: 模型通过阅读【插件描述】来决定是否调用该插件

插件生态

  • 插件分类体系:
    • 第三方插件:免费插件 + 付费插件(需要申请API Key)
    • 自定义插件(自己创建):集成你需要的任何API

常见插件类别

  • 信息查询:搜索、新闻、天气、地图
  • 数据分析:股票、汇率、图表生成
  • 内容创作:图片生成、视频编辑
  • 效率工具:邮件、日历、翻译、计算器

获取当前时间案例

未添加插件:

加了插件之后:

案例:智能天气助手

什么时候需要自定义插件:

  1. 官方插件没有你想要的功能
  2. 付费插件费用太贵
  3. 想连接特定的第三方API服务
  4. 需要对接企业内部系统

操作步骤:

适用场景:个人开发者快速搭建天气查询插件,无需服务器,30分钟内完成
技术栈:FastAPI + localtunnel + Dify
最终效果:在Dify中通过自然语言查询天气,如"北京今天天气如何?"

1.FastAPI 服务搭建

1.1 安装依赖

bash
# 创建虚拟环境(推荐)
python -m venv weather-env
source weather-env/bin/activate  # Linux/Mac
# weather-env\Scripts\activate   # Windows

# 安装依赖
pip install fastapi uvicorn requests

1.2完整代码实现

python
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
import requests

app = FastAPI()

# 身份验证令牌(个人使用,简单安全)
VALID_TOKEN = "itcast"

# 城市编码数据(直接硬编码,无需外部文件)
CITY_CODES = {
    "北京": "101010100",
    "上海": "101020100",
    "广州": "101280101",
    "深圳": "101280601",
    "杭州": "101210101",
    "成都": "101270101",
    "武汉": "101200101",
    "西安": "101110101",
    "南京": "101190101",
    "重庆": "101040100",
    "天津": "101030100",
    "苏州": "101190401",
    "郑州": "101180101",
    "长沙": "101250101",
    "青岛": "101120201",
    "大连": "101070201",
    "宁波": "101210401",
    "厦门": "101230201",
    "福州": "101230101",
    "济南": "101120101",
    "合肥": "101220101",
    "南昌": "101240101",
    "昆明": "101290101",
    "南宁": "101300101",
    "贵阳": "101260101",
    "哈尔滨": "101050101",
    "长春": "101060101",
    "沈阳": "101070101",
    "石家庄": "101090101",
    "太原": "101100101",
    "呼和浩特": "101080101",
    "乌鲁木齐": "101130101",
    "拉萨": "101140101",
    "兰州": "101110501",
    "西宁": "101150101",
    "银川": "101170101",
    "海口": "101310101",
    "三亚": "101310201"
}

class WeatherRequest(BaseModel):
    location: str

@app.post("/weather")
def get_current_weather(request: Request, body: WeatherRequest):
    """
    天气查询接口
    - 需要Authorization头认证
    - 返回自然语言格式的天气信息
    """
    # 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("低温 ", "")
        temperature = f"{high}/{low}"
        
        # 5. 返回自然语言格式
        return f"{location}今天是{weather_type},温度{temperature}"
    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)

1.3 启动服务

bash
python main.py

预期输出:

INFO:     Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)

2.公网穿透配置

2.1 安装 localtunnel

bash
# 全局安装  
npm install -g localtunnel

2.2 启动穿透服务

bash
# 新开一个终端窗口  
lt --port 8081

✅ 预期输出:

your url is: https://bitter-lions-boil.loca.lt

your url is: https://random-name-123.loca.lt

复制这个URL

⚠️ 重要:保持这个终端窗口开启,关闭后链接失效

3.服务测试验证

Postman 测试

  1. 创建 POST 请求

  2. URL: https://your-url.loca.lt/weather

  3. Headers:

    • Authorization: Bearer itcast

    • Content-Type: application/json

  4. Body (raw, JSON):

json
{"location": "北京"}

结果:

4.Dify 插件配置

4.1 OpenAPI 3.1.0 Schema

json
{
  "openapi": "3.0.0",
  "info": {
    "title": "天气查询API",
    "description": "查询中国城市当前天气信息",
    "version": "v1.0.0"
  },
  "servers": [
    {
      "url": "https://bitter-lions-boil.loca.lt"
    }
  ],
  "paths": {
    "/weather": {
      "post": {
        "summary": "查询城市天气",
        "operationId": "get_weather",
        "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"
      }
    }
  }
}

4.2 Dify 插件创建步骤

  1. 登录 Dify 控制台
    • 选择你的工作空间
  2. 创建插件
    • 导航到 「插件」「创建插件」
    • 选择 「OpenAPI」 类型
    • 点击 「下一步」
  3. 配置 OpenAPI Schema
    • 将上面的 JSON Schema 完整复制到编辑器中
    • 关键步骤:替换 servers.url 为你的 localtunnel URL
JSON
"servers": [
  {
    "url": "https://weather-api-abc123.loca.lt",  // ← 替换这里!
  }
]
  1. 配置认证

    • 在插件配置页面,找到 「Authentication」 部分

    • 选择 「Bearer Token」 类型

    • 「Value」 字段输入:itcast

    • 点击 「保存」

  2. 测试插件

    • 在测试区域输入:

      {
       "location": "北京"
      }

    • 点击 「测试」 按钮

    • 预期结果:

  3. 启用插件

    • 点击 「发布」 按钮

    • 选择要启用该插件的 「应用」

    • 在应用中就可以使用天气查询功能了

5. 在 Dify 应用中使用

6、故障排除指南

常见问题及解决方案

问题现象原因分析解决方案
Reached maximum retriesDify无法访问localhost必须使用公网URL,不能用localhost
{"detail":"Not Found"}路由路径错误检查代码中是@app.post("/weather")
403 Forbiddentoken不匹配确认VALID_TOKEN = "itcast"和Dify配置一致
连接超时localtunnel断开重启lt --port 8081,更新Dify中的URL
城市未找到城市不在CITY_CODES中在代码中添加该城市编码

快速诊断命令

bash
# 1. 检查服务是否运行  
lsof -i :8081  # Mac/Linux  
netstat -ano \| findstr 8081  # Windows  
  
# 2. 检查公网可达性  
curl -I https://your-url.loca.lt  
  
# 3. 完整测试命令  
curl -X POST https://your-url.loca.lt/weather \  
  -H "Authorization: Bearer itcast" \  
  -H "Content-Type: application/json" \  
  -d '{"location": "杭州"}'

7、维护与优化建议

7.1 临时方案(日常使用)

  • 保持终端开启:两个终端(FastAPI + localtunnel)需要一直运行

  • 每日重启:local tunnel 链接24小时后可能失效,每天重启一次

  • 快速更新URL:创建一个脚本自动更新Dify配置

7.2 永久方案(推荐升级)

text
# 部署到免费云服务(Render.com示例)  
# 1. 创建render.com账号  
# 2. 创建Web Service  
# 3. 连接GitHub仓库  
# 4. 设置环境变量(如果需要)  
# 5. 部署完成,获取永久URL

7.3 功能扩展

  • 添加更多城市:在CITY_CODES字典中添加新城市

  • 丰富天气信息:返回湿度、风力等更多字段

  • 错误重试机制:添加请求重试逻辑

  • 缓存机制:避免频繁请求天气API