/ Development  

Unlock the power of conversation with ChatGPT; The ultimate 'Discussion' BB

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

You couldn’t miss it; Only when you live under a rock! ChatGPT is the new hype…Who am I to not embed it into our platform; Just because we can and because I’m always interested how these types of integrations can benefit our solution. ChatGPT is all about questioning/answering and for that we have off-course an interesting building block available on our platform. In entity modelling principles we call it the ‘Discussion’ BB, and we make it a 2.0 version with this post…


Let get right into it…

First, some basic questions from my own curiosity:

  1. Where is “GPT” standing for? Well, a quick google brought me this answer: Generative Pre-trained Transformer.
  2. Where can we find this ChatGPT? This is the direct link. You see it’s part of the OpenAI platform
  3. Do you need an account? Yes, you can create it via this link!
  4. Is it free? Yes and No; Yes, when you just use the regular chat UI as far as I’ve seen; No, when you want to call it though the API layer. Your account creation is granted with $18.00 credits to consume the API for a 3 months free trial; Every 1K calls/tokens will cost you $0.002 😁. More information on pricing can be found here. The free alternative? Well, this one was helpful too, but less smart!?
  5. Is it great? Ohw yeah!, but let’s find out when we call it; keep on reading…

The first ChatGPT API call

Once you have your account available, login to the OpenAI platform, and open the API-keys section:

chat_gpt_001

Hit the button ‘Create new secret key’ and just copy that magic string; Via this string you gain access to the API layer without username/password, so keep it to yourself!

The next step is to make an HTTP POST request (with valid body) to a special API link. I make this post request via a tool called CURL for Windows, but you can also use Postman or any other fancy tool you like. This will be our first call to make; Make sure to update those three ... with your just copied API key:

1
curl -X POST https://api.openai.com/v1/chat/completions -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer ..." -d "{""model"": ""gpt-3.5-turbo"", ""messages"": [{""role"": ""user"", ""content"": ""What is your name?""}], ""temperature"": 0.5}"

NOTES:

  • Watch those double quotes in Windows! JSON wants to have them, but Windows is the culprit here!
  • Check the correct URL (the one with /chat); You can also find this URL: https://api.openai.com/v1/completions, but this uses the older text-davinci-003 model; Which is also more expensive to call!

These are 2 locations to read more about this POST call and those parameters:

Once this post is online there is most likely a version “GPT 4”…Have a “Google” yourself!

This is the response JSON message from my CURL command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"id":"...",
"object":"chat.completion",
"created":0,
"model":"gpt-3.5-turbo-0301",
"usage":{
"prompt_tokens":12,
"completion_tokens":30,
"total_tokens":42
},
"choices":[
{
"message":{
"role":"assistant",
"content":"\n\nAs an AI language model, I don't have a name, but you can call me OpenAI. How can I assist you today?"
},
"finish_reason":"stop",
"index":0
}
]
}

NICEEEE!…Hello “OpenAI”! Next step…


Implementation into an AppWorks entity ‘Discussion’ BB

If your VM is already up and running, login to it with your developer account. Jump into your favorite workspace and related project. We will start with a first simple ‘Case’ entity. Give it a case_name property, generate all the default BBs and make it nice for a first publication.

chat_gpt_002

Now add the ‘Discussion’ BB to the entity and open the related ‘Discussions’ entity (behind the ‘Advanced configuration’!). You see it’s just a new entity with a new set of building blocks. Add the ‘Web service’ BB to this related entity with the ‘Read’ and ‘Update’ operations exposed. We will call these operations from a BPM which we trigger from a new ‘Rule’ BB on the related ‘Discussions’ entity. Give the new discussions rule the name e_on_create_start_bpm and let it start a BPM; Like this:

chat_gpt_003

Notes:

  1. There is no service operation ‘Create’ on a ‘Discussion’ BB! So, that’s why we do a workaround with the ‘Update’ service call. My first intention was to just create new ‘Discussions’ entity instances as a REPLY, but there is no possibility to make it like that…Too bad! Also, the /app/admin Typed ReST layer is not exposing discussion magic to play with it; It’s what it is!
  2. That bpm_call_chat_gpt BPM is just a simple 1 activity BPM for now; Short-lived! because of direct/synched feedback and saved in the bpms folder of our project. We’ll update this BPM later; Comment me if you don’t have a clue how to start this on your own!?

This will be the result on the related ‘Discussions’ entity:

chat_gpt_004

So, what have we created? When we create a new discussion entity instance, we start a BPM that will eventually call ChatGPT and will manage the response for us with an update on the discussion entity instance.
Great, but how to manually create a new discussion instance? For this we add the ‘Discussions’ panel in the default layout of our ‘Case’ entity! I place it next to the form panel:

