cloudflare / chanfana

OpenAPI 3 and 3.1 schema generator and validator for Hono, itty-router and more!
https://chanfana.pages.dev
MIT License
288 stars 38 forks source link

Add OpenAI plugin support #41

Closed ericlewis closed 1 year ago

ericlewis commented 1 year ago

This makes it much easier to spin up a plugin for ChatGPT with Cloudflare Workers by automating the serving of /.well-known/ai-plugin.json given a simple configuration when creating the router. The plugin spec already relies on OpenAPI specs, making this a pretty great match.

There are few things that could also be simple to automate, but below is the most bare-bones support.

Here is a simple example:

import {
  Query,
  Int,
  AuthType,
  APIType,
  SchemaVersion,
  OpenAPIRoute,
  OpenAPIRouter,
} from "@cloudflare/itty-router-openapi";

const router = OpenAPIRouter({
  schema: {
    info: {
      title: "Hacker News",
      version: "1.0",
    },
  },
  aiPlugin: {
    schema_version: SchemaVersion.V1,
    name_for_human: "Hacker News",
    name_for_model: "hackernews",
    description_for_human: "Get the top hacker news headlines.",
    description_for_model:
      "Plugin for retrieving the current top stories on Hacker News. Use it whenever a user asks something that might be related to Hacker News or current tech news.",
    contact_email: "person@example.com",
    legal_info_url: "https://example.com/legal",
    logo_url: "https://example.com/icon.png",
    auth: {
      type: AuthType.NONE,
    },
    api: {
      type: APIType.OPENAPI,
      url: "https://url-of-this-instance/openapi.json",
      has_user_authentication: false,
    },
  },
});

const Story = {
  title: String,
  url: String,
  user: String,
  domain: String,
  points: Int,
  time: Int,
  time_ago: String,
  comments_count: Int,
  type: String,
  id: Int,
};

class HackerNewsTopStories extends OpenAPIRoute {
  static schema = {
    summary: "Collects the current top stories.",
    parameters: {
      count: Query(Int, {
        description: "number of stories to return.",
        default: 10,
        required: false,
      }),
    },
    responses: {
      200: {
        schema: {
          headlines: [Story],
        },
      },
    },
  };

  async handle(_request: Request, _env: any, _ctx: any, data: Record<string, any>) {
    const response: (typeof Story)[] = await (
      await fetch("https://api.hnpwa.com/v0/news/1.json")
    ).json();
    return { headlines: response.slice(0, data.count) };
  }
}

router.get("/topStories", HackerNewsTopStories);

export default {
  fetch: router.handle,
};
github-actions[bot] commented 1 year ago

🧪 A prerelease is available for testing 🧪

You can install this latest build in your project with:

npm install --save https://prerelease-registry.devprod.cloudflare.dev/itty-router-openapi/runs/4631112013/npm-package-itty-router-openapi-41

Or you can immediately run this with npx:

npx https://prerelease-registry.devprod.cloudflare.dev/itty-router-openapi/runs/4631112013/npm-package-itty-router-openapi-41
G4brym commented 1 year ago

Hey @ericlewis This looks very good, I will test this tomorrow and post the feedback here Thanks for contributing

ericlewis commented 1 year ago

I held back a version I use which simplifies a lot of the configuration portions in favor of something more direct (and less to debug). you will notice in my example that the URI for the OpenAPI schema is manually specified.

Here is how it looks when I take a few liberties to improve the DX

const router = OpenAPIRouter({
  aiPlugin: {
    human: {
      name: "Hacker News",
      description: "Get the top hacker news headlines.",
    },
    model: {
      name: "hackernews",
      description: "Plugin for retrieving the current top stories on Hacker News. Use it whenever a user asks something that might be related to Hacker News or current tech news.",
    },
    contact: "person@example.com",
  },
});

this uses defaults and automatically uses the host name for given requests.

ericlewis commented 1 year ago

okay with all the changes thus far, the example should look more like this now:

import {
  Query,
  Int,
  OpenAPIRoute,
  OpenAPIRouter,
} from "@cloudflare/itty-router-openapi";

const router = OpenAPIRouter({
  schema: {
    info: {
      title: "Hacker News",
      version: "1.0",
    },
  },
  aiPlugin: {
    name_for_human: "Hacker News",
    name_for_model: "hackernews",
    description_for_human: "Get the top hacker news headlines.",
    description_for_model:
      "Plugin for retrieving the current top stories on Hacker News. Use it whenever a user asks something that might be related to Hacker News or current tech news.",
    contact_email: "person@example.com",
    legal_info_url: "https://example.com/legal",
    logo_url: "https://example.com/icon.png",
  },
});

const Story = {
  title: String,
  url: String,
  user: String,
  domain: String,
  points: Int,
  time: Int,
  time_ago: String,
  comments_count: Int,
  type: String,
  id: Int,
};

class HackerNewsTopStories extends OpenAPIRoute {
  static schema = {
    summary: "Collects the current top stories.",
    parameters: {
      count: Query(Int, {
        description: "number of stories to return.",
        default: 10,
        required: false,
      }),
    },
    responses: {
      200: {
        schema: {
          headlines: [Story],
        },
      },
    },
  };

  async handle(_request: Request, _env: any, _ctx: any, data: Record<string, any>) {
    const response: (typeof Story)[] = await (
      await fetch("https://api.hnpwa.com/v0/news/1.json")
    ).json();
    return { headlines: response.slice(0, data.count) };
  }
}

router.get("/topStories", HackerNewsTopStories);

export default {
  fetch: router.handle,
};
G4brym commented 1 year ago

Everything seams good! Thanks for contributing this, i will open a new release right after merging this, and add docs on how to use it next week to the README.md