Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Add an LLM call

The quickstart's greet flow returned a hard-coded string. Real flows use LLMs. This tutorial adds a @flexible block to the greet flow: it calls a model with a goal and an input, and gets back a structured output the runtime type-checks. It assumes you've completed the quickstart and have greet.scaicore on disk.

1. Declare a model role#

LLM calls go through roles, not model names. A role is a slot the runtime can swap providers behind. Declare one in the @core block:

scaicore
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@core HelloWorld {
    version = "1.0.0"
    instance = :stateless

    @llm {
        primary = {
            model = "scailabs/poolnoodle-omni"
            temperature = 0.4
            modalities = [:text, :structured_output]
        }
    }
}

primary is conventional for the main role; you can declare additional roles (reviewer, extractor, …) and reference them by name from @flexible blocks via the llm_role field.

2. Declare an output type#

Structured outputs need a type the compiler can verify against. Declare it at module scope:

scaicore
1
2
3
4
5
6
type Greeting = {
    message: string,
    mood: Mood
}

type Mood = enum[happy, neutral, encouraging]

The runtime translates Greeting into a JSON schema and asks the provider to honor it.

3. Replace the @rigid body with @flexible#

The old greet flow:

scaicore
1
2
3
4
5
@flow greet(name: string): string {
    @rigid {
        return "Hello, ${name}!"
    }
}

Becomes:

scaicore
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@flow greet(name: string): Greeting {
    result = @flexible {
        goal = "Generate a warm, contextual greeting for the named person"
        input = { name: name }
        output: Greeting
    }
    @rigid {
        return result
    }
}

Three things changed: the flow's return type is now Greeting, the work moved into a @flexible block that asks the model for a Greeting-shaped value, and a small @rigid returns it. The result = binding captures the @flexible block's output into the surrounding scope.

4. Check it#

scaicore check runs the full pipeline including type-check and verifier. It should pass:

bash
1
scaicore check greet.scaicore

If you see E206 — undefined model you misspelled a role name. If you see E300 — type mismatch your output: doesn't line up with the flow's declared return type — they need to match.

5. Run it#

scaicore run will compile and execute, but the CLI host's default model provider is a stub that returns mock values. To run against a real provider you'll either:

  • Embed the runtime in your own host and pass a real ModelProvider implementation, or
  • Configure the CLI host with provider credentials (see your model provider's docs for the role config).

Either way:

bash
1
scaicore run greet.scaicore --flow greet --input '{"name": "Ada"}'

A successful run prints the structured output, e.g.:

json
1
2
3
4
{
  "message": "Welcome back, Ada — good to see you again.",
  "mood": "encouraging"
}

What you gained#

Compared to the quickstart flow you now have: structured output the runtime type-checks against Greeting, a role-based model declaration that lets you swap providers without touching flow source, and (once a real provider is wired up) text generation that adapts to the input.

Next: Add a human approval step to require sign-off before the greeting is returned.

Updated 2026-05-18 11:03:23 View source (.md) rev 5