chat_gpt_005

Let’s do a full publication of the project, create a new case instance, open it, and add a discussion topic. After this, check the PIM for that BPM instance!

chat_gpt_006

I do this with my awdev developer account, so we don’t have any security issues!
Trust me on the BPM instance in the PIM…It’s there! 😇

So far, so good. Now that we have a discussion instance we can also ‘Read’ and ‘Update’ it through service calls! Only, before we can try it we need to create a new service container managing the requests for us. Use these steps to create one in your own organization (all other setting in the wizard can be left as is):

  • Open the ‘System Resource Manager’ artifact
  • Create a new service group and go through the wizard
  • The type will be Application Server Connector
  • I use sg_app_server for the group name
    • Select the one web service interface from our entity ‘Discussions’ BB
  • I use sc_app_server for the service name
    • Set the startup type to ‘Automatic’
    • Mark the option ‘Assign OS Process’

When the service container is up and running we can open the ‘Web Service Interface Explorer’ artifact and search for ReadDiscussions. Right-click it and evaluate it. This will be the input:

1
2
3
4
5
6
7
8
9
10
11
12
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<ReadDiscussions xmlns="http://schemas/aw_tipsaw_generic/case.Discussions/operations">
<ns0:Discussions-id xmlns:ns0="http://schemas/aw_tipsaw_generic/case.Discussions">
<!--This is the itemId of the parent 'Case' entity instance-->
<ns0:ItemId>080027008c65a1edada201e0ec191120.1</ns0:ItemId>
<!--This is the itemId of the related 'Discussions' entity instance-->
<ns0:ItemId1>080027008c65a1edada20482b9408201.1.1</ns0:ItemId1>
</ns0:Discussions-id>
</ReadDiscussions>
</SOAP:Body>
</SOAP:Envelope>

Hit the ‘Invoke’ button and verify a correct response…It’s a green light from my side!

Where did I get those 2 ItemIds from? Well, that’s the power of the Chrome developer console <F12>. Make sure to only filter on ‘Fetch/XHR’ calls; After a page refresh, you can find out this information:

chat_gpt_007

With this knowledge in the pocket, we can now also make the UpdateDiscussions call where we can now update the body text:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<UpdateDiscussions xmlns="http://schemas/aw_tipsaw_generic/case.Discussions/operations">
<ns0:Discussions-id xmlns:ns0="http://schemas/aw_tipsaw_generic/case.Discussions">
<ns0:ItemId>080027008c65a1edada201e0ec191120.1</ns0:ItemId>
<ns0:ItemId1>080027008c65a1edada20482b9408201.1.1</ns0:ItemId1>
</ns0:Discussions-id>
<ns0:Discussions-update xmlns:ns0="http://schemas/aw_tipsaw_generic/case.Discussions">
<ns2:Discussion-update xmlns:ns2="http://schemas.opentext.com/entitymodeling/buildingblocks/Discussions.Discussion">
<ns2:Body>Hello world...</ns2:Body>
</ns2:Discussion-update>
</ns0:Discussions-update>
</UpdateDiscussions>
</SOAP:Body>
</SOAP:Envelope>

A refresh in runtime will have my Topic1 get an up-to-date body:

chat_gpt_008

Right, the first services are in place where we can manipulate our discussion entity instance! All great, but we miss one service call!? Yes, that’s the service call to ChatGPT! Almost forgotten! 🤣
How can we make this possible from the AppWorks platform? There is only one ring to rule them all; In our case that will be a call over the HTTP connector. In this post I already shared my HTTP connector experiences. I just follow it (starting at section “Add a run-time reference of type ‘Application Connector’”) with new parts of content.

The config.xml configuration file in the HTTP connector service container, which is saved in the XML Store location /OpenText/HttpConnector/config.xml, will have this content:

1
2
3
4
5
6
7
8
<configurations xmlns="http://httpconnector.opentext.com/1.0/configuration">
<connections>
<connection id="OPENAI-API">
<url>https://api.openai.com</url>
<check-certificate>false</check-certificate>
</connection>
</connections>
</configurations>

We will create a new ‘XML Schema’ type of document (in a schemas folder) with this content part on document name scm_chat_gpt_input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="open_ai" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="open_ai">
<element xmlns="http://www.w3.org/2001/XMLSchema" name="sendRequest">
<complexType>
<sequence>
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" name="model" />
<element name="messages" maxOccurs="unbounded" minOccurs="0">
<complexType>
<sequence>
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="role" type="xs:string" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="content" type="xs:string" />
</sequence>
</complexType>
</element>
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:float" name="temperature" />
</sequence>
</complexType>
</element>
</schema>

