Sunday, 16 July 2023

Salesforce CLI OpenAI Plug-in


Image generated using StableDiffusion 2.1 via https://huggingface.co/spaces/stabilityai/stable-diffusion

Introduction

I've finally found time to have a play around with the OpenAI API, and was delighted to find that it has a NodeJS library, so I can interact with it via Typescript or JavaScript rather than learning Python. Not that I have any issue with learning Python, but it's not something that is that useful for my day to day work and thus not something I want to invest effort in right now.

As I've said many times in the past, anyone who knows me knows that I love a Salesforce CLI Plug-in, and that is where my mind immediately went once I'd cloned their Github repo and been through the (very) quick overview. 

The Plug-In

I wasn't really interested in surfacing a chat bot via the Salesforce CLI - for a start, it wouldn't really add anything over and above the public OpenAI chat page. 

The first area I investigated was asking it to review Apex classes and call out any inefficiencies or divergence from best practice. While this was entertaining, and found some real issues, it was also wildly inaccurate (and probably something my Evil Co-Worker will move forward with to mess with everyone). 

I then built out a couple of commands to generate titles and abstracts for conference sessions based on Salesforce development concepts, but as the Dreamforce call for papers is complete for this year, that didn't seem worth the effort.

I landed on a set of explainer commands that would ask the AI model to explain something to help a more junior developer:

  • apex - explain a topic in the context of the Apex programming language
  • cli - explain a Salesforce CLI command, along with an example
  • salesforce - explain a Salesforce topic using appropriate language for a specific audience - admins, devs, execs etc.
Accessing the OpenAI API is very straightforward:

Install the OpenAI Package

> npm install openai

Create the API instance
    import { Configuration, OpenAIApi } from 'openai';

    const configuration = new Configuration({
        apiKey: process.env.OPENAI_API_KEY,
    });
    const openai = new OpenAIApi(configuration);
Note that the API Key comes from the OPENAI_API_KEY environment variable - if you want to try out the plug-in yourself you'll need an OpenAI account and your own key. 

Create the Completion

This is where the prompt is passed to the model so that it can generate a response:
    const completion = await openai.createCompletion({
        model: 'text-davinci-003',
        max_tokens: maxTokens,
        prompt,
        temperature
      });

      result = (completion.data.choices[0].text as string);
Note that the completions API is legacy and received it's final update in July 2023. I chose this simply because it's cheap. When you are writing a new plug-in connecting to a third party system you get a lot of stuff wrong and I was keen not to rack up too large a bill while I was learning! The Chat Completions API is slightly different, in that it receives a list of messages and can call functions with results. It's not dramatically different though, so I felt anything I learned applied to both.

I moved this into a shared function, hence most of the call is parameterised. The createCompletion function can take many parameters, but here's the explanation of those that I've used:
  • model - the large language model to use
  • max_tokens - the maximum number of tokens to generate the response. A token is typically thought to be around 4 characters, so will sometimes represent words and sometimes parts of words. Input and output token counts are how you get charged for API use, so you want to keep them down where you can. The function defaults to 16 which in practice is fairly useless for a response to a human, so I've typically upped it to 256
  • prompt - the question/task that I've set the model
  • temperature - a value between 0 and 2.0 that indicates how accurate a response I want. I find that 0.6 works pretty well. If you change this, do it in small increments, as I asked it to review an Apex class with a temperature of 1.5 and got pretty much gibberish.
And that's about it. The rest of the code is available at the Github repository - it's mostly standard CLI Plug-In stuff of handling flags and displaying the response.

The Plug-In In Action

Once I've installed the plug-in from NPM, using the following command :

> sf plugins install bbai

I can ask for some explanations. First, explain the Salesforce concept of groups. As I don't provide details of the audience, it defaults to programmers:

> sf bbai explain salesforce -t groups

Here are the results



Groups in Salesforce are a way to organize and manage users in the system. They allow you to categorize users based on their roles and responsibilities, allowing for more streamlined access to information. For example, you can create a group for all the programmers within your organization, allowing them to access the same resources, documents, and tasks. Additionally, groups can be used to assign tasks, send messages, and create reports. By creating groups, you can quickly and easily manage permissions, tasks, and data access for your programming team.


