top of page

Why a Valid OpenAPI Specification is Key to API Success in 2023: Lessons from creating an OpenAI SDK

I would like to start this article by emphasising that my observations and recommendations are in no way a reflection of the success or viability of OpenAI. As a market leader in AI, they have already demonstrated their value to customers and the industry. Rather, my goal is to share my experiences working with their API and highlight potential areas for improvement. In this review, I will discuss the importance of a valid OpenAPI specification and how it can contribute to even greater successful adoption of their API, drawing from my recent work creating an OpenAI SDK. By sharing these insights, I hope to contribute to OpenAI's continued growth and success.

As a developer or a manager in the tech industry, you know that APIs are fueling the software that's eating the world, as TechCrunch stated in 2015, quoting Marc Andreessen's famous words, "Software is eating the world". To ensure that your API is adopted by developers, it's essential to release it with an OpenAPI specification that exposes it in a developer-friendly way. OpenAPI Generator is one of the popular tools to generate client SDKs from your specification, and according to OpenAI's GitHub repository, it's now leveraging this tool to generate SDKs for its API. However, you might encounter errors using this approach, and in 2023, it's doubtful that a developer will read through all your API documentation to integrate your API into their software. That's where tooling such as OpenAPI Generator, NSwagStudio, or Kiota comes in handy. In my last role as a Software Engineering Lead, I know firsthand the importance of OpenAPI specifications in driving API adoption, so I recommend using them as a standard practice. In this article, I will use the OpenAI OpenAPI specification as a example to show the importance of OpenAPI specifications for your API development process.

The OpenAPI Specification is a powerful tool that allows developers to describe and document their APIs in a standardized way. In 2023, this standardisation is more important than ever, as the number of APIs continues to grow exponentially, and developers are under increasing pressure to build and deploy high-quality, reliable APIs at scale. The OpenAPI Specification provides a clear and concise way to define API endpoints, parameters, responses, and other key elements, making it easier for developers to understand and use APIs. By adhering to the OpenAPI standard, developers can ensure that their APIs are consistent and well-documented, reducing the risk of errors, bugs, and other issues that can arise when integrating with multiple APIs. Additionally, the OpenAPI Specification is designed to be machine-readable, making it easier to automate API testing, validation, and other tasks, further improving the efficiency and reliability of the API development process. Overall, the OpenAPI Specification is a critical tool for any developer looking to build high-quality APIs in 2023 and beyond.

In this use case scenario, let's assume you've been tasked with integrating OpenAI into your company's existing product offerings. Your company has legacy projects built on .NET Framework 4.6.2, projects using .NET 6 and a mobile team developing with Xamarin. To make matters worse, your company also has teams that include Data Scientists who primarily use Python and front-end developers who use Angular for dashboard development. Your superior has asked you to please give a timeline for incorporating OpenAI tools into these teams' next agile sprint, which kicks off in two weeks. After conducting some research, you discover that OpenAI offers an official library for Python, so that's covered. Unfortunately, there is only one community library available for C#/.NET, on the OpenAI site, and it is limited to .NET 6. You find some open-source projects that support .NET Standard 2.0+, .NET Core 2.0+, and .NET Framework 4.6.2+, such as Netizine.OpenAI (I wrote the library to learn about OpenAI, yet even I think there is a better way), but relying on third-party libraries for updates could be a potential issue. Additionally, you come across a package in the npm Registry supporting typescript that integrates with OpenAI, but its last update was ten months ago. Given the strict software security requirements at your company, you need to consider the effort required to deliver a viable solution within the given timeline.

To expedite the integration process, the recommended approach on the OpenAI GitHub repository is to use the official OpenAI Python library and generate SDKs using the OpenAPI Generator. This tool can automatically generate client SDKs from the OpenAI API specification available on their GitHub repository. To start, you must install the OpenAPI Generator by running the following command.

npm install @openapitools/openapi-generator-cli -g

