Large language models (LLMs) are powerful artificial intelligence (AI) models trained on vast amounts of text data. They can understand and generate human-like text, making them versatile for a wide range of natural language processing (NLP) tasks. While often associated with conversational AI or writing assistance, LLMs can do much more, including translation, summarization, and text classification.

In this post, I’ll show you how I built an automation in an afternoon that can classify and summarize cases using Prompt Builder, Flow, and a sprinkling of Apex. My automation will summarize and classify cases based on their subject and description, but you could do the same for any Salesforce object and with any text field.

To follow along, you’ll need access to an org with an Einstein license. I built my demo using the free 5-day Einstein Generative AI-enabled Developer Edition trial org.

Building a case summarizer and classifier with Prompt Builder

The heart of any text classifier is a model. In the olden days (pre-GPT-3.5), you’d need to build and train a custom model and then integrate it with Salesforce. This process could take weeks, or even months, and involved both the data science and development teams. Today, we can use Prompt Builder to prompt an LLM from within Salesforce programmatically. With Prompt Builder, you don’t need to manually integrate with individual LLM providers or manage and store API credentials. Instead, you simply write a prompt template and select a model from a drop-down menu.

We’ll start by building a new prompt template in Prompt Builder, which is a new builder that makes it easy to prompt LLMs within the Salesforce Platform without writing a line of code. With Prompt Builder, you can easily (and responsibly, thanks to the Einstein Trust Layer) integrate your CRM data, Data Cloud, and external data sources with a variety of LLMs using merge fields and grounding. Currently, Prompt Builder supports four types of templates: Sales Email, Field Generation, Record Summary, and Flex.

First, sign up for your free 5-day Einstein Generative AI-enabled Developer Edition trial org. In your trial org, activate Einstein Generative AI features with the following steps:

  1. Navigate to Setup
  2. Search for the “Einstein Setup” Quick Find box
  3. Select Einstein Setup
  4. Enable Turn on Einstein.

Next, activate Prompt Builder by assigning the Prompt Template Manager permission set to your username. This will allow you to create and manage prompt templates in Prompt Builder.

Refresh the page and search for “Prompt Builder“ in the Quick Find box and select Prompt Builder. In the Prompt Builder Setup page, click New Prompt Template to configure your new prompt template.

We won’t be generating text for an email (which would use the Sales Email template) or a field (which would use the Field Generation template), so we’ll choose the Flex template. We’ll set the target object to Case, name the API object myCase, and name the prompt template Summarize Classify Cases.

The New Prompt Template window where we build a Summarize Classify Case prompt template

Using Prompt Builder, we can write a prompt template that can summarize and classify a case by type and reason by analyzing its Subject, Description, and Case Comment fields and returning the classification as JSON. Why JSON? We’ll use the LLM’s response to set a record’s fields using a flow. Constraining the response to JSON ensures that the LLM’s response will be structured in a predictable format with predictable values that match our Type and Reason picklist field values. JSON also allows us to return multiple outputs that we can then process with Apex and use without our flow.

Paste the text below into the prompt template text area field.

You are a API service for a company that services mechanical, electrical, electronic, and other forms of hardware. Your job is to categorize cases by Type and Case Reason and summarize them using the Case's Case Comments, Subject, and Description.

Case Type and Case Reason have predefined values. They are listed below:

Case Type: Electrical, Mechanical, Electronic, Structural, Other, null
Case Reason: Installation, Equipment Complexity, Performance, Breakdown, Equipment Design, Feedback, Other, null

Categorize the case below.

Priority: {!$Input:myCase.Priority}
Subject: {!$Input:myCase.Subject}
Description: {!$Input:myCase.Description}
Case Comments: {!$RelatedList:myCase.CaseComments.Records}

Now categorize and summarize the case. Output your categorization and summary as valid JSON with the keys "caseType", "reason", and "summary". Always include each key.Ensure that you return valid JSON.

Below is an example output:

