Agents
An agent gives the model tools it may call. Your tools run in your service, never in the API, so the conversation goes back and forth until the model has what it needs. The SDK runs that loop for you.
Run an agent
Define each tool with its JSON Schema and a run function, then call llm.runAgent. The SDK sends the conversation, runs the tools the model asks for, sends the results back, and repeats until the model answers or a brake stops it.
Agent
import { createClient, defineTool } from '@vovix/llm'
const llm = createClient({ baseUrl: 'https://api.llm.vovix.io/v1', apiKey: process.env.LLM_API_KEY! })
const getWeather = defineTool<{ city: string }>({
name: 'get_weather',
description: 'Current weather for a city',
schema: {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
},
run: async ({ city }) => weatherService.current(city),
})
const result = await llm.runAgent({
model: 'openai.gpt-oss-120b',
messages: [{ role: 'user', content: 'Should I bring an umbrella in Tokyo today?' }],
tools: [getWeather],
maxTokens: 600,
maxTurns: 6,
maxUsd: 0.05,
})
console.log(result.status, result.text)
A tool that throws — or arguments that aren't valid JSON, fail parse, or name an unknown tool — does not end the run. The error is sent to the model as that tool's result, so it can correct its call. Errors from the API itself, such as BUDGET_EXCEEDED, are thrown as VovixLlmError.
Tool
- Name
name- Type
- string
- Description
snake_case. The name the model calls.
- Name
description- Type
- string
- Description
What the tool does, up to 1000 characters. The model reads it to decide when to call the tool.
- Name
schema- Type
- object
- Description
JSON Schema for the arguments, with a top-level
type: "object".
- Name
parse- Type
- function
- Description
Optional. Validate or clamp the arguments before
run— for examplezodSchema.parse. The model chooses the arguments, and they can be wrong.
- Name
run- Type
- function
- Description
Your implementation. Its return value is sent to the model as JSON; a string is sent as-is.
Only name, description and schema leave your service. parse and run stay local.
Options
runAgent accepts every attribute of Chat, plus:
- Name
tools- Type
- array
- Description
One to 64 tools.
- Name
maxTurns- Type
- integer
- Description
Most model turns for the whole run. Default
8. Stops a model that keeps calling tools.
- Name
maxUsd- Type
- number
- Description
Stop once the run has spent this much, summed over every turn. Checked after each turn, so the last turn can go over. Each turn resends the whole conversation, so cost grows quickly with turns.
- Name
onStep- Type
- function
- Description
Called after each turn with that turn's
step, once its tools have run — for logging or progress.
Result
- Name
status- Type
- string
- Description
done— the model answered.max_turnsormax_usd— a brake stopped the run.
- Name
text- Type
- string
- Description
The model's text from the last turn.
- Name
messages- Type
- array
- Description
The whole conversation, tool calls and results included. Store it to continue the conversation later.
- Name
steps- Type
- array
- Description
One entry per turn:
turn,text,toolRuns(eachname,input,ok,output),usage,costUsd,latencyMs,requestId.
- Name
turns- Type
- integer
- Description
Model turns made.
- Name
usage- Type
- object
- Description
Tokens summed across every turn.
- Name
costUsd- Type
- number
- Description
Cost summed across every turn. Absent if any turn used a model without a catalog price.
Without the SDK
Each request to the endpoint is one model turn. The API keeps no session state, and it never runs tools, so a client in another language drives the loop itself:
- Call
POST /v1/agents/runwith the conversation and your tool definitions. - If
statusisdone, the model answered — stop. - If
statusispending_tools, run each call inpendingToolCalls, append onetoolmessage per call to the returnedmessages, and call again with that array.
Stop after a fixed number of turns of your own: nothing in a single request limits how often the model calls tools across requests.
Run a turn
Accepts every attribute of Chat, plus tools.
Required attributes
- Name
model- Type
- string
- Description
A model id from the catalog.
- Name
messages- Type
- array
- Description
The conversation so far, including earlier tool calls and their
toolresults.
- Name
maxTokens- Type
- integer
- Description
Up to
32000.
- Name
tools- Type
- array
- Description
One to 64 tools. Each has a
snake_casename, an optionaldescription(up to 1000 characters) and a JSON Schemaschemafor its arguments.
Optional attributes
- Name
maxTurns- Type
- integer
- Description
Accepted,
1to16, but it never triggers here: a request makes exactly one model call. Limit turns in your loop, or userunAgent.
- Name
maxUsd- Type
- number
- Description
Accepted, up to
10, but it never triggers here for the same reason. Your service's daily budget still applies.
Request
curl https://api.llm.vovix.io/v1/agents/run \
-H "x-api-key: $LLM_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "openai.gpt-oss-120b",
"messages": [
{ "role": "user", "content": "Should I bring an umbrella in Tokyo today?" }
],
"tools": [
{
"name": "get_weather",
"description": "Current weather for a city",
"schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
],
"maxTokens": 600
}'
Response
{
"status": "pending_tools",
"text": "",
"messages": [
{ "role": "user", "content": "Should I bring an umbrella in Tokyo today?" },
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_0",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Tokyo\"}" }
}
]
}
],
"pendingToolCalls": [
{
"id": "call_0",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Tokyo\"}" }
}
],
"steps": [{ "turn": 1, "text": "", "toolRuns": [], "latencyMs": 1240 }],
"turns": 1,
"usage": { "inputTokens": 142, "outputTokens": 38 },
"costUsd": 0.0000441
}
Response
- Name
status- Type
- string
- Description
done— the model answered.pending_tools— runpendingToolCallsand call again.
- Name
text- Type
- string
- Description
The model's text from this turn. It can be non-empty next to
pendingToolCalls— some models say what they are about to do.
- Name
messages- Type
- array
- Description
The full conversation, including the assistant message with its
tool_calls. Append your tool results and send it back.
- Name
pendingToolCalls- Type
- array
- Description
Tool calls to run. Each has an
id, and afunctionwith anameand JSON-encodedarguments.
- Name
steps- Type
- array
- Description
One entry for this turn:
turn,text,toolRuns(always empty here),usage,costUsd,latencyMs,requestId.
- Name
turns- Type
- integer
- Description
Always
1.
- Name
usage- Type
- object
- Description
Tokens for this turn.
- Name
costUsd- Type
- number
- Description
Cost of this turn.