llm@v1

Ask an AI model, no API key needed, billed per token.

Versionllm@v1
Runs ininbound-call and your own branches · api-initiated and inbound-sms · hangup, postHangup and events.ready

Parameters

NameTypeRequiredDefaultDescription
feedbackobjectSound 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.enabledbooleanfalseTurns the sound on.
accepts ${{ }}
feedback.soundbeep, loading, heartbeat"heartbeat"Built-in sound to play, unless audioFile is set.
accepts ${{ }}
feedback.audioFileobjectAn audio file of your library, by its SID (REST API v2 identifier). audio file of your library: sid
feedback.delaynumber0Seconds to wait before playing: 0..60
accepts ${{ }}
modelstringOne 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
systemstringThe instructions of the model (the system prompt).
1..20000 characters
promptstringThe question: a shortcut for one user message. Either prompt or messages, not both.
1..20000 characters
messagesarray of objectThe conversation, user and assistant messages in turn (the instructions go in system). Either messages or prompt, not both.
1..50 items · accepts ${{ }}
messages[].roleuser, assistantyesAuthor of the message.
messages[].contentstringyesText of the message.
1..20000 characters
maxTokensinteger1024Maximum number of tokens of the reply.
1..8192 · accepts ${{ }}
temperaturenumberSampling temperature: lower is more predictable, higher is more creative. Not sent by default: the model chooses.
0..2 · accepts ${{ }}
jsonSchemaobjectA 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"]).
timeoutinteger30Seconds 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: cancelOrder

A 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: $answer

Result

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

FieldTypeDescription
okbooleantrue when the model answered
errorstringOnly when ok is false: timeout, or the error of the provider or of Callr (an unknown model, a reply that does not follow jsonSchema...)
textstringThe reply of the model (the raw JSON text with jsonSchema)
jsonanyWith jsonSchema: the reply, parsed (a JSON value of that shape)
modelstringThe model that answered: the model asked, the default model of the account, or the fallback
provideranthropic, openai, mistralProvider of the model that answered
usageobjectThe tokens billed
usage.inputintegerTokens of the prompts (system, messages or prompt)
usage.outputintegerTokens of the reply
fallbackstringOnly when another model than the one asked answered: why (for example, the model asked is deprecated)
truncatedbooleantrue when the reply was cut at maxTokens
timeoutbooleantrue 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
  }
}

Did this page help you?