R&D Projects

icon picker
SmartSuite AI with Live Internet

A Simple Experiment with Google Apps Script, PaLM 2, and SmartSuite
If you came here from the for the source code, feel free to .
My interest in SmartSuite has grown recently, so I thought I’d try to understand its API boundaries by instrumenting an automated AI workflow. Once I had that working, I turned my attention to an approach that includes live Internet content much the way I did for
. I chose Google Apps Script for the scripting environment for this initial SmartSuite excursion because it’s so simple to script up API calls and deploy them for automated updates. I already had a rich supply of functions I had developed for Google’s PaLM 2 LLM that I write about on from time to time.

Generative AI is Big

Anyone who says it isn’t, probably thought the World Wide Web was a fad. It will change everything. But to do so, each of our little microcosms must adapt. SmartSuite’s initial step in the AI direction, a writing assistant in SmartDoc fields, is wonderful. But it requires humans to do work, and by work, I mean getting good at AI prompts, which is clearly a challenge.
Inferences need to possess three key attributes to pay hyper-dividends.
Templated
Abstract
Automated
Unless you can define prompts that perform fluidly in a data-centric climate, the hyper-productivity made possible by generative AI will be fleeting at best, and counter-productive at worst. Let’s examine the current SmartDoc AI feature as it relates to these three attributes.
It’s a one-and-done feature. You select a predefined prompt or write one yourself, and it generates content using the OpenAI LLM. If you don’t create the ideal prompt, you must start over. The insert below feature is a plus, but this is not a template - it cannot be saved or reused again and again.
CleanShot 2023-09-03 at 06.29.10@2x.png
It is not abstracted from the writing canvas. This is to say that this AI feature cannot exist as a component that can be refreshed, tuned, or modified. It is not an object that an automation process can address.
These are all valid shortcomings, but I understand — it’s beta and early in the march toward the full embrace of generative AI.

Project Vision

In my zest to understand SmartSuite’s API, I quickly envisioned a SmartDoc field as an AI canvas—a place to render the results for external generative AI workflows. I’m a fan of the SmartDoc field because I’ve long believed that data-centric solutions need to lean on narratives as much as text-centric solutions need to lean on data. It’s no secret that I’ve found a delightful balance in
.
question-mark
Could AI write an entire report and place it in a SmartDoc? Could a collection of rows do this for different reporting outcomes?
Achieving this, even without the inclusive benefits of data from the live Internet, is advantageous. It meets all three of the key attributes for hyper-productive benefits of generative AI. However, achieving this in the current state of SmartSuite requires using its API (as far as I know).
error
CAUTION: I am new to SmartSuite, and there’s a possibility I missed some key functionality concerning the ability to blend generative AI into rows and fields. I also believe that Make or many of the workflow adhesives could be used to achieve similar outcomes. Still, I wanted to explore a sustainable pathway that didn’t involve higher costs or uncontrolled dependencies on middle-tier services.

Exploration Objectives

Everything stems from the main function, . The premise of this automated workflow is simple - at some interval, refresh all the AI fields. What defines an AI field? In my approach, any field name that begins with "AI.". The main function pulls the records from the designated app, looks for fields that contain AI prompts, and processes the prompts, sending the AI output to the designated field in the prompt.
In this example, the prompt is a formula that blends the data from each row; [News Live] is the designated SmartDoc field that will receive the output of the inference.
CleanShot 2023-09-03 at 06.51.13@2x.png
Example AI Prompt Field
In this formula field, I’ve instrumented the prompt to specify a dynamic stock symbol and a reference to include live Internet data. But what Internet data exactly?
This live data included in the AI inference is achieved by a call to whose results are compressed to ensure we can pass the results safely to the LLM in less than four-k bytes. The query bound by double curly brackets narrowly identifies the live Internet search results we want to hand the AI process to ensure its output is grounded in the most recent known information about the topic - on this example, recent news about Rivian’s stock.
The outcome of this inference is updated into the [News Live] target field, in this example. In contrast to inferences lacking access to the open web, this response includes news items from 2023. Formatting is not perfect, but passable.
CleanShot 2023-09-03 at 06.59.12@2x.png
Example Live AI Output
My prototype supports both live web and static inferencing based on the LLM (PaLM 2 in this case). It stopped training from the Web in early 2023, and this stasis is reflected in the News output on the left. But as you can see, the News Live field includes stories as recent as a few weeks ago. With GPT models, the contrast is worse - it sopped learning in mid- 2021, hence the push to create plugins that overcome this this weakness.
CleanShot 2023-09-02 at 19.40.06@2x.png
Comparative Static (left) and Live (right) AI Outputs

Takeaways

There’s no doubt that generative AI and SmartSuite have a bright future. I look forward to blending these ideas seamlessly and without writing a lot of code. until then, we code.

Source Code

This content includes the apps script server-side source code, but be forewarned - this is lean code. It does not have a lot of error handlers or clean variable management. Someday, perhaps, but for now, it’s just a skeleton to get something working.
One of the more interesting functions is . It’s rudimentary, so set your expectations low, but it handles the basic stuff pretty well. Since it’s easy to get LLMs to generate Markdown format, it sets up the possibility of transforming it into SmartDoc rich text objects. It’s apparent that I still have a lot to learn about this aspect of SmartSuite, but I really wanted to prove that generative AI from external workflows could target the SmartDoc field.

Refresh AI Fields

Main Function
//
// refresh ai fields
//
function refreshAIFields() {

// set the app id
var appID = cSSApplicationID;

// get the field structure
var response = listFields_(appID);
var oFields = JSON.parse(response).structure;

// get the records
var response = listRecords_(appID);
oRecords = JSON.parse(response).items;

for (var i in oRecords) {

for (let [key, value] of Object.entries(oRecords[i])) {

// Logger.log(JSON.stringify(oRecords[i]));
var recordID = oRecords[i].id;

// get this key's field name
var fieldName = getFieldNameByID(key, oFields);

// any ai fields?
// if ((value.toString().length > 0) && (fieldName.indexOf("AI.") > -1)) {
if ((value.toString().length > 0) && (fieldName.indexOf("AI.StockPriceLive") > -1) && (value.toString().toLowerCase().indexOf("live data: {") > -1)) {
// console.log(`${key}: ${value}`);

// extract and transform the prompt (if necessary)
var targetFieldName = value.toString().split("[")[1].split("]")[0];
var targetFieldID = getFieldIDByName(targetFieldName, oFields);
// console.log(`${key}: ${targetFieldName} / ${targetFieldID}`);

// extract live data (if it exists)
if (value.toString().toLowerCase().indexOf("live data: {") > -1) {

// parse the query
var q = value.toString().toLowerCase().split("live data: {{")[1].split("}}")[0];
Logger.log(q);

// perform the search
var liveData = serpAPI(q, "5d");
// Logger.log(liveData);
// DriveApp.createFile("serp.json", liveData);

// update the prompt to include the live data
var begPos = value.toString().toLowerCase().indexOf("live data: {{") + 11;
var endPos = value.toString().toLowerCase().indexOf("}}") + 2;
// value = value.substring(0, begPos) + "`" + "...liveData..." + "`" + value.substring(endPos);
value = value.substring(0, begPos) + "`" + liveData + "`" + value.substring(endPos);
// Logger.log(value);

}

// process the inference
var response = palmCreateTextCompletion_(value.toString());
Logger.log(response);
var oOutput = JSON.parse(response);
var output = oOutput.candidates[0].output;
// Logger.log(oOutput);

if (value.toString().toLowerCase().indexOf("markdown") > -1) {

var oContent = markdownToSmartDoc_(output);

var output = {
"data": {
"type": "doc",
"content": oContent
}
}
}

// update the target field
var payload = {
[targetFieldID] : output
}
// Logger.log(JSON.stringify(payload));
updateRecord(recordID, payload);

}
}

}
return(true);

}

List Fields

//
// list fields
//
function listFields_(appID) {

// set the app id
appID = (appID) ? appID : cSSApplicationID;

// build the url
var url = cSSAPIEndpoint + "applications/" + appID + "/";

// call the smartsuite api
const options = {
method: 'GET',
headers: {
'Authorization' : 'Token ' + cSSAPIToken,
'ACCOUNT-ID' : cSSAccountID,
'Content-Type' : 'application/json'
}
};
response = UrlFetchApp.fetch(url, options);

return(response);

}
I’m also happy to consult on more complex use cases for SmartSuite AI Live. Reach out anytime. I believe there is a horizon of opportunities to build more advanced applications based on this concept.

List Records

//
// list records
//
function listRecords_(appID) {

// set the app id
appID = (appID) ? appID : cSSApplicationID;

// build the url
var url = cSSAPIEndpoint + "applications/" + appID + "/records/list/";

// create the payload
var payload = {
"sort": [],
"filter": {}
}

// call the smartsuite api
const options = {
method: 'POST',
headers: {
'Authorization' : 'Token ' + cSSAPIToken,
'ACCOUNT-ID' : cSSAccountID,
'Content-Type' : 'application/json'
},
'payload' : JSON.stringify(payload)
};
response = UrlFetchApp.fetch(url, options);

return(response);

}


Get Field Name by ID

Helper Function
//
// get field name by field id
//
function testGetFieldNameByID() {
var fieldID = "s956e627d7";
Logger.log("FieldName: " + getFieldNameByID(fieldID));
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.