It’s simpler than it sounds. Seriously. Just the amount of callouts is a bit annoying.
Once you have your OpenAI assistant ready – there are blogs and articles out there that can describe better how to assistant than we do, like this Medium entry – all we need to do is to turn this OpenAI article into valid Salesforce code.
The following sequence of actions needs to be implemented:
- Create a new thread in your assistant
- Add your message to the thread (that you pass as parameter to the method)
- Create a run of the assistant with optional instructions
- Poll the run until successful
- Retrieve the run results and extract the message
On the platform configuration side, you’d just need to make sure to:
- have your OpenAI Subscription Key handy
- allowlist the OpenAI endpoint under your Remote Site Settings – usually https://api.openai.com unless self-hosted
- create an OpenAI Named Credential – this is optional but just makes the callouts handy – the code will go with a Named Credential
Now just paste and save the code below and call the method getOpenAIResponse() with the following three String parameters to return a beautiful (yet unparsed) OpenAI response:
- assistantId: The OpenAI ID of the assistant to call.
- userMessage: The user input for the assistant to process.
- instructionsMessage: An optional message for the assistant run – put null if not required.
public class Gpt4Service {
// Replace with your OpenAI subscription key
private static final String SUBSCRIPTION_KEY = '{put your subscription key here}';
// How many times the assistant run should be polled
private static final Integer NUMBER_POLLS = 10;
// How many seconds should be skipped between two polls - make sure that NUMBER_POLLS * SLEEP_LENGTH < 120
private static final Integer SLEEP_LENGTH = 10;
@AuraEnabled(Cacheable=false)
public static String getOpenAIResponse(String assistantId, String userMessage, String instructionsMessage) {
// Instantiate a new HTTP request and set its endpoint, method, and headers
//return response.getBody();
// Step 2: Create a thread
HttpRequest threadRequest = new HttpRequest();
threadRequest.setEndpoint('callout:OpenAI/v1/threads');
threadRequest.setMethod('POST');
// Set headers
threadRequest.setHeader('Content-Type', 'application/json');
threadRequest.setHeader('Authorization', 'Bearer ' + SUBSCRIPTION_KEY);
threadRequest.setHeader('OpenAI-Beta', 'assistants=v1');
// Send the request and store the response
Http threadHhttp = new Http();
HttpResponse threadResponse;
try {
threadResponse = threadHhttp.send(threadRequest);
// Check if the request was successful (HTTP status code 200)
if (threadResponse.getStatusCode() >= 200 && threadResponse.getStatusCode() < 300) {
System.debug('Success: ' + threadResponse.getBody());
Map<String, Object> threadResponseData = (Map<String, Object>) JSON.deserializeUntyped(threadResponse.getBody());
if(threadResponseData.containsKey('id')){
String threadId = String.valueOf(threadResponseData.get('id'));
// return threadResponse.getBody();
// Step 3: Add messages to thread
// Initialize HTTP objects
HttpRequest addMessageRequest = new HttpRequest();
HttpResponse addMessageResponse;
Http addMessageHttp = new Http();
// Configure the HttpRequest
addMessageRequest.setEndpoint('callout:OpenAI/v1/threads/' + threadId + '/messages');
addMessageRequest.setMethod('POST');
addMessageRequest.setHeader('Content-Type', 'application/json');
addMessageRequest.setHeader('Authorization', 'Bearer ' + SUBSCRIPTION_KEY);
addMessageRequest.setHeader('OpenAI-Beta', 'assistants=v1');
// Set the request body
String requestBodyThread = '{' +
'"role": "user",' +
'"content": "' + userMessage + '"' +
'}';
addMessageRequest.setBody(requestBodyThread);
// Perform the HTTP request
try {
addMessageResponse = addMessageHttp.send(addMessageRequest);
if (addMessageResponse.getStatusCode() >= 200 && addMessageResponse.getStatusCode() < 300) {
System.debug('Success: ' + addMessageResponse.getBody());
//return addMessageResponse.getBody();
// Step 4: Create a Run
HttpRequest createRunRequest = new HttpRequest();
HttpResponse createRunResponse;
Http createRunHttp = new Http();
// Configure the HttpRequest object
createRunRequest.setEndpoint('callout:OpenAI/v1/threads/' + threadId + '/runs');
createRunRequest.setMethod('POST');
createRunRequest.setHeader('Authorization', 'Bearer ' + SUBSCRIPTION_KEY);
createRunRequest.setHeader('Content-Type', 'application/json');
createRunRequest.setHeader('OpenAI-Beta', 'assistants=v1');
// Set the JSON body for the request
// TODO: Update output instructions from Custom Metadata
String createRunRequestBody = '{' +
'"assistant_id": "' + assistantId + '"';
if(String.isNotBlank(instructionsMessage)){
createRunRequestBody += (',"instructions": "' + instructionsMessage + '"');
} +
createRunRequestBody += '}';
createRunRequest.setBody(createRunRequestBody);
// Execute the HTTP request
try {
createRunResponse = createRunHttp.send(createRunRequest);
if (createRunResponse.getStatusCode() >= 200 && createRunResponse.getStatusCode() < 300) {
System.debug('Response Body: ' + createRunResponse.getBody());
Map<String, Object> createRunResponseData = (Map<String, Object>) JSON.deserializeUntyped(createRunResponse.getBody());
if(createRunResponseData.containsKey('id')){
String runId = String.valueOf(createRunResponseData.get('id'));
// return createRunResponse.getBody();
// Step 5: Poll the run
Boolean runSuccessful = false;
Integer countPolls = 0;
while(runSuccessful == false && countPolls < NUMBER_POLLS){
countPolls++;
// Initialize HTTP request, response, and client objects
HttpRequest pollRunRequest = new HttpRequest();
HttpResponse pollRunResponse;
Http pollRunHttp = new Http();
// Configure the HttpRequest object
pollRunRequest.setEndpoint('callout:OpenAI/v1/threads/' + threadId + '/runs/' + runId);
pollRunRequest.setMethod('GET');
pollRunRequest.setHeader('Content-Type', 'application/json');
pollRunRequest.setHeader('Authorization', 'Bearer ' + SUBSCRIPTION_KEY);
pollRunRequest.setHeader('OpenAI-Beta', 'assistants=v1');
// Execute the HTTP request
try {
pollRunResponse = pollRunHttp.send(pollRunRequest);
if (pollRunResponse.getStatusCode() >= 200 && pollRunResponse.getStatusCode() < 300) {
System.debug('Response Body: ' + pollRunResponse.getBody());
Map<String, Object> pollRunResponseData = (Map<String, Object>) JSON.deserializeUntyped(pollRunResponse.getBody());
if(pollRunResponseData.containsKey('status') && String.valueOf(pollRunResponseData.get('status')) == 'completed'){
runSuccessful = true;
// Step 6: Read Message
// Initialize the HTTP request, response, and client objects
HttpRequest listMessagesRequest = new HttpRequest();
HttpResponse listMessagesResponse;
Http listMessagesHttp = new Http();
// Configure the HttpRequest object
listMessagesRequest.setEndpoint('callout:OpenAI/v1/threads/' + threadId + '/messages');
listMessagesRequest.setMethod('GET');
listMessagesRequest.setHeader('Content-Type', 'application/json');
listMessagesRequest.setHeader('Authorization', 'Bearer ' + SUBSCRIPTION_KEY);
listMessagesRequest.setHeader('OpenAI-Beta', 'assistants=v1');
// Execute the HTTP request
try {
listMessagesResponse = listMessagesHttp.send(listMessagesRequest);
if (listMessagesResponse.getStatusCode() >= 200 && listMessagesResponse.getStatusCode() < 300) {
// Return or process the successful response
return listMessagesResponse.getBody();
} else {
// Handle Get Run Results Response Status Code Error
}
} catch(System.CalloutException e) {
// Handle Get Run Results Callout Exception
}
} else {
sleep(SLEEP_LENGTH);
}
} else {
// Handle Poll Run Response Status Code Error
}
} catch(System.CalloutException e) {
// Handle Poll Run Callout Exception
}
} // End while
} else {
// Handle No Run ID returned
}
} else {
// Handle Run Response Status Code Error
}
} catch(System.CalloutException e) {
// Handle Run Response Callout Exception
}
} else {
// Handle Instruction Response Status Code Error
}
} catch(System.CalloutException e) {
// Handle Instruction Callout Exception
}
} else {
// Handle No Thread ID returned
}
} else {
// Handle Thread Response Status Code Error
}
} catch(System.CalloutException e) {
// Handle Thread Callout Exception
}
return null;
}
public static void sleep(Integer secs) {
Long epochToDate = System.currentTimeMillis();
Long epochStop = epochToDate + (secs * 1000);
while (epochToDate <= epochStop) {
epochToDate = System.currentTimeMillis();
}
}
}
Call the method from a LWC, wrap some frontend around it and you’ll basically have your own, functional but slightly slow (a callout might take a minute or two and has also already timed out in practice) chatbot.
You might want to optimize the process by moving out the status poll as Queueable class, if the AI response is not required synchronously.
Update, May 2024: Fancy your OpenAI Assistant analyzing your very own data instead of the web? Here’s a sequel how to upload your custom data for your OpenAI Assistant to consume.