We also create a second ‘XML Schema’ type of document with this content part on document name scm_chat_gpt_output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="open_ai" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="open_ai">
<element xmlns="http://www.w3.org/2001/XMLSchema" name="sendResponse">
<complexType>
<sequence>
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" name="id" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" name="object" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime" name="created" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" name="model" />
<element name="usage">
<complexType>
<sequence>
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="prompt_tokens" type="xs:integer" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="completion_tokens" type="xs:integer" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="total_tokens" type="xs:integer" />
</sequence>
</complexType>
</element>
<element name="choices">
<complexType>
<sequence>
<element name="message">
<complexType>
<sequence>
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="role" type="xs:string" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="content" type="xs:string" />
</sequence>
</complexType>
</element>
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="finish_reason" type="xs:string" />
<element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="index" type="xs:integer" />
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>

Have a close look at the XSDs and recognize the input/output parameters we used in the CURL-HTTP-POST request in the beginning of the post. We will use these schema documents during the creation of the custom webservice…continue reading in the referenced post!

Is there a person in the audience asking where the hell I got those XSD parts from? If so, check this URL to convert JSON to XML, XML to XSD, and some extra input/effort from yourself! 😉

During the creation of a new ‘Web Service’ type of document, I will use the name ChatGPTWebService and namespace open_ai:

chat_gpt_009

In the next screen of the webservice creation wizard, we will use this input (with the corresponding schema fragments!):

chat_gpt_010

Once the service is generated, it needs an implementation! You can use this XML part:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<implementation xmlns:c="http://schemas.cordys.com/cws/1.0" xmlns="http://httpconnector.opentext.com/1.0/implementation" xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" type="HTTP">
<connection-id>OPENAI-API</connection-id>
<uri>/v1/chat/completions</uri>
<http-method>POST</http-method>
<request-handler class="com.opentext.applicationconnector.httpconnector.impl.RestRequestHandler">
<req-headers>
<header name="Accept">application/json</header>
<header name="Content-Type">application/json</header>
<header name="Authorization">Bearer ...</header>
</req-headers>
<!--
This schema validation part makes sure the integer/float values are NOT converted into String values!
See also the "HTTP connector service contract" section of the developer guide.
-->
<enable-schema-validation>true</enable-schema-validation>
</request-handler>
<response-handler class="com.opentext.applicationconnector.httpconnector.impl.RestResponseHandler" />
<valid-response-code>200</valid-response-code>
<namespaces />
</implementation>

Notes:

  • Watch again those 3 ... in the authorization header which you need to replace with your own API key!
  • The enable-schema-validation element solves an issue during my testing.
  • See also the advanced development guide section “Developing Web services using the HTTP connector” for more information on this implementation part.

When all is fine and published (I hope you didn’t forget to create the HTTP service container like I forgot! 🤣), you can evaluate the HTTP ‘sendRequest’ service with this input…I hope it starts to fall in place now; correct?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<sendRequest xmlns="open_ai">
<model>gpt-3.5-turbo</model>
<messages>
<role>user</role>
<content>What is your name?</content>
</messages>
<!-- The next messages element makes sure (for some stupid reason) the data is converted into an Array!? -->
<messages>
<role>system</role>
<content>...</content>
</messages>
<temperature>0.5</temperature>
</sendRequest>
</SOAP:Body>
</SOAP:Envelope>

This is my interesting response:

chat_gpt_011

We’re one step closer to our end goal!

Now, let’s glue it all together in our bpm_call_chat_gpt BPM. We left off with a one-activity flow which we first extend with 2 extra activities, and have a double-check on the execution mode:

chat_gpt_012

The next step is to insert a ‘Web Service Operation’ to each activity (it’s in the right-click context menu for an activity). In pseudocode the BPM will look like this:

1
2
3
4
5
6
7
8
var discussion = ReadDiscussions({parent.ItemId}, {rootEntityInstanceId.ItemId1});
var chatGptResponse = sendRequest("gpt-3.5-turbo", {discussion.body}, 0.5);
UpdateDiscussions(
DiscussionsId({parent.ItemId}, {rootEntityInstanceId.ItemId1}),
DiscussionsUpdate(
concat({discussion.body}, {chatGptResponse.choices[0].message.content})
)
);

Because the ChatGPT is an external service call out of our control, we add a fallback scenario where we catch the exception if the service is not responding. The end-result (incl. that catch) will look like this:

chat_gpt_013

Now what? Well, we need to add mappings for all the service calls. So, in the bottom you can go to the ‘Message Map’ tab and apply all the mappings like this consolidated overview:

chat_gpt_014