This should enable you to quickly generate SDKs for various programming languages and platforms, including Python, TypeScript, and C#. With the SDKs in place, developers can easily integrate OpenAI into your company's existing projects without the need for extensive manual coding or development work. This approach is meant to not only save time but also help ensure consistency and accuracy in the integration process.

Following this approach, let's move on to generating the SDK. We'll be using the latest published OpenAI OpenAPI specification, which is version 1.2.0 updated on Mar 1, 2023 at the time of writing. To generate the client SDK for .NET Framework, we can run the following command:

openapi-generator-cli generate -i ./openapi.yaml --generator-name csharp

This results in the following output:



Unfortunately, as you can see from the error message, we received the exception: "There were issues with the specification." This same issue will occur if you try to generate an SDK for .NET 6.


It appears that the issue with generating an SDK for .NET Framework or .NET 6 is not specific to the .NET platform but rather a problem with the OpenAPI specification file itself. After conducting a quick search, it appears that other users have reported similar issues with generating code in other languages, such as Java. Specifically, there are open issues on the OpenAI OpenAPI GitHub repository (#14) that report a "SpecValidationException" error when generating Java code from the OpenAPI spec file.

Therefore, it's likely that the problem lies with the OpenAPI spec rather than with the .NET platform or the OpenAPI Generator. At this point, we can either reach out to OpenAI for support or revert to a third-party SDK, but as Chat-GPT3 highlights below, there are no guarantees about using these SDKs.


Ensuring compliance with OpenAPI specs is crucial in enabling developers to integrate our APIs with minimal risk and effort seamlessly. In order to address the error, I performed validation on the published OpenAI spec using Stoplight Studio, which is one of my preferred tools for OpenAPI Design, Planning and Modeling. By copying the OpenAPI-Spec yaml from the repository on GitHub (https://github.com/openai/openai-openapi/blob/master/openapi.yaml) into Stoplight, I was able to quickly identify several errors.


To ensure the proper integration of our APIs, it is crucial to pay attention to errors in the OpenAPI spec. In the case of the OpenAI spec, most errors are related to incorrect examples, which are easy to fix. However, it is essential to note that some SDK generators may be affected by the errors I discovered in the spec.



The error shown above, in particular, can cause complications in the code generation process, and it is essential to address it to ensure successful API integration. If you consider the following code snippet in C#, it would be unable to handle the default value inf.

public void CreateChatCompletionRequest(int max_tokens = inf)
{
}

Luckily, OpenAI has a well-documented spec. From the description, "The maximum number of tokens allowed for the generated answer. By default, the number of tokens the model can return will be (4096 - prompt tokens)", it seems reasonable to set the default value to 4096. However, it's worth noting that the chat with ChatGPT-3 shown below showcases the impact of ChatGPT-3 and how it is transforming how we approach software development.


After making these changes and updating the examples from "[[1212, 318, 257, 1332, 13]]" to [[1212, 318, 257, 1332, 13]] so that the spec does not define the example as a string, we now have what appears to be a valid OpenAPI spec with only warnings (In production the goal should be to try to get these down to 0).


After saving the modified spec locally, I attempted to rerun the following command:

openapi-generator-cli generate -i ./openapi.yaml --generator-name csharp

Unfortunately, there were still errors. As developers, we regularly encounter errors when dealing with multiple HTTP APIs that require different API SDKs. In such cases, Kiota can be a valuable tool in your toolkit. Kiota is a command line tool designed to generate API clients from any OpenAPI spec, providing a strongly typed experience and all the necessary features of a high-quality API SDK. With Kiota, you can eliminate the need for multiple API SDK dependencies, making it easier for you to explore, discover, and call any HTTP API with minimal effort.

Kiota is a lightweight and fast code generator that allows you to generate code only for the parts of the API that you need. This means that the generated code footprint is only what you require to make you productive. The tool leverages OpenAPI.NET, which provides a valuable object model for OpenAPI documents in .NET, along with common serializers to extract raw OpenAPI JSON and YAML documents from the model.

To get started with Kiota, simply execute the following command to install the tool.

dotnet tool install --global --prerelease Microsoft.OpenApi.Kiota

Next, create a class library targeting all the target frameworks you wish to support.

<Project Sdk="Microsoft.NET.Sdk"

  <PropertyGroup>
    <TargetFrameworks>net462;netstandard2.0;netstandard2.1;net6.0;net7.0</TargetFrameworks>
  </PropertyGroup>

</Project>>

Next, run the following command with the same spec file we used earlier.

kiota generate --language csharp --openapi openapi.yaml --namespace-name OpenAI --class-name OpenAIClient --clear-cache true --log-level warning --output ./

This still fails to generate the SDK, though, but we now have a helpful error message.


The error message helps us understand that the expand property in the CreateClassificationRequest and the CreateAnswerRequest are invalid. Switching back to Spotlight Studio, I can see that the yaml defines the expand property as follows:


The items array is defined as items: {}, but the subtype of the array is not defined. To fix this, I changed the items to be of type: object.

          type: arra
          nullable: true
          items:
            type: object

Rerun the command, and we can now generate a .NET SDK supporting .NET Standard 2.0+, .NET 6+, and .NET Framework 4.6.2+.



The next step is to determine what dependencies our project has. To do this, run the following command.

kiota info -d openapi.yaml -l CSharp

As seen from the output, we see that the following references need to add to the solution.


Please add these references and recompile your project, and as you can see, we now have generated assemblies for all our .NET use cases we can deliver for the next sprint.


Next, we need to create a typescript-angular package using the OpenAPI Generator to auto-generate client SDKs from the spec with the following command:


openapi-generator-cli generate -i ./openapi.yaml --generator-name typescript-angular


Unfortunately, we still have errors the generator could not handle, so let's fix these errors in the spec.


However, when reviewing the output of the generator, it's important to pay attention to potential issues with default values.

For example, when reviewing the output, we can see that some of the default values were flagged as being of the wrong type. Upon closer inspection, I found that the issue was with the default values in the CreateFineTuneRequest and CreateAnswerRequest models.

In the case of CreateFineTuneRequest, the only array is nullable, but the default value of null returns an error in the OpenAPI Generator. This seems correct to be honest, but to fix the generation, there are two ways to fix this: set the default value to an empty array, which may require testing to ensure it doesn't cause exceptions, or remove the default value altogether. A similar issue was found with the default value of null for logit_bias in CreateAnswerRequest.

Fortunately, by identifying these issues and making the necessary adjustments to the OpenAPI spec, we were able to generate a TypeScript client for our Angular SDK.

As a developer or tech manager, it's important to be aware of potential issues with default values in OpenAPI specs and to ensure that they are compliant with the standards to avoid any problems during code generation.


As a software developer, you probably know that having a correct OpenAPI spec can save significant time and resources. By ensuring that your spec is compliant with OpenAPI standards and testing it against the main SDK generators, you can free up time to focus on other aspects of development, such as writing unit tests.

However, there can be issues with incomplete specs. When attempting to build a OpenAI test client, I noticed that the security scheme could not be determined from the spec. Upon switching to Stoplight Studio, I discovered that the security information was missing from the spec. I also came across the following issue logged with OpenAI. OpenAPI document missing the security scheme definition · Issue #25 · openai/openai-openapi (github.com)

After further investigation, I found that there were no securitySchemes specified in the current OpenAPI specification. To address this issue, I added the security scheme under components in the spec, as follows:


Following that, right at the end of the spec, I set up a global security scheme that would be applied to all API requests.


The name must match the arbitrary name defined in securitySchemas.

To update the code, I ran the same command I used to generate the SDK.

kiota generate --language csharp --openapi openapi.yaml --namespace-name OpenAI --class-name OpenAIClient --clear-cache true --log-level warning --output ./

I then added the following code to the generated code to implement a custom Access Token Provider interface based on the fact that the Kiota documentation states that if the target API requires an Authorization bearer <token> header but doesn’t rely on the Microsoft identity platform, you can implement your own authentication provider by inheriting from BaseBearerTokenAuthenticationProvider.

Next, I created a new class called OpenAIAuthenticationProvider.cs in the generated project and added the following references to Microsoft.Extensions.Configuration.Abstractions and Microsoft.Extensions.Logging.Abstractions.

In the OpenAIAuthenticationProvider.cs file I added the following code.

public class OpenAIAuthenticationProvider : IAccessTokenProvide
    {
        private readonly IConfiguration _configuration;
        private readonly ILogger<OpenAIAuthenticationProvider> _logger;


        public OpenAIAuthenticationProvider(IConfiguration configuration, ILogger<OpenAIAuthenticationProvider> logger)
        {
            _configuration = configuration;
            _logger = logger;
        }


        public async Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object> additionalAuthenticationContext = default, CancellationToken cancellationToken = default)
        {
            try
            {
                string apiKey = _configuration["OpenAI:ApiKey"];
                if (string.IsNullOrEmpty(apiKey))
                {
                    _logger.LogError("API key not found in configuration.");
                    return null;
                }
                return await Task.FromResult(apiKey);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to retrieve API key.");
                return null;
            }
        }


        public AllowedHostsValidator AllowedHostsValidator { get; }
    }r

To test my implementation, I decided to ask ChatGPT-3 what the meaning of life is, as you tend to do at 3 AM ;-) so I created a new .NET 6 Console App, added the following references:


