Salesforce Models API Feedback Gathering

Salesforce Models API Feedback Gathering

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:

  1. Go to Reports Tab.
  2. Select All Folders.
  3. Click “Einstein Generative AI Audit & Feedback” Folder.
  4. You will see multiple reports available.
  5. You can use “User Feedback” report to view users feedback.

Dashboard:

  1. Go to Dashboards Tab.
  2. Select All Folders.
  3. Click “Einstein Generative AI Audit & Feedback” Folder.

Reference Article:

https://help.salesforce.com/s/articleView?id=ai.generative_ai_feedback_enable.htm&type=5

Leave a Reply