Use Tools to Enhance the Response from the LLM

Note:

The content in this help topic pertains to SuiteScript 2.1.

The following code sample shows how to create tools and pass them to the large language model (LLM) using a Suitelet. For more information about tools in the N/llm module, see Tooling in the N/llm Module. For more information about Suitelets, see SuiteScript 2.x Suitelet Script Type.

The sample starts by defining a set of tool definitions and tool handlers. Tool definitions use llm.createTool(options) to create llm.Tool objects, which represent the available tools that the LLM can request to enhance its response. Tool handlers contain the logic of each tool, and this logic runs when a tool call is requested by the LLM. The sample defines three tools that use SuiteQL queries from the N/query module.

Next, the sample defines functions that create the Suitelet form, display the conversation history, and run the main tooling loop. In the main tooling loop, the sample calls llm.generateText(options) to generate an initial response, providing the set of available tools along with the preamble, conversation history, and user prompt. After the initial response is received, the sample looks for any tool call requests (which are included in the Response.toolCalls property). If the LLM requested a tool call, the sample calls the associated handler and creates a tool result using llm.createToolResult(options). The sample sends the tool results back to the LLM in a subsequent call to llm.generateText(options), and it repeats this process until no further tool calls are requested.

Finally, the sample defines functions that display the results, create a form footer and add a submit button, and define the onRequest() function that's provided to the onRequest entry point for Suitelets.

Note:

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
 */

