callModel
Call a language model with tools, structured output, and system prompts.
Quick Example
import { callModel } from '@noetic-tools/core';
const chat = callModel({
id: 'chat',
model: 'openai/gpt-4o',
instructions: 'You are a helpful assistant.',
});What It Does
callModel calls a language model. You provide the model name and optionally a system prompt, tools, structured output schema, and generation parameters. The interpreter handles message formatting, tool execution loops, and response parsing.
API Reference
Options
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique step identifier |
model | Lazy<string> | Yes | Model identifier (e.g. 'openai/gpt-4o', 'claude-sonnet-4-20250514'), or a (ctx) => string getter resolved per execution |
instructions | Lazy<string | undefined> | No | System prompt prepended to the conversation. Accepts a (ctx) => string | undefined getter |
tools | Lazy<Tool[] | undefined> | No | Allowed tool subset (undefined = all, [] = none). Accepts a (ctx) => Tool[] | undefined getter; function-form tools are resolved per execution and do not contribute to the pre-computed ctx.unifiedTools |
output | StandardSchemaV1<unknown, O> | No | Schema for structured output — any Standard Schema v1 validator (Zod is the default) |
outputJsonSchema | Record<string, unknown> | No | Explicit JSON Schema override/fallback for non-Zod output. Zod uses its native converter; other validators can implement StandardJSONSchemaV1. A validation-only schema must provide this field or conversion throws MISSING_JSON_SCHEMA |
params | ModelParams | No | Generation parameters |
emit | boolean | (eventType, data) => boolean | No | Controls framework event emission for this step. Defaults to true; false suppresses all framework events; a filter function decides per event |
projection | ProjectionPolicy | No | Projection policy for this step's context window, overriding the harness-level default |
Lazy<T> means T | ((ctx: Context) => T | Promise<T>) — pass a plain value, or a function that the interpreter resolves against the live context each time the step runs (useful for model routing, dynamic prompts, and context-dependent tool sets).
ModelParams
| Property | Type | Description |
|---|---|---|
temperature | number | Sampling temperature |
topP | number | Nucleus sampling threshold |
maxTokens | number | Maximum tokens to generate |
stopSequences | string[] | Stop sequences |
Tool Interface
Tools passed to callModel follow the standard Noetic Tool interface. See invokeTool for the full definition.
Structured Output
Provide a Zod schema to output and the model response will be parsed and validated at runtime.
import { z } from 'zod';
import { callModel } from '@noetic-tools/core';
const extract = callModel({
id: 'extract-entities',
model: 'openai/gpt-4o',
instructions: 'Extract named entities from the text.',
output: z.object({
people: z.array(z.string()),
places: z.array(z.string()),
organizations: z.array(z.string()),
}),
});Any Standard Schema v1 validator works in place of Zod. Non-Zod schemas are validated via ~standard.validate (sync or Promise results supported) and the parsed/transformed value is returned. The model constraint resolves through Zod, then the Standard JSON Schema v1 trait, then outputJsonSchema. For non-Zod schemas, the explicit field overrides the trait and falls back when conversion throws. See invokeTool for a Valibot trait example.
If the model response does not match the schema, a validation error is thrown (NoeticError kind model_parse_error; see Error Model).
With Tools
import { z } from 'zod';
import { callModel, tool } from '@noetic-tools/core';
const searchTool = tool({
name: 'search',
description: 'Search the knowledge base',
input: z.object({
query: z.string(),
}),
output: z.object({
results: z.array(z.string()),
}),
execute: async (args) => {
return {
results: ['Result 1', 'Result 2'],
};
},
});
const researcher = callModel({
id: 'research',
model: 'openai/gpt-4o',
instructions: 'Answer the question using the search tool.',
tools: [searchTool],
});When the model emits a tool call, the runtime executes the tool and feeds the result back. To loop until the model stops calling tools, wrap the callModel step in a loop with until.noToolCalls().
Unified Tool Set
Before execution, the harness collects all tools from every callModel step in the step tree plus layer-provided tools into a single unified set. Every LLM call sends the full set (preserving prompt cache), while tools on each step narrows which tools the model may actually invoke.
tools: undefined(or omitted) -- the model may call any tool in the unified set.tools: [searchTool]-- the model may only callsearchTool.tools: []-- no tools are available for this step.
Auto-Injected Layer Tools
Functions declared in a context layer's provides field are automatically included in the unified tool set. These tools are namespaced as layerId/fnName (e.g., scratchpad/update). You do not need to pass them in the tools array -- the runtime resolves them from the active context layers.
Tuning Generation
import { callModel } from '@noetic-tools/core';
const creative = callModel({
id: 'brainstorm',
model: 'openai/gpt-4o',
instructions: 'Generate creative ideas.',
params: {
temperature: 0.9,
maxTokens: 2e3,
},
});Related
- runCode -- custom async logic.
- invokeTool -- invoke a tool directly without an LLM.
- Loop & Until -- wrap a callModel step in a ReAct-style loop.