Salesforce Models API provides Apex classes and REST endpoints that can be used in your application to interact with the large language models (LLMs) from the Salesforce partners, including Anthropic, Google, and OpenAI.
All the requests made to the Salesforce Models API go through the Einstein Trust Layer which provides more security to the LLM requests.
To know more, kindly check the following:
In this Blog Post, we are going to see how to gather feedback from the users and report on it. I have used Custom Metadata Type to store the Model Name and API Name. Check the following for more information:
Enable “Feedback” in Salesforce Setup. “Einstein Feedback and Monitoring Setup”.

Sample Code:
Apex Class:
public class ConversationSummaryController {
/*
Apex class to summarize the conversation entries of a messaging session
@param selectedModel: Selected model
@param strMessagingSessionId: String value of the messaging session Id
@return Map < String, String >: Map of the generated Id and feedback text
*/
@AuraEnabled( cacheable = true )
public static Map < String, String > summarizeEntries(
String selectedModel,
String strMessagingSessionId
) {
String strConcatenatedMessages = 'Summarize the following:\n';
Map < String, String > mapResponse = new Map < String, String >();
// Fetching Messaging Session record
MessagingSession objMS = [
SELECT Conversation.ConversationIdentifier, ConversationId
FROM MessagingSession
WHERE Id =: strMessagingSessionId
];
System.debug(
'ConversationIdentifier is ' +
objMS.Conversation.ConversationIdentifier
);
// GET Request to Connect API
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:Fetch_Conversation_Entries' +
'/services/data/v60.0/connect/conversation/' +
objMS.Conversation.ConversationIdentifier +
'/entries?queryDirection=FromStart'
);
req.setMethod( 'GET' );
HttpResponse res = h.send(req);
system.debug(
'Conversation Entries are ' + res.getBody()
);
Map < String, Object > jsonMap =
( Map < String, Object > )JSON.deserializeUntyped( res.getBody() );
List < Object > conversationEntries = ( List < Object > ) jsonMap.get( 'conversationEntries' );
for ( Object objEntry : conversationEntries ) {
Map < String, Object > entryMap = ( Map < String, Object > ) objEntry;
Map < String, Object > senderMap = ( Map < String, Object > ) entryMap.get( 'sender' );
strConcatenatedMessages += ( String ) entryMap.get( 'messageText' ) + '\n';
}
// Creating generate text request
aiplatform.ModelsAPI.createGenerations_Request request =
new aiplatform.ModelsAPI.createGenerations_Request();
// Specifying model
System.debug( 'selectedModel is:' + selectedModel );
request.modelName = selectedModel;
// Create request body
aiplatform.ModelsAPI_GenerationRequest requestBody =
new aiplatform.ModelsAPI_GenerationRequest();
request.body = requestBody;
// Add prompt to body
requestBody.prompt = strConcatenatedMessages;
try {
// Invoking the Models API
aiplatform.ModelsAPI modelsAPI = new aiplatform.ModelsAPI();
aiplatform.ModelsAPI.createGenerations_Response response =
modelsAPI.createGenerations( request );
System.debug(
'Request Id is: ' +
response.Code200.generation.id
);
mapResponse.put( 'generatedId', response.Code200.generation.id );
System.debug(
'Models API response: ' +
response.Code200.generation.generatedText
);
mapResponse.put( 'message', response.Code200.generation.generatedText );
// Handling Exception
} catch( aiplatform.ModelsAPI.createGenerations_ResponseException e ) {
System.debug(
'Response code: ' +
e.responseCode
);
System.debug(
'The following exception occurred: ' + e
);
mapResponse.put( 'message', e.toString() );
}
return mapResponse;
}
/*
Apex class to summarize the conversation entries of a messaging session
@param strGoodBad: String value of the feedback type
@param strFeedback: String value of the feedback text
@param strGenerationId: String value of the generation Id
@param strMessagingSessionId: String value of the messaging session Id
@return String: Response after submitting feedback
*/
@AuraEnabled( cacheable = true )
public static String submitFeedback(
String strGoodBad,
String strFeedback,
String selectedModel,
String strGenerationId,
String strMessagingSessionId
) {
String strResponse;
// Create submit feedback request
aiplatform.ModelsAPI.submitFeedback_Request request = new aiplatform.ModelsAPI.submitFeedback_Request();
UUID randomUUID = UUID.randomUUID();
System.debug(
'randomUUID String value is ' +
randomUUID.toString()
);
// Provide feedback information in body
aiplatform.ModelsAPI_FeedbackRequest feedbackRequest
= new aiplatform.ModelsAPI_FeedbackRequest();
feedbackRequest.id = randomUUID.toString();
feedbackRequest.generationId = strGenerationId;
feedbackRequest.feedback = strGoodBad;
feedbackRequest.feedbackText
= strMessagingSessionId + ' - ' +
selectedModel + ' - ' + strFeedback;
feedbackRequest.source = 'HUMAN';
request.body = feedbackRequest;
try {
// Submit feedback
aiplatform.ModelsAPI modelsAPI = new aiplatform.ModelsAPI();
aiplatform.ModelsAPI.submitFeedback_Response response
= modelsAPI.submitFeedback(request);
System.debug(
'Models API response: ' +
response.Code202.message
);
strResponse = response.Code202.message;
// Handle error
} catch( aiplatform.ModelsAPI.submitFeedback_ResponseException e ) {
System.debug(
'Response code: ' +
e.responseCode
);
System.debug(
'The following exception occurred: ' +
e
);
strResponse = 'Error occurred ' + e.responseCode;
}
return strResponse;
}
}
Lightning Web Component:
HTML:
<template>
<lightning-card title="Conversation Summary" icon-name="standard:conversation">
<template lwc:if={showSpinner}>
<lightning-spinner
alternative-text="Loading"
size="large">
</lightning-spinner>
</template>
<div class="slds-m-around_medium">
<div style="width: 250px;">
<lightning-combobox
name="Select Model"
label="Select Model"
value={selectedModel}
options={modelOptions}
placeholder="Select Model"
onchange={handleModelChange}>
</lightning-combobox>
<br/>
</div>
<template if:true={error}>
<p>{error}</p>
</template>
<template if:true={conversationSummary}>
<p>{conversationSummary}</p>
</template>
</div>
<template if:true={selectedModel}>
<lightning-button
label="Summarize"
title="Summarize"
icon-position="right"
icon-name="utility:refresh"
onclick={summarizeConversation}
class="slds-m-around_medium">
</lightning-button>
<div class="slds-m-around_medium">
<template if:true={summaryGeneratedBool}>
<lightning-input
type="text"
label="Feedback"
value={strFeedback}
class="slds-m-around_medium"
onchange={handleFeedbackChange}>
</lightning-input>
<lightning-button
label="Like"
icon-position="right"
icon-name="utility:like"
onclick={submitModelFeedback}
class="slds-m-around_medium">
</lightning-button>
<lightning-button
label="Dislike"
icon-position="right"
icon-name="utility:like"
onclick={submitModelFeedback}
class="slds-m-around_medium">
</lightning-button>
</template>
</div>
</template>
</lightning-card>
</template>
JavaScript:
import { LightningElement, api, wire } from 'lwc';
import { gql, graphql } from 'lightning/uiGraphQLApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import submitFeedback from '@salesforce/apex/ConversationSummaryController.submitFeedback';
import summarizeEntries from '@salesforce/apex/ConversationSummaryController.summarizeEntries';
export default class ConversationSummary extends LightningElement {
error;
strFeedback;
generatedId;
selectedModel;
conversationSummary;
showSpinner = false;
summaryGeneratedBool = false;
@api recordId;
@wire( graphql, {
query: gql`
query getSamples {
uiapi {
query {
LLM__mdt(
orderBy: {
MasterLabel: { order: ASC }
}
) {
edges {
node {
MasterLabel {
value
}
Value__c : Value__c {
value
}
}
}
}
}
}
}
`
} )
graphql;
get modelOptions() {
console.log( this.graphql );
return this.graphql.data?.uiapi.query.LLM__mdt.edges.map(
( edge ) => ( {
label: edge.node.MasterLabel.value,
value: edge.node.Value__c.value
}
) );
}
handleModelChange( event ) {
this.selectedModel = event.target.value;
}
handleFeedbackChange( event ) {
this.strFeedback = event.target.value;
}
/*
Summarize the conversation entries of a messaging session
*/
summarizeConversation() {
this.showSpinner = true;
summarizeEntries( {
selectedModel: this.selectedModel,
strMessagingSessionId: this.recordId
} )
.then( result => {
console.log( 'result is', JSON.stringify( result ) );
if ( result.message ) {
this.conversationSummary = result.message;
}
if ( result.message ) {
this.conversationSummary = result.message;
}
if ( result.generatedId ) {
this.generatedId = result.generatedId;
}
this.summaryGeneratedBool = true;
this.showSpinner = false;
} )
.catch( error => {
console.log( 'Error Occured', JSON.stringify( error ) );
this.error = error;
this.summaryGeneratedBool = false;
this.showSpinner = false;
} );
}
/*
Submit feedback to the model
*/
submitModelFeedback( event ) {
if (
this.strFeedback &&
this.strFeedback.length > 0
) {
this.showSpinner = true;
let strGoodBad;
const selectedLabel = event.target.label;
console.log( 'selectedLabel::', event.target.label );
if ( selectedLabel == 'Like' ) {
console.log( 'Like' );
strGoodBad = 'GOOD';
} else if ( selectedLabel == 'Dislike' ) {
console.log( 'Dislike' );
strGoodBad = 'BAD';
}
submitFeedback( {
strGoodBad: strGoodBad,
strFeedback: this.strFeedback,
selectedModel: this.selectedModel,
strGenerationId: this.generatedId,
strMessagingSessionId: this.recordId
} )
.then( result => {
console.log( 'result is', JSON.stringify( result ) );
if ( result.includes( 'Error' ) ) {
this.dispatchEvent(
new ShowToastEvent( {
variant: 'error',
mode: 'dismissable',
title: 'Feedback Submission',
message: result
} )
);
this.error = result;
} else {
this.dispatchEvent(
new ShowToastEvent( {
variant: 'success',
mode: 'dismissable',
title: 'Feedback Submission',
message: 'Feedback submitted successfully'
} )
);
}
this.showSpinner = false;
} )
.catch( error => {
console.log( 'Error Occured', JSON.stringify( error ) );
this.error = error;
this.showSpinner = false;
} );
} else {
this.dispatchEvent(
new ShowToastEvent( {
variant: 'error',
mode: 'dismissable',
title: 'Feedback Submission',
message: 'Please enter feedback!!!'
} )
);
}
}
}
js-meta.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>63.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
Output:

Report:
- Go to Reports Tab.
- Select All Folders.
- Click “Einstein Generative AI Audit & Feedback” Folder.
- You will see multiple reports available.
- You can use “User Feedback” report to view users feedback.

Dashboard:
- Go to Dashboards Tab.
- Select All Folders.
- Click “Einstein Generative AI Audit & Feedback” Folder.
Reference Article:
https://help.salesforce.com/s/articleView?id=ai.generative_ai_feedback_enable.htm&type=5