and after updating my appsettings.json file, added the following code to my program.cs file.

using Microsoft.Extensions.Configuration
using Microsoft.Extensions.Logging;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary;
using OpenAI;
using OpenAI.Models;


var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables()
    .Build();


var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddConsole();
    builder.SetMinimumLevel(LogLevel.Information);
});


var logger = loggerFactory.CreateLogger<OpenAIAuthenticationProvider>();
var accessTokenProvider = new OpenAIAuthenticationProvider(configuration, logger);
var authenticationProvider = new BaseBearerTokenAuthenticationProvider(accessTokenProvider);
var request = new HttpClientRequestAdapter(authenticationProvider);
var openAIClient = new OpenAIClient(request);
var chatCompletionRequest = new CreateChatCompletionRequest
{
    Model = "gpt-3.5-turbo"
};
var chatCompletionRequestMessage = new List<ChatCompletionRequestMessage>();
var chatMessage = new ChatCompletionRequestMessage
{
    Role = ChatCompletionRequestMessage_role.User,
    Content = "What is the meaning of life?"
};
chatCompletionRequestMessage.Add(chatMessage);
chatCompletionRequest.Messages = chatCompletionRequestMessage;
var response = await openAIClient.Chat.Completions.PostAsync(chatCompletionRequest);
if (response?.Choices != null)
{
    foreach (var choice in response.Choices)
    {
        Console.WriteLine(choice.Message?.Content);
    }
};

The output for those wondering what ChatGPT-3 thinks about my question:



As a senior tech manager, you understand the importance of API integration and the potential risks associated with it. However, there is a simple solution to mitigate these risks and streamline the integration process: ensuring your OpenAPI specifications are correct.

As an experienced software engineer, I wrote this article to demonstrate how a correct OpenAPI spec can save significant time and resources. In just a few hours, I fixed the OpenAI OpenAPI spec, generated a .NET SDK supporting multiple .NET frameworks and generated an Angular client. Had the spec been correct from the start, these SDKs could have been created in under 10 minutes.

By testing client generation from your spec before publishing it, you can ensure that SDKs are generated correctly and integration can be achieved in very little time without the use of 3rd party libraries. This not only saves time and resources, but it also reduces support queries and provides developers with a simple integration process.

As a software company deploying APIs, investing in a fully compliant OpenAPI spec can actually reduce costs by eliminating the need to manage SDK development teams and reducing support queries. Additionally, clients will appreciate the ease of integration and be more likely to choose your service.

If you are releasing OpenAPI specifications, I am available for consulting to ensure that your spec is correct and fully compliant. With my experience in API integration and development, I can help your company streamline the integration process and reduce the associated risks.

Comentários


bottom of page