Now the same command for an exec audience - notice the difference?

> sf bbai explain salesforce -t groups -s executive

Here are the results



Groups in Salesforce allow organizations to manage and control access to records and data. Groups are used to assign users to roles within the organization and to grant them access to the appropriate records. By using groups, organizations can easily assign the same set of permissions to multiple users, allowing them to quickly and easily manage access rights. This makes it easier to manage user permissions, and also helps to ensure that users only have access to the data and records they need to do their job. Groups also streamline the process of granting and revoking access rights, making it easier for organizations to stay secure and compliant.


And now I want to start working with some of these concepts, but I don't understand how to get my code into the instance via the CLI:

> sf bbai explain cli -c "source push"             

Here are the results



Salesforce CLI command source push is used to deploy source from the local environment (e.g. a developer’s machine) to a Salesforce org. This command is useful in a DevOps process, as it enables developers to quickly and easily deploy their code changes to a Salesforce org for testing and deployment.


Example Execution: 

sfdx force:source:push --targetusername devorg --sourcepath /path/to/source/directory/


The command above will deploy all source code in the specified directory to the org with the username devorg.

Conclusions

Something that really stood out in this was the simplicity of the integration - it's literally a few lines of code. Which is as it should be - with generative AI, English is the programming language so most of the effort should go into the prompt rather than getting set up to send the prompt. Looking ahead a few years, I can see this being a game changer for integrating systems. Rather than starting with a book of API specifications that you need to adhere to, there will be a single entry point where you send conversational requests to a model trained on the specific data. "Return the id and all name and address fields of all the contacts we have that work for financial services customers in JSON format", without needing to develop that specific interface.

The Cost

This was top of mind for me during this experiment. I've seen situations in the past where request based charging really racked up during testing. OpenAI allows you to set a hard and soft limit, so that you can't get into that situation, but it also charges by the token for both input and output. When you aren't that familiar with how many tokens might be in a 2-3 line prompt. After building my plug-in, with the usual number of mistakes, and testing it for several hours, I'd used the princely sum of $0.16. 

While that might sound like there's nothing to worry about, but the latest models are more expensive per token and can handle significantly more tokens both for input and output, so you may find that things stack up quickly. I've seen news stories lauding the ability to send an entire book to a generative AI as context, but no mention of how much you'll be charged to do that!

More Information






Sunday, 9 July 2023

Salesforce World Tour London : AI Day

AI Day London

A steampunk image of a machine with a brain.

Image created using Stable Diffusion 2.1

It's been just over a week since Salesforce World Tour : AI Day, and I'll wager less than two weeks since the events team found it it was going to be rebranded AI Day! Kudos to them though, it was seamless and if you weren't following the tech news you'd never have guessed!

Those of us attending the UK event experienced great timing this year. The fact that it happened a few weeks after the launch of AI Cloud in New York means that cutting edge features have just reached demo state well outside of the usual release cycle. For example, we got the first public demo of Prompt Studio, which is the kind of thing that would typically land at Dreamforce in San Francisco, rather than a World Tour event on the other side of the world. I'm also fairly sure that we wouldn't have been treated to a deep dive in machine learning from Patrick Stokes if he wasn't leading the AI technical charge. 

Spare a thought for the poor execs who are suddenly having to trip out a lot more than usual at this time of year, but if you want to position yourself as a leader in the AI space, you need some representation from Head Office. Bad news for them, but good news for us.

Prompt Studio looks good from what we saw in a short demo in a keynote. As expected, Salesforce are taking a cautious approach with guardrails around not just the AI, but who can create the prompts that drive the AI. There's some chatter on the socials that every admin is now a prompt engineer, but that feels like a simplistic view to me. Marketing and Legal are a couple of departments that will be very interested in contributing to and signing off prompts, rather than viewing it as regular configuration. Any admin who does become a prompt engineer could be looking at a rather lucrative career though, as that role is currently paying up to $335k/year, although it isn't expected to be around that long.

We also saw several other demos of GPT-as-an-Assistant across Sales, Service, Slack and Platform. This last one was particularly interesting for me, covering as it did automatic generation of Apex code from descriptive comments. This represents a significant change for us developers from the usual enhancements on the development side - typically we are keen to learn the detail of the new features so that we can start customising Salesforce with them. With GPT it's closer to handing off our work to a junior colleague - explaining what we want, waiting for it to appear and then reviewing it to see how well it matches our requirements. And, if history is anything to go by, realising that there was a whole layer of complexity that we forgot to mention!

