返回顶部

实战:ChatGPT Plugin 开发

ChatGPT Plugin 开发指南

 插件可以上传到 openai的插件商店中使用,也可以在里面使用别人的插件

其原理就是让chatgpt 调用能够调用我们的服务

 插件开发推荐步骤

要构建一个插件,理解端到端流程是很重要的。
1
创建一个清单文件并将其托管在yourdomain.com/.well-known/ai-plugin.json上
该文件包含有关插件的元数据(名称,徽标等),以及有关身份验证的详细信息
必需的(验证类型,OAuth url等),以及您想要的端点的OpenAPI规范
暴露。
该模型将看到OpenAPI描述字段,这可以用来提供一个自然的
不同字段的语言描述。
我们建议在开始时只暴露1-2个端点,数量最少
参数来最小化文本的长度。插件描述、API请求和API
使用ChatGPT将所有响应插入到对话中。这与上下文不符
模型的极限。
2在ChatGPT界面中注册插件
从顶部的下拉菜单中选择插件模型,然后选择“Plugins”,“plugin Store”,和
最后“开发你自己的插件”。
如果需要身份验证,则提供OAuth 2 client_id和client_secret或API
关键。
用户激活你的插件
用户必须在ChatGPT用户界面中手动激活插件。(ChatGPT不会使用您的插件
默认情况下)。
您将能够与100个其他用户共享您的插件(只有其他开发人员可以安装)
未经证实的插件)。
如果需要OAuth,用户将通过OAuth重定向到您的插件登录。
4用户开始对话
OpenAI将在ChatGPT的消息中注入插件的紧凑描述,对ChatGPT不可见
最终用户。这将包括插件描述、端点和示例。
当用户询问相关问题时,模型可能会选择从您的
插件;对于POST请求,我们要求开发人员构建一个用户
确认流程以避免破坏操作。
该模型将把API调用结果合并到对用户的响应中。
该模型可能在其响应中包含从API调用返回的链接。这些将是
显示为丰富的预览(使用OpenGraph协议,我们将site_name,title,
描述、图片和url字段)。
该模型还可以在markdown中格式化来自API的数据,并且ChatGPT UI将呈现
自动降价。以上翻译结果来自有道神经网络翻译(YNMT)· 通用场景

 项目实战 Tolist

openapi: 3.0.1
info:
  title: TODO Plugin
  description: A plugin that allows the user to create and manage a TODO list using ChatGPT. If you do not know the user's username, ask them first before making queries to the plugin. Otherwise, use the username "global".
  version: 'v1'
servers:
  - url: http://localhost:5003
paths:
  /todos/{username}:
    get:
      operationId: getTodos
      summary: Get the list of todos
      parameters:
      - in: path
        name: username
        schema:
            type: string
        required: true
        description: The name of the user.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/getTodosResponse'
    post:
      operationId: addTodo
      summary: Add a todo to the list
      parameters:
      - in: path
        name: username
        schema:
            type: string
        required: true
        description: The name of the user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/addTodoRequest'
      responses:
        "200":
          description: OK
    delete:
      operationId: deleteTodo
      summary: Delete a todo from the list
      parameters:
      - in: path
        name: username
        schema:
            type: string
        required: true
        description: The name of the user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/deleteTodoRequest'
      responses:
        "200":
          description: OK

components:
  schemas:
    getTodosResponse:
      type: object
      properties:
        todos:
          type: array
          items:
            type: string
          description: The list of todos.
    addTodoRequest:
      type: object
      required:
      - todo
      properties:
        todo:
          type: string
          description: The todo to add to the list.
          required: true
    deleteTodoRequest:
      type: object
      required:
      - todo_idx
      properties:
        todo_idx:
          type: integer
          description: The index of the todo to delete.
          required: true
openapi.yaml
{
    "schema_version": "v1",
    "name_for_human": "TODO List (no auth)",
    "name_for_model": "todo",
    "description_for_human": "Manage your TODO list. You can add, remove and view your TODOs.",
    "description_for_model": "Plugin for managing a TODO list, you can add, remove and view your TODOs.",
    "auth": {
      "type": "none"
    },
    "api": {
      "type": "openapi",
      "url": "http://localhost:5003/openapi.yaml"
    },
    "logo_url": "http://localhost:5003/logo.png",
    "contact_email": "legal@example.com",
    "legal_info_url": "http://example.com/legal"
  }
  
ai-plugin.json
import json

import quart
import quart_cors
from quart import request

app = quart_cors.cors(quart.Quart(__name__), allow_origin="https://chat.openai.com")

# Keep track of todo's. Does not persist if Python session is restarted.
_TODOS = {}

@app.post("/todos/<string:username>")
async def add_todo(username):
    request = await quart.request.get_json(force=True)
    if username not in _TODOS:
        _TODOS[username] = []
    _TODOS[username].append(request["todo"])
    return quart.Response(response='OK', status=200)

@app.get("/todos/<string:username>")
async def get_todos(username):
    return quart.Response(response=json.dumps(_TODOS.get(username, [])), status=200)

@app.delete("/todos/<string:username>")
async def delete_todo(username):
    request = await quart.request.get_json(force=True)
    todo_idx = request["todo_idx"]
    # fail silently, it's a simple plugin
    if 0 <= todo_idx < len(_TODOS[username]):
        _TODOS[username].pop(todo_idx)
    return quart.Response(response='OK', status=200)

@app.get("/logo.png")
async def plugin_logo():
    filename = 'logo.png'
    return await quart.send_file(filename, mimetype='image/png')

@app.get("/.well-known/ai-plugin.json")
async def plugin_manifest():
    host = request.headers['Host']
    with open("./.well-known/ai-plugin.json") as f:
        text = f.read()
        return quart.Response(text, mimetype="text/json")

@app.get("/openapi.yaml")
async def openapi_spec():
    host = request.headers['Host']
    with open("openapi.yaml") as f:
        text = f.read()
        return quart.Response(text, mimetype="text/yaml")

def main():
    app.run(debug=True, host="0.0.0.0", port=5003)

if __name__ == "__main__":
    main()
main.py

 

样例项目:待办(Todo)管理插件

实战:天气预报(Weather Forecast)插件开发

{
    "schema_version": "v1",
    "name_for_human": "Weather Forecast",
    "name_for_model": "weather",
    "description_for_human": "Global Weather Forecast. You can ask the current or future weather of any city around the world.",
    "description_for_model": "Plugin for managing weather forecasts. Search current weather and future forecasts through provided api.",
    "auth": {
      "type": "none"
    },
    "api": {
      "type": "openapi",
      "url": "http://localhost:5002/openapi.yaml"
    },
    "logo_url": "http://localhost:5002/logo.png",
    "contact_email": "pjt73651@gmail.com",
    "legal_info_url": "http://example.com/legal"
  }
  
ai-plugin.json
openapi: 3.0.1
info:
  title: Weather Forecast
  description: A Plugin that allows the user to forecast current or future weather using ChatGPT. Ask for clarification if a user request is ambiguous.
  version: 'v1'
servers:
  - url: http://localhost:5002
paths:
  /weather/current:
    get:
      operationId: getCurrentWeather
      summary: Get the current weather of the city
      parameters:
      - in: query
        name: city
        schema:
            type: string
        required: true
        description: The city and state, e.g. San Francisco, CA.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/getCurrentWeather'
  /weather/forecast:
    get:
      operationId: getNDayWeatherForecast
      summary: Forecast the weather of the city in a few days
      parameters:
      - in: query
        name: num_days
        schema:
            type: integer
        required: true
        description: The number of days to forecast, e.g. 5
      - in: query
        name: city
        schema:
            type: string
        required: true
        description: The city and state, e.g. San Francisco, CA.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/getNDayWeatherForecast'
components:
  schemas:
    getCurrentWeather:
      type: object
      properties:
        weather:
          type: string
          description: The current weather of the city.
    getNDayWeatherForecast:
      type: object
      properties:
        weather:
          type: string
          description: The weather of the city in a few days.
openapi.yaml
# 导入 json 模块,用于 json 数据的序列化和反序列化
import json
# 导入 os 模块,用于获取第三方天气平台 API_KEY
import os
# 导入 requests 模块,用于访问第三方天气平台
import requests

# 导入 quart 模块,用于构建异步 Web 应用
import quart
# 导入 quart_cors 模块,用于处理跨域资源共享 (CORS)
import quart_cors
# 从 quart 模块导入 request 对象,用于处理 HTTP 请求
from quart import request

# 创建一个支持 CORS 的 Quart 应用实例,允许来自 "https://chat.openai.com" 的跨域请求
app = quart_cors.cors(quart.Quart(__name__), allow_origin="https://chat.openai.com")

WEATHER_API_KEY = os.getenv("WEATHER_API_KEY")

def get_citycode(city):
    url = "https://restapi.amap.com/v3/geocode/geo"
    params = {
        "city": city,
        "key": WEATHER_API_KEY,
        "address": city
    }

    try:
        response = requests.get(url, params=params)
        response.raise_for_status()
        # 从 response 中获取 citycode
        data = response.json()
        citycode = data["geocodes"][0]["adcode"]
        print(f"{city}: {citycode}")
        return citycode
    except requests.exceptions.RequestException as e:
        print(f"Error occurred during GET request: {e}")
        return None


