Integrating OpenAI Chat Completion into an existing SAP Build App
One activity that I like to engage in on weekends is learning about new compute topics that are outside my normal area of expertise (during the week, I’m a Senior Architect on the SAP BTP SDK for Android team). A couple of topics that have been on my radar for a while are “low-code, no-code” (specifically SAP’s Build Apps offering) and OpenAI APIs (specifically the “Chat Completion” API), so I figured “Why not do both at the same time… how difficult could that be?” Short answer – not difficult, not difficult at all, but that’s not to say there weren’t a few stumbling blocks along the way.
Step 1- Create an App with SAP Build
First things first – getting up to speed on SAP Build Apps. Being a complete novice in this area, I did a quick search for tutorials and landed almost immediately on “Create an Application with SAP Build Apps” – a perfect introduction for a beginner like me. About an hour later, I had a functional app for scanning food product barcodes and displaying product name, image and caloric information – Step 1 accomplished!
Step 2 – Integrate the Chat Completion API
First, some exploration
On to Step 2, which I rightfully assumed might take a bit more than an hour. First, what enhancement could I make to the food scanner app within a reasonable amount of time, while still providing some relevant functionality. My first thought was, “I know… put a chat client into the app!”, but that didn’t really seem like it would fit the “reasonable amount of time” requirement and, as it turns out, Kirill Leventcov has already done some great work in this area, so no need to retread old ground.
Going back then to the tutorial app and the product information displayed, I thought it might be interesting to display something more interesting than the caloric information… what about a clever marketing slogan based on the product name?
Not bad… It reads a bit like a radio spot from the 1960’s, but pretty decent overall. One problem – it’s not going to fit very well within a mobile app because of the long length of the text, so I wanted to tighten it up a bit…
Much better. Combining the two prompts, I settled on “Write a marketing slogan explaining why I should buy product name” as the prompt to be used in the app.
Next, learning about the API and creating a key
Now, on to the fun part – adding the OpenAI “Chat Completion” API to the app. Fortunately, OpenAI has a great guide for its Chat Completion API. After reading through the guide and poking around a bit in the API documentation, I had a fairly good idea regarding the minimum parameters needed for the request as well as how to parse the response.
First, I had to get an OpenAI API Key to make any calls to any of the OpenAI APIs. For this, I went to https://platform.openai.com, created an account, and then created an API Key (available by navigating to “View API Keys” under account information and pressing “Create new secret key”) :
As the dialog indicates, I made sure to copy the key before pressing OK, because I knew I wouldn’t see it again (and don’t worry… I deleted this key right after creating this blog post, so don’t get any ideas!).
Next, unfortunately, I had to enter a payment method for billing purposes. At the time of this writing, there’s no free tier when it comes to Chat Completion requests. Fortunately, the pricing is quite reasonable at $0.002 USD per 1K tokens (for reference, the chat completion used in this blog uses around 50 tokens). All the testing I did to write this blog entry (combined with some other miscellaneous poking around) came to a grand total of $0.05 USD :
Storing the API Key
In order to keep the API Key in a central location where it can easily be changed (or revoked) as needed, I created an App Variable for it. These are located in the “APP VARIABLES” section of the “VARIABLES” pane. I created a new App Variable, called it “APIKEY” and gave it the initial value of “Bearer ” (without the quotes) followed by the secret key I just created :
Creating the OpenAI data entity
Having added the “Open Food Facts” data entity in the earlier tutorial, I was pretty sure I’d need another data entity for OpenAI, which should be a Direct REST Integration as well. Here’s what I entered for the “Base” information, with the mandatory parts outlined in red :
The “Resource ID” and “Short description” can be anything, and the Resource URL of “https://api.openai.com/v1” comes from the OpenAI docs. The value of the AUTHORIZATION header (created by clicking on the + sign in the HTTP Header section) will be resolved at runtime using the APIKEY App Variable from the previous step (more on this later). Accordingly, I set both “Is static” and “Is optional” to off.
Next I came to the most complex (for me, anyway) part of this whole exercise; configuring the “POST” request. First, I clicked on “CREATE RECORD (POST)” and toggled “Create record (POST) is current disabled, enable to configure” to “on” to ensure that “Method enabled” was toggled to true. Then, I entered “/chat/completions” in the “Relative Path” box as shown in the OpenAI API docs :
Then, I clicked on the SCHEMA tab to configure the Schema for the request and selected “Custom Schema” for both “Create record (POST) request schema” and “Create record (POST) response schema” to start creating the schema.
Pressing the “ADD PROPERTY” button to the right of “Properties of this schema”, I added an element with a “Key” value of “messages”, a “Value Type” of “List” and a “List Item Type” of “Object” :
Note that this automatically adds a property of the object with the key value of “id” of type text. Clicking on this property, I changed the Key to “content” and then added another Text property, “role”, checking “Value is required” for both. There were more parameters I could have added here (based on the API documentation available at https://platform.openai.com/docs/api-reference/chat/create), but I kept it to a minimum for the time being :
It was also at this point that I made the accidental discovery that changing the “Value type” from “Text” to “Number” resulted in a new “Initial value” field being added to the property panel (and that the field remained after toggling the “Value type” back to “Text”). Taking advantage of this, I was able to add initial values for the following properties :
- role (inside the “messages” Object) – Initial value = user (this specifies that the content is entered by the user)
- model – Initial value = gpt-3.5-turbo (the gpt-3.5-turbo model is the latest latest readily available language model, optimized for speed)
This prepared me for the next step which was…
Running a test
Having configured the request schema, I was ready to run a test (which also helped in creating the schema for the Response). I clicked on the “TEST” tab, and then on the “ABC” button below “Authorization” :
On the next screen, I clicked on “Data and Variables” :
Next, I clicked on “App Variable” :
Finally, I selected “APIKEY” (the only App Variable available) and “SAVE” :
This, in turn, brought me back to the “TEST” tab with the Authorization Header now initialized. I then clicked on “Custom Object” in the “Record properties” section to fill that in :
I noted that “gpt-3.5-turbo” and “user” were filled in automatically using the initial values from the previous step. The the full text I entered in the content box was “Write a marketing slogan explaining why I should buy Lakritsi Original” :
I then pressed save and, having entered the test data, pressed “RUN TEST”. After a short period of time, the following appeared :
A Status of “OK” was a definite requirement before proceeding, as it was my only indication that everything was configured properly.
Creating the Response schema
Having confirmed a Status of “OK”, I then proceeded to create the Response schema. This was as simple as pressing “SET SCHEMA FROM RESPONSE”, which took me back to the SCHEMA tab once more. Scrolling down to the bottom, I saw :
Expanding the “choices” section and then the “message” section within that produced this :
As was the case with the POST request, it’s really the content that matters in the response. I’ll come back to this later when modifying the Scan button logic.
First, though, I pressed “SAVE DATA ENTITY” to complete the configuration (and then saved the whole app for good measure – I definitely didn’t want to have to create that POST Request schema again).
Creating a Variable
Next, I added the variable to store the content of the response in order to eventually display it in the UI. To do this, I started by switching to the “Variables” view and clicking on “PAGE VARIABLES”, then “ADD PAGE VARIABLE” :
I then created a new Text Variable named “slogan” :
Finally, I saved the app again.
Adding more logic
Now it was time to add the logic steps to make the call to the Chat Completion API and populate the results into the Page Variable I’d just created.
Next, just as I used a “Get record” call to retrieve the product data in the tutorial from Step 1, I knew that I was going to use a “Create record” call against the OpenAI Data Entity in order to create the marketing slogan. So I dragged a “Create record” component into the logic editor and created a connection between the top connector on the “Get record” node for “OpenFoodFacts” and the input to the new “Create Record” node :
Then, I clicked on the “Create record” node and noticed that, in the Inputs section on the right, “OpenAI” was already selected as the Resource name (since OpenFoodFacts doesn’t have a Create record request). Right away though I could also see that I was going to have to enter the Authorization Header value again :
Here, I went through the same steps of selecting the APIKEY App Variable that I followed when running the Test Request; I won’t bother repeating them here.
Once I was finished with that step, this is what the INPUTS panel looked like :
I then clicked on “Custom object” under “Record properties”. Here, as expected, the model was already filled in :
Then, I clicked on “Custom list (0 items)” and then “Add a value”, noting that “user” was prefilled :
Next I wanted to populate the “content” parameter with a formula composed of the static string “Write a marketing slogan explaining why I should buy” followed by the product name from the OpenFoodFacts GET request.
To do this, I pressed on the “ABC” button under content, which brought up a collection of possible binding types. I clicked on “Formula” :
In the Formula dialog, I clicked in the Formula edit box, which brought up a context-sensitive editor. I entered the static string “Write a marketing slogan explaining why I should buy ” (with the “non-smart” quotes) followed by a plus sign :
The validation logic complained that the syntax is incomplete… which it was. To complete it, I entered “product_name” in the search box :
Then, I selected the entry labeled data.OpenFoodFacts1.product.product_name and pressed the Enter key (double-click would have worked as well) :
Then I pressed “SAVE” followed by “SAVE” two more times, and then saved the app.
Setting the Variable
I knew I was entering the home stretch at this point… just one more logic step to go and then I could add the new marketing slogan to the UI. For this step, I dragged a “Set page variable” component to the right of the “Create record” node and drew a connection from the top output on the “Create record” node to the Variables’ input :
Then, I clicked on the “Set page variable” node that was just added. In the Properties pane, I noted that the “slogan” variable name was already entered (since it’s the only Page Variable I had) and clicked on the “ABC” button below “Assigned Value” :
On the next screen, I clicked on “Formula” :
This took me to a Formula dialog containing an edit box with a pair of empty quotes :
I clicked in the “Formula” edit box to bring up the context-sensitive editor :
I removed the quotes and typed in “choices” (without the quotes) to narrow down the list of prospective source values :
I then clicked on the one ending with “.message.content”, followed by a double-click (the Enter Key would have worked here as well) and then “SAVE”, which took me back to the Page Variable properties :
At this point I saved the app again.
Updating the UI
And now, I had just one more step before being able to test out the updated app. Back in the UI Canvas view, I selected the Text element that was being used to display the caloric content :
In the “Properties” tab, I wanted to edit the formula to refer to the new page variable instead, so I clicked the box to the right of the “Formula” icon that contains the text starting with “data.OpenFoodFacts1” which displayed the context-sensitive edit dialog for the formula.
I then removed the existing content and selected “Page Variables” below the Search box, which displayed my “slogan” page variable :
I double clicked on “pageVars.slogan”, pressed “SAVE” and then “SAVE” again before finally saving the app one final time.
Testing the completed app
I then loaded the app into the SAP Build Apps preview app on my phone and tried scanning various food products, treating me to an assortment of clever and innovative product slogans! Here’s one that was generated for a bag of prawn crisps I happened to have sitting around :
All things considered, not a bad way to spend an afternoon. I had broadened my knowledge of products in the SAP BTP portfolio and learned a bit about OpenAI as well!
I hope that you found reading this blog to be time well spent as well. Please let me know if you have any questions or suggested improvements… I’m still a novice on both of these topics, so all comments are welcome.
Great blog! I spent a good part of my Sunday following your technical insights, and it was definitely worth my time.
Thanks! I'm glad you found it helpful.
Truly this is great post!
I have been looking for something akin to this for a couple of weeks.
I'm trying to replicate it and got stuck at creating customer schema under "Create Record (POST) Request Schema". Could you please post the print screens showing the properties of "messages" ?
Not sure how you got content and role under "messages"!?
Glad you liked the post!
The screenshot showing the content under messages is the one titled "Creating the request schema", but I see now that I could have provided some additional clarification on that step. So, for the sake of completeness, once I started creating the custom schema, the steps were :
I hope this helps! I'll try adding some more content to the blog post to help clarify this step.
I greatly appreciate your quick help.
By the way, I'm getting the following error now while testing API call response.
Looks like it's related to value type of "content"! Could you please advise?
error while testing API call response
Yes... that's an error on my part in my previous response, as well as one of the screen shots in the blog post; "messages" should be a list of objects, not an individual object :
Thanks for catching that (and sorry about the confusion).
P.S. Here's the new screen shot I'm going to add to the blog, showing the initial appearance of the "messages" element (before "id" is changed to "prompt") :
Thank YOU 🙂
It worked well. Finally could complete it.
Really appreciate your efforts in posting this blog and answering the questions as well.
I need your advice on "how to manipulate the data I got from REST API call before displaying it on Build Apps?" For example, I don't want to display the 1st paragraph of the information received from ChatGPT. How can I achive this?
That's a little outside my experience level with Build Apps. But from looking at the docs my guess is that it will require the use of some of the "text" Formula Functions (e.g., "Contains", "Matches_regex", "Substring") to strip out the unwanted text and extract the information you need.
Thank you Mark, for the pointers.
Will check that.