One constant attendee at the London events is Kavindra Patel, VP of Trailblazer Engagement at Salesforce. I always try to catch up with him, and this year was no different. In a shocking turn of events, he let me in on the secret that there's quite a focus on AI at Salesforce right now!

And, of course, we had the Golden Hoodie award. This year it went to my fellow MVP and co-organiser of the London Salesforce Developer group, Todd Halfpenny, seen here preparing for a duel with Zahra Bahrololoumi CBE, CEO of Salesforce UK&I.

You can catch up with the World Tour keynote, and various other sessions, on Salesforce+. If you weren't able to attend in person I'd highly recommend it, as it is a good overview of where Salesforce will be in a few months once it's all built out. If you are interested in AI in general or AI Cloud in particular, but not sure where to start, join me on 19th July when I'll be doling out advice about preparing for the rise of the machines. 

Additional Information





Tuesday, 4 July 2023

Predictions for Digital Life in 2035

Predictions for Digital Life in 2035


Pew Research Center recently published the results from their 16th "Future of the Internet" research that aggregates experts' opinions on important digital issues. 

As you can imagine much of the focus was on Generative and other Artificial Intelligence applications. A fair number of the respondents were enthusianxious about the changes, with 42% being equally excited and concerned about what they expect to see, 37% being more concerned than excited. and only 18% more excited than concerned. Clearly the pessimists are currently in charge with a whopping 79% being as-or-more-concerned than excited. 

The concerns will again come as no surprise to anyone who has been following the AI news and opinion over the last year or two:

  • That the motivation will be for profit and power, leading to data collection aimed at controlling or coercing behaviour with ethics as an afterthought
  • Loss of privacy and jobs, leading to a rise in poverty and reduction in dignity
  • Human knowledge drowning in an ocean of meaningless or flat out wrong information generated by or using AI
  • Health impact as tech encourages us to become even more isolated from humans, or feeds our worst paranoia
  • Government and regulation being unable to keep up with the pace of change, with an end game of autonomous weapons and cyber warfare being waged by machines without human oversight. Essentially the first steps towards Terminators.
It's not all bad news though, with benefits expected to include:
  • Enhancements in healthcare, education, fitness, nutrition, entertainment, transportation and energy. Our digital assistants will free us up from much of the drudgery, leaving us more time to enjoy these improved offerings, which will of course be entirely integrated and friction-free.
  • Increased amplification when people speak up for their human rights, and easy collaboration with others who wish to mobilise to demand the same. Access to data and better communication tools will help people live better and safer lives around the globe.
  • Improved digital literacy, with the desire that this will see the return of trusted news and information sources. Failing that, at least an assurance that information is factual and verified.
  • A regulatory environment that promotes pro-social activities and minimises anti-social ones. 
One thing that jumps out at me from the list of negatives and positives is how much more detailed the Orwellian nightmares are - many of those that are concerned have really thought those concerns all the way through to the bitter end!

It's a relatively lengthy report at 230 odd pages, but an interesting read, and at times entertaining (especially the more extreme views, as always).  An awful lot of those canvassed expect most technological innovations to be a double-edged sword - Howard Rheingold, author of "The Virtual Community", advises us to ask of any new technology 'What would 4chan do with it?'. Jonathan Grudin, Affiliate Professor of Information Science at the University of Washington, paints a picture of "a Sorcerer’s Apprentice’s army of feverishly acting brooms with no sorcerer around to stop them." as the sheer scale of digital activities, and the incredible speeds at which they are carried out, far outpaces our ability to verify and correct, so we stand by helplessly and watch.

Louis Rosenbery, CEO and Chief Scientist at Unanimous AI, predicts that we'll be in full Star Trek mode by 2035, as keyboards, mice, touchscreen input and flat screen display output are swept aside by conversational interfaces. Like Captain Kirk, we'll just ask the computer to carry out a task in plain language (although for some reason Kirk still needed humans to change the velocity and direction of the Enterprise, and patch through a call, so maybe our jobs are safe after all).