"caseType": "Mechanical",
"reason": "Installation",
"summary": "The case titled 'Mechanical issue' has a priority level of High and is categorized under Mechanical. The case comments indicate that engineering was contacted to obtain a PDF of the installation instructions. The diagram was then emailed to the customer, and a response is currently awaited. Additional information shared notes that the device was manufactured in 2020."

We can quickly test and iterate on our prompt template by previewing a response using the 00001002 Case record, which has some case comments, and the OpenAI GPT 3.5 Turbo model. The model should respond with a JSON string with caseType, reason, and summary keys.

The Prompt Template workspace with our prompt template specified above.

If you’re happy with the output, activate the prompt template by clicking Activate. Otherwise, you can continue to iterate on the prompt template language and try out different models.

Transforming our LLM’s response with Apex

We’ll start by creating an Apex method to parse our prompt template’s output.

The sole method of our Apex class will be used in a flow, and therefore it requires the InvocableMethod annotation. This annotation exposes the method to Flow and specifies a label and description for the Flow action. As an invocable method, the method takes in a list and returns a list. In our case, our method will expect a list of JSON strings, which are generated by our prompt template, and output a list of CaseSummary objects that include the caseType, reason, and summary strings. We can make these strings available as inputs to our flow by adding the InvocableVariable annotation above each one. This annotation exposes ‌class variables to Flow as input or output parameters.

Using Salesforce’s Code Builder or Visual Studio Code Salesforce Extension Pack, create and deploy the CaseSummarizationTemplateParser Apex class below.

Now that we have our prompt template and some Apex glue code to make it usable as an object, it’s time to build our flow.

Using our prompt template in a flow

We can automatically update a case that doesn’t have a reason, type, or quick summary by using our prompt template and Apex class in a record-triggered flow. Here’s how it works: Whenever a case is created or updated, we’ll check whether it has a subject or description and an empty Reason, Type, or Quick Summary field. If a case has a subject or description, but is missing a type, reason, or quick summary, we’ll attempt to populate it using our Summarize Classify Cases prompt template.

Navigate to Flow Builder and create a record-triggered flow for the Contact object. Set the following trigger conditions:

  • Object: Case
  • Trigger: A record is created or updated
  • Entry Conditions
    • Condition Requirements: Custom Condition Logic is Met
      • Condition Login: (1 OR 2 OR 3) AND (4 OR 5)
        1. Field: Reason, Operator: Equals, Value: {!$GlobalConstant.EmptyString}
        2. Field: Type, Operator: Equals, Value: {!$GlobalConstant.EmptyString}
        3. Field: Quick_Summary__c, Operator: Equals, Value: {!$GlobalConstant.EmptyString}
        4. Field: Subject, Operator: Does Not Equal, Value: {!$GlobalConstant.EmptyString}
        5. Field: Description, Operator: Does Not Equal, Value: {!$GlobalConstant.EmptyString}
  • Optimize the Flow for: Actions and Related Records

The configuration screen for the record-triggered flow.

Adding the Summarize and Categorize Case prompt template action

We can use our Summarize Classify Cases prompt template as a Flow action. We’ll add a Prompt Template action as the first element in our flow, passing in the triggering record ({$!Record}). Passing the triggering record to the prompt template populates the merge fields in the template, so that we send the right data to our LLM every time. This seamless integration between data and prompting is what makes Prompt Builder so useful.

Add an Action element under the Start element. In the New Action modal, search for your “Summarize Classify Cases” prompt template and select it. Configure it as follows:

  • Label: Summarize Classify Prompt Template
  • API Name: Summarize_Classify_Prompt_Template (default value)
  • Input Value: {!$Record}

The Prompt Template action for summarizing a Case record.

When our Action executes, it will create an output: Summarize_Classify_Prompt_Template.promptResponse.

Deserializing the prompt template response

The output from Summarize and Summarize Classify Cases prompt template outputs as a string. If our goal was to output the response as free-form text, that would be fine, but we’re working with a JSON string. The Parse Case Summary Apex action will transform this string into an Apex object that exposes the individual caseType, reason, and summary fields for later use in the flow.

Add an Action element after the “Summarize Classify Prompt Template” action. Search for “Parse Case Summary” in the New Action search box and select the Apex class.

