Objeck AI & ML Developer Guide

All AI and ML capabilities are standard library — no third-party packages, no pip install, no package.json. Import the relevant bundle and go.

Authentication

OpenAI

Create openai_api_key.dat in your working directory containing your API key. The static helper reads it:

use API.OpenAI;

token := EndPoint->GetApiKey();

Gemini

Create gemini_api_key.dat in your working directory. Use the static helper:

use API.Google.Gemini;

token := EndPoint->GetApiKey();

Ollama

No authentication needed. Ollama runs locally at http://localhost:11434 by default. Start it with ollama serve.


OpenAI

Chat & Text

Compile: obc -src your.obs -lib net,net_server,json,cipher,misc,openai

use API.OpenAI.Responses, Collection;

# Single-turn
response := Response->Respond("gpt-4o-mini",
    Pair->New("user", "Explain JIT compilation in one sentence.")<String, String>,
    token);
response->GetText()->PrintLine();
# Multi-turn conversation
messages := Vector->New()<Pair<String, String>>;
messages->AddBack(Pair->New("system", "You are a helpful assistant.")<String, String>);
messages->AddBack(Pair->New("user", "What is Objeck?")<String, String>);
messages->AddBack(Pair->New("assistant", "Objeck is a JIT-compiled OO language.")<String, String>);
messages->AddBack(Pair->New("user", "What platforms does it support?")<String, String>);

response := Response->Respond("gpt-4o-mini", messages, Nil, token);   # Nil: no output schema
response->GetText()->PrintLine();
# With reasoning control
response := Response->Respond("o3-mini",
    Pair->New("user", "Prove Fermat's Last Theorem.")<String, String>,
    ReasoningEffort->HIGH,
    Verbosity->HIGH,
    Nil, token);
response->GetText()->PrintLine();

Vision

use API.OpenAI, API.OpenAI.Responses, System.IO.Filesystem, Collection;

bytes := FileReader->ReadBinaryFile("photo.jpg");
image := ImageQuery->New("What is in this image?", bytes, ImageQuery->MimeType->JPEG);
query := Pair->New("user", image)<String, ImageQuery>;

response := Response->Respond("gpt-4o", query, token);
response->GetText()->PrintLine();

Realtime (Audio)

Sends audio or text and receives both a text transcript and PCM audio response over a WebSocket connection.

Compile: add -lib sdl2 for audio playback

use API.OpenAI, Game.SDL2, System.IO.Filesystem;

# Text → text + audio
response := Realtime->Respond("What time is it in Tokyo?",
    "gpt-4o-realtime-preview", token);
if(response <> Nil) {
    text := response->GetFirst();
    "Response: {$text}"->PrintLine();

    # Play audio (PCM 16-bit LE, 24kHz mono)
    audio := response->GetSecond();
    Mixer->PlayPcm(audio->Get(), 24000, AudioFormat->SDL_AUDIO_S16LSB, 1);
};
# Audio file → text + audio
audio_bytes := FileReader->ReadBinaryFile("question.pcm16");   # raw PCM 16-bit LE, 24kHz mono
response := Realtime->Respond(audio_bytes, "gpt-4o-realtime-preview", "alloy", token);

Image Generation

use API.OpenAI, System.IO.Filesystem;

# DALL-E 3, 1024×1024
image := Image->Create("A photorealistic Objeck logo on a circuit board",
    "dall-e-3", Image->Size->DALLE3_1024_1024, token);
if(image <> Nil) {
    urls := image->GetUrls();
    each(url in urls) {
        url->GetUrl()->PrintLine();
    };
};
# Edit with a mask
original := FileReader->ReadBinaryFile("room.png");
mask     := FileReader->ReadBinaryFile("mask.png");
image := Image->Edit("room.png", original,
    "Add a bookshelf to the left wall",
    "mask.png", mask, "dall-e-2", 1,
    Image->Size->DALLE2_1024_1024, "url", "", token);

Embeddings

use API.OpenAI;

values := Embedding->Create("Objeck is a JIT-compiled language",
    "text-embedding-3-small", token);
if(values <> Nil) {
    dim := values->Size();
    "Dimensions: {$dim}"->PrintLine();   # 1536
};
# Reduced dimensions
values := Embedding->Create("search query text",
    "text-embedding-3-large", 256, token);

Moderation

use API.OpenAI;

result := Moderation->Check("I want to hurt someone.", token);
if(result <> Nil) {
    flagged := result->IsFlagged();
    "Flagged: {$flagged}"->PrintLine();

    if(flagged) {
        score := result->GetScore("violence");
        "violence score: {$score}"->PrintLine();
    };
};

Categories: harassment, harassment/threatening, hate, hate/threatening, self-harm, self-harm/instructions, self-harm/intent, sexual, sexual/minors, violence, violence/graphic


Batch Processing

Process up to 50,000 requests asynchronously at 50% of standard API cost. Responses are available within 24 hours.

use API.OpenAI;

# 1. Upload a .jsonl file of requests (or reuse the one already uploaded)
file := File->LoadOrCreate("requests.jsonl", "batch", token);

# 2. Create the batch job
job := Batch->Create(file->GetId(), "/v1/chat/completions", token);
"Batch ID: {$job->GetId()}"->PrintLine();
"Status: {$job->GetStatus()}"->PrintLine();   # "validating"

# 3. Poll for completion
job := Batch->Get(job->GetId(), token);
if(job->IsComplete()) {
    output_id := job->GetOutputFileId();
    "Output file: {$output_id}"->PrintLine();
};

# 4. List recent jobs
jobs := Batch->List(token);
each(j in jobs) {
    id := j->GetId();
    status := j->GetStatus();
    "{$id}: {$status}"->PrintLine();
};

Request format (requests.jsonl — one JSON object per line):

{"custom_id":"r1","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hello"}]}}

Gemini

Compile: obc -src your.obs -lib net,net_server,json,cipher,misc,gemini

Generate Content

