Using OpenAPI definitions with LLMs through GPTScript tools

May 9, 2024 by Grant Linville
Using OpenAPI definitions with LLMs through GPTScript tools

OpenAPI is a common format for describing APIs. Many apps and services will come with an OpenAPI definition file that describes the endpoints and operations that are available. Each operation consists of a path on the server, an HTTP method (such as GET or POST), and sometimes parameters or a request body.

One feature that was recently added to GPTScript allows you to provide an OpenAPI definition file to GPTScript, as though it were a normal tool file. Internally, GPTScript will generate a tool for each operation in the OpenAPI definition, and when the large language model calls that tool, GPTScript will make the correct HTTP request to the server, and return the contents of the response body to the LLM. This makes it easy to interact with APIs using LLMs like GPT-4. This tutorial will demonstrate how to use this feature.

The Definition

This is the OpenAPI definition file that we will use:

# petstore.yaml openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore license: name: MIT servers: - url: http://127.0.0.1:5000 paths: /pets: get: summary: List all pets operationId: listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer maximum: 100 format: int32 responses: '200': description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/Pets" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" post: summary: Create a pet operationId: createPets tags: - pets requestBody: content: application/json: schema: $ref: '#/components/schemas/Pet' required: true responses: '201': description: Null response default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById tags: - pets parameters: - name: petId in: path required: true description: The id of the pet to retrieve schema: type: string responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "#/components/schemas/Pet" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" components: schemas: Pet: type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string Pets: type: array maxItems: 100 items: $ref: "#/components/schemas/Pet" Error: type: object required: - code - message properties: code: type: integer format: int32 message: type: string

This is adapted from the Swagger Petstore example.

This file defines two paths and three operations. Create this file on your system and name it "petstore.yaml".

The Server

Here is a small Python Flask app that serves this API:

# app.py from flask import Flask, request, jsonify app = Flask(__name__) # Mock data for demonstration purposes pets = [ {"id": 1, "name": "Rex", "tag": "dog"}, {"id": 2, "name": "Mittens", "tag": "cat"} ] @app.route('/pets', methods=['GET']) def list_pets(): limit = request.args.get('limit', default=100, type=int) return jsonify(pets[:limit]) @app.route('/pets', methods=['POST']) def create_pet(): new_pet = request.json pets.append(new_pet) # Simple storage for demonstration, would typically save to a database return jsonify({}), 201 @app.route('/pets/<int:petId>', methods=['GET']) def show_pet_by_id(petId): pet = next((pet for pet in pets if pet['id'] == petId), None) if pet: return jsonify(pet) else: return jsonify({"code": 404, "message": "Pet not found"}), 404 # Error handling for the API @app.errorhandler(404) def not_found(error): return jsonify({"code": 404, "message": "Not found"}), 404 @app.errorhandler(500) def internal_error(error): return jsonify({"code": 500, "message": "Internal server error"}), 500 if __name__ == '__main__': app.run(debug=True)

Create this file on your system and name it "app.py". If you do not have Flask installed, you can install it by running "pip install flask" (or "pip3 install flask", depending on how Python is installed on your system).

Start the server by running "flask run" in the directory containing the app.py file. The server will start on http://localhost:5000.

Using the API in GPTScript

Now we will use GPTScript to interact with this API. Export the environment variable OPENAI_API_KEY with your OpenAI key if you have not done so already. Next, let's write a small script to test this out:

# petstore.gpt tools: ./petstore.yaml List all the pets.

Save this file and call it "petstore.gpt". It should be in the same directory as the petstore.yaml OpenAPI definition. Then, run the script with "gptscript petstore.gpt". Your output should look something like this:

The pets are: 1. Rex (dog) 2. Mittens (cat)

This shows that GPTScript made the correct API call to list the pets. Now let's ask it to add a new pet with ID 3, and then list all the pets again:

# petstore.gpt tools: ./petstore.yaml Only make one function call at a time. Add a new pet with ID 3. His name is Mark and he is a Lizard. After you do that, list all the pets.

The output should look something like this:

The pets listed are: 1. Rex (Dog) 2. Mittens (Cat) 3. Mark (Lizard)

The only operation we have not used yet is the "showPetById" operation. Let's try it:

# petstore.gpt tools: ./petstore.yaml Which pet has ID 3?

The output should look something like this:

The pet with ID 3 is named Mark and has the tag "Lizard".

That brings us to the end of this tutorial. For more details about using OpenAPI definitions with GPTScript, see the docs. If you'd like more information about using OpenAPI with Acorn, we also hosted a livestream where we discussed it in more detail, which I've embedded below.

Grant Linville is a software engineer at Acorn Labs.