If you have a couple of hours to spare, it's well worth a read. My main takeaway is that even those who are positive are sounding a note of caution equally loudly, and I think that's an appropriate view. It dovetails nicely with most of the work that I do in my professional life - I'm not so much looking for things that will work correctly, as a lot of that is fairly obvious. Instead I'm looking for problems - what won't work, are there unintended side effects, is it future-proof, will it scale? It's easy to get caught up in the hype and hoopla of an emerging technology like Generative AI, and let the FOMO trick you into doing something rash. With any new technology, always take a step back, a deep breath, and evaluate with cold eyes.

Will 2035 be digital heaven or digital hell? I think, like always, it will be six of one, half a dozen of the other - a constant battle that leads to an uneasy balance.

Additional Information





Sunday, 14 May 2023

Light DOM Scoped Slots in Summer 23

Introduction

It's that time again - another Salesforce release is a few weeks away, the preview release notes are out, and preview scratch orgs can be spun up to try out some of the new features. There's a few changes around Lightning Components in the upcoming release, and the first one that caught my eye was the concept of scoped slots, made possible by the general availability of the light DOM.

The light DOM is what we used to call the DOM before web components came along - the standard Document Object Model representing the structure of a web page that is visible and accessible to any JavaScript that cares to look. The light aspect comes from the fact that web components by default use shadow DOM - a hidden DOM structure attached to the regular DOM that only the component can see. 

Scoped slots are an inversion of the usual slot behaviour seen with Lightning Web Components. The standard case allows a parent component to pass markup into a child component to render in specific locations. Scoped slots turns this on its head and allows child components to pass information up to parent components that can be rendered in a specific slot. All of which sounds very cool, but at first glance it seemed a solution looking for a problem. The example in the release notes didn't do much to change this view, as they showed a child iterating a list and the parent rendering the contents of the list item. Given that the parent can iterate the list just as easily as the child, I couldn't see the value that was added.

One thing I've found with Lightning Web Components is a lot of the new features have equivalents in another JavaScript framework - Vue.js seems to be the prime candidate at the moment, and sure enough there's the concept of scoped slots there. Unfortunately the various blogs that I read on this topic didn't help my understanding enormously - when you aren't familiar with the framework then the examples aren't always helpful. What I did take away from this is the value in scoped slots is to separate the generation of the data from the rendering, so that it is available for re-use. The release notes example didn't really provide this as the list iteration is the same regardless of whether you do it in the parent or child, so I needed to figure out another use case.

The Sample

Simple list iteration is easy, but what about the case where I have a JavaScript object created from an Apex Map and want to iterate that? I can't do this in markup, instead I have to create a list of properties from the object. This feels like a situation where a child component that can handle any conversion and iteration required and simply send the properties back to the parent. 

My component that handles this is called mapIterator, and when it receives an object via an @api property, it creates a list from it:

set objMap(value) {
    this._objMap=value;
    if (this._objMap) {
        this.values=[];
        for (let key in this._objMap) {
            this.values.push(this._objMap[key]);
        }
    }
}

and the HTML markup simply iterates this and makes each entry available to the parent via the lwc:slot-bind directive, remembering to render in light DOM mode:

<template lwc:render-mode="light"> 
    <template for:each={values} for:item="value">
        <slot key={value} lwc:slot-bind={value}></slot>
    </template>
</template>

I have several parent components that make use of this - one to generate a simple list of accounts, one to generate a list of account cards, and one to generate a list of opportunity cards. The mapIterator provides the conversion and iteration of the object properties to each of this with no changes required. You can find all of these at the Github repository, but here's the markup from the simple list :

<c-map-iterator obj-map={accountsMap}>
    <template lwc:slot-data="value">
        <div class="slds-p-left_small slds-p-bottom_xx-small">
            <strong>ID:</strong> {value.Id}
        </div>
        <div class="slds-p-left_large slds-p-bottom_small">
            <strong>Name:</strong>{value.Name}
        </div>
    </template>
</c-map-iterator>

Access to the element from the iterator is provided by the lwc:slot-data directive, and as you can see the parent handles all the presentation side of things.

Conclusion

