TUTORIAL

Build Custom GPTs and Plugins

Create custom ChatGPT plugins and GPTs

Build Custom GPTs and Plugins: A Production-Ready Tutorial

1. Brief Overview

Custom GPTs and ChatGPT Plugins represent a significant leap forward in tailoring AI to specific needs. They allow developers and creators to extend the capabilities of ChatGPT, transforming it from a generalized assistant into a specialized tool for a vast array of applications. This tutorial provides a comprehensive, hands-on guide to building both Custom GPTs using OpenAI's no-code interface and developing powerful ChatGPT Plugins from scratch.

At its core, this technology is about customization and integration. Custom GPTs are personalized versions of ChatGPT that you can create for a specific purpose. You can define their instructions, upload custom knowledge files, and enable capabilities like web browsing or image generation. This makes it possible to create, for example, a "Code Review Assistant" that critiques code based on your company's style guide, or a "Marketing Copy Generator" that writes in your brand's unique voice. The primary audience for Custom GPTs includes professionals, educators, and enthusiasts who want to create specialized AI assistants without writing a single line of code.

ChatGPT Plugins, on the other hand, are for developers looking to connect ChatGPT to external services and APIs. A plugin allows the model to perform actions in the real world, such as booking a flight, ordering groceries, or querying a private database. This transforms the language model into an active participant in workflows, capable of interacting with the digital world on a user's behalf. If you're a developer aiming to integrate your application or service with ChatGPT's massive user base, building a plugin is the way to go. This tutorial will focus on the technical aspects of creating a plugin, which has now been integrated into the "Actions" feature of Custom GPTs.

2. Key Concepts

Before diving into the practical examples, it's essential to understand the core components that make up Custom GPTs and Plugins.

  1. Custom GPTs: These are configured through the GPT Builder, a user-friendly interface within ChatGPT. The key elements you'll work with are:
  2. Instructions: A set of directives that define the GPT's personality, goals, and constraints. This is the "prompt engineering" part of creating a Custom GPT.
  3. Knowledge: You can upload files (PDFs, text files, etc.) to provide your Custom GPT with a private knowledge base. This is ideal for information that is not publicly available on the internet.
  4. Capabilities: These are built-in tools that you can enable for your GPT, including:
  5. Web Browsing: Allows the GPT to search the web for real-time information.
  6. DALL-E Image Generation: Enables the GPT to create images.
  7. Code Interpreter: Provides a sandboxed Python environment for running code, analyzing data, and working with files.
  8. Actions: This is the evolution of the plugin system. Actions allow a Custom GPT to interact with external APIs. You define these actions using an OpenAPI specification, just like with plugins.
  1. Plugins (now "Actions"): While the term "Plugin Store" is being phased out in favor of the "GPT Store," the underlying technology for connecting to external APIs remains the same. The core components of a plugin are:
  2. Manifest File (ai-plugin.json): A JSON file that contains metadata about your plugin, such as its name, description, authentication requirements, and the location of your API specification. This is the first file ChatGPT looks for when connecting to your plugin.
  3. OpenAPI Specification (openapi.yaml): A YAML or JSON file that describes your API in a standardized format. It details the available endpoints, the expected inputs and outputs, and how to authenticate. ChatGPT uses this file to understand how to call your API.
  4. API Server: A web server that hosts your manifest file, OpenAPI specification, and the actual API endpoints. This server is responsible for executing the actions your plugin advertises.
  1. Workflow: The interaction between ChatGPT, your plugin, and the user follows a specific sequence:
  2. The user makes a request to ChatGPT.
  3. ChatGPT determines if the request requires an action from your plugin based on the descriptions in your manifest and OpenAPI spec.
  4. If an action is needed, ChatGPT calls the appropriate endpoint on your API server.
  5. Your API server processes the request and returns a response to ChatGPT.
  6. ChatGPT uses the response from your API to formulate its final answer to the user.

3. Practical Code Examples

This section provides a step-by-step guide to creating a simple "Todo" plugin using Python and Flask. This plugin will allow users to add and list tasks.

3.1. Prerequisites

3.2. Step 1: Set Up Your Project

First, create a new directory for your project and set up a Python virtual environment.


mkdir todo-plugin
cd todo-plugin
python3 -m venv venv
source venv/bin/activate
pip install Flask

3.3. Step 2: Create the API Server

Create a file named main.py and add the following code. This will be our Flask web server.


from flask import Flask, jsonify, request, send_from_directory
from flask_cors import CORS
import json

app = Flask(__name__)
CORS(app)  # Enable CORS for all routes

# In-memory "database" for todos
todos = {"user1": ["buy milk", "walk the dog"]}

@app.route('/todos/<string:username>', methods=['GET'])
def get_todos(username):
    """
    Get the list of todos for a user.
    """
    return jsonify(todos.get(username, []))

@app.route('/todos/<string:username>', methods=['POST'])
def add_todo(username):
    """
    Add a new todo to the user's list.
    The todo is in the request body.
    """
    request_data = request.get_json()
    todo = request_data.get('todo')
    if username not in todos:
        todos[username] = []
    todos[username].append(todo)
    return jsonify({"status": "success", "todo": todo})

@app.route('/logo.png')
def plugin_logo():
    return send_from_directory('.', 'logo.png')

