Skip to Content
Technical Articles
Author's profile photo Mark Fogle

Integrating OpenAI Chat Completion into an existing SAP Build App

Background

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? 

Armed with the product name from the tutorial used in Step 1 (“Lakritsi Original”), I headed over to OpenAI Playground and submitted “Write some marketing copy explaining why I should buy Lakritsi Original” and received an eloquent response :

Using%20the%20Chat%20Playground%20to%20generate%20some%20marketing%20copy

Using the Chat Playground to generate some marketing copy

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… 

Using the Chat Playground to generate a slogan instead

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”) : 

Creating%20the%20API%20Key

Creating the API 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 : 

API%20usage%20costs%20for%20developing%20the%20integration

API usage costs for developing this integration

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 :

Creation%20of%20the%20APIKEY%20App%20Variable

Creation of the APIKEY App Variable

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 :

Base information for the Data Entity

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  :

Base%20configuration%20of%20the%20POST%20request

Base configuration for the POST request

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” :

Creating%20the%20POST%20Schema%20-%20Step%201

Creating the POST Schema – Step 1

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 :

Creating%20the%20POST%20Schema%20-%20Step%202

Creating the POST Schema – Step 2

 

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” : 

Initializing%20the%20Authorization%20Header%20for%20the%20test

Initializing the Authorization Header for the test

 On the next screen, I clicked on “Data and Variables” :

Selecting%20Data%20and%20Variables

Selecting Data and Variables

Next, I clicked on “App Variable” :

Selecting%20App%20Variable

Selecting App Variable

Finally, I selected “APIKEY” (the only App Variable available) and “SAVE” :

Saving%20the%20APIKEY

Saving the APIKEY

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 :

TEST%20Tab%20with%20Authorization%20Header%20initialized

TEST Tab with Authorization Header initialized

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” : 

Creating the test Request

I then pressed save and, having entered the test data, pressed “RUN TEST”. After a short period of time, the following appeared : 

Results%20of%20a%20successful%20test

Results of a successful test

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 : 

Top%20level%20of%20the%20Response%20schema

Top level of the Response schema

Expanding the “choices” section and then the “message” section within that produced this : 

Expanding%20the%20choices%20and%20message%20elements

Expanding the “choices” and “message” elements

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” : 

Adding a Page Variable

 

I then created a new Text Variable named “slogan” :

Creating%20a%20Text%20variable%20called%20slogan

Creating a 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. 

First, I went back to the UI Canvas View, selected the “Scan” button and pressed the “Add logic to BUTTON 1” link. I hadn’t made any changes to the food scanner tutorial, so it looked like this :


Initial%20state%20of%20the%20Scan%20Button%20logic

Initial state of the Scan Button logic

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 : 

Adding%20the%20Create%20record%20node

Adding the 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 :

Entering the Authorization Header in the Create Record node

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 :

Create record INPUTS

I then clicked on “Custom object” under “Record properties”. Here, as expected, the model was already filled in :

Object properties with model filled in

Then, I clicked on “Custom list (0 items)” and then “Add a value”, noting that “user” was prefilled : 

Adding%20the%20role%20and%20content

Adding the role and content

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” : 

Creating%20a%20formula%20for%20the%20content%20element

Creating a formula for the “content” element

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 : 

Beginning%20of%20the%20content%20element%20formula

Beginning of the “content” element formula

The validation logic complained that the syntax is incomplete… which it was. To complete it, I entered “product_name” in the search box : 

Searching%20for%20product_name

Searching for “product_name”

Then, I selected the entry labeled data.OpenFoodFacts1.product.product_name and pressed the Enter key (double-click would have worked as well) : 

The%20completed%20content%20Formula

The completed “content” Formula

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 : 

Adding a new “Page variable” node

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” : 

Customize the Variable

On the next screen, I clicked on “Formula” :

Creating%20a%20Formula%20for%20the%20Page%20Variable

Creating a Formula for the Page Variable

This took me to a Formula dialog containing an edit box with a pair of empty quotes : 

