SuiteScript
NetSuite Unknown
2026-07-23

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.

Affects:SuiteScript 2.1N/llm ModuleN/query Module

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 a llm.Tool definition with a name, description, and parameter list.
  • llm.createToolParameter(options) — defines a single parameter (name, description, type). Types come from the llm.ToolParameterType enum (e.g., llm.ToolParameterType.STRING).
  • llm.createToolResult(options) — wraps handler output into the format expected by options.toolResults. Accepts the originating llm.ToolCall object via options.call and an options.outputs array.

Key objects and properties

  • llm.Tool — the tool definition object passed in options.tools.
  • llm.ToolCall — returned in Response.toolCalls; contains call.name (matches your tool name) and call.parameters (key-value map matching your parameter definitions).
  • llm.ToolParameterType — enum for parameter typing (at minimum STRING; the source does not enumerate other members).
  • options.chatHistory / Response.chatHistory — pass the returned chatHistory from each response into subsequent calls to maintain conversational context across the tool-call loop.

Execution flow

  1. Define tools with llm.createTool() and corresponding handler functions in your script.
  2. Call llm.generateText({ prompt, tools, chatHistory }).
  3. Inspect result.toolCalls. If non-empty, iterate over each ToolCall, invoke your handler using call.parameters, and package each output with llm.createToolResult({ call, outputs: [{ result: value }] }).
  4. Make a follow-up call: llm.generateText({ tools, toolResults, chatHistory: result.chatHistory }). No prompt is required on follow-up calls.
  5. Repeat steps 3–4 until result.toolCalls is 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

  1. Check module availability. The source does not specify a release version. Confirm the N/llm module and its tooling APIs are available in your account's SuiteScript 2.1 environment before writing against them. Look for llm.createTool in the API reference for your release.
  2. Plan governance. The documentation is silent on governance-unit cost for generateText calls 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.
  3. 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.parameters as untrusted input. Parameterize SuiteQL (use params, not string concatenation) and enforce role-based access before executing record operations.
  4. 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.tools array per use case rather than registering every possible tool on every call.
  5. 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.
  6. Use chatHistory correctly. Always forward the chatHistory from the previous Response into the next generateText call, 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 full N/llm module reference.
  • Governance cost per generateText invocation (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.