define(['N/ui/serverWidget', 'N/llm', 'N/query'], (serverWidget, llm, query) => {
    const TOOL_HANDLERS = {
      getNumberOfCustomerPurchasesForItem: (options) => {
        const res = query.runSuiteQL({
          query: "SELECT count(*) cnt from transactionline where entity=? and item=?",
          params: [options.userId, options.itemId]
        }).asMappedResults();
        return res[0]["cnt"];
      },

      findItemId: (options) => {
        const res = query.runSuiteQL({
          query: "SELECT id from item where itemid=?",
          params: [options.itemName]
        }).asMappedResults();
        return res.length === 0 ? -1 : res[0]["id"];
      },

      findUserId: (options) => {
        const res = query.runSuiteQL({
          query: "SELECT id from entity where fullname=?",
          params: [options.userName]
        }).asMappedResults();
        return res.length === 0 ? -1 : res[0]["id"];
      }
    };

    const TOOL_DEFINITIONS = {
        'findUserId': llm.createTool({
            name: "findUserId",
            description: "Looks up user id based on his/her name",
            parameters: [
                llm.createToolParameter({ name: "userName", description: "name of the user", type: "STRING" })
            ]
        }),
        'findItemId': llm.createTool({
            name: "findItemId",
            description: "Looks up item id based on its name",
            parameters: [
                llm.createToolParameter({ name: "itemName", description: "name of the item", type: "STRING" })
            ]
        }),
        'getNumberOfCustomerPurchasesForItem': llm.createTool({
            name: "getNumberOfCustomerPurchasesForItem",
            description: "Looks up how many times did user purchase given item, both user and item are specified by id.",
            parameters: [
                llm.createToolParameter({ name: "userId", description: "id of the user", type: "INTEGER" }),
                llm.createToolParameter({ name: "itemId", description: "id of the item", type: "INTEGER" })
            ]
        })
    }

    const createFromHeader = (context) => {
        const form = serverWidget.createForm({ title: 'Tool Use Example' });

        const fieldgroup = form.addFieldGroup({
            id: 'fieldgroupid',
            label: 'Chat'
        });
        fieldgroup.isSingleColumn = true;

        const history = form.addField({
            id: 'custpage_history',
            type: serverWidget.FieldType.LONGTEXT,
            container: 'fieldgroupid',
            label: "History"
        });
        history.updateDisplayType({ displayType: serverWidget.FieldDisplayType.HIDDEN });

        const numChats = form.addField({
            id: 'custpage_num_chats',
            type: serverWidget.FieldType.INTEGER,
            container: 'fieldgroupid',
            label: "History Size"
        });
        numChats.updateDisplayType({ displayType: serverWidget.FieldDisplayType.HIDDEN });
        return form;
    }

    const displayHistory = (context, form) => {
        const historySize = parseInt(context.request.parameters["custpage_num_chats"] || "0", 10);
        form.getField({id: 'custpage_num_chats'}).defaultValue = String(historySize + 2);
        //numChats.defaultValue = String(historySize + 2);

        // Recreate the chat history fields (in reverse order)
        for (let i = historySize - 2; i >= 0; i -= 2) {
            const you = form.addField({
                id: 'custpage_hist' + (i + 2),
                type: serverWidget.FieldType.TEXTAREA,
                label: 'You',
                container: 'fieldgroupid'
            });
            const yourMessage = context.request.parameters["custpage_hist" + i];
            you.defaultValue = yourMessage;
            you.updateDisplayType({ displayType: serverWidget.FieldDisplayType.INLINE });

            const chatbot = form.addField({
                id: 'custpage_hist' + (i + 3),
                type: serverWidget.FieldType.TEXTAREA,
                label: 'ChatBot',
                container: 'fieldgroupid'
            });
            const chatBotMessage = context.request.parameters["custpage_hist" + (i + 1)];
            chatbot.defaultValue = chatBotMessage;
            chatbot.updateDisplayType({ displayType: serverWidget.FieldDisplayType.INLINE });
        }
    }

    const runToolLoop = (prompt, chatHistory) => {
        const preamble = "You are an AI assistant. Don't be chatty. If you're asking for further
               information, keep it very brief and instructive. All the input variables for tools 
               have to be explicitly stated.";
        const tools = Object.values(TOOL_DEFINITIONS);

        let llmResult = llm.generateText({
            modelFamily: llm.ModelFamily.COHERE_COMMAND,
            preamble,
            tools,
            prompt,
            chatHistory: chatHistory ? JSON.parse(chatHistory) : undefined
        });

        while (llmResult.toolCalls.length > 0) {
            const toolResults = [];
            for (const call of llmResult.toolCalls) {
                const resultValue = TOOL_HANDLERS[call.name](call.parameters);
                const toolResult = llm.createToolResult({
                    call,
                    outputs: [{ result: resultValue }]
                });
                toolResults.push(toolResult);
            }

            llmResult = llm.generateText({
                modelFamily: llm.ModelFamily.COHERE_COMMAND,
                tools,
                toolResults,
                chatHistory: llmResult.chatHistory
            });
        }
        return llmResult;
    }

    const displayResult = (prompt, llmResult, form) => {
        const promptField = form.addField({
                id: 'custpage_hist0',
                type: serverWidget.FieldType.TEXTAREA,
                label: 'You',
                container: 'fieldgroupid'
            });
            promptField.defaultValue = prompt;
            promptField.updateDisplayType({ displayType: serverWidget.FieldDisplayType.INLINE });

            const resultField = form.addField({
                id: 'custpage_hist1',
                type: serverWidget.FieldType.TEXTAREA,
                label: 'ChatBot',
                container: 'fieldgroupid'
            });
            resultField.defaultValue = llmResult.text;
            resultField.updateDisplayType({ displayType: serverWidget.FieldDisplayType.INLINE });

            form.getField({id: 'custpage_history'}).defaultValue = JSON.stringify(llmResult.chatHistory);
            //history.defaultValue = JSON.stringify(llmResult.chatHistory);
    }

    const createFormFooter = (form) => {
    // Field for new prompt input
        form.addField({
            id: 'custpage_text',
            type: serverWidget.FieldType.TEXTAREA,
            container: 'fieldgroupid',
            label: 'Prompt'
        });

        form.addSubmitButton({ label: 'Submit' });
    }

    const onRequest = (context) => {

        const form = createFromHeader(context);

        if (context.request.method === 'POST') {
            displayHistory(context, form);

            const prompt = context.request.parameters["custpage_text"];
            const chatHistory = context.request.parameters["custpage_history"];

            const llmResult = runToolLoop(prompt, chatHistory)

            displayResult(prompt, llmResult, form);

        } else { // GET
            form.getField({id: 'custpage_num_chats'}).defaultValue = '0';
            //numChats.defaultValue = "0";
        }

        createFormFooter(form);
        context.response.writePage(form);
    };

    return { onRequest };
  }); 

        

Related Topics

General Notices