Initial “binding type” formula

I clicked in the “Formula” edit box to bring up the context-sensitive editor : 

Context-sensitive Formula editor

I removed the quotes and typed in “choices” (without the quotes) to narrow down the list of prospective source values : 

Entering%20choices%20into%20the%20formula

Entering “choices” into the formula

 

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 :

Completed%20Page%20Variable

Completed Page Variable

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 : 

Initial%20contents%20of%20the%20Text%20element

Initial contents of the Text element

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 : 

Updating%20the%20UI%20with%20the%20slogan%20page%20variable

Text element formula

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 :

A%20slogan%20generated%20by%20OpenAI%20for%20a%20bag%20of%20crisps

A slogan generated by OpenAI for a bag of crisps

Conclusion

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.

Disclaimer:
SAP notes that posts about potential uses of generative AI and large language models are merely the individual poster’s ideas and opinions, and do not represent SAP’s official position or future development roadmap. SAP has no legal obligation or other commitment to pursue any course of business, or develop or release any functionality, mentioned in any post or related content on this website.

Assigned Tags

      13 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Raoul Shiro
      Raoul Shiro

      Great blog! I spent a good part of my Sunday following your technical insights, and it was definitely worth my time.

      Author's profile photo Mark Fogle
      Mark Fogle
      Blog Post Author

      Thanks! I'm glad you found it helpful.

      Author's profile photo Venkata Vyza
      Venkata Vyza

      Hello Mark,

      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"!?

       

      Thank you,

      Venkat

      Author's profile photo Mark Fogle
      Mark Fogle
      Blog Post Author

      Hi Venkat,

      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 :

      1. Click on ADD PROPERTY
      2. Enter "messages" as the key and select "Object" from "Complex types" at the bottom of the "Value Type" drop down.
      3. This will create an object with one property, "id" of type Text :
      4. Either click on the "id" property and change its name to "content" or remove it (using the REMOVE PROPERTY button in the PROPERTIES panel) and add a new Text property called "content"
      5. Click on the plus sign to the right of "object with 1 property" and add a second Text property called role.  For this one, if you change the Value Type to "Number" and then back to "Text", an "Initial value" field will appear at the bottom of the PROPERTIES panel.  You can enter "user" here (without quotes) to provide a default role so that the value doesn't need to be specified on each call.

      I hope this helps! I'll try adding some more content to the blog post to help clarify this step.

      Regards,

      Mark

      Author's profile photo Venkata Vyza
      Venkata Vyza

      Hello Mark,

      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%20while%20testing%20API%20call%20response

      error while testing API call response

       

      Thank you,

      Venkat Vyza

      Author's profile photo Mark Fogle
      Mark Fogle
      Blog Post Author

      Hi Venkat,

      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).

       

      Regards,

      Mark

      Author's profile photo Mark Fogle
      Mark Fogle
      Blog Post Author

      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") :

      Author's profile photo Venkata Vyza
      Venkata Vyza

      Thank YOU 🙂

      It worked well. Finally could complete it.

      Really appreciate your efforts in posting this blog and answering the questions as well.

      Best,

      Venkat Vyza

      Author's profile photo Mark Fogle
      Mark Fogle
      Blog Post Author

      You're welcome!

      Author's profile photo Venkat Vyza
      Venkat Vyza

      Hi Mark,

      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?

       

      Thank you,

       

      Venkat Vyza

      Author's profile photo Mark Fogle
      Mark Fogle
      Blog Post Author

      Hi Venkat,

      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.

      Regards,

      Mark

      Author's profile photo Venkata Vyza
      Venkata Vyza

      Thank you Mark, for the pointers.

      Will check that.

       

      Best regards,

      Venkat Vyza

      Author's profile photo Ravi Kant Ranjan
      Ravi Kant Ranjan

      Hi Mark,

      Thanks for writing such an insightful blog. Successfully completed it. I am sure its going to help me in my other use case going forward. Also if you have any more uses please share so that we can practice and make our hands even more stronger.

      Thank you,

      Ravikant