use API.Google.Gemini;

token := EndPoint->GetApiKey();

# Simple text
content := Content->New("user")->AddPart(TextPart->New("Why is the sky blue?"));
candidates := Model->GenerateContent("models/gemini-2.0-flash", content, token);
if(candidates <> Nil & <>candidates->IsEmpty()) {
    candidates->First()->GetAllText()->PrintLine();
};
# With system instruction
system := Content->New("system")->AddPart(TextPart->New("Reply only in haiku."));
candidates := Model->GenerateContent("models/gemini-2.0-flash",
    content, system, token);
# Image + text
bytes := System.IO.Filesystem.FileReader->ReadBinaryFile("chart.png");
content := Content->New("user")
    ->AddPart(TextPart->New("Summarize this chart."))
    ->AddPart(BinaryPart->New(bytes, "image/png"));
candidates := Model->GenerateContent("models/gemini-2.0-flash", content, token);
# Multi-turn chat session
chat := Chat->New("models/gemini-2.0-flash", token);
chat->SetSystemInstruction(Content->New("system")->AddPart(TextPart->New("You are a coding assistant.")));

r1 := chat->SendPart(TextPart->New("What is a closure?"), "user");
r1->GetAllText()->PrintLine();

r2 := chat->SendPart(TextPart->New("Show me an example in Objeck."), "user");
r2->GetAllText()->PrintLine();

Structured Output

Force the model to return valid JSON matching a schema.

use API.Google.Gemini, Data.JSON.Scheme, Collection;

content := Content->New("user")
    ->AddPart(TextPart->New("Top 3 programming languages by popularity."));

# Define schema: array of {name: string, rank: integer}
schema := ParameterType->New(["name", "rank"], true);
schema->AddProp("name", ParameterType->New(ParameterType->Type->STRING));
schema->AddProp("rank", ParameterType->New(ParameterType->Type->INTEGER));
resp_schema := Pair->New("application/json", schema)<String, ParameterType>;

candidates := Model->GenerateContent("models/gemini-2.5-flash", content, resp_schema, token);
if(candidates <> Nil & <>candidates->IsEmpty()) {
    candidates->First()->GetAllText()->Trim()->PrintLine();
};

Search Grounding

Connects the model to live Google Search for up-to-date answers.

use API.Google.Gemini;

content := Content->New("user")
    ->AddPart(TextPart->New("What major AI models were released this month?"));

candidates := Model->GenerateContentWithGrounding("models/gemini-2.0-flash", content, token);
if(candidates <> Nil & <>candidates->IsEmpty()) {
    candidates->First()->GetAllText()->PrintLine();
};

Embeddings

use API.Google.Gemini, Collection;

# Single embedding
content := Content->New("user")->AddPart(TextPart->New("machine learning"));
values := Model->EmbedContent(content, token);   # uses models/embedding-001
dim := values->Size();
"Dimensions: {$dim}"->PrintLine();   # 768
# Batch — multiple texts in one round-trip
texts := Vector->New()<String>;
texts->AddBack("Objeck is JIT-compiled");
texts->AddBack("Python uses an interpreter");
texts->AddBack("Rust is memory-safe");

embeddings := Model->BatchEmbedContent("models/text-embedding-004", texts, token);
each(i : embeddings) {
    emb := embeddings->Get(i);
    dim := emb->Size();
    "  [{$i}] dim={$dim}"->PrintLine();
};

# Cosine similarity between first two
a := embeddings->Get(0)->Get();
b := embeddings->Get(1)->Get();
# Task-typed embedding (improves retrieval quality)
content := Content->New("user")->AddPart(TextPart->New("What is Objeck?"));
values := Model->EmbedContent(content, "FAQ title", Model->TaskType->RETRIEVAL_DOCUMENT, token);

Files API

Upload files once and reference them across multiple requests without re-uploading.

use API.Google.Gemini, System.IO.Filesystem;

# Upload
data := FileReader->ReadBinaryFile("report.pdf");
file := FileManager->Upload("Q1 Report", data, "application/pdf", token);
if(file <> Nil & file->IsActive()) {
    "URI: {$file->GetUri()}"->PrintLine();
    "Name: {$file->GetName()}"->PrintLine();   # "files/abc123"
};

# List
files := FileManager->List(token);
each(f in files) {
    name := f->GetName();
    state := f->GetState();
    "{$name}: {$state}"->PrintLine();
};

# Delete
FileManager->Delete("files/abc123", token);

Context Caching

Cache large, reused content server-side to avoid paying re-tokenization costs on every request.

use API.Google.Gemini, System.IO.Filesystem;

# Cache a large system prompt or document for 5 minutes
large_context := FileReader->ReadFile("legal_document.txt");
content := Content->New("user")->AddPart(TextPart->New(large_context));

item := CachedContent->Create(
    "models/gemini-1.5-pro-001", content, 300, "legal-doc-cache", token);
if(item <> Nil) {
    "Name: {$item->GetName()}"->PrintLine();
    tokens := item->GetTokenCount();
    "Tokens cached: {$tokens}"->PrintLine();
    "Expires: {$item->GetExpireTime()}"->PrintLine();
};

# List active caches
items := CachedContent->List(token);
each(item in items) {
    name := item->GetName();
    expires := item->GetExpireTime();
    "{$name} — expires {$expires}"->PrintLine();
};

# Clean up
CachedContent->Delete("cachedContents/abc123", token);

Ollama (Local Models)

Run open-source models locally. No API key, no data leaves your machine.

Prerequisites: Install Ollama and pull a model: ollama pull llama3.2

Compile: obc -src your.obs -lib net,json,cipher,misc,ollama

use API.Ollama;