@app.route('/.well-known/ai-plugin.json')
def plugin_manifest():
    return send_from_directory('.well-known', 'ai-plugin.json')

@app.route('/openapi.yaml')
def openapi_spec():
    return send_from_directory('.', 'openapi.yaml')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5001, debug=True)

3.4. Step 3: Create the Manifest File

Create a directory named .well-known and inside it, a file named ai-plugin.json.


mkdir .well-known
touch .well-known/ai-plugin.json

Add the following content to ai-plugin.json. Replace your_domain with the actual domain where you will host this plugin. For local testing, you can use a service like ngrok to expose your local server to the internet.


{
    "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": "Plugin for managing a todo list. You can add, remove, and view your todos.",
    "auth": {
        "type": "none"
    },
    "api": {
        "type": "openapi",
        "url": "http://your_domain/openapi.yaml",
        "is_user_authenticated": false
    },
    "logo_url": "http://your_domain/logo.png",
    "contact_email": "support@example.com",
    "legal_info_url": "http://www.example.com/legal"
}

3.5. Step 4: Create the OpenAPI Specification

In the root of your project directory, create a file named openapi.yaml.


openapi: 3.0.1
info:
  title: Todo Plugin
  description: A plugin that allows the user to create and manage a todo list.
  version: 'v1'
servers:
  - url: http://your_domain
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:
                type: array
                items:
                  type: string
    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:
              type: object
              properties:
                todo:
                  type: string
                  description: The todo item to add.
      responses:
        '200':
          description: OK

Remember to replace your_domain in this file as well.

3.6. Step 5: Run and Test the Plugin

  1. Run the server:
  2. 
        python3 main.py
    

You should see output indicating that the server is running on port 5001.

  1. Expose your local server (if necessary): If you are developing locally, use ngrok to get a public URL.
  2. 
        ngrok http 5001
    

ngrok will give you a URL like https://random-string.ngrok.io. Use this as your_domain.

  1. Install the plugin in ChatGPT:
  2. Go to ChatGPT, select GPT-4.
  3. Click on your profile and go to "My GPTs".
  4. Click "Create a GPT".
  5. Go to the "Configure" tab.
  6. Click "Add actions".
  7. In the "Schema" section, you can import your OpenAPI schema from the URL (https://random-string.ngrok.io/openapi.yaml).
  8. Test it in the "Preview" pane.
  1. Interact with your plugin:
  2. "Add 'buy groceries' to my todo list for user1"
  3. "What's on my todo list for user1?"

Expected Output: ChatGPT will call your API, and you will see the requests in your Flask server's console. The model will then respond with the data from your API.

4. Best Practices

  1. Descriptive Naming and Descriptions: The descriptionformodel in your manifest and the summary in your OpenAPI spec are crucial. The model uses these to decide when to use your plugin. Be clear and concise.
  2. Robust Error Handling: Your API should handle errors gracefully. Return appropriate HTTP status codes (e.g., 400 for bad requests, 404 for not found) and informative JSON error messages.
  3. Secure Your Plugin: The example uses no authentication for simplicity. For production, always use authentication. The supported methods are API keys and OAuth 2.0. Never expose sensitive data without proper authentication.
  4. Keep Payloads Small: Design your API to return only the necessary information. Large responses can be slow and may exceed the model's context limit.
  5. Idempotent Operations: Whenever possible, make your POST, PUT, and DELETE operations idempotent. This means that making the same request multiple times has the same effect as making it once. This can prevent duplicate data creation if the user repeats a request.
  6. Provide a Health Check Endpoint: It's a good practice to have a simple endpoint (e.g., /health) that returns a 200 OK status. This can be used for monitoring to ensure your plugin is online.
  7. Iterative Testing: Use the "Preview" pane in the GPT Builder extensively. Test various phrasings of the same request to see how the model interprets them.

5. Common Pitfalls to Avoid

  1. CORS Errors:
  2. Error Message: You might see an error in your browser's developer console about "Cross-Origin Resource Sharing".
  3. Problem: The browser is blocking ChatGPT from calling your API because it's on a different domain.
  4. Fix: Ensure you have enabled CORS on your server. In the Python example, this is done with the flask_cors library: CORS(app).
  1. Manifest Not Found:
  2. Error Message: When installing the plugin, ChatGPT might say "Could not find manifest file."
  3. Problem: ChatGPT could not access your ai-plugin.json file. This is usually due to an incorrect URL, your server being down, or the file not being served at the correct path (/.well-known/ai-plugin.json).
  4. Fix: Double-check your domain and ensure your server is running and accessible. Use curl http://your_domain/.well-known/ai-plugin.json to verify that the file is being served correctly.
  1. OpenAPI Schema Validation Errors:
  2. Error Message: ChatGPT might report "Unable to parse OpenAPI schema."
  3. Problem: Your openapi.yaml file has syntax errors or does not conform to the OpenAPI specification.
  4. Fix: Use an online OpenAPI validator to check your schema for errors. Pay close attention to indentation in YAML files.
  1. Vague Descriptions:
  2. Problem: The model doesn't use your plugin when it should, or uses it incorrectly.
  3. Fix: Refine the descriptions in your manifest and OpenAPI spec. Be explicit about what each endpoint does and what the parameters are. For example, instead of "adds a todo", use "adds a new todo item to a user's list".

6. Next Steps and Additional Resources