llm@v1
Ask an AI model, no API key needed, billed per token.
| Version | llm@v1 |
| Runs in | inbound-call and your own branches · api-initiated and inbound-sms · hangup, postHangup and events.ready |
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
feedback | object | Sound played to the caller while the action runs: a built-in sound or an audio file of your library (at most one of sound / audioFile). | ||
feedback.enabled | boolean | false | Turns the sound on. accepts ${{ }} | |
feedback.sound | beep, loading, heartbeat | "heartbeat" | Built-in sound to play, unless audioFile is set.accepts ${{ }} | |
feedback.audioFile | object | An audio file of your library, by its SID (REST API v2 identifier). audio file of your library: sid | ||
feedback.delay | number | 0 | Seconds to wait before playing: 0..60 accepts ${{ }} | |
model | string | One of the models listed in the AI section of your rates (Billing > Pricing), for example claude-sonnet-5-5, gpt-6.1-terra or mistral-large-latest. Default: the default model of your account (Account > Preferences). 1..64 characters | ||
system | string | The instructions of the model (the system prompt). 1..20000 characters | ||
prompt | string | The question: a shortcut for one user message. Either prompt or messages, not both.1..20000 characters | ||
messages | array of object | The conversation, user and assistant messages in turn (the instructions go in system). Either messages or prompt, not both.1..50 items · accepts ${{ }} | ||
messages[].role | user, assistant | yes | Author of the message. | |
messages[].content | string | yes | Text of the message. 1..20000 characters | |
maxTokens | integer | 1024 | Maximum number of tokens of the reply. 1..8192 · accepts ${{ }} | |
temperature | number | Sampling temperature: lower is more predictable, higher is more creative. Not sent by default: the model chooses. 0..2 · accepts ${{ }} | ||
jsonSchema | object | A JSON Schema the reply must follow: the model answers with a JSON value of that shape, parsed in result.json (result.text keeps the raw answer). The models apply it strictly: every object needs additionalProperties: false and all its properties in required (an optional value is typed [string, "null"]). | ||
timeout | integer | 30 | Seconds to wait for the reply. 1..120 · accepts ${{ }} |
Every parameter that accepts ${{ }} can be computed from an expression.
How it works
Nothing to set up: no API key is needed, and Callr bills the tokens per model (see the AI section of your rates, Billing > Pricing).
The model. Without model, the default model of your account (Account > Preferences) answers. model must be one of the models (Anthropic, OpenAI or Mistral) listed in the AI section of your rates (Billing > Pricing): result.model and result.provider tell which one answered. When the model asked is no longer available (deprecated by its provider), the closest model answers instead and result.fallback says why.
The reply. result.text is the reply. With jsonSchema, the model answers with a JSON value of that shape, parsed in result.json: every object of the schema needs additionalProperties: false and all its properties in required. result.truncated is true when the reply was cut at maxTokens; when the model does not answer within timeout, ok is false with error: timeout.
On a call, feedback plays a sound while waiting for the reply.
Examples
A question, the reply spoken
prompt is a shortcut for one user message; system gives the instructions.
- label: ask
action: recognize@v3
params:
tts:
text: Ask me anything
result: $question
- action: llm@v1
params:
system: You answer on the phone, in one short sentence.
prompt: ${{ $question.text }}
timeout: 10
# feedback: sound played to the caller while waiting for the reply
feedback:
enabled: true
sound: heartbeat
result: $answer
- if: >
!$answer.ok
then:
- action: play@v3
params:
tts:
text: I am sorry, I was not able to reply within the time limit.
- goto:
label: ask
- action: play@v3
params:
tts:
text: ${{ $answer.text }}A structured reply (jsonSchema)
With jsonSchema, the reply follows the schema and is parsed in result.json.
- action: llm@v1
params:
model: claude-sonnet-5-5
system: Classify what the caller wants.
prompt: ${{ $question.text }}
jsonSchema:
type: object
additionalProperties: false
required: [intent, orderId]
properties:
intent:
type: string
enum: [order_status, cancel_order, other]
orderId:
type: [string, "null"]
description: The order number said by the caller, if any
timeout: 10
result: $intent
- if: $intent.ok && $intent.json?.intent === 'order_status'
then:
- goto:
label: orderStatus
- if: $intent.ok && $intent.json?.intent === 'cancel_order'
then:
- goto:
label: cancelOrderA conversation (messages)
messages gives the turns of the conversation so far, the model answers the last user message.
- action: llm@v1
params:
model: mistral-large-latest
system: You are the assistant of Acme. Answer in French, in two sentences at most.
messages:
- role: user
content: Bonjour, quels sont vos horaires ?
- role: assistant
content: Nous sommes ouverts du lundi au vendredi, de 9 h à 18 h.
- role: user
content: ${{ $question.text }}
maxTokens: 200
temperature: 0.3
result: $answerResult
Set result: $myVariable to keep it. Reply of the model: ok, the text of the reply (and json with a jsonSchema), the model that answered and the tokens used
| Field | Type | Description |
|---|---|---|
ok | boolean | true when the model answered |
error | string | Only when ok is false: timeout, or the error of the provider or of Callr (an unknown model, a reply that does not follow jsonSchema...) |
text | string | The reply of the model (the raw JSON text with jsonSchema) |
json | any | With jsonSchema: the reply, parsed (a JSON value of that shape) |
model | string | The model that answered: the model asked, the default model of the account, or the fallback |
provider | anthropic, openai, mistral | Provider of the model that answered |
usage | object | The tokens billed |
usage.input | integer | Tokens of the prompts (system, messages or prompt) |
usage.output | integer | Tokens of the reply |
fallback | string | Only when another model than the one asked answered: why (for example, the model asked is deprecated) |
truncated | boolean | true when the reply was cut at maxTokens |
timeout | boolean | true when the model did not answer within timeout |
{
"ok": true,
"text": "The capital of France is Paris.",
"model": "claude-sonnet-5-5",
"provider": "anthropic",
"usage": {
"input": 28,
"output": 9
}
}Updated 1 day ago