Configure the Parse Case Summary to use the output of your template:

  • Label: Parse Prompt Template Response
  • API Name: Parse_Prompt_Template_Response (default value)
  • Include: Enabled
  • Input Value: {!Summarize_Classify_Prompt_Template.promptResponse}

The Apex action for parsing our prompt template response.

The output of the Parse Prompt Template Response action is a Parse_Prompt_Template_Response resource that contains reason, type, and summary values.

Handling existing field values

Our flow triggers when a reason or type field is empty. There might be some cases that have a type but no reason, or visa-versa. We wouldn’t want to override a field that’s already been set. Here’s where Formulas come in handy. We can create a Formula resource that will only use our AI-suggested field values when the corresponding field on the record is blank. This approach keeps the existing field value if one is present, and only uses the AI-generated value if the field is empty.

Create Formula resources for the Parse_Prompt_Template_Response.reason, Parse_Prompt_Template_Response.type, and Parse_Prompt_Template_Response.summary as follows.

  • Reason
    • API Name: Reason
    • Resource Type: Formula
    • Data Type: Text
    • Formula: IF(ISBLANK(TEXT({!$Record.Reason})), {!Parse_Prompt_Template_Response.reason}, TEXT({!$Record.Reason}))
  • Type
    • API Name: Type
    • Resource Type: Formula
    • Data Type: Text
    • Formula: IF(ISBLANK(TEXT({!$Record.Type})), {!Parse_Prompt_Template_Response.caseType}, TEXT({!$Record.Type}))
  • Type
    • API Name: Summary
    • Resource Type: Formula
    • Data Type: Text
    • Formula: IF(ISBLANK({!$Record.Quick_Summary__c}), {!Parse_Prompt_Template_Response.summary}, {!$Record.Quick_Summary__c})

The Formula Resource for returning a Reason field value.

Using these Formula values, we can ensure that we only replace empty or missing values with our Flow.

Updating the Case record fields

Our final step is updating the triggering record. Add an Update Records element to the end of the flow. Use the Reason, Type, and Summary Formula resources to update the Record.reason, Record.type, and Record.Quick_Summary__c fields, respectively, as follows.

  • Label: Summarize Classify Update
  • API Name: Summarize_Classify_Update (default value)
  • How to Find records to Update and Set Their Values: Use the case record that triggered the flow
  • Set field values:
    • Type ← {!Type}
    • Reason ← {!Reason}
    • Quick__Summary__c ← {!Summary}

The Update Records element for updating the triggering record.

Testing the flow

Now that you’ve finished building your flow, it’s time to test it out. You can test your flow by running the debugger in Flow Builder on Case record 00001002 This will allow you to see how your flow would change the record without making any changes.

The Debug Details screen showcasing the Flow interview.

If you’re happy with your flow, activate it by clicking Activate. To test the activated flow, navigate to the case’s record page, remove the reason or type fields, and save. Your update will trigger the flow, which will automatically re-populate the field(s) using our case classification flow.

Voila! We’ve automated how we label and classify cases with Prompt Builder, Flow, and a sprinkling of Apex.


Using traditional predictive AI, we’d need to build and train at least two custom models using pre-labeled data to summarize and classify cases successfully. This could take weeks, if not months. We achieved the same effect in an afternoon with the power of large language models and Prompt Builder.

The approach outlined in this post is adaptable. With small modifications to the prompt template, Flow, and Apex class, you could easily rework this automation to classify any number of standard or custom Salesforce objects, such as categorizing Leads by industry and revenue potential, classifying Opportunities by product line and deal size, and tagging Knowledge Articles by topic.

To learn more about the tools and technologies used in this post, check out the Trailhead modules for Prompt Builder, Apex, and Flow Builder.

Large language models open up a world of possibilities for AI-powered automation within Salesforce. With creativity, configuration, and a bit of code, you can build AI-powered solutions faster than ever before.


About the author

Charles Watkins is a Developer Advocate at Salesforce and a full-stack software developer focused on the core Salesforce Platform. You can find him on X.

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS