Example plugins 插件示例demo
- 前言
- Introduction 导言
- Learn how to build a simple todo list plugin with no auth 了解如何构建一个简单的待办事项列表插件,无需授权
- Learn how to build a simple todo list plugin with service level auth 了解如何构建一个简单的待办事项列表插件与服务级别身份验证
- Learn how to build a simple sports stats plugin 了解如何构建一个简单的体育统计插件
- Learn how to build a semantic search and retrieval plugin 了解如何构建语义搜索和检索插件
- 其它资料下载
前言
在ChatGPT中学习构建插件是一个有趣和富有挑战性的过程。通过使用官方提供的四个例子,我们可以逐步了解如何构建ChatGPT插件,从而更好地理解插件的规范使用。也希望能帮助到大家快速掌握构建插件的各种技能和工具。
Introduction 导言
To get started building, we are making available a set of simple plugins that cover different authentication schemas and use cases. From our simple no authentication todo list plugin to the more powerful retrieval plugin, these examples provide a glimpse into what we hope to make possible with plugins.
为了开始构建,我们提供了一组简单的插件,涵盖了不同的身份验证模式和用例。从我们简单的无身份验证的todo列表插件到更强大的检索插件,这些示例提供了我们希望通过插件实现的东西的一瞥。
During development, you can run the plugin locally on your computer or through a cloud development environment like GitHub Codespaces, Replit, or CodeSandbox.
在开发过程中,您可以在计算机上本地运行插件,也可以通过GitHub Codespaces,Replit或CodeSandbox等云开发环境运行插件。
Learn how to build a simple todo list plugin with no auth 了解如何构建一个简单的待办事项列表插件,无需授权
To start, define an ai-plugin.json
file with the following fields:
首先,使用以下字段定义一个 ai-plugin.json
文件:
{"schema_version": "v1","name_for_human": "TODO Plugin (no auth)","name_for_model": "todo","description_for_human": "Plugin for managing a 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": "PLUGIN_HOSTNAME/openapi.yaml","is_user_authenticated": false},"logo_url": "PLUGIN_HOSTNAME/logo.png","contact_email": "support@example.com","legal_info_url": "https://example.com/legal"
}
Note the PLUGIN_HOSTNAME
should be the actual hostname of your plugin server.
注意 PLUGIN_HOSTNAME
应该是插件服务器的实际主机名。
Next, we can define the API endpoints to create, delete, and fetch todo list items for a specific user.
接下来,我们可以定义API端点来为特定用户创建、删除和获取待办事项列表项。
import jsonimport quart
import quart_cors
from quart import request# Note: Setting CORS to allow chat.openapi.com is only required when running a localhost plugin 注意:只有在运行localhost插件时才需要设置CORS以允许chat.openapi.com
app = quart_cors.cors(quart.Quart(__name__), allow_origin="https://chat.openai.com")_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"]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("ai-plugin.json") as f:text = f.read()# This is a trick we do to populate the PLUGIN_HOSTNAME constant in the manifesttext = text.replace("PLUGIN_HOSTNAME", f"https://{host}")return quart.Response(text, mimetype="text/json")@app.get("/openapi.yaml") # openai规范yaml文件
async def openapi_spec():host = request.headers['Host']with open("openapi.yaml") as f:text = f.read()# This is a trick we do to populate the PLUGIN_HOSTNAME constant in the OpenAPI spec 这是我们在OpenAPI规范中填充PLUGIN_HOSTNAME常量的一个技巧text = text.replace("PLUGIN_HOSTNAME", f"https://{host}")return quart.Response(text, mimetype="text/yaml")def main():app.run(debug=True, host="0.0.0.0", port=5002)if __name__ == "__main__":main()
Last, we need to set up and define a OpenAPI specification to match the endpoints defined on our local or remote server. You do not need to expose the full functionality of your API via the specification and can instead choose to let ChatGPT have access to only certain functionality.
最后,我们需要设置和定义OpenAPI规范,以匹配本地或远程服务器上定义的端点。您不需要通过规范公开API的全部功能,而是可以选择让ChatGPT仅访问某些功能。
There are also many tools that will automatically turn your server definition code into an OpenAPI specification so you don’t need to do it manually. In the case of the Python code above, the OpenAPI specification will look like:
还有许多工具可以自动将服务器定义代码转换为OpenAPI规范,因此您不需要手动执行此操作。在上面的Python代码中,OpenAPI规范看起来像这样:
openapi: 3.0.1
info:title: TODO Plugindescription: 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: PLUGIN_HOSTNAME
paths:/todos/{username}:get:operationId: getTodossummary: Get the list of todosparameters:- in: pathname: usernameschema:type: stringrequired: truedescription: The name of the user.responses:"200":description: OKcontent:application/json:schema:$ref: '#/components/schemas/getTodosResponse'post:operationId: addTodosummary: Add a todo to the listparameters:- in: pathname: usernameschema:type: stringrequired: truedescription: The name of the user.requestBody:required: truecontent:application/json:schema:$ref: '#/components/schemas/addTodoRequest'responses:"200":description: OKdelete:operationId: deleteTodosummary: Delete a todo from the listparameters:- in: pathname: usernameschema:type: stringrequired: truedescription: The name of the user.requestBody:required: truecontent:application/json:schema:$ref: '#/components/schemas/deleteTodoRequest'responses:"200":description: OKcomponents:schemas:getTodosResponse:type: objectproperties:todos:type: arrayitems:type: stringdescription: The list of todos.addTodoRequest:type: objectrequired:- todoproperties:todo:type: stringdescription: The todo to add to the list.required: truedeleteTodoRequest:type: objectrequired:- todo_idxproperties:todo_idx:type: integerdescription: The index of the todo to delete.required: true
Learn how to build a simple todo list plugin with service level auth 了解如何构建一个简单的待办事项列表插件与服务级别身份验证
To start, define an ai-plugin.json
file with the following fields:
首先,使用以下字段定义一个 ai-plugin.json
文件:
{"schema_version": "v1","name_for_human": "TODO Plugin (service level auth)","name_for_model": "todo","description_for_human": "Plugin for managing a 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": "service_http","authorization_type": "bearer","verification_tokens": {"openai": "758e9ef7984b415688972d749f8aa58e"}},"api": {"type": "openapi","url": "https://example.com/openapi.yaml","is_user_authenticated": false},"logo_url": "https://example.com/logo.png","contact_email": "support@example.com","legal_info_url": "https://example.com/legal"
}
Notice that the verification token is required for service level authentication plugins. The token is generated during the plugin installation process in the ChatGPT web UI.
请注意,服务级别身份验证插件需要验证令牌。令牌是在ChatGPT Web UI中的插件安装过程中生成的。
Next, we can define the API endpoints to create, delete, and fetch todo list items for a specific user. The endpoints also check that the user is authenticated.
接下来,我们可以定义API端点来为特定用户创建、删除和获取待办事项列表项。端点还检查用户是否经过身份验证。
import jsonimport quart
import quart_cors
from quart import request# Note: Setting CORS to allow chat.openapi.com is only required when running a localhost plugin 注意:只有在运行localhost插件时才需要设置CORS以允许chat.openapi.com
app = quart_cors.cors(quart.Quart(__name__), allow_origin="https://chat.openai.com")_SERVICE_AUTH_KEY = "REPLACE_ME"
_TODOS = {}def assert_auth_header(req):assert req.headers.get("Authorization", None) == f"Bearer {_SERVICE_AUTH_KEY}"@app.post("/todos/<string:username>")
async def add_todo(username):assert_auth_header(quart.request)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):assert_auth_header(quart.request)return quart.Response(response=json.dumps(_TODOS.get(username, [])), status=200)@app.delete("/todos/<string:username>")
async def delete_todo(username):assert_auth_header(quart.request)request = await quart.request.get_json(force=True)todo_idx = request["todo_idx"]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("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=5002)if __name__ == "__main__":main()
Last, we need to set up and define a OpenAPI specification to match the endpoints defined on our local or remote server. In general, the OpenAPI specification would look the same regardless of the authentication method. Using an automatic OpenAPI generator will reduce the chance of errors when creating your OpenAPI specification so it is worth exploring the options.
最后,我们需要设置和定义OpenAPI规范,以匹配本地或远程服务器上定义的端点。一般来说,OpenAPI规范看起来都是一样的,与身份验证方法无关。使用自动OpenAPI生成器将减少创建OpenAPI规范时出错的可能性,因此值得探索这些选项。
openapi: 3.0.1
info:title: TODO Plugindescription: 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: https://example.com
paths:/todos/{username}:get:operationId: getTodossummary: Get the list of todosparameters:- in: pathname: usernameschema:type: stringrequired: truedescription: The name of the user.responses:"200":description: OKcontent:application/json:schema:$ref: '#/components/schemas/getTodosResponse'post:operationId: addTodosummary: Add a todo to the listparameters:- in: pathname: usernameschema:type: stringrequired: truedescription: The name of the user.requestBody:required: truecontent:application/json:schema:$ref: '#/components/schemas/addTodoRequest'responses:"200":description: OKdelete:operationId: deleteTodosummary: Delete a todo from the listparameters:- in: pathname: usernameschema:type: stringrequired: truedescription: The name of the user.requestBody:required: truecontent:application/json:schema:$ref: '#/components/schemas/deleteTodoRequest'responses:"200":description: OKcomponents:schemas:getTodosResponse:type: objectproperties:todos:type: arrayitems:type: stringdescription: The list of todos.addTodoRequest:type: objectrequired:- todoproperties:todo:type: stringdescription: The todo to add to the list.required: truedeleteTodoRequest:type: objectrequired:- todo_idxproperties:todo_idx:type: integerdescription: The index of the todo to delete.required: true
Learn how to build a simple sports stats plugin 了解如何构建一个简单的体育统计插件
This plugin is an example of a simple sports stats API. Please keep in mind our domain policy and usage policies when considering what to build.
这个插件是一个简单的体育统计API的例子。在考虑构建什么时,请记住我们的域策略和使用策略。
To start, define an ai-plugin.json
file with the following fields:
首先,使用以下字段定义一个 ai-plugin.json
文件:
{"schema_version": "v1","name_for_human": "Sport Stats","name_for_model": "sportStats","description_for_human": "Get current and historical stats for sport players and games.","description_for_model": "Get current and historical stats for sport players and games. Always display results using markdown tables.","auth": {"type": "none"},"api": {"type": "openapi","url": "PLUGIN_HOSTNAME/openapi.yaml","is_user_authenticated": false},"logo_url": "PLUGIN_HOSTNAME/logo.png","contact_email": "support@example.com","legal_info_url": "https://example.com/legal"
}
Note the PLUGIN_HOSTNAME
should be the actual hostname of your plugin server.
注意 PLUGIN_HOSTNAME
应该是插件服务器的实际主机名。
Next, we define a mock API for a simple sports service plugin.
接下来,我们为一个简单的体育服务插件定义一个模拟API。
import json
import requests
import urllib.parseimport quart
import quart_cors
from quart import request# Note: Setting CORS to allow chat.openapi.com is only required when running a localhost plugin 注意:只有在运行localhost插件时才需要设置CORS以允许chat.openapi.com
app = quart_cors.cors(quart.Quart(__name__), allow_origin="https://chat.openai.com")
HOST_URL = "https://example.com"@app.get("/players")
async def get_players():query = request.args.get("query")res = requests.get(f"{HOST_URL}/api/v1/players?search={query}&page=0&per_page=100")body = res.json()return quart.Response(response=json.dumps(body), status=200)@app.get("/teams")
async def get_teams():res = requests.get("{HOST_URL}/api/v1/teams?page=0&per_page=100")body = res.json()return quart.Response(response=json.dumps(body), status=200)@app.get("/games")
async def get_games():query_params = [("page", "0")]limit = request.args.get("limit")query_params.append(("per_page", limit or "100"))start_date = request.args.get("start_date")if start_date:query_params.append(("start_date", start_date))end_date = request.args.get("end_date")if end_date:query_params.append(("end_date", end_date))seasons = request.args.getlist("seasons")for season in seasons:query_params.append(("seasons[]", str(season)))team_ids = request.args.getlist("team_ids")for team_id in team_ids:query_params.append(("team_ids[]", str(team_id)))res = requests.get(f"{HOST_URL}/api/v1/games?{urllib.parse.urlencode(query_params)}")body = res.json()return quart.Response(response=json.dumps(body), status=200)@app.get("/stats")
async def get_stats():query_params = [("page", "0")]limit = request.args.get("limit")query_params.append(("per_page", limit or "100"))start_date = request.args.get("start_date")if start_date:query_params.append(("start_date", start_date))end_date = request.args.get("end_date")if end_date:query_params.append(("end_date", end_date))player_ids = request.args.getlist("player_ids")for player_id in player_ids:query_params.append(("player_ids[]", str(player_id)))game_ids = request.args.getlist("game_ids")for game_id in game_ids:query_params.append(("game_ids[]", str(game_id)))res = requests.get(f"{HOST_URL}/api/v1/stats?{urllib.parse.urlencode(query_params)}")body = res.json()return quart.Response(response=json.dumps(body), status=200)@app.get("/season_averages")
async def get_season_averages():query_params = []season = request.args.get("season")if season:query_params.append(("season", str(season)))player_ids = request.args.getlist("player_ids")for player_id in player_ids:query_params.append(("player_ids[]", str(player_id)))res = requests.get(f"{HOST_URL}/api/v1/season_averages?{urllib.parse.urlencode(query_params)}")body = res.json()return quart.Response(response=json.dumps(body), 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("ai-plugin.json") as f:text = f.read()# This is a trick we do to populate the PLUGIN_HOSTNAME constant in the manifesttext = text.replace("PLUGIN_HOSTNAME", f"https://{host}")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()# This is a trick we do to populate the PLUGIN_HOSTNAME constant in the OpenAPI spectext = text.replace("PLUGIN_HOSTNAME", f"https://{host}")return quart.Response(text, mimetype="text/yaml")def main():app.run(debug=True, host="0.0.0.0", port=5001)if __name__ == "__main__":main()
Last, we define our OpenAPI specification:
最后,我们定义OpenAPI规范:
openapi: 3.0.1
info:title: Sport Statsdescription: Get current and historical stats for sport players and games.version: 'v1'
servers:- url: PLUGIN_HOSTNAME
paths:/players:get:operationId: getPlayerssummary: Retrieves all players from all seasons whose names match the query string.parameters:- in: queryname: queryschema:type: stringdescription: Used to filter players based on their name. For example, ?query=davis will return players that have 'davis' in their first or last name.responses:"200":description: OK/teams:get:operationId: getTeamssummary: Retrieves all teams for the current season.responses:"200":description: OK/games:get:operationId: getGamessummary: Retrieves all games that match the filters specified by the args. Display results using markdown tables.parameters:- in: queryname: limitschema:type: stringdescription: The max number of results to return.- in: queryname: seasonsschema:type: arrayitems:type: stringdescription: Filter by seasons. Seasons are represented by the year they began. For example, 2018 represents season 2018-2019.- in: queryname: team_idsschema:type: arrayitems:type: stringdescription: Filter by team ids. Team ids can be determined using the getTeams function.- in: queryname: start_dateschema:type: stringdescription: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or after this date.- in: queryname: end_dateschema:type: stringdescription: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or before this date.responses:"200":description: OK/stats:get:operationId: getStatssummary: Retrieves stats that match the filters specified by the args. Display results using markdown tables.parameters:- in: queryname: limitschema:type: stringdescription: The max number of results to return.- in: queryname: player_idsschema:type: arrayitems:type: stringdescription: Filter by player ids. Player ids can be determined using the getPlayers function.- in: queryname: game_idsschema:type: arrayitems:type: stringdescription: Filter by game ids. Game ids can be determined using the getGames function.- in: queryname: start_dateschema:type: stringdescription: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or after this date.- in: queryname: end_dateschema:type: stringdescription: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or before this date.responses:"200":description: OK/season_averages:get:operationId: getSeasonAveragessummary: Retrieves regular season averages for the given players. Display results using markdown tables.parameters:- in: queryname: seasonschema:type: stringdescription: Defaults to the current season. A season is represented by the year it began. For example, 2018 represents season 2018-2019.- in: queryname: player_idsschema:type: arrayitems:type: stringdescription: Filter by player ids. Player ids can be determined using the getPlayers function.responses:"200":description: OK
Learn how to build a semantic search and retrieval plugin 了解如何构建语义搜索和检索插件
The ChatGPT retrieval plugin is a more fully featured code example. The scope of the plugin is large, so we encourage you to read through the code to see what a more advanced plugin looks like.
ChatGPT检索插件是一个功能更全的代码示例。这个插件的范围很大,所以我们鼓励你通读代码,看看更高级的插件是什么样子的。
The retrieval plugin includes: 检索插件包括:
- Support for multiple vector databases providers
支持多个矢量数据库提供程序 - All 4 different authentication methods
所有4种不同的身份验证方法 - Multiple different API features 多种不同的API功能
其它资料下载
如果大家想继续了解人工智能相关学习路线和知识体系,欢迎大家翻阅我的另外一篇博客《重磅 | 完备的人工智能AI 学习——基础知识学习路线,所有资料免关注免套路直接网盘下载》
这篇博客参考了Github知名开源平台,AI技术平台以及相关领域专家:Datawhale,ApacheCN,AI有道和黄海广博士等约有近100G相关资料,希望能帮助到所有小伙伴们。