创建一个插件需要三个步骤:
- 构建一个API
- 以OpenAPI yaml或JSON格式记录API
- 创建一个JSON清单文件,该文件将定义插件的相关元数据
本节其余部分的重点将是通过定义OpenAPI规范和清单文件来创建一个待办事项列表插件。
一、Plugin manifest
每个插件都需要ai-plugin.json
文件,它需要托管在API的域中。例如,一个名为example.com的公司会通过 https://example.com 域访问插件JSON文件,因为这是他们的API托管的地方。当你通过ChatGPT UI安装插件时,我们在后端寻找一个位于/.well-known/ai-plugin.json
的文件。如果没有找到文件,则无法安装插件。
插件的最小定义如下所示:
{
"schema_version": "v1",
"name_for_human": "TODO Plugin",
"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": "http://localhost:3333/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://vsq7s0-5001.preview.csb.app/logo.png",
"contact_email": "support@example.com",
"legal_info_url": "http://www.example.com/legal"
}
如果您想查看插件文件的所有可能选项,可以参考下面的定义。
不同的认证方式举例如下:
# App-level API keys
type ManifestServiceHttpAuth = BaseManifestAuth & {
type: 'service_http';
authorization_type: HttpAuthorizationType;
verification_tokens: {
[service: string]?: string;
};
}
# User-level HTTP authentication
type ManifestUserHttpAuth = BaseManifestAuth & {
type: 'user_http';
authorization_type: HttpAuthorizationType;
}
type ManifestOAuthAuth = BaseManifestAuth & {
type: 'oauth';
# OAuth URL where a user is directed to for the OAuth authentication flow to begin.
client_url: string;
# OAuth scopes required to accomplish operations on the user's behalf.
scope: string;
# Endpoint used to exchange OAuth code with access token.
authorization_url: string;
# When exchanging OAuth code with access token, the expected header 'content-type'. For example: 'content-type: application/json'
authorization_content_type: string;
# When registering the OAuth client ID and secrets, the plugin service will surface a unique token.
verification_tokens: {
[service: string]?: string;
};
}
manifest 清单文件中某些字段的长度也有一些限制,这些限制会随着时间的推移而改变:
name_for_human
最大50个字符name_for_model
最多50个字符description_for_human
最大长度为120个字符- 仅
description_for_model
最多8000个字符(将随着时间的推移而减少)
另外,我们对API响应体长度也有一个100k字符的限制(会随着时间的推移而减少),这个限制也会发生变化。
二、OpenAPI definition
下一步是构建OpenAPI规范来记录API。除了OpenAPI规范和清单文件中定义的内容外,ChatGPT中的模型对您的API一无所知。这意味着如果您有一个广泛的API,您不需要向模型公开所有功能,并且可以选择特定的端点。例如,如果您有一个社交媒体API,您可能想让模型通过GET请求访问站点的内容,但防止模型能够评论用户的帖子,以减少垃圾邮件的机会。
OpenAPI规范是位于API之上的包装器。基本的OpenAPI规范如下所示:
openapi: 3.0.1
info:
title: TODO Plugin
description: A plugin that allows the user to create and manage a TODO list using ChatGPT.
version: 'v1'
servers:
- url: http://localhost:3333
paths:
/todos:
get:
operationId: getTodos
summary: Get the list of todos
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/getTodosResponse'
components:
schemas:
getTodosResponse:
type: object
properties:
todos:
type: array
items:
type: string
description: The list of todos.
我们从定义规范版本、标题、描述和版本号开始。当一个查询在ChatGPT中运行时,它将查看info部分中定义的描述,以确定该插件是否与用户查询相关。您可以在提示工程部分阅读更多关于提示的内容。
请记住OpenAPI规范中的以下限制,这些限制可能会发生变化:
- API规范中每个API端点
description/summary
字段最多200个字符 - API规范中每个API参数描述字段最多200个字符
由于我们在本地运行这个示例,所以我们希望将服务器设置为指向本地主机URL。OpenAPI规范的其余部分遵循传统的OpenAPI格式,您可以通过各种在线资源了解更多关于OpenAPI格式的知识。还有许多工具可以基于底层API代码自动生成OpenAPI规范。
三、Running a plugin
一旦为API创建了API、清单文件和OpenAPI规范,现在就可以通过ChatGPT UI连接插件了。你的插件可能在两个不同的地方运行,要么在本地的开发环境中,要么在远程服务器上。
如果你有一个本地版本的API正在运行,你可以将插件接口指向那个本地设置。要将插件与ChatGPT连接,您可以导航到插件商店,然后选择“安装未经验证的插件”。
如果插件在远程服务器上运行,则需要先选择“开发自己的插件”,然后选择“安装未经验证的插件”。您可以简单地将插件清单文件添加到./ known路径并开始测试您的API。但是,对于清单文件的后续更改,必须将新更改部署到公共站点,这可能需要很长时间。在这种情况下,我们建议设置一个本地服务器作为API的代理。这允许您快速创建OpenAPI规范和清单文件的更改原型。
3.1 Setup a local proxy of your public API
下面的Python代码是如何设置面向公共API的简单代理的示例。
import requests
import os
import yaml
from flask import Flask, jsonify, Response, request, send_from_directory
from flask_cors import CORS
app = Flask(__name__)
PORT = 3333
CORS(app, origins=[f"http://localhost:{PORT}", "https://chat.openai.com"])
api_url = 'https://example'
@app.route('/.well-known/ai-plugin.json')
def serve_manifest():
return send_from_directory(os.path.dirname(__file__), 'ai-plugin.json')
@app.route('/openapi.yaml')
def serve_openapi_yaml():
with open(os.path.join(os.path.dirname(__file__), 'openapi.yaml'), 'r') as f:
yaml_data = f.read()
yaml_data = yaml.load(yaml_data, Loader=yaml.FullLoader)
return jsonify(yaml_data)
@app.route('/openapi.json')
def serve_openapi_json():
return send_from_directory(os.path.dirname(__file__), 'openapi.json')
@app.route('/<path:path>', methods=['GET', 'POST'])
def wrapper(path):
headers = {
'Content-Type': 'application/json',
}
url = f'{api_url}/{path}'
print(f'Forwarding call: {request.method} {path} -> {url}')
if request.method == 'GET':
response = requests.get(url, headers=headers, params=request.args)
elif request.method == 'POST':
print(request.headers)
response = requests.post(url, headers=headers, params=request.args, json=request.json)
else:
raise NotImplementedError(f'Method {request.method} not implemented in wrapper for {path=}')
return response.content
if __name__ == '__main__':
app.run(port=PORT)
四、Writing descriptions
当用户向插件发出一个可能是潜在请求的查询时,模型会查看OpenAPI规范中端点的描述以及清单文件中的description_for_model
。就像提示其他语言模型一样,你会想要测试多个提示和描述,看看哪个效果最好。
OpenAPI规范本身是一个很好的地方,可以为模型提供关于API的各种细节的信息——哪些函数可用,带有哪些参数,等等。除了为每个字段使用富有表现力的、信息丰富的名称外,规范还可以为每个属性包含“描述”字段。例如,这些可用于提供关于函数的功能或查询字段期望的信息的自然语言描述。模型将能够看到这些,它们将指导它使用API。如果字段被限制为某些值,您还可以提供一个具有描述性类别名称的“enum”。
" description_for_model "属性让你可以自由地指导模型如何使用你的插件。总的来说,ChatGPT背后的语言模型具有很高的理解自然语言和遵循指令的能力。因此,这是一个放置关于插件功能和模型应该如何正确使用它的一般说明的好地方。使用自然语言,最好是简洁而有描述性和客观的语气。你可以看一些例子来了解它应该是什么样的。我们建议以“Plugin for…”开头description_for_model
,然后列举API提供的所有功能。
五、Best practices
以下是在OpenAPI规范中编写description_for_model和描述以及设计API响应时需要遵循的一些最佳实践:
- 你的描述不应该试图控制ChatGPT的情绪、个性或准确的反应。ChatGPT的设计目的是为插件编写适当的响应。
不好的例子:
When the user asks to see their todo list, always respond with "I was able to find your todo list! You have [x] todos: [list the todos here]. I can add more todos if you'd like!"
当用户要求查看他们的todo列表时,总是用“I was able to find your todo list!”你有[x]个待办事项:[在这里列出待办事项]。如果你愿意,我可以再加一些待办事项!”
很好的例子:
[no instructions needed for this]
[不需要说明]
- 你的描述不应该鼓励ChatGPT在用户没有要求你的插件的特定服务类别时使用该插件。
不好的例子:
Whenever the user mentions any type of task or plan, ask if they would like to use the TODOs plugin to add something to their todo list.
每当用户提到任何类型的任务或计划时,询问他们是否愿意使用TODOs插件在待办事项列表中添加一些内容。
很好的例子:
The TODO list can add, remove and view the user's TODOs.
TODO列表可以添加、删除和查看用户的TODO。
- 你的描述不应该为ChatGPT使用插件规定特定的触发器。ChatGPT被设计成在适当的时候自动使用您的插件。
不好的例子:
When the user mentions a task, respond with "Would you like me to add this to your TODO list? Say 'yes' to continue."
当用户提到一个任务时,回复“您想让我把这个添加到您的TODO列表中吗?”说‘是’继续。”
很好的例子:
[no instructions needed for this]
[不需要说明]
- 除非必要,插件API响应应该返回原始数据,而不是自然语言响应。ChatGPT将使用返回的数据提供自己的自然语言响应。
不好的例子:
I was able to find your todo list! You have 2 todos: get groceries and walk the dog. I can add more todos if you'd like!
我找到你的待办事项清单了!你有两件事要做:买东西和遛狗。如果你愿意,我可以再加一些待办事项!
很好的例子:
{ "todos": [ "get groceries", "walk the dog" ] }
{"todos":["买杂货","遛狗"]}
六、Debugging
默认情况下,聊天不会显示插件调用和其他未显示给用户的信息。为了更全面地了解模型是如何与插件交互的,你可以通过点击屏幕左下角的“Debug”按钮打开Debug窗格。这将打开到目前为止的对话的原始文本表示,包括插件调用和响应。
对插件的模型调用通常包括来自模型的消息(“助手”),其中包含发送给插件的类似json的参数,然后是来自插件的响应(“工具”),最后是来自模型的消息,利用插件返回的信息。
在某些情况下,比如在插件安装过程中,浏览器的javascript控制台中可能会出现错误。