# One-shot generation
response := Completion->Generate("llama3.2", "What is 2 + 2?");
response->PrintLine();
# With temperature control
opts := Options->New()->SetTemperature(0.2);
response := Completion->Generate("llama3.2", "List 3 capitals of Europe.", opts);
response->PrintLine();
# Multi-turn chat
chat := Chat->New("llama3.2");
r1 := chat->Send("My name is Alice.");
r2 := chat->Send("What is my name?");
r2->PrintLine();   # "Your name is Alice."
# Vision (multimodal models)
image := System.IO.Filesystem.File->New("photo.jpg");
response := Completion->Generate("llava", "Describe this image.", image);
response->PrintLine();
# Local embeddings
values := Model->Embeddings("nomic-embed-text", "machine learning");
dim := values->Size();
"Dimensions: {$dim}"->PrintLine();
# Model management
models := Model->List();
each(m in models) {
    m->GetPulled()->GetName()->PrintLine();
};
Model->Pull("phi3");

ONNX Local Inference

Run ML models locally using the ONNX Runtime. The Windows and macOS libraries are each built with one accelerator (DirectML on Windows x64, QNN on Windows ARM64, CoreML on macOS), with the CPU as the fallback. The Linux x64 library is CPU-only, and the Linux ARM64 packages include no ONNX library. See execution providers for the ep setting.

Compile: obc -src your.obs -lib models,net,json,cipher,opencv,onnx

Face Recognition

Uses InsightFace buffalo_l: SCRFD 10G-KPS detector + ArcFace R50 (512-dim embeddings).

use API.Onnx, System.IO.Filesystem;

session := FaceSession->New("det_10g.onnx", "w600k_r50.onnx");

img1 := FileReader->ReadBinaryFile("person_a.jpg");
img2 := FileReader->ReadBinaryFile("person_b.jpg");

r1 := session->Recognize(img1, 0.5);
r2 := session->Recognize(img2, 0.5);

if(r1->GetSize() > 0 & r2->GetSize() > 0) {
    emb1 := r1->GetResults()[0]->GetEmbedding();
    emb2 := r2->GetResults()[0]->GetEmbedding();

    sim := FaceSession->Compare(emb1, emb2);
    "Similarity: {$sim}"->PrintLine();
    same := sim > 0.35;
    "Same person: {$same}"->PrintLine();
};

session->Close();
# Detection only (no embedding)
session := FaceSession->New("det_10g.onnx");
result := session->Detect(img_bytes, 0.5);
count := result->GetSize();
"Faces detected: {$count}"->PrintLine();

Object Detection (YOLO)

use API.Onnx, System.IO.Filesystem;

labels := String->New[80];   # COCO labels
labels[0] := "person"; labels[1] := "bicycle"; # ... fill all 80

session := YoloSession->New("yolov8n.onnx");
img := FileReader->ReadBinaryFile("street.jpg");

result := session->Inference(img, 640, 640, 0.5, labels);
detections := result->GetClassifications();
each(d in detections) {
    name := d->GetName();
    conf := d->GetConfidence();
    "{$name}: {$conf}"->PrintLine();
};

session->Close();

Phi-3 / Phi-3 Vision

Run Microsoft's Phi-3 SLM locally for text or vision tasks.

use API.Onnx, System.IO.Filesystem;

tokenizer := Phi3Tokenizer->New("tokenizer.json");
session   := Phi3Session->New("phi3-mini-4k-instruct.onnx");

prompt     := "<|user|>\nWhat is the capital of France?<|end|>\n<|assistant|>\n";
token_ids  := tokenizer->Encode(prompt);
eos_tokens := Int->New[1]; eos_tokens[0] := 32007;

result  := session->Generate(token_ids, 200, 0.7, eos_tokens);
decoded := tokenizer->Decode(result->GetTokens());
decoded->PrintLine();

session->Close();
# Phi-3 Vision (image + text): the image goes between the prefix and suffix tokens
vision := Phi3VisionSession->New(
    "phi3v-vision.onnx",
    "phi3v-embed.onnx",
    "phi3v-decoder.onnx");

img    := FileReader->ReadBinaryFile("diagram.png");
prefix := tokenizer->Encode("<|user|>\n");
suffix := tokenizer->Encode("Describe this diagram.<|end|>\n<|assistant|>\n");

result := vision->Generate(img, prefix, suffix, 300, 0.7, eos_tokens);
tokenizer->Decode(result->GetTokens())->PrintLine();

vision->Close();

Image Classification (ResNet)

use API.Onnx, System.IO.Filesystem;

labels := FileReader->ReadFile("imagenet_labels.txt")->Split("\n");
session := ResNetSession->New("resnet50.onnx");
img     := FileReader->ReadBinaryFile("cat.jpg");

result := session->Inference(img, 224, 224, labels);
top  := result->GetName();
conf := result->GetConfidence();
"Predicted: {$top} ({$conf})"->PrintLine();

session->Close();

Any Model (generic run)

The six classes above each handle one model family. For anything they do not cover -- a model with several inputs, a text or audio model, or a model whose output you want to decode yourself -- Session runs any ONNX graph. Ask it what the model wants, build the tensors, run it:

use API.Onnx, API.Models, Collection;

session := Session->New("model.onnx");
if(<>session->IsOpen()) {
  return;
};

# what does this model take and return?
info := session->GetModelInfo();
specs := info->GetInputs();
each(spec in specs) {
  spec->ToString()->PrintLine();      # e.g.  data: FP32[-1,3,224,224]
};

inputs := Vector->New()<Tensor>;
inputs->AddBack(Tensor->New("data", [1, 3, 224, 224], pixels));

outputs := session->Run(inputs);
each(tensor in outputs) {
  tensor->ToString()->PrintLine();
};

session->Close();

A dimension the model leaves to you is reported as -1 -- a batch size or a sequence length. You choose it; nothing guesses.

You never state an element type. The model's own declaration decides what each input becomes, so FP32, FP16, INT64, UINT8 and BOOL models are all driven the same way.