def _get_current_weather(city):
    citycode = get_citycode(city)

    url = "https://restapi.amap.com/v3/weather/weatherInfo"
    params = {
        "city": citycode,
        "key": WEATHER_API_KEY,
    }

    try:
        response = requests.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        # 从 response 中提取天气相关信息
        w = data["lives"][0]
        weather = f"今天{w['province']}{w['city']}天气{w['weather']},温度{w['temperature']}°C,湿度{w['humidity']}%,风向{w['winddirection']},风力{w['windpower']}。"
        return weather
    except requests.exceptions.RequestException as e:
        print(f"Error occurred during GET request: {e}")
        return None


def _get_n_day_weather_forecast(city, num_days):
    if num_days > 3 or num_days < 0:
        return "最多查询未来3天的预报"
   
    citycode = get_citycode(city)

    url = "https://restapi.amap.com/v3/weather/weatherInfo"
    params = {
        "city": citycode,
        "key": WEATHER_API_KEY,
        "extensions": "all"
    }

    try:
        response = requests.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        # 从 response 中提取天气相关信息
        forecast = data["forecasts"][0]["casts"][num_days]
        date = forecast["date"]
        day_weather = forecast["dayweather"]
        night_weather = forecast["nightweather"]
        day_temp = forecast["daytemp"]
        night_temp = forecast["nighttemp"]
        day_wind = forecast["daywind"]
        night_wind = forecast["nightwind"]
        day_power = forecast["daypower"]
        night_power = forecast["nightpower"]

        weather = f"{date},白天天气{day_weather},夜晚天气{night_weather},白天温度{day_temp}°C,夜晚温度{night_temp}°C,白天风向{day_wind},夜晚风向{night_wind},白天风力{day_power},夜晚风力{night_power}。"

        return weather
    except requests.exceptions.RequestException as e:
        print(f"Error occurred during GET request: {e}")
        return None



# 定义一个路由处理器,处理 GET 请求,路由为 "/weather/current"
@app.get("/weather/current")
async def get_current_weather():
    # 从请求参数中获取城市名
    city = request.args.get("city")
    
    response = _get_current_weather(city)
    # 创建并返回一个 Quart 响应对象,状态码为 200,响应体为 response 的 json 形式
    return quart.Response(json.dumps(response), status=200)

# 定义一个路由处理器,处理 GET 请求,路由为 "/weather/forecast"
@app.get("/weather/forecast")
async def get_n_day_weather_forecast():
    # 从请求参数中获取城市名和预报天数
    city = request.args.get("city")
    num_days = int(request.args.get("num_days"))

    response = _get_n_day_weather_forecast(city, num_days)
    # 创建并返回一个 Quart 响应对象,状态码为 200,响应体为 response 的 json 形式
    return quart.Response(json.dumps(response), status=200)


# 定义一个路由处理器,处理 GET 请求,路由为 "/logo.png"
@app.get("/logo.png")
async def plugin_logo():
    # 文件名为 'weather-forecast.png'
    filename = 'weather-forecast.png'
    # 使用 quart.send_file 方法发送文件,并指定 MIME 类型为 'image/png'
    return await quart.send_file(filename, mimetype='image/png')

# 定义一个路由处理器,处理 GET 请求,路由为 "/.well-known/ai-plugin.json"
@app.get("/.well-known/ai-plugin.json")
async def plugin_manifest():
    # 从请求头中获取主机名
    host = request.headers['Host']
    # 打开并读取 ai-plugin.json 文件
    with open("./.well-known/ai-plugin.json") as f:
        text = f.read()
    # 返回一个 Quart 响应对象,响应体为 text,MIME 类型为 "text/json"
    return quart.Response(text, mimetype="text/json")

# 定义一个路由处理器,处理 GET 请求,路由为 "/openapi.yaml"
@app.get("/openapi.yaml")
async def openapi_spec():
    # 从请求头中获取主机名
    host = request.headers['Host']
    # 打开并读取 openapi.yaml 文件
    with open("openapi.yaml") as f:
        text = f.read()
    # 返回一个 Quart 响应对象,响应体为 text,MIME 类型为 "text/yaml"
    return quart.Response(text, mimetype="text/yaml")

# 定义 main 函数,运行 Quart 应用
def main():
    # 启动 Quart 服务器,开启 debug 模式,监听所有 IP 地址,端口为 5001
    app.run(debug=True, host="0.0.0.0", port=5002)
    

def test():
    city = "上海"
    num_days = 2
    weather_info = _get_current_weather(city)
    print(weather_info)

    weather_forecast = _get_n_day_weather_forecast(city, num_days)
    print(weather_forecast)

# 如果该文件是直接运行的,而不是作为模块导入的,则调用 main 函数
if __name__ == "__main__":
    test()
    main()
main.py

 

posted @ 2023-10-17 16:35  Crazymagic  阅读(131)  评论(0编辑  收藏  举报