ChatGPT是一款由OpenAI推出的先进对话模型,其强大的自然语言处理能力使得它成为构建智能对话系统和人机交互应用的理想选择。为了进一步拓展ChatGPT的功能和适应不同领域的需求,OpenAI提供了插件开发平台,让开发者可以定制化和扩展ChatGPT的能力。
OpenAI 插件将 ChatGPT 连接到第三方应用程序。这些插件使 ChatGPT 能够与开发人员定义的 API 进行交互,从而增强 ChatGPT 的功能并允许其执行广泛的操作。插件使 ChatGPT 能够执行以下操作:
插件开发人员公开一个或多个 API 端点,并附有标准化清单文件和 OpenAPI 规范。这些定义了插件的功能,允许 ChatGPT 使用文件并调用开发人员定义的 API。
AI 模型充当智能 API 调用者。给定 API 规范和何时使用 API 的自然语言描述,模型会主动调用 API 来执行操作。例如,如果用户询问“我应该在巴黎哪里住几晚?”,模型可能会选择调用酒店预订插件 API,接收 API 响应,并结合 API 数据生成面向用户的答案及其自然语言能力。
要构建插件,了解端到端流程是非常有必要的,流程如下所示:
如果需要 OAuth,用户将通过 OAuth 重定向到您的插件进行登录。
创建插件需要 3 个步骤:
每个插件都需要一个ai-plugin.json文件,该文件需要托管在API的域名上。例如,名为example.com的公司将通过https://example.com域名使插件的JSON文件可访问,因为这是他们API托管的位置。当通过ChatGPT UI安装插件时,在后端我们会查找位于/.well-known/ai-plugin.json的文件。/.well-known文件夹是必需的,并且必须存在于自己的域名上,以便ChatGPT可以与自己的插件连接。如果找不到文件,则无法安装插件。对于本地开发,可以使用HTTP,但如果指向远程服务器,则需要使用HTTPS。
所需ai-plugin.json文件的最小定义如下所示:
{ "schema_version": "v1", "name_for_human": "TODO List", "name_for_model": "todo", "description_for_human": "Manage your TODO list. You can add, remove and view your TODOs.", "description_for_model": "Help the user with managing a TODO list. You can add, remove and view your TODOs.", "auth": { "type": "none" }, "api": { "type": "openapi", "url": "http://localhost:3333/openapi.yaml" }, "logo_url": "http://localhost:3333/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; }; }
上述提到的清单文件中某些字段的长度存在限制,这些限制可能会发生变化。还对API响应体强加了最大长度限制,目前为100,000个字符,但这也可能随着时间变化而调整。
总体而言,最佳实践是尽可能简洁地描述和响应,因为模型有有限的上下文窗口。
接下来的步骤是构建OpenAPI规范以文档化API。ChatGPT中的模型除了OpenAPI规范和清单文件中定义的内容外,对你的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中运行查询时,它会查看信息部分中定义的描述,以确定插件是否与用户查询相关。
OpenAPI规范中的限制:
一旦为API、清单文件和API的OpenAPI规范创建完成,现在可以通过ChatGPT UI连接插件了。我们创建的插件可能运行在两个不同的地方,要么是在开发环境的本地,要么是在远程服务器上。
如果在本地运行API的版本,可以将插件界面指向自己的本地主机服务器。要将插件与ChatGPT连接,请导航到插件商店,然后选择“开发自己的插件”。输入自己的本地主机和端口号(例如localhost:3333)。请注意,目前仅支持本地开发的auth类型为none。
如果插件在远程服务器上运行,则需要首先选择“开发自己的插件”进行设置,然后再选择“安装未验证的插件”将其安装到您的环境中。只需将插件的清单文件添加到yourdomain.com/.well-known/路径中,然后开始测试自己的API。但是,请注意,对于清单文件的后续更改,您需要将新更改部署到公共站点上,这可能需要较长的时间。在这种情况下,我们建议设置一个本地服务器作为自己的API的代理,这样可以快速原型化OpenAPI规范和清单文件的更改。
为了开始构建,官方提供了一组涵盖不同身份验证模式和用例的简单插件。从简单的无身份验证待办事项列表插件到更强大的检索插件,这些示例让我们了解了希望通过插件实现的目标。
在开发过程中,可以在计算机上本地运行该插件,也可以通过GitHub Codespaces、Replit或CodeSandbox等云开发环境运行该插件。
首先,查看无身份验证页面,然后定义一个ai-plugin.json包含以下字段的文件
{ "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": "PLUGIN_HOSTNAME/openapi.yaml" }, "logo_url": "PLUGIN_HOSTNAME/logo.png", "contact_email": "support@example.com", "legal_info_url": "https://example.com/legal" }
请注意,PLUGIN_HOSTNAME应该是插件服务器的实际主机名。
接下来,我们可以定义 API 端点来为特定用户创建、删除和获取待办事项列表项。
import json import quart import quart_cors from quart import request # Note: Setting CORS to allow chat.openapi.com is only required when running a localhost plugin 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 manifest text = 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 spec 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()
最后,我们需要设置和定义 OpenAPI 规范以匹配本地或远程服务器上定义的端点。无需通过规范公开 API 的全部功能,而是可以选择让 ChatGPT 仅访问某些功能。
还有许多工具可以自动将您的服务器定义代码转换为 OpenAPI 规范,因此无需手动执行此操作。对于上面的 Python 代码,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. 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: 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
首先,查看服务级别身份验证页面,然后定义一个ai-plugin.json包含以下字段的文件:
{ "schema_version": "v1", "name_for_human": "TODO List (service 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": "service_http", "authorization_type": "bearer", "verification_tokens": { "openai": "Replace_this_string_with_the_verification_token_generated_in_the_ChatGPT_UI" } }, "api": { "type": "openapi", "url": "https://example.com/openapi.yaml" }, "logo_url": "https://example.com/logo.png", "contact_email": "support@example.com", "legal_info_url": "https://example.com/legal" }
请注意,服务级别身份验证插件需要验证令牌。设置服务访问令牌后,该令牌是在 ChatGPT Web UI 中的插件安装过程中生成的。
还需要将“Example.com”更新为远程服务器的名称。
接下来,我们可以定义 API 端点来为特定用户创建、删除和获取待办事项列表项。端点还检查用户是否经过身份验证。
import json import quart import quart_cors from quart import request app = quart_cors.cors(quart.Quart(__name__)) # This key can be anything, though you will likely want a randomly generated sequence. _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()
最后,我们需要设置并定义 OpenAPI 规范以匹配远程服务器上定义的端点。一般来说,无论身份验证方法如何,OpenAPI 规范看起来都是一样的。使用自动 OpenAPI 生成器将减少创建 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. 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: 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
插件开发平台为开发者提供了一系列API和工具,使其可以自定义ChatGPT的输入输出、增加特定任务的支持以及集成外部数据和服务。开发者可以通过插件实现特定的领域知识、自定义回答模式、定制话题导向等功能,从而让ChatGPT更贴合特定的使用场景和用户需求。
在插件开发过程中,开发者可以借助ChatGPT的先进预训练模型,以及丰富的开发文档和示例代码来快速上手。插件支持多种编程语言,并与现有的ChatGPT API无缝集成,保证了开发的便捷性和灵活性。
值得注意的是,插件开发平台也注重模型的安全性和可控性。OpenAI提供了强大的监管措施和审核流程,确保插件的使用符合社区准则,并防止滥用或不当行为。
总体而言,ChatGPT插件开发平台为开发者提供了一个广阔的创作空间,让他们可以将ChatGPT打造成更具个性和实用性的智能对话系统。通过这个平台,开发者可以将ChatGPT的潜力发挥到极致,为用户提供更加智能、定制化的交互体验。