An integer tensor keeps its integers. The conversion to Float happens only at the native crossing, which is the one place that needs it, and refuses a value past 2^53 rather than rounding it -- 9,007,199,254,740,992, so every token id and class index is far inside it. A server takes integers as JSON integers and has no such limit, which is why the conversion lives at the boundary that cares rather than in the tensor.

Preprocessing and decoding are yours, in Objeck. That is the trade: more work than YoloSession, and it works for models no per-family class exists for. programs/frameworks/opencv_onnx/ab_phi3.obs drives Phi-3 through it with 67 inputs, which is what the shape is for.


Remote Inference Servers

API.Inference talks to a model server over HTTP instead of loading a model in-process. Use it when the model is not an ONNX file you have locally: a model served from a GPU machine, a TensorFlow model you would rather not convert, or anything already deployed behind Triton, KServe, TorchServe or TF Serving.

Two wire protocols, because no single one covers the field:

ProtocolServed byShape
OPEN_INFERENCE (default)Triton, KServe, OpenVINO Model ServerKServe v2: flat data, with shape and datatype stated
TF_SERVINGTensorFlow Servingv1 :predict: nesting carries the shape, no datatype
use API.Inference, API.Models, Collection;

client := Client->New("http://gpu-box:8000");

inputs := Vector->New()<Tensor>;
inputs->AddBack(Tensor->New("input", [1, 4], [5.1, 3.5, 1.4, 0.2]));

outputs := client->Predict("iris", inputs);
if(outputs = Nil) {
  EndPoint->GetLastError()->PrintLine();
  return;
};

each(tensor in outputs) {
  tensor->ToString()->PrintLine();
};

A failed call returns Nil and the reason is in EndPoint->GetLastError(). Servers report errors in a JSON body rather than only in the status line, so a body that will not parse means something else answered -- a proxy's error page, a truncated response -- and the body itself is reported.

Ask the server what a model expects before guessing at names and shapes:

if(client->IsReady("iris")) {
  client->Metadata("iris")->ToString()->PrintLine();
};

And when a request is rejected, print the body rather than reading the error:

client->DescribeRequest(inputs)->PrintLine();

The two protocols shape the same tensors very differently and a tensor nested to the wrong depth looks identical in Objeck, so seeing what was sent is usually faster than interpreting what came back.

v2, shape [4]     {"inputs":[{"data":[...],"datatype":"FP32","name":"input","shape":[4]}]}
v1, shape [4]     {"inputs":{"input":[1.0,2.0,3.0,4.0]}}
v1, shape [2,2]   {"inputs":{"input":[[1.0,2.0],[3.0,4.0]]}}

-lib models,inference,net,json,cipher,gen_collect. Neither is part of any @ alias; name the libraries, or add a group to configobjk.ini.

One interface over both

Tensor, EndPoint and the Engine interface live in API.Models, which both backends link. So calling code can name a backend once and not again:

use API.Models, API.Onnx, API.Inference, Collection;

engine : API.Models.Engine;
if(use_gpu_box) {
  engine := HttpEngine->New("http://gpu-box:8000", "iris");
}
else {
  engine := OnnxEngine->New("iris.onnx");
};

if(<>engine->IsOpen()) {
  EndPoint->GetLastError()->PrintLine();
  return;
};

outputs := engine->Run(inputs);
engine->Close();

IsOpen differs in kind between them and the difference is worth knowing: OnnxEngine reports whether a file loaded, once; HttpEngine asks the server and the answer can change between calls, so it is not cached.

What the interface deliberately leaves out is anything only one backend has -- GetModelInfo on ONNX, Metadata and DescribeRequest on HTTP. Reach them through GetSession() or GetClient(); an interface that carried the union of two backends would describe neither.


Computer Vision (OpenCV)

Compile: obc -src your.obs -lib json,cipher,opencv

use API.OpenCV;

# Load, process, save
image   := Image->Load("photo.jpg");
gray    := image->CvtColor(ColorConversionCodes->BGR2GRAY);
blurred := gray->GaussianBlur(5, 5, 0.0);   # sigma 0: derived from the kernel size
edges   := blurred->Canny(50, 150);
edges->Save("edges.jpg");
use API.OpenCV, API.Onnx, System.IO.Filesystem;

# Face detection: the OpenCV bundle has no cascade classifier, so detect faces
# with the ONNX FaceSession (add onnx to -lib) and draw the boxes with OpenCV
bytes   := FileReader->ReadBinaryFile("group.jpg");
session := FaceSession->New("det_10g.onnx");
faces   := session->Detect(bytes, 0.5);
count   := faces->GetSize();
"Faces: {$count}"->PrintLine();

image := Image->Load(bytes);
green := Scalar->New(0.0, 255.0, 0.0);
detections := faces->GetDetections();
each(face in detections) {
    image := image->DrawRectangle(face->GetBounds(), green, 2);
};
image->Save("faces.jpg");
session->Close();
# Resize and color conversion
resized := image->Resize(Size->New(320, 240));
hsv     := resized->CvtColor(ColorConversionCodes->BGR2HSV);
hsv->Save("output.jpg");

Natural Language Processing

Compile: obc -src your.obs -lib gen_collect,nlp

use System.NLP;

# Sentiment analysis
text      := "This product is absolutely wonderful!";
sentiment := SentimentAnalyzer->Classify(text);
sentiment->PrintLine();   # "positive"
# TF-IDF vectorization
docs := String->New[3];
docs[0] := "cats are pets";
docs[1] := "dogs are pets";
docs[2] := "birds can fly";

tfidf := TF_IDF->New();
tfidf->Fit(docs);

vector := tfidf->Transform("cats and dogs");
each(v in vector) {
    v->PrintLine();
};
# Tokenization
tokens := Tokenizer->WordTokenize("The quick brown fox");
each(t in tokens) {
    t->PrintLine();
};
# Text similarity (cosine over the TF-IDF vectors above)
sim := TextSimilarity->CosineSimilarity(tfidf->Transform("cats are pets"), tfidf->Transform("dogs are pets"));
"Similarity: {$sim}"->PrintLine();

