DEV Community

Cover image for OpenAI (LLM) Function Call Schema Generator from Swagger (OpenAPI) Document
Jeongho Nam
Jeongho Nam

Posted on

OpenAI (LLM) Function Call Schema Generator from Swagger (OpenAPI) Document

Outline

https://github.com/wrtnio/openai-function-schema

Made an OpenAI function schema library, @wrtnio/openai-function-schema.

It supports OpenAI function call schema definitions, and converter from Swagger (OpenAPI) document to the OpenAI function call schema definitions. Also, @wrtnio/openai-function-schema provides function call executor from the schema definitions, so that you can easily execute the remote Restful API operation with OpenAI composed arguments.

The best use case of @wrtnio/openai-function-schema what I imagine is, you hang up every API operations of your backend server by converting Swagger document to @wrtnio/openai-function-schema, so that make it possible to to call your server operations in the chat session through the OpenAI function calling feature.

For reference, @wrtnio/openai-function-schema supports every versions of Swagger (OpenAPI) specifications.

  • Swagger v2.0
  • OpenAPI v3.0
  • OpenAPI v3.1

Now, let's setup @wrtnio/openai-function-schema, and take advantages of it.

npm install @wrtnio/openai-function-schema
Enter fullscreen mode Exit fullscreen mode
import {
  IOpenAiDocument,
  IOpenAiFunction,
  OpenAiComposer,
  OpenAiFetcher,
} from "@wrtnio/openai-function-schema";
import fs from "fs";
import typia from "typia";
import { v4 } from "uuid";

import { IBbsArticle } from "../../../api/structures/IBbsArticle";

const main = async (): Promise<void> => {
  // COMPOSE OPENAI FUNCTION CALL SCHEMAS
  const swagger = JSON.parse(
    await fs.promises.readFile("swagger.json", "utf8"),
  );
  const document: IOpenAiDocument = OpenAiComposer.document({ 
    swagger 
  });

  // EXECUTE OPENAI FUNCTION CALL
  const func: IOpenAiFunction = document.functions.find(
    (f) => f.method === "put" && f.path === "/bbs/articles",
  )!;
  const article: IBbsArticle = await OpenAiFetcher.execute({
    document,
    function: func,
    connection: { host: "http://localhost:3000" },
    arguments: [
      // imagine that arguments are composed by OpenAI
      v4(),
      typia.random<IBbsArticle.ICreate>(),
    ],
  });
  typia.assert(article);
};
main().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Command Line Interface

########
# LAUNCH CLI
########
# PRIOR TO NODE V20
$ npm install -g @wrtnio/openai-function-schema
$ npx wofs

# SINCE NODE V20
$ npx @wrtnio/openai-function-schema

########
# PROMPT
########
--------------------------------------------------------
 Swagger to OpenAI Function Call Schema Converter
--------------------------------------------------------
? Swagger file path: test/swagger.json
? OpenAI Function Call Schema file path: test/plain.json
? Whether to wrap parameters into an object with keyword or not: No
Enter fullscreen mode Exit fullscreen mode

You can easily convert Swagger (OpenAPI) documents to OpenAI function schemas just by CLI command.

When you run npx @wrtnio/openai-function-schema (or npx wofs after global setup), the CLI (Command Line Interface) will inquiry those arguments. After you fill all of them, the OpenAI fuction call schema file of IOpenAiDocument type would be created to the target location.

If you want to specify arguments without prompting, you can fill them like below:


# PRIOR TO NODE V20
$ npm install -g @wrtnio/openai-function-schema
$ npx wofs --input swagger.json --output openai.json --keyword false

# SINCE NODE V20
$ npx @wrtnio/openai-function-schema
    --input swagger.json 
    --output openai.json 
    --keyword false
Enter fullscreen mode Exit fullscreen mode

Here is the list of IOpenAiDocument files generated by CLI command.

Features

Here is the schema definitions and functions of @wrtnio/openai-function-schema.

If you want to utilize @wrtnio/openai-function-schema in the API level, you should start from composing IOpenAiDocument through OpenAiComposer.document() method.

After composing the IOpenAiDocument data, you may provide the nested IOpenAiFunction instances to the OpenAI, and the OpenAI may compose the arguments by its function calling feature. With the OpenAI automatically composed arguments, you can execute the function call by OpenAiFetcher.execute() method.

Here is the example code composing and executing the IOpenAiFunction.

import {
  IOpenAiDocument,
  IOpenAiFunction,
  OpenAiComposer,
  OpenAiFetcher,
} from "@wrtnio/openai-function-schema";
import fs from "fs";
import typia from "typia";
import { v4 } from "uuid";

import { IBbsArticle } from "../../../api/structures/IBbsArticle";

