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.
Contents
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:
| Protocol | Served by | Shape |
|---|---|---|
OPEN_INFERENCE (default) | Triton, KServe, OpenVINO Model Server | KServe v2: flat data, with shape and datatype stated |
TF_SERVING | TensorFlow Serving | v1 :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.
- Model:
model.onnxoronnx/model.onnx. - Tokenizer:
tokenizer.json(WordPiece), orvocab.txtwithtokenizer_config.json. - Labels:
config.json'sid2label. - Scores: softmax, or sigmoid for multi-label and one-label models, as
transformerschooses them.
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.
- Pinned to one commit: every file of a snapshot comes from the same commit.
- Verified: each file is checked against the Hub's SHA-256 (large files) or git blob hash (small files), and its size. A mismatch is not kept.
- Streamed: downloads go to disk, so file size is limited only by disk space.
- Cached: a second run downloads nothing, and
OBJECK_HUB_OFFLINE=1works from the cache alone. Python's cache is read as a fallback, never written. - Tokens stay with the Hub: a token (
HF_TOKEN) is sent to the Hub only, never to its file CDN.
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
Graph Search
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
| Alias | Expands to | Use for |
|---|---|---|
@std | json, json_stream, net, cipher | everyday networked apps |
@web | net_h2, net_quic, web_server, json_rpc, net_server, net, json, cipher | HTTP/2, HTTP/3, the embedded server, JSON-RPC |
@data | xml, regex, csv, query, rss, misc, net, json, cipher | parsing and querying structured data |
@ml | gemini, openai, ollama, net_server, misc, net, json, cipher | LLM clients: OpenAI, Gemini, Ollama |
@ai | ai, ml, ml_lab, nlp, csv, json | local System.ML / System.ML.Lab / System.AI / System.NLP work |
@vision | opencv, onnx, nlp_onnx, models, nlp, json, cipher | OpenCV and ONNX inference, BERT text embeddings |
@game | sdl2, sdl_game, sdl_gl, json, gen_collect | SDL 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
| Capability | Class | Library | Key |
|---|---|---|---|
| Chat completion | Response | openai | OpenAI |
| Vision (image input) | Response + ImageQuery | openai | OpenAI |
| Realtime audio | Realtime | openai | OpenAI |
| Image generation | Image | openai | OpenAI |
| Text embeddings | Embedding | openai | OpenAI |
| Content moderation | Moderation | openai | OpenAI |
| Batch processing | Batch | openai | OpenAI |
| Generate (text/vision) | Model::GenerateContent | gemini | Gemini |
| Structured JSON output | Model::GenerateContent + schema | gemini | Gemini |
| Search grounding | Model::GenerateContentWithGrounding | gemini | Gemini |
| Gemini embeddings | Model::EmbedContent | gemini | Gemini |
| Batch embeddings | Model::BatchEmbedContent | gemini | Gemini |
| File upload | FileManager | gemini | Gemini |
| Context caching | CachedContent | gemini | Gemini |
| Local text gen | Completion / Chat | ollama | None |
| Local embeddings | Model::Embeddings | ollama | None |
| Face recognition | FaceSession | onnx | None |
| Object detection | YoloSession | onnx | None |
| Local SLM | Phi3Session | onnx | None |
| Image classification | ResNetSession | onnx | None |
| Pose estimation | OpenPoseSession | onnx | None |
| Segmentation | DeepLabSession | onnx | None |
| Any ONNX model | Session, Tensor | models, onnx | None |
| Model served over HTTP | Client, Tensor | models, inference | None |
| Either, behind one interface | Engine, OnnxEngine, HttpEngine | models + one backend | None |
| Computer vision | Image, VideoCapture | opencv | None |
| Sentiment / TF-IDF | SentimentAnalyzer, TF_IDF | nlp | None |
| BERT tokenization | WordPieceTokenizer, ApiSequenceTokenizer | nlp | None |
| Sentence embeddings (BERT) | BertEmbedder | nlp_onnx + models, onnx, nlp | None |
| Text classification (fine-tuned BERT) | TextClassifier, Prediction | nlp_onnx + models, onnx, nlp | None |
| Hugging Face Hub downloads | HubClient | ai_hub + net, json, cipher | Network (cache works offline) |
| Linear / regularized regression | LinearRegression, RidgeRegression, LassoRegression, ElasticNet | ml | None |
| Linear classifiers | LogisticRegression, SVM, Perceptron | ml | None |
| Kernel SVMs | KernelSVM (linear, RBF, poly, sigmoid), OneClassSVM | ml | None |
| Probabilistic classifiers | GaussianNaiveBayes, NaiveBayes | ml | None |
| Trees & ensembles | DecisionTree, RandomForest, AdaBoost | ml | None |
| Nearest neighbors | KNearestNeighbors, KDTree | ml | None |
| Clustering | KMeans, DBSCAN, GaussianMixture | ml | None |
| Decomposition | PCA | ml | None |
| Neural network | NeuralNetwork | ml | None |
| Classifier head over embeddings | EmbeddingClassifier | ml | None |
| Graph search | Dijkstra, AStar, BreadthFirst, DepthFirst | ai | None |
| Game playing | Minimax, MonteCarloTreeSearch | ai | None |
| Metaheuristics | GeneticAlgorithm, SimulatedAnnealing, HillClimbing | ai | None |
| Reinforcement learning | QLearning, Sarsa, MarkovDecisionProcess | ai | None |
Model Recommendations
| Task | OpenAI | Gemini | Ollama |
|---|---|---|---|
| General chat | gpt-4o-mini | gemini-2.0-flash | llama3.2 |
| Reasoning | o3-mini | gemini-2.5-pro | qwen2.5 |
| Vision | gpt-4o | gemini-2.0-flash | llava |
| Realtime audio | gpt-4o-realtime-preview | — | — |
| Embeddings | text-embedding-3-small | text-embedding-004 | nomic-embed-text |
| Image gen | dall-e-3 | — | — |
| Fast + cheap | gpt-4o-mini | gemini-2.5-flash | phi3 |
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.