BERT tokens and sentence embeddings

WordPieceTokenizer is BERT's tokenizer (Hugging Face's BertTokenizer, checked id for id on a 13,000-line corpus); it reads a model's vocab.txt and gives the input_ids, attention_mask and token_type_ids a BERT-family ONNX model takes. ApiSequenceTokenizer builds a vocabulary of API names from malware traces. BpeTokenizer is RoBERTa's and GPT-2's byte-level BPE (checked id for id and offset for offset against Hugging Face's tokenizers, with DistilRoBERTa's own 50,265-token vocabulary). Both implement SubwordTokenizer, and their encodings carry each token's character offsets (GetOffsets, BPE). BertEmbedder (library nlp_onnx) runs an exported BERT through ONNX Runtime and pools its output into one vector per text — CLS, mean over the real tokens, or the model's pooler. The encoder is frozen: train a classifier on the embeddings. Design and limits: docs/ML_BERT_DESIGN.md.

Compile: obc -src your.obs -lib models,opencv,cipher,json,onnx,nlp,nlp_onnx (or -lib @vision)

use System.NLP;
use System.NLP.Onnx;

tokenizer := WordPieceTokenizer->New("vocab.txt");
tokenizer->Tokenize("unaffable")->Size()->PrintLine();   # 3: un ##aff ##able
encoding := tokenizer->Encode("He's unaffable.", 16);    # [CLS] ... [SEP] [PAD]...

embedder := BertEmbedder->New("model.onnx", "vocab.txt", 128);
if(embedder->IsOpen()) {
    embedder->SetPooling(BertEmbedder->Pooling->MEAN);
    X := embedder->Embed(["CreateFileW ReadFile CloseHandle", "RegOpenKeyExA RegSetValueExA"]);
    embedder->Close();
};

Text classification with a fine-tuned model

TextClassifier (library nlp_onnx) runs a fine-tuned BERT-family classifier the way Python's pipeline("text-classification") does. Point it at the model's folder: what save_pretrained writes, with an ONNX export beside it, or a Hub repository's files as downloaded.

The parity test checks ids, logits, scores and labels against transformers on three model shapes. Design: docs/AI_FOUNDATION_DESIGN.md §7.5.

use System.NLP.Onnx;

classifier := TextClassifier->New("distilbert-malware");     # the model folder
if(classifier->IsOpen()) {
    each(p in classifier->Classify(["CreateFileW WriteFile CloseHandle", "RegSetValueExW"])) {
        p->GetLabel()->PrintLine();      # e.g. malware, from config.json's id2label
        p->GetScore()->PrintLine();      # its probability
    };
    classifier->Close();
};

Models from the Hugging Face Hub

HubClient (library ai_hub) downloads a model repository's files by name into a local cache, as Python's from_pretrained does, and returns the snapshot folder the model classes open.

The default cache is .objeck/hub in your home folder; set OBJECK_HUB_CACHE to move it. Design: docs/AI_FOUNDATION_DESIGN.md §7.3.

Compile: obc -src your.obs -lib gen_collect,cipher,json,net,ai_hub

use System.AI.Hub;

hub := HubClient->New();
folder := hub->Snapshot("Xenova/distilbert-base-uncased-finetuned-sst-2-english",
    ["config.json", "tokenizer.json", "tokenizer_config.json", "onnx/model.onnx"]);
if(folder = Nil) {
    hub->GetError()->ErrorLine();
};

Machine Learning (System.ML)

Classical machine learning, pure standard library — no native model runtimes needed. Every estimator follows the same API: Fit, Predict (and PredictClass for classifiers), Score, IsFitted, and Store/Load for model persistence. Stochastic algorithms take a seed for reproducible results.

obc -src program.obs -lib ml,gen_collect,csv -dest program.obe

Regression

use System.ML;

# ordinary least squares with an intercept: y = 2x + 1
X := [[1.0], [2.0], [3.0], [4.0]];
y := [[3.0], [5.0], [7.0], [9.0]];

model := LinearRegression->New();
model->Fit(X, y, true);
"R²={$model->GetRSquared()} RMSE={$model->GetRMSE()}"->PrintLine();

preds := model->Predict([[5.0]]);
preds[0,0]->PrintLine();    # ~11.0
# ridge (L2): closed form, shrinks coefficients as alpha grows
ridge := RidgeRegression->New(0.1);
ridge->Fit(X, y);

# lasso (L1): drives uninformative feature coefficients exactly to zero
lasso := LassoRegression->New(0.5);
lasso->Fit(Xs, ys);
coeffs := lasso->GetCoefficients();   # sparse

# elastic net mixes both penalties
enet := ElasticNet->New(0.1, 0.5);    # alpha, l1_ratio
enet->Fit(X, y);

Classification

# logistic regression with L2 regularization
log := LogisticRegression->New(0.5, 2000, 0.001);
log->Fit(X, y);                        # y holds 0.0 / 1.0 labels
labels := log->PredictClass(X);        # Bool[]
acc := log->Score(X, y);

# linear SVM (hinge loss) and perceptron share the same shape
svm := SVM->New(0.01, 0.1, 500);       # lambda, learning rate, epochs
svm->Fit(X, y);
margins := svm->Predict(X);            # signed distances from the plane

# kernel SVM (C-SVC): scikit-learn's SVC parameters, LIBSVM's solver
kernel_svm := KernelSVM->New(SvmKernel->RBF, 10.0, 0.1);   # kernel, C, gamma
kernel_svm->Fit(Xs, labels);           # Bool[] labels; scale features first
scores := kernel_svm->DecisionFunction(Xs_test);           # signed margins
recall := Metrics->RecallAtFpr(scores, labels_test, 0.001);
# LINEAR, RBF, POLY, SIGMOID; gamma 'scale' (default) or SetGammaAuto();
# SetClassWeightBalanced(), SetCacheSize(mb), GetSupportVectorCount()