There is definite value here in separating the conversion/iteration from the rendering of the content. That said, I think you might run into issues if you are rendering the iterated elements using markup that must be a direct descendant of a containing element. In that case you won't be able to separate everything, as you get the child element markup in the way. I think there you'd likely need specialisations of the iterator that also renders the container, which won't feel quite as clean.

More Information




Sunday, 30 April 2023

Reactive Flow Components in Spring 23


Introduction

A typical scenarios where I favour lightning pages over flows is where a user's decisions around field values need to have a major impact on the rest of the elements being displayed, and where a wizard interface would slow the user down too much. A great example of this is our BrightMEDIA order booking page. As the user inputs information about the type of advert, where it needs to appear, what size it is and when it appears, the price is dynamically recalculated and displayed. The user typically has the customer on the phone, and there may be a few cycles of tweaking the details to hit a price point - what happens if we make it a bit smaller, move it to another section etc. Paging back and forward through the wizard wouldn't be a great experience, along with the need to keep saving the order as navigation between pages takes place. 

With the Spring 23 release of Salesforce, it looks like in future flow will become a candidate for this kind of user interface with the advent of the Reactive Flow Components beta. Simply put, you can wire the input of one component up to the output of another component, and react when changes occur. 

As this is a beta, you'll have to opt-in to it for any org where you want to use reactive components by checking the box in the Automation Settings 

The Sample

A lot of the demos around reactive anything tends to be a table of accounts that updates when the user inputs the desired number of entries. That's exactly what I started with to get a handle on how this works, but it's not overly interesting, so I decided to return to our BrightMEDIA accelerator for inspiration. 

One of the more complex Lightning Components we have in the order booking page is the data picker. This can render as a drop down or a calendar, with dates available for selection rendered in specific styles depending on whether capacity is available, if they are contiguous with a selected start date, and many other rules. 

So for my sample I have a couple of custom Lightning Web Components:

  • A Publication Picker - this is a simple choice style component, but I've had to go custom as the standard choice component doesn't yet have reactive support. Each publication has a checkbox that indicates if it publishes on weekends as well as weekdays.
  • A Date Picker - this is a calendar component that by default displays a 7 day week, but if the selected Publication doesn't publish on weekends, switches to a 5 day week.
I create a simple screen flow and add a two column screen component, with my two custom components:



The Publication Picker defines an output property named publishesWeekends, which is set to the value from the selected Publication. I wire this to the weekends input property on the datePicker:


When I run the flow, as I switch my choices between Publications, the Date Picker changes how it renders to reflect 7 days a week publishing:


or 5 days a week:


It's early days for this, but definitely a step in the right direction. This feels like an area where you'll benefit from developer support to create truly reactive experiences, as this will really pop with custom components that can change their styling based on user selections. 

The Code


You can find the code behind the sample in Github. Spin up a scratch org, deploy the code, create a couple of publications and run the flow.  Don't forget to opt in to the beta as outlined above, or like me you'll sit there scratching your head trying to figure out what you've broken :)
More Information



Saturday, 15 April 2023

BrightSIGN: Updating a Record when a Signature is Captured


It's been a while since I've had the time to work on the BrightSIGN (formerly known as Signature Capture), but I had a couple of days off this week and what better way to spend them than a bit of front end coding.

The Problem

A feature requested by a user posting a review on the app exchange was the ability to update a field on the record that the signature image is attached to. There are a number of ways to achieve this, but one thing was for sure - this wasn't going into the package. Dynamically generating update statements to change records in the subscriber org is the kind of thing that really slows down the security review!

The scenario I wanted to satisfy was a Lightning App Builder page that would show a BrightSIGN component, and once the user had saved the signature image, update the record and hide the BrightSIGN component so they weren't asked to sign multiple times.

I was originally thinking that I'd go the route of a trigger on Attachment/ContentDocument, depending on how the component was configured, but I decided against this for a couple of reasons - 

  1. I'd have to make some assumptions about the name of the attachment/content document in order to determine that it had been created by BrightSIGN, which would lead to false positives if attachments/documents with the same name were used elsewhere
  2. I'd be detached from the front end and unable to signal to the container that the record had been updated - this is key when I want to conditionally render parts of the page depending on whether the record has been 'signed' or not.

The Solution


