N/llm Module Now Supports Tool Calling for LLM-Driven SuiteScript Workflows
The N/llm module in SuiteScript 2.1 now exposes a tool-calling API that lets LLMs invoke custom business logic — such as SuiteQL queries — mid-generation, loop on the results, and produce richer responses.
What changed
The N/llm module gains a tool-calling subsystem. When you call llm.generateText(options) or llm.generateTextStreamed(options), you can now pass an options.tools array of llm.Tool objects. The LLM inspects these tool definitions (name + description + parameter schema) and may return llm.ToolCall objects in Response.toolCalls (or StreamedResponse.toolCalls for the streamed variant) instead of — or alongside — generated text. Your script executes the requested logic, packages results with llm.createToolResult(options), and feeds them back via options.toolResults in a follow-up generateText call. The loop repeats until the LLM returns zero tool-call requests.
New APIs
llm.createTool(options)— builds allm.Tooldefinition with a name, description, and parameter list.llm.createToolParameter(options)— defines a single parameter (name, description, type). Types come from thellm.ToolParameterTypeenum (e.g.,llm.ToolParameterType.STRING).llm.createToolResult(options)— wraps handler output into the format expected byoptions.toolResults. Accepts the originatingllm.ToolCallobject viaoptions.calland anoptions.outputsarray.
Key objects and properties
llm.Tool— the tool definition object passed inoptions.tools.llm.ToolCall— returned inResponse.toolCalls; containscall.name(matches your tool name) andcall.parameters(key-value map matching your parameter definitions).llm.ToolParameterType— enum for parameter typing (at minimumSTRING; the source does not enumerate other members).options.chatHistory/Response.chatHistory— pass the returnedchatHistoryfrom each response into subsequent calls to maintain conversational context across the tool-call loop.
Execution flow
- Define tools with
llm.createTool()and corresponding handler functions in your script. - Call
llm.generateText({ prompt, tools, chatHistory }). - Inspect
result.toolCalls. If non-empty, iterate over eachToolCall, invoke your handler usingcall.parameters, and package each output withllm.createToolResult({ call, outputs: [{ result: value }] }). - Make a follow-up call:
llm.generateText({ tools, toolResults, chatHistory: result.chatHistory }). Nopromptis required on follow-up calls. - Repeat steps 3–4 until
result.toolCallsis empty.
Example: SuiteQL lookup tool
The documentation provides a concrete example — a findUserId tool that runs SELECT id FROM entity WHERE fullname=? via N/query.runSuiteQL() and returns the matched ID (or -1 on miss). This confirms that tool handlers can execute arbitrary SuiteScript logic including database queries, record operations, and external calls.
What to do
- Check module availability. The source does not specify a release version. Confirm the
N/llmmodule and its tooling APIs are available in your account's SuiteScript 2.1 environment before writing against them. Look forllm.createToolin the API reference for your release. - Plan governance. The documentation is silent on governance-unit cost for
generateTextcalls with tools. Each loop iteration is another API call. Measure governance consumption in a sandbox, especially for tool-call chains that may loop multiple times. - Validate tool handler security. Oracle explicitly warns: if a handler reads or mutates NetSuite data, you must implement your own authorization and validation. The LLM controls which tool is called and what parameter values are sent — treat
call.parametersas untrusted input. Parameterize SuiteQL (useparams, not string concatenation) and enforce role-based access before executing record operations. - Keep tool sets small and scoped. The docs warn that providing an unnecessarily large set of tools degrades LLM performance and response clarity. Curate the
options.toolsarray per use case rather than registering every possible tool on every call. - Handle errors in handlers. Return a sentinel value or descriptive error string from your handler on failure — do not throw. An unhandled exception will break the tool-call loop.
- Use
chatHistorycorrectly. Always forward thechatHistoryfrom the previousResponseinto the nextgenerateTextcall, or the LLM loses conversational context between tool-call iterations.
Open questions
- The source only shows
llm.ToolParameterType.STRING. Other supported types (number, boolean, object, array) are not documented here — check the fullN/llmmodule reference. - Governance cost per
generateTextinvocation (with and without tools) is not stated. - Maximum number of tools per call, maximum parameter count per tool, and maximum loop iterations are not specified.
- Whether tool calling is supported in all script types (Scheduled, Map/Reduce, Suitelet, RESTlet, etc.) or only a subset is not mentioned.
Source: Oracle NetSuite Release Notes