Notes:

  • You need to read the consolidated view from right (the source) to left (the target). Please, don’t shoot the messenger! 😅
  • Where is bpm:Discussions-id/bpm:ItemId/text() magically come from? Well, have a second look at the first BPM instance in the PIM! Look at the full message map (in XML format!) when you enabled the monitoring for it on the BPM document itself. Copy this full message map XML of the BPM instance and reuse it into your XPath editor during your BPM activity manipulations! The advantage here is that you also have real values to test with…That’s a free tip of the bro’s. Again, comment me if you have no clue at all.
  • The sendRequest input is partly hard-coded, but you can use the ‘Application configuration’ entity (right-click on your project) to make it configurable…Your administrators will love you for it!
  • You see the concatenation of the body part in the UpdateDiscussions section; Smart copy it from below!
  • chr(10) is the ASCII character for \n; Or in dummy talk a ‘New Line/Line Feed’.
  • My lesson learned: The order of input properties is important. So <temperature>0.5</temperature> must be last!
  • I also make use of 2 process specific messages looking like this:

chat_gpt_014a

This is for you to make that “smart” copy! 😉

1
concat(ns2:ReadDiscussionsOutput/ns2:ReadDiscussionsResponse/ns3:Discussions/ns5:Discussion/ns5:Body/text(), chr(10), 'ChatGPT: ', ns6:sendRequestOutput/ns6:sendRequestResponse/ns6:choices/ns6:message/ns6:content/text())

When ready, do your publication and have a check into runtime…Interesting stuff!

chat_gpt_015

Ohw boy, what fun will we have with this…It’s a party. 🎉

Trust me…The first time you’ll fail, but eventually you will make it come alive. The BPM service calls took the most of my time making the calls correct. I have faith in you! 😇


Other resources

My RSS reader (Yes, it’s old but works perfectly!) provided me this post on creating their own chatroom calling ChatGPT…Well, my next thought was the AWP entity ‘Discussion’ BB!

After some research someone even created a nice Java Maven dependency for OpenAI; For this post I used version 0.11.0 which supports the ChatCompletionRequest object (using gpt-3.5-turbo)!

Have a start with you own API on https://start.spring.io/ with a ReST implementation like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@RestController
@RequestMapping("/api/v1/chat")
public class ChatController {

private final ChatService chatService;

public ChatController() {
this.chatService = new ChatService();
}

@PostMapping
public ResponseEntity<String> send(@RequestBody Message message) {
String response = chatService.sendManual(message.message());
return ResponseEntity.ok(response);
}

private record Message(String message) {
}

private static class ChatService {
private static final String API_KEY = "...";

public String send(final String message) {
final OpenAiService service = new OpenAiService(API_KEY);

final List<ChatMessage> messages = new ArrayList<>();
final ChatMessage systemMessage = new ChatMessage(ChatMessageRole.USER.value(), message);
messages.add(systemMessage);

ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
.builder()
.model("gpt-3.5-turbo")
.messages(messages)
.build();
return service.createChatCompletion(chatCompletionRequest).getChoices().get(0).getMessage().getContent();
}

public String sendManual(final String message) {
final String result;
final HttpPost httpPost = new HttpPost("https://api.openai.com/v1/chat/completions");
final String json = "{\"model\": \"gpt-3.5-turbo\", \"messages\": [{\"role\": \"user\", \"content\": \"" + message + "\"}], \"temperature\": 0.5}";

try {
final StringEntity entity = new StringEntity(json);
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
httpPost.setHeader("Authorization", "Bearer " + API_KEY);

try (CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpPost)) {
result = EntityUtils.toString(response.getEntity());
}
} catch (IOException e) {
throw new ChatException(e);
}
return result;
}

private static class ChatException extends RuntimeException {
public ChatException(IOException e) {
Logger.getLogger(this.getClass().getName(), e.getLocalizedMessage());
}
}
}
}

You can also follow this as head start for your Swagger OpenAPI!

Does this follow the happy flow? For sure! Does it oversee the fail-scenarios? NOPE…That’s for you to manage.
Is it Unit tested? My friend, sometimes you need to do some things on your own! 🙃

The CURL command for this local API will eventually be: curl -X POST http://localhost:8080/api/v1/chat -H "Content-Type: application/json" -d "{""message"": ""What is your name?""}"

The above information is just a bunch of statements to trigger your creativity! Nice stuff to play with…trust me on that one.


“DONE”, DONE, DONE…The world of API is now open for consumption from our beloved platform! We saw interesting new insights again and the first question after this post will be: WHAT ELSE!?. That’s a thing for you to find out, but here is a list for some AI inspiration:

Yes, we’re only at the beginning of machine learning; Have a search for “Quantum ML”. Sweet dreams and a great weekend; Till next week in other great AppWorks Tips topic.

An extra assignment for you this time: Check the discussion topic on a certain keyword before it starts communicating externally; Like Ask ChatGPT or Simon says…Have fun! 🤠

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?