I ended up writing very little code to satisfy this requirement, as I was able to leverage the existing SignatureCaptured event and the force:recordData standard component.  In the example scenario I'm setting a field with the API name 'Signature_Captured__c' on an Account record once the user has 'signed' it.

To start with, I created a Lightning App Builder record page for Accounts, which I dropped a BrightSIGN component onto. This component is conditionally rendered and only appears if the Signature_Captured__c checkbox is not set :



I then created a new local component - SigCapUpdateRecord - which retrieves the field that I'm interested in for the current record using the force:recordData component, thus saving me writing any Apex code :
<force:recordData aura:id="recordUpdater"
    recordId="{!v.recordId}"
    fields="Signature_Captured__c"
    targetFields="{!v.theRecordFields}"
    targetError="{!v.recordLoadError}"
    mode="EDIT"
    />    

This component also has a handler for the SignatureCaptured event:

<aura:handler event="BGSIGCAP:SignatureCapturedEvt" action="{!c.handleCaptured}"/>

Thus when the user saves the signature image, the handleCaptured method from my component's controller is invoked. This sets the Signature_Captured__c field and updates the record using the force:recordData component, again avoiding me having to write any Apex code. Once the update is complete, the view is refreshed to notify the container that it's changed and allow any conditional rendering to be re-evaluated:

handleCaptured : function(component, event, helper) 
{
    component.get('v.theRecordFields').Signature_Captured__c=true;

    component.find("recordUpdater").saveRecord(function(saveResult) {
        var resultsToast = $A.get("e.force:showToast");
        if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
            resultsToast.setParams({
                "type":"success",
                "title": "Record Signed",
                "message": "The record whas been marked as signed."
            });

            resultsToast.fire();

            $A.get("e.force:refreshView").fire();
        }
        
        // error handling
    });
}

You can see a very brief video of this below :


The Code


You can find the code in the BrightSIGN Samples repository - look for the SigCapUpdateRecord component. Note that you will need to either deploy the samples or manually create a Signature_Captured__c field on the Account sObject in order to try this out.

Related Posts




Saturday, 1 April 2023

Artificial Indolence - Your Automated Accomplice



Hot on the heels of the Einstein GPT launch at TrailblazerDX, it gives me great pleasure to announce Artificial Indolence. If you are lazy, workshy, or just plain incompetent, Artificial Indolence can help you to hang on to your job while underperforming, far longer than anyone would reasonably expect.

Artificial Indolence is a general purpose tool (GPT), with use cases across the enterprise, including:

Case Work Deflector

The lazy service rep's worst nightmare - in spite of the best efforts of Einstein, the customer was able to raise a case and it's been assigned to you. Drawing on a training set of over a billion work-dodging communications, Artificial Indolence presents you with a tailored response that clearly explains why you are the worst person to be assigned work for the specific customer and problem. Clearly calling out your lack of ability and experience, it will be obvious to any manager that this can only end in disaster. If you've chosen the Communication Bus add-on, it will also identify a range of your peers who the work would be better assigned to and throw them under the bus.

Closed Lost Justifier

Our most successful feature to date, and perfect for the sales rep who just can't be bothered. When your lack of effort inevitably leads to an Opportunity being marked Closed Lost, Artificial Indolence generates a plausible reason why the stars didn't align in spite of your herculean efforts.

Activity Delayer

Rather than disappointing your customer all at once by not completing a job on time, drip feed the disappointment over weeks if not months with Artificial Indolence Activity Delayer - the customer will receive confusing communications outside working hours, and with any luck will blame themselves for not responding. If you've sprung for the Meeting Avoidance add-on, Indolence will automatically schedule meetings when most of the invitees have conflicts - the meeting will never happen and it's because everyone other than you is too busy!

Coverage Concealer

One for the developers who aren't interested in writing unit tests - Artificial Indolence will generate huge classes with 100% test coverage to bring the org coverage up to 75%. As these classes are based on genuine examples found in other orgs, they serve a real purpose (unless the examples were also generated by Indolence of course). Good enough to convince Salesforce to increase your Apex character limit, or three months free!


As befits it's purpose, Artificial Indolence is right now a collection of smoke, mirrors, mock ups and brochureware. Stay tuned for details of the pilot program that nobody will be accepted on to.