Guru: The OpenAI API – The Easy Way
May 13, 2024 Dan Darnell
When it comes to programming in a particular programming language there is what you can do and what you can do easily. I use RPG every day and have done so since the System/34 days. I love the language but I’ve also picked up other programming languages over time because sometimes RPG isn’t the right tool for the job.
I’ve been using IBM EGL – Enterprise Generation Language – to create IBM i applications since 2009. IBM made a push at one time to entice RPG programmers to pick up the language and toolset (Rational Business Developer) for modernization efforts but eventually gave up that push. They did convince some of us though and new releases of RBD still come out about as regularly as RDi release and the EGL language continues to see enhancements too.
The idea behind EGL is that you do all of your front end and back end development in one language, EGL, and the tooling knows to generate the front end in JavaScript and the back end in Java. Deployment is the only time you deal with the target languages – and that’s basically just sending a WAR file to an application server. This one-language-to-rule-them-all approach means that you can debug across tiers and never know that you are technically switching technologies. I can set a break point in the UI and seamlessly step into my back end services, seeing nothing but EGL code all along the way.
This introduction brings us back to that word “easily” and also to the main topic of this article: interacting with the OpenAI API. One of the easiest (there is that word again) ways to interact with OpenAI API is with RESTful service calls. I used EGL as an implementation language for OpenAI because it is service-savvy right out of the box. I don’t have to add any additional libraries of functionality or jump through any hoops with SQL functions in order to call a REST service. In fact, for this article, a quick prototype with the Postman tool became a working EGL application with a UI and service calls in the span of maybe an hour. I spent far more time reading the OpenAI documentation than I did creating a working application.
As we look at the coding involved you will learn a little bit about OpenAI and a little bit about EGL. You might decide to try EGL for yourself (there is a free download that I will link to later) or you might just decide how you want to approach the same set of tasks in RPG or your own modernization toolset.
OpenAI can do a lot of things – and I mean a lot of things. One of those things, maybe the one you’ve already seen the most of, is perform generative AI in searches. A simplified description of generative AI: you search for things and then you ask more questions about the results, essentially fine-tuning the conversation along the way.
In our example application this means we start with a prompt for input, ideally a question or request for information, and then we get an answer from OpenAI. The original question is not thrown away, however, the next question to OpenAI includes the first question, and so on in an additive way until we chose to “reset” and start anew. Time for a look at that simple AI example I created. Here is a request for some movies in a specific format.
Now let’s try to modify this result by asking another question. Remember, as long as I don’t hit the “Reset” button my application will interact with OpenAI in an additive fashion.
Can we get even fancier?
Yes and no. The AI honored our request for no James Cameron movies but “forgot” about our third CSV column. This is still very common behavior when working with an AI. They frequently misunderstand things and sometimes flat out make things up. Let’s reset the AI and ask it about itself.
Fair enough. I’m one of those who has experimented extensively with code generation using an AI and, like so many others, I have found everything from beautifully generated examples (including some nice free-format RPG code) to absolute dreck so full of errors that it made me want to declare AI a conceptual failure. Your mileage may vary.
Let’s get into the code. The basic premise is that we need to put together a JSON-formatted request, call a RESTful endpoint of the OpenAI API, and parse the JSON-formatted results to get our answer.
The user interface is what EGL calls a “Rich UI Handler” and it contains the layout and functional elements necessary to display our text areas and process our buttons. We won’t dissect the UI in detail but I thought you might want to see a few elements. Here is a simple button definition:
b1 Button{ text="Send It", onClick ::= b1_onClick };
And here is the logic to process the “on click” event by invoking an internal service. The internal “AskQuestion” service contains the logic for talking to the OpenAI API:
function b1_onClick(event Event in) questions.appendElement(ta1.text); call z.AskQuestion(questions, answer, status) returning to return_onClick; end function return_onClick(answer string in, status Status in) ta2.text = answer; end
The basic service logic for “AskQuestion” is just a few lines of EGL code to establish an external service interface, set the headers on the service, and call an endpoint on the service. In its simplest form, it is just three lines of code:
oi OpenInterface {@bindService{}};
ServiceLib.setRestRequestHeaders(oi, headers);
result string = oi.postChatCompletions(chatCompletions);
Fleshed out fully to include accumulating our questions and parsing the JSON answer, it looks like this:
// Set up the authorization header with our API key headers Dictionary{}; headers["Authorization"] = getMessage("apikey"); // Bind to service and set headers on request oi OpenInterface {@bindService{}}; ServiceLib.setRestRequestHeaders(oi, headers); // Accumulate messages (our questions to the AI) messages Message[0]; for(i int from 1 to questions.getSize()) messages.appendElement( new Message {role = "user", content = questions[i]} ); end // Create the structure for an AI interaction and invoke the service chatCompletions ChatCompletions; chatCompletions.messages = messages; result string = oi.postChatCompletions(chatCompletions); // Parse the JSON results into a dictionary and pull the answer from the results resultDictionary Dictionary{}; ServiceLib.convertFromJSON(result, resultDictionary); answer = resultDictionary.choices[1].message.content;
What you don’t see in the code snippet is the definition of data structures (called Record parts in EGL’s terminology) matching the JSON request format and setting up the default values for the call. We’re able to populate everything except the question (“messages”) since it is a variable determined at runtime. Here’s that data structure:
record ChatCompletions model string = "gpt-4"; messages Message[]; temperature int = 1; top_p int = 1; n int = 1; stream boolean = false; max_tokens int = 500; presence_penalty int = 0; frequency_penalty int = 0; end record Message role string; content string; end
Another behind-the-scenes setup is the EGL Interface part for the service. Here we indicate that we’ll be using the ChatCompletions structure and that the request format is JSON. The translation of the data structure to JSON happens automatically when the service is called. This is also where we specify the OpenAI API endpoint (“/v1/chat/completions”):
interface OpenInterface function postChatCompletions(cc ChatCompletions in) returns (string) {@PostRest {uriTemplate="/v1/chat/completions", requestFormat = JSON, responseFormat = NONE }}; end
Let’s look again at how we handle the results from the service call:
resultDictionary Dictionary{}; ServiceLib.convertFromJSON(result, resultDictionary); answer = resultDictionary.choices[1].message.content;
One of my favorite features in EGL is its ability to easily turn JSON into a dictionary part. A dictionary is a flexible data structure that can contain simple values, arrays, arrays of arrays and so on. The result of the service call is a fairly complex JSON structure but with one function call (“convertFromJSON”) we can turn the whole JSON result into a dictionary and then drill into the content we need. Here’s a view into the dictionary that is created for us when we ask the AI the timeless question about chickens and eggs:
Well, to our question of which came first, the chicken or the egg, the AI demurs and offers a politically non-controversial answer. On this philosophical question we are no closer to an answer but it seems safe to say that AI is here to stay.
Making meaningful use of AI is our job as programmers and this is already happening today and accelerating at an insanely rapid pace. Where I once would have had a custom request to run an SQL inquiry over payroll data I can now give the payroll data to an OpenAI API “assistant” and have the desired results created for the customer in the customer’s desired format. Before you can say, “hold on a minute,” the OpenAI API assistant features will be out of beta and customers will be interacting with the AI assistants themselves as a matter of routine. You want to be the one who enabled that capability in your software, right? It’s good that it is easy to do because the future was yesterday.
Rational Business Developer trial version – follow links to “Try it free.” See https://www.ibm.com/products/rational-business-developer
Dan Darnell is a co-owner and vice president at Arkansas Data Services. Dan loves to write code, talk about code, design things, and, most of all, to work with other people to solve problems and create solutions. Dan has worked successfully as a consultant, managed companies large and small, and has written for international publications (including two books on Java for the IBM i). You can reach Dan at ddarnell@ark-data-services.com.