const main = async (): Promise<void> => {
  // COMPOSE OPENAI FUNCTION CALL SCHEMAS
  const swagger = JSON.parse(
    await fs.promises.readFile("swagger.json", "utf8"),
  );
  const document: IOpenAiDocument = OpenAiComposer.document({ 
    swagger 
  });

  // EXECUTE OPENAI FUNCTION CALL
  const func: IOpenAiFunction = document.functions.find(
    (f) => f.method === "put" && f.path === "/bbs/articles",
  )!;
  const article: IBbsArticle = await OpenAiFetcher.execute({
    document,
    function: func,
    connection: { host: "http://localhost:3000" },
    arguments: [
      // imagine that arguments are composed by OpenAI
      v4(),
      typia.random<IBbsArticle.ICreate>(),
    ],
  });
  typia.assert(article);
};
main().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

By the way, above example code's target operation function has multiple parameters. You know what? If you configure a function to have only one parameter by wrapping into one object type, OpenAI function calling feature constructs arguments a little bit efficiently than multiple parameters case.

Such only one object typed parameter is called keyword parameter, and @wrtnio/openai-function-schema supports such keyword parameterized function schemas. When composing IOpenAiDocument by OpenAiComposer.document() method, configures option.keyword to be true, then every IOpenAiFunction instances would be keyword parameterized. Also, OpenAiFetcher understands the keyword parameterized function specification, so that performs proper execution by automatic decomposing the arguments.

Here is the example code of keyword parameterizing.

import {
  IOpenAiDocument,
  IOpenAiFunction,
  OpenAiComposer,
  OpenAiFetcher,
} from "@wrtnio/openai-function-schema";
import fs from "fs";
import typia from "typia";
import { v4 } from "uuid";

import { IBbsArticle } from "../../../api/structures/IBbsArticle";

const main = async (): Promise<void> => {
  // COMPOSE OPENAI FUNCTION CALL SCHEMAS
  const swagger = JSON.parse(
    await fs.promises.readFile("swagger.json", "utf8"),
  );
  const document: IOpenAiDocument = OpenAiComposer.document({ 
    swagger,
    options: {
      keyword: true, // keyword parameterizing
    }
  });

  // EXECUTE OPENAI FUNCTION CALL
  const func: IOpenAiFunction = document.functions.find(
    (f) => f.method === "put" && f.path === "/bbs/articles",
  )!;
  const article: IBbsArticle = await OpenAiFetcher.execute({
    document,
    function: func,
    connection: { host: "http://localhost:3000" },
    arguments: [
      // imagine that argument is composed by OpenAI
      {
        id: v4(),
        body: typia.random<IBbsArticle.ICreate>(),
      },
    ],
  });
  typia.assert(article);
};
main().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

At last, there can be some special API operation that some arguments must be composed by user, not by LLM (Large Language Model). For example, if an API operation requires file uploading or secret key identifier, it must be composed by user manually in the frontend application side.

For such case, @wrtnio/openai-function-schema supports special option IOpenAiDocument.IOptions.separate. If you configure the callback function, it would be utilized for determining whether the value must be composed by user or not. When the arguments are composed by both user and LLM sides, you can combine them into one through OpenAiDataComposer.parameters() method, so that you can still execute the function calling with OpenAiFetcher.execute() method.

Here is the example code of such special case:

import {
  IOpenAiDocument,
  IOpenAiFunction,
  IOpenAiSchema,
  OpenAiComposer,
  OpenAiDataCombiner,
  OpenAiFetcher,
  OpenAiTypeChecker,
} from "@wrtnio/openai-function-schema";
import fs from "fs";
import typia from "typia";

import { IMembership } from "../../api/structures/IMembership";

const main = async (): Promise<void> => {
  // COMPOSE OPENAI FUNCTION CALL SCHEMAS
  const swagger = JSON.parse(
    await fs.promises.readFile("swagger.json", "utf8"),
  );
  const document: IOpenAiDocument = OpenAiComposer.document({
    swagger,
    options: {
      keyword: true,
      separate: (schema: IOpenAiSchema) =>
        OpenAiTypeChecker.isString(schema) &&
        (schema["x-wrtn-secret-key"] !== undefined ||
          schema["contentMediaType"] !== undefined),
    },
  });

  // EXECUTE OPENAI FUNCTION CALL
  const func: IOpenAiFunction = document.functions.find(
    (f) => f.method === "patch" && f.path === "/membership/change",
  )!;
  const membership: IMembership = await OpenAiFetcher.execute({
    document,
    function: func,
    connection: { host: "http://localhost:3000" },
    arguments: OpenAiDataCombiner.parameters({
      function: func,
      llm: [
        // imagine that below argument is composed by OpenAI
        {
          body: {
            name: "Wrtn Technologies",
            email: "master@wrtn.io",
            password: "1234",
            age: 20,
            gender: 1,
          },
        },
      ],
      human: [
        // imagine that below argument is composed by human
        {
          query: {
            secret: "something",
          },
          body: {
            secretKey: "something",
            picture: "https://wrtn.io/logo.png",
          },
        },
      ],
    }),
  });
  typia.assert(membership);
};
main().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)