# one-class SVM: fit on normal rows, flag what falls outside
detector := OneClassSVM->New(SvmKernel->RBF, 0.05);       # kernel, nu
detector->Fit(Xs_normal);
anomaly := detector->PredictScore(Xs_test);   # -decision: higher = stranger
flags := detector->IsOutlier(Xs_test);        # Bool[]; Predict gives +1/-1

# multiclass Gaussian naive Bayes over integer labels 0..C-1
gnb := GaussianNaiveBayes->New();
gnb->Fit(X, y);
classes := gnb->PredictClass(X);       # Int[]
# trees and ensembles work on Bool[,] rows: features..., label LAST
data := [
  [false, false, false],
  [false, true,  false],
  [true,  false, false],
  [true,  true,  true]];    # label = f0 AND f1

tree := DecisionTree->New(4, 1);       # max depth, min samples
tree->Fit(data);
tree->Predict([true, true])->PrintLine();

forest := RandomForest->New(16);       # bootstrap + majority vote
forest->Fit(data);
forest->Score(data)->PrintLine();

booster := AdaBoost->New(16);          # boosted decision stumps
booster->Fit(data);
# continuous-feature forest: Float[,] rows, Bool[] labels, scores to threshold
forest := RandomForestClassifier->New(100, -1, 1, 42);   # trees, max_depth (-1 = None), min_samples, seed
forest->SetMinSamplesLeaf(5);          # scikit-learn's min_samples_leaf
forest->Fit(X, labels);
scores := forest->PredictScore(X_test);
importances := forest->GetFeatureImportances();   # mean decrease in Gini, sums to 1
seconds := forest->GetFitSeconds();

Runnable lab sweeps (SVM kernel / C / gamma, forest trees / depth / leaf size, on NSL-KDD or synthetic data of its shape) are in programs/examples/ml/.

# k-nearest neighbors with an exact KDTree index
tree := KDTree->New(matrix);
nearest := tree->Nearest(3, [57.0, 170.0]);   # row indexes, closest first

Clustering & Decomposition

# DBSCAN discovers the cluster count and labels outliers -1
scanner := DBSCAN->New(0.8, 3);        # eps, min points
scanner->Fit(X);
labels := scanner->GetLabels();
count := scanner->GetNumClusters();

# Gaussian mixture: soft clustering by EM, reproducible per seed
gmm := GaussianMixture->New(2, 7);     # components, seed
gmm->Fit(X);
resp := gmm->Predict(X);               # responsibilities
hard := gmm->PredictClass(X);

# PCA: project to the top components, reconstruct, explain variance
pca := PCA->New(1);
pca->Fit(X);
reduced := pca->Transform(X);
ratios := pca->GetExplainedVarianceRatio();

Neural Network

use System.ML, Collection;

# feed-forward network with hidden/output bias: learns XOR
inputs := Vector->New()<FloatMatrixRef>;
inputs->AddBack(FloatMatrixRef->New([[0.0], [0.0]]));
inputs->AddBack(FloatMatrixRef->New([[0.0], [1.0]]));
inputs->AddBack(FloatMatrixRef->New([[1.0], [0.0]]));
inputs->AddBack(FloatMatrixRef->New([[1.0], [1.0]]));

targets := Vector->New()<FloatMatrixRef>;
targets->AddBack(FloatMatrixRef->New([[0.01]]));
targets->AddBack(FloatMatrixRef->New([[0.99]]));
targets->AddBack(FloatMatrixRef->New([[0.99]]));
targets->AddBack(FloatMatrixRef->New([[0.01]]));

# inputs, hidden factor, outputs, rate, iterations
network := NeuralNetwork->Train(2, inputs, 8, 1, targets, 0.5, 12500);
network->Confidence(FloatMatrixRef->New([[0.0], [1.0]]))->PrintLine();  # ~0.99

Classifier head over embeddings

EmbeddingClassifier trains a logistic or multi-layer ReLU head with binary cross-entropy by minibatch Adam or SGD, at a configurable learning rate, with class weights for imbalance and a per-epoch training-loss history. It is the trainable half of a frozen-encoder classifier: feed it BertEmbedder rows (see Natural Language Processing above) or any fixed-size features.

use System.ML;

head := EmbeddingClassifier->New([64], 0.001, 20, 32, 42);   # hidden sizes, rate, epochs, batch, seed
head->SetBalancedWeights();                                 # or SetClassWeights(negative, positive)
head->Fit(X_train, y_train);                                # Float[,], Bool[]
loss := head->GetLossHistory();                             # one value per epoch, for a loss plot
probabilities := head->PredictProba(X_test);
labels := head->PredictClass(X_test, 0.5);

Model Persistence

Every estimator round-trips through Store/Load; loaded models reproduce the original predictions exactly.

model->Store("model.dat");
loaded := LinearRegression->Load("model.dat");
loaded->Predict(X);

Lab support (System.ML.Lab)

The plumbing around a parameter sweep, in ml_lab.obl (-lib ml,ml_lab,csv,json or -lib @ai): a deterministic stratified hold-out split, class-ratio resampling of the training set against a fixed test set, a wall-clock stopwatch, a sweep runner that collects accuracy, precision, recall, F1, FPR, false negatives and fit time per value (mean and standard deviation over seeds), a result table that renders as text, Markdown, CSV and JSON, and standalone SVG charts. Bad input raises LabError with a message naming the problem. Design: ML_LAB_SUPPORT_DESIGN.md; a complete run is programs/examples/ml/lab_sweeps.obs.

use System.ML, System.ML.Lab;

# 70/30 stratified split from a seed; every stratum lands in both halves
split := Partition->TrainTest(X, y, 0.3, 42, true);
scaler := FeatureScaler->New();
scaler->Fit(split->GetTrainX());            # training half only
train_z := scaler->Transform(split->GetTrainX());
test_z := scaler->Transform(split->GetTestX());

# the class-imbalance experiment: resample the TRAINING half, the test set is untouched
train := Resample->ToPositiveRatio(train_z, split->GetTrainY(), 0.05, 42);
# a sweep: the Trial builds, fits under a Stopwatch and predicts for one value
class ForestTrial implements Trial {
  @s : DataSplit;
  New(s : DataSplit) { @s := s; }
  method : public : Run(value : Float, name : String, seed : Int) ~ TrialResult {
    forest := RandomForestClassifier->New(value->As(Int), 6, 2, seed);
    sw := Stopwatch->New();
    sw->Start();
    forest->Fit(@s->GetTrainX(), @s->GetTrainY());
    ms := sw->Stop();
    return TrialResult->New(forest->Predict(@s->GetTrainX()), @s->GetTrainY(),
      forest->Predict(@s->GetTestX()), @s->GetTestY(), ms);
  }
}

exp := Experiment->New("Random forest: number of trees", "n_estimators");
exp->SetValues([10.0, 50.0, 100.0, 200.0, 500.0]);
exp->SetSeeds([1, 2, 3]);                        # mean ± std per cell
exp->AddSetting("max_depth", "6");               # the footnote
table := exp->Run(ForestTrial->New(split));

table->ToText()->PrintLine();
table->Save("rf_trees.md");                      # .md, .csv, .json or text by extension
table->ToChart(["test_accuracy", "precision", "recall", "f1"])->Save("rf_trees.svg");
table->ToChart(["train_accuracy", "test_accuracy"])->Save("overfitting.svg");
# charts on their own: lines with error bars, a log x axis, grouped bars, a loss curve
c := Chart->New("Recall vs C", "C", "recall");
c->SetLogX(true);
c->AddLine("rbf", cs, recall_rbf, recall_std);
c->Save("recall_vs_c.svg");

loss := Chart->LossCurve("Training loss", ["lr=0.1", "lr=0.01"], losses);  # rows = series, columns = epochs

Classic AI (System.AI)

Search, game playing, optimization and tabular reinforcement learning — -lib ai (or the @ai alias). All stochastic algorithms are seeded for reproducible runs.

obc -src program.obs -lib ai,gen_collect -dest program.obe
use System.AI;

graph := Graph->New(6);                # nodes 0..5
graph->AddEdge(0, 1, 1.0);             # directed; add both ways if undirected
graph->AddEdge(1, 2, 1.0);
graph->AddEdge(2, 5, 1.0);
graph->AddEdge(0, 5, 100.0);

# minimum cost
result := Dijkstra->FindPath(graph, 0, 5);
"cost={$result->GetCost()}"->PrintLine();          # 3.0
path := result->GetPath();                          # [0, 1, 2, 5]

# A* with a per-node heuristic (admissible -> same cost, fewer expansions)
heuristic := [3.0, 2.0, 1.0, 0.0, 0.0, 0.0];
result := AStar->FindPath(graph, 0, 5, heuristic);

# fewest hops / reachability
result := BreadthFirst->FindPath(graph, 0, 5);      # takes the direct edge
result := DepthFirst->FindPath(graph, 0, 5);

Game Playing

Implement the GameState interface (GetMoves, Apply, IsTerminal, Evaluate, IsMaximizing — evaluation is always from the maximizing player's perspective), then search it:

# perfect play with alpha-beta pruning
searcher := Minimax->New(9);            # depth limit
best := searcher->FindBestMove(state);

# Monte Carlo tree search for bigger branching factors
mcts := MonteCarloTreeSearch->New(2000, 1.414, 7);  # playouts, UCB1 c, seed
best := mcts->FindBestMove(state);

Optimization

# genetic algorithm over Bool[] chromosomes: implement FitnessFunction
class OneMax implements FitnessFunction {
  New() {}
  method : public : Fitness(genes : Bool[]) ~ Float {
    count := 0;
    each(i : genes) { if(genes[i]) { count += 1; }; };
    return count->As(Float);
  }
}

ga := GeneticAlgorithm->New(40, 24, 0.02, 0.9, 7);  # pop, genes, mutate, cross, seed
best := ga->Run(OneMax->New(), 80);
"fitness: {$ga->GetBestFitness()}"->PrintLine();    # 24.0
# simulated annealing / hill climbing over Float[] states: implement EnergyFunction
sa := SimulatedAnnealing->New(10.0, 0.995, 0.5, 7); # temp, cooling, step, seed
best := sa->Run(energy, [-8.0, 9.0], 4000);

hc := HillClimbing->New(0.5, 50, 5.0, 7);           # step, patience, restart range, seed
best := hc->Run(energy, start, 3000);

Reinforcement Learning

Implement the Environment interface (GetNumStates, GetNumActions, Reset, Step), then train:

agent := QLearning->New(0.2, 0.95, 0.2, 7);  # alpha, gamma, epsilon, seed
agent->Train(env, 400, 60);                  # episodes, max steps
best := agent->BestAction(state);

# on-policy variant
sarsa := Sarsa->New(0.2, 0.95, 0.2, 7);
sarsa->Train(env, 400, 60);
# explicit MDP solved by value iteration
mdp := MarkovDecisionProcess->New(6, 2);     # states, actions
mdp->AddTransition(0, 1, 1, 1.0, -1.0);      # state, action, next, prob, reward
mdp->AddTransition(4, 1, 5, 1.0, 10.0);
mdp->Solve(0.95, 0.000001, 1000);            # gamma, tolerance, max iterations
policy := mdp->GetPolicy();
values := mdp->GetValues();

Library Aliases

-lib accepts @-prefixed aliases that expand to groups of libraries, defined in lib/configobjk.ini next to the installed .obl files:

obc -src program.obs -lib @ai -dest program.obe
AliasExpands toUse for
@stdjson, json_stream, net, ciphereveryday networked apps
@webnet_h2, net_quic, web_server, json_rpc, net_server, net, json, cipherHTTP/2, HTTP/3, the embedded server, JSON-RPC
@dataxml, regex, csv, query, rss, misc, net, json, cipherparsing and querying structured data
@mlgemini, openai, ollama, net_server, misc, net, json, cipherLLM clients: OpenAI, Gemini, Ollama
@aiai, ml, ml_lab, nlp, csv, jsonlocal System.ML / System.ML.Lab / System.AI / System.NLP work
@visionopencv, onnx, nlp_onnx, models, nlp, json, cipherOpenCV and ONNX inference, BERT text embeddings
@gamesdl2, sdl_game, sdl_gl, json, gen_collectSDL games, 2D and OpenGL

Aliases and explicit names mix in any order, for either --target (-lib @ai,json and -lib json,@ai are the same). Groups are user-editable: add a section to configobjk.ini and reference it as @yourname.

A group must be closed under dependencies — the linker loads exactly what the group lists and does not chase a member's own dependencies, so a group that omits one fails on every use, even from a program that never touches it.

An unknown alias names the config file and lists the groups it does define. An unresolvable class names the library that would supply it:

Error: Unable to resolve external library class: 'Web.HTTP.Server.Request'.
        Add it with '-lib net_server'

See cli_options.md for the same table alongside the rest of obc's options.


Quick Reference

CapabilityClassLibraryKey
Chat completionResponseopenaiOpenAI
Vision (image input)Response + ImageQueryopenaiOpenAI
Realtime audioRealtimeopenaiOpenAI
Image generationImageopenaiOpenAI
Text embeddingsEmbeddingopenaiOpenAI
Content moderationModerationopenaiOpenAI
Batch processingBatchopenaiOpenAI
Generate (text/vision)Model::GenerateContentgeminiGemini
Structured JSON outputModel::GenerateContent + schemageminiGemini
Search groundingModel::GenerateContentWithGroundinggeminiGemini
Gemini embeddingsModel::EmbedContentgeminiGemini
Batch embeddingsModel::BatchEmbedContentgeminiGemini
File uploadFileManagergeminiGemini
Context cachingCachedContentgeminiGemini
Local text genCompletion / ChatollamaNone
Local embeddingsModel::EmbeddingsollamaNone
Face recognitionFaceSessiononnxNone
Object detectionYoloSessiononnxNone
Local SLMPhi3SessiononnxNone
Image classificationResNetSessiononnxNone
Pose estimationOpenPoseSessiononnxNone
SegmentationDeepLabSessiononnxNone
Any ONNX modelSession, Tensormodels, onnxNone
Model served over HTTPClient, Tensormodels, inferenceNone
Either, behind one interfaceEngine, OnnxEngine, HttpEnginemodels + one backendNone
Computer visionImage, VideoCaptureopencvNone
Sentiment / TF-IDFSentimentAnalyzer, TF_IDFnlpNone
BERT tokenizationWordPieceTokenizer, ApiSequenceTokenizernlpNone
Sentence embeddings (BERT)BertEmbeddernlp_onnx + models, onnx, nlpNone
Text classification (fine-tuned BERT)TextClassifier, Predictionnlp_onnx + models, onnx, nlpNone
Hugging Face Hub downloadsHubClientai_hub + net, json, cipherNetwork (cache works offline)
Linear / regularized regressionLinearRegression, RidgeRegression, LassoRegression, ElasticNetmlNone
Linear classifiersLogisticRegression, SVM, PerceptronmlNone
Kernel SVMsKernelSVM (linear, RBF, poly, sigmoid), OneClassSVMmlNone
Probabilistic classifiersGaussianNaiveBayes, NaiveBayesmlNone
Trees & ensemblesDecisionTree, RandomForest, AdaBoostmlNone
Nearest neighborsKNearestNeighbors, KDTreemlNone
ClusteringKMeans, DBSCAN, GaussianMixturemlNone
DecompositionPCAmlNone
Neural networkNeuralNetworkmlNone
Classifier head over embeddingsEmbeddingClassifiermlNone
Graph searchDijkstra, AStar, BreadthFirst, DepthFirstaiNone
Game playingMinimax, MonteCarloTreeSearchaiNone
MetaheuristicsGeneticAlgorithm, SimulatedAnnealing, HillClimbingaiNone
Reinforcement learningQLearning, Sarsa, MarkovDecisionProcessaiNone

Model Recommendations

TaskOpenAIGeminiOllama
General chatgpt-4o-minigemini-2.0-flashllama3.2
Reasoningo3-minigemini-2.5-proqwen2.5
Visiongpt-4ogemini-2.0-flashllava
Realtime audiogpt-4o-realtime-preview——
Embeddingstext-embedding-3-smalltext-embedding-004nomic-embed-text
Image gendall-e-3——
Fast + cheapgpt-4o-minigemini-2.5-flashphi3

Example Programs

Full working examples are in programs/frameworks/:

openai/   openai_chat.obs     openai_images.obs   openai_moderation.obs
          openai_batch.obs    openai_tune.obs      openai_responses.obs

gemini/   gemini_image.obs    gemini_audio.obs     gemini_files.obs
          gemini_cache.obs    gemini_grounding.obs gemini_embed.obs

ollama/   test_0.obs (generate)   test_1.obs (vision)
          test_2.obs (chat)       test_3.obs (models)

opencv_onnx/  demo_face.obs    demo_yolo.obs    demo_resnet.obs
              demo_phi3_*.obs  demo_phi3v_*.obs

programs/examples/ml/bert_malware_lab.obs runs a BERT malware-classification lab's three experiments — class imbalance with a fixed test set, MAX_SEQ_LENGTH, and learning rate with per-epoch loss curves — on a frozen BERT encoder and a trained EmbeddingClassifier head, from a CSV of API-call traces or a synthetic corpus (--synthetic). Its header says how this differs from the lab's end-to-end fine-tuning.

A complete System.ML.Lab run -- two parameter sweeps with tables, CSVs and SVG charts -- is programs/examples/ml/lab_sweeps.obs.