Find Similar Items Using Embeddings
The following code sample shows how to generate embeddings to determine the similarity of item names using a Suitelet and llm.embed(options). For more information about Suitelets, see SuiteScript 2.x Suitelet Script Type.
The sample starts by defining a helper function, cosineSimilarity()
, that calculates the cosine similarity of two vectors. This function is used later in the sample to compare the embeddings of a selected item with those of other items to determine how similar they are. For more information about cosine similarity, see Cosine similarity.
Next, the sample defines the onRequest()
function, which will be provided to the onRequest
entry point for Suitelets. For a GET
request, the sample creates a simple form with a dropdown list to select an item. A SuiteQL query is used to retrieve available items and populate the list. The form also includes a submit button.
For a POST
request, the sample creates a list of inputs to provide to llm.embed(options). Each input contains an item name, and llm.embed(options) generates embeddings for each one using the Cohere Embed English model. Note that you can't use the same models you use when calling llm.generateText(options) to generate embeddings and must use a dedicated embed model. For a list of available embed models, see llm.EmbedModelFamily.
Finally, the sample builds an array of similarity results and uses cosineSimilarity()
to calculate how similar the selected item is to other available items. The similarity results are sorted by highest similarity value, and the results are displayed. The similarity value is between 0 and 1, with higher values representing greater similarity to the selected item.
The following screenshots show the UI of the Suitelet and the calculated similarity results:
This script sample uses the define
function, which is required for an entry point script (a script you attach to a script record and deploy). You must use the require
function if you want to copy the script into the SuiteScript Debugger and test it. For more information, see SuiteScript 2.x Global Objects.
/**
* @NApiVersion 2.1
* @NScriptType Suitelet
* @NModuleScope SameAccount
*/
define(['N/ui/serverWidget','N/query', 'N/llm'],
function(serverWidget, query, llm) {
function cosineSimilarity(array1, array2) {
const dotProduct = array1.reduce((sum, value, index) => sum + value
* array2[index], 0);
const magnitude1 = Math.sqrt(array1.reduce((sum, value) => sum +
value * value, 0));
const magnitude2 = Math.sqrt(array2.reduce((sum, value) => sum +
value * value, 0));
return dotProduct / (magnitude1 * magnitude2);
}
function onRequest(context) {
if (context.request.method === 'GET') {
var form = serverWidget.createForm({
title: 'Item Similarity Form'
});
var selectField = form.addField({
id: 'custpage_myselect',
type: serverWidget.FieldType.SELECT,
label: 'Select an Item to find similar items for'
});
var res = query.runSuiteQL("SELECT id, case when displayname
is not null then displayname else itemid end name from item
where rownum <= 96 order by id").asMappedResults();
for (let i = 0; i < res.length; i++)
{
selectField.addSelectOption({
value: res[i]['id'],
text: res[i]['name']
});
}
form.addSubmitButton({
label: 'Submit'
});
context.response.writePage(form);
} else {
var selectedValue = context.request.parameters.custpage_myselect;
var res = query.runSuiteQL("SELECT id, case when displayname
is not null then displayname else itemid end name from item
where rownum <= 96 order by id").asMappedResults();
const inputs = [];
let selectedName;
let selectedIndex = 0;
for (let i = 0; i < res.length; i++)
{
if (res[i]['id'] == selectedValue)
{
selectedName = res[i]["name"];
selectedIndex = i;
}
inputs.push(res[i]["name"]);
}
var embeddingResult = llm.embed({
embedModelFamily: llm.EmbedModelFamily.COHERE_EMBED_ENGLISH,
inputs: inputs
});
var item = embeddingResult.embeddings[selectedIndex];
var similarityResults = [];
for (var j = 0; j < embeddingResult.inputs.length; j++)
similarityResults.push({
itemName: embeddingResult.inputs[j],
similarity: cosineSimilarity(item, embeddingResult.embeddings[j])
});
similarityResults.sort((a,b) => b.similarity - a.similarity);
context.response.write(JSON.stringify(similarityResults, null, 2));
}
}
return {
onRequest: onRequest
};
}
);