Skip to content
imaged

Text & image generation

Stream an answer, reuse conversation context, and cancel generation.

Use your own .slab model and a runtime license. Model files carry their input sizes, preprocessing, labels, and backend preferences.

chat.cplus

C+ setup →
// Ask a vision-language model about an image and stream the answer, then
// ask a follow-up question in the same conversation.
//
// start() reads the prompt; next_token() returns one piece of text per
// call. There are no callbacks: the app pulls tokens in a loop.

import "stdlib/io" as io;
import "stdlib/env" as env;
import "stdlib/fs" as fs;
import "stdlib/result" as result;
import "stdlib/option" as option;
import "stdlib/str" as _;
import "ffi/api" as slab;

fn stream(ref session: slab::Session, ref inp: slab::Input, ref out: slab::Output) -> bool {
    if !session.start(inp, out) { return false; }
    while session.next_token(out) == 1 {
        io::print(out.text());
    }
    io::println("");
    return out.finish_reason() != slab::FINISH_NONE;
}

fn main() -> i32 {
    var cfg = slab::Config::new();
    cfg.set_models_dir("models");
    guard let option::Option::Some(path) = env::var("SLAB_RUNTIME_LICENSE_FILE") else { return 1; };
    guard let result::Result::Ok(token) = fs::read_to_string(path.view()) else { return 1; };
    if !cfg.set_runtime_license(token.view()) { return 1; }

    var session = slab::Session::new(cfg);
    session.set_context_length(2048);
    if !session.open("vlm.slab") {
        io::eprintln("Cannot open the model: ${session.error_message()}");
        return 1;
    }

    var photo = slab::Frame::new();
    if !photo.load_file("images/receipt.jpg") {
        io::eprintln("Cannot load the image: ${photo.error_message()}");
        return 1;
    }

    var inp = slab::Input::new();
    var out = slab::Output::new();
    inp.set_max_tokens(64);
    inp.set_temperature(0.0);
    // Large images are split into tiles instead of being shrunk.
    inp.set_image_tiled(true, 1536);

    // Turn 1: a question about the image.
    let _ = inp.add_image_message("user", photo, "What is this document?");
    if !stream(session, inp, out) {
        io::eprintln("Generation failed: ${session.error_message()}");
        return 1;
    }

    // Turn 2: send the conversation again with the answer and a new question.
    // The shared beginning is reused, not recomputed.
    let answer = out.full_text().trim().to_text();
    let _ = inp.add_message("assistant", answer.view());
    let _ = inp.add_message("user", "What is the total amount?");
    if !stream(session, inp, out) {
        io::eprintln("Generation failed: ${session.error_message()}");
        return 1;
    }
    io::println("prompt ${out.prompt_tokens()} tokens (${out.cached_prompt_tokens()} reused), generated ${out.generated_tokens()}");

    // Forget the conversation before an unrelated one.
    let _ = session.reset();
    return 0;
}