TensorCode Docs

DocsTypeScriptGuides

Operations and providers#

Every operation uses one calling convention:

const output = op.call(value, { context });   // Python: op(value, context=...)
const later = await op.acall(value, { context });

Implement forward(value, context) (and optionally aforward) and invoke the instance through call/acall, so tracing keeps the boundary. context holds conditioning data and is null when absent. Required operands belong in the primary value. Pure operations opt into replay by overriding get replayable() { return true; }. Importing the operation modules does not load model weights or touch the network.

This guide mirrors the Python operations guide. The concepts, configuration keys and persisted formats are identical; only the API spelling is camelCase.

Vectors (tensorcode/ops/vec)#

import { tensor } from 'tensorcode/nn';
import * as vec from 'tensorcode/ops/vec';

const textSpace = new vec.Space('application.text', 64);
const sharedSpace = new vec.Space('application.retrieval', 32);
const encode = new vec.VocabularyEncoder({
  vocabulary: ['refund', 'transfer', 'card'], dimensions: 64,
  output_space: textSpace.configuration(),
});
const project = new vec.Transform({
  architecture: 'linear', input_space: textSpace.configuration(),
  output_space: sharedSpace.configuration(),
});
const query = project.call(encode.call('refund'));   // a Latent in sharedSpace

new Space(name, dimensions, { version, organization, dtype, device }) identifies a representation. Compatibility compares all of its fields. Equal dimensions alone do not make two spaces compatible. A shared name is a contract you author; it is not evidence of trained alignment.

new Latent(tensor, space, { mask, coordinates, sources, metadata }) keeps the supplied tensor. The last dimension must match the space. A boolean mask matches the leading dimensions, and coordinates add a coordinate axis to that leading shape.

OperationContract
TextEncoder(config)Owned T5/BERT-family text transformer: raw text → output_space. readout is 'sequence', 'pooled' (masked mean) or output encoding
ImageEncoder(config)Owned ViT plus its ViTImageProcessor: CHW image → output_space. preprocess takes tensors, decoded images, URLs (fetched while the call blocks, as in Python), file paths, base64 text or data URIs; apreprocess fetches without blocking
TextDecoder(config)input_space → generated text through an explicit linear or identity bridge
ImageDecoder(config)Owned latent diffusion (diffusers UNet2DConditionModel with every block family it can run, AutoencoderKL, DDIM): input_space → RGB pixels. Sampling needs context.noise or context.seed; fromFoundation imports a diffusers-format repository
VocabularyEncoder(config)A vocabulary list, dimensions, optional output_space; lowercase regex tokens, mean-pooled trainable embeddings
Transform(config)Owned linear, mlp or native transformer; declared input_space and output_space; returns a Latent
Classify(config)Owned head with input_space and labels; returns a Prediction with logits, softmax probabilities and value/values
PatchEncoder(config)Owned convolution; patch_size, in_channels, output_space; CHW/BCHW images → spatial latent patches
Decode(config)Owned linear/mlp/transformer readout: input_space → a tensor of output_dimensions
Score(config)Owned candidate scorer with declared query_space, candidate_space and score meaning
Decide(config) / Retrieve(config)Parameter-free selection: largest, and k for retrieval

Encoders and decoders also live in tensorcode/ops/vec/encode and tensorcode/ops/vec/decode. Their class identities (tensorcode.ops.vec.encode.TextEncoder, ...) match Python's.

Image files decode without dependencies and give the pixels Python's libraries give: decodeImage(bytesOrPath, { mode, applyExifOrientation }) is torchvision.io.decode_image, loadImage(source) is transformers' load_image_as_tensor, and openImage(bytesOrPath) is PIL.Image.open, returning a RasterImage with convert, resize(size, Resampling.BOX, ...), exifTranspose() and toTensor(). PNG, JPEG, GIF, WebP and BMP are supported.

const pixels = encoder.preprocess(['photo.jpg', vec.openImage(bytes).convert('RGB')]);
const thumbnail = vec.openImage('photo.png').resize([64, 64], vec.Resampling.LANCZOS);

Candidate scoring takes new CandidateSet(query, candidates, identities, metadata). Candidate tensors have shape [..., N, features], and the query and candidate batch shapes must agree. Identities are unique, stable strings.

const candidates = new vec.CandidateSet(
  new vec.Latent(tensor([[1, 0, 0, 0]]), new vec.Space('shared', 4)),
  new vec.Latent(tensor([[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]]]), new vec.Space('shared', 4)),
  ['a', 'b', 'c'],
);
const score = new vec.Score({
  architecture: 'mlp', hidden_dimensions: [8],
  query_space: { name: 'shared', dimensions: 4 },
  candidate_space: { name: 'shared', dimensions: 4 },
  meaning: 'learned unnormalized relevance',
});
const scored = score.call(candidates);
const decision = new vec.Decide({ largest: true }).call(scored);
const retrieval = new vec.Retrieve({ k: 2, largest: true }).call(scored);

Scores have shape [..., N] and keep their declared meaning. They are not probabilities unless you make them so. Selectors skip masked candidates, and retrieval cannot return more than the valid count.

Constructors take JSON configuration and create every parameter up front, so you can build the optimizer immediately. Owned operations support savePretrained(directory) and await X.fromPretrained(directory). Supported await X.fromFoundation(repo, { revision, ... }) factories import pretrained BERT, RoBERTa, DistilBERT, T5, ViT or CLIP weights; any newly added projections start untrained. The advanced X.fromModule(module, options) factories wrap a tensorcode/nn module you supply. Arbitrary executable modules cannot be rebuilt safely from data-only artifacts, so saving one throws instead of silently dropping its behavior.

See examples/ownedVectorLifecycle.ts for a complete train → save → reload → resume program.

Messages and model providers (tensorcode/ops/text)#

new Message(role, content) holds plain text or immutable parts (new TextPart(text), new ImagePart({ data | url, mediaType, detail })). Encoding never downloads URLs.

TextEncoder, ImageEncoder and TextDecoder are pure message serialization operations. The structured operations Transform, Classify, Decide, Score and Retrieve can either own a local seq2seq model or wrap an explicit external provider:

import * as text from 'tensorcode/ops/text';

class Rules implements text.Model {
  complete(request: text.ModelRequest): text.ModelOutput {
    return new text.ModelOutput({ structured: { label: 'billing', abstained: false } });
  }
}

const route = text.Classify.fromModel(new Rules(), {
  labels: ['billing', 'technical'],
  descriptions: { billing: 'payments, charges and refunds' },
  instructions: 'Route the support ticket',
});
const result = route.call([new text.Message('user', 'I was charged twice')]);
console.log(result.label); // billing

Providers implement Model (complete), AsyncModel (acomplete), BatchModel or QuestionModel (completeQuestions). A function (messages) => string is also accepted by Transform.fromModel. Structured responses are validated strictly. A contract violation raises InvalidModelOutput, and nothing is repaired.

Classifications and decisions select only the configured alternatives. A returned distribution must contain exactly those alternatives, with finite values in [0, 1] that sum to one within 0.001. Results are frozen records: ClassificationResult(label, { distribution, confidence, abstained }), DecisionResult(choice, ...), ScoreResult(value, ...) and RetrievalResult(keys, items, { scores, abstained }).

Owned operations take native_config and an embedded tokenizer configuration, or bootstrap from a T5 checkpoint:

const route = await text.Classify.fromFoundation('google/flan-t5-small', {
  revision: '<commit>',
  config: { labels: ['billing', 'technical'], decoding: 'likelihood' },
});

decoding: 'generate' (the default) generates the JSON response as text. decoding: 'likelihood' encodes the input once and scores every alternative with the decoder in one batch. It always returns a complete distribution, but the probabilities stay uncalibrated. Owned text operations declare a teacher-forced objective, so Trainer.fromTool(operation) trains them directly.

text.ask(messages, { name: operation, ... }) answers several named structured questions about the same messages, and await text.aask(...) is the async form. When every operation wraps the same QuestionModel, all questions travel in one exchange.

Integrations (tensorcode/integrations)#

AdapterBehavior
new OpenAICompatibleModel({ baseUrl, model, apiKey, api: 'chat_completions' | 'responses', timeout })Text and images, strict JSON schemas
new JevModel({ apiKey, baseUrl, model })The TypeSafe /v1/systemone mapping; completeQuestions sends several questions in one request
await LocalModel.fromPretrained(modelId, { revision }) or new LocalModel(model, processor, { modelId })An explicitly supplied Transformers.js model and processor (optional peer @huggingface/transformers)

HTTP adapters use fetch with one buffered request and no retries or fallback. Like Python's, their complete blocks until the response arrives (the request runs on a worker thread), so op.call and text.ask work with them; acall/aask are the asynchronous forms. LocalModel.complete runs the model in that worker thread (loaded from the fromPretrained arguments, or from an explicit worker: { module, exportName } loader for a supplied model). In a browser main thread, which cannot block, the blocking methods raise SynchronousCallUnavailable (a NotImplementedError), and a provider built with an injected fetch function is asynchronous only. Redirects, refusals and truncated responses raise ProviderError subclasses: ProviderHTTPError (with .status), ProviderTimeout and ProviderProtocolError. Credentials never appear in configuration or error messages.

Graphs (tensorcode/ops/graph)#

new Graph(nodes, { edges, sources, identity, attributes, nodeAttributes, edgeAttributes, sourceAnchors }) preserves the supplied node identities, relation strings, competing edges and source evidence, deep-frozen. new SourceAnchor(source, { target, location, attributes }) targets a graph, a node or an edge index.

Graph operations (Encode, TextEncode, Decode, TextDecode, Transform, Score, Retrieve, Decide, Classify) are symbolic stubs, as in Python. Every call throws NotImplementedError.

Write your own operation#

import { readFileSync } from 'node:fs';
import { Operation, type Context } from 'tensorcode/ops';

class ReadText extends Operation<string, string> {
  forward(path: string, context: Context | null): string {
    if (context) throw new Error('ReadText does not consume context');
    return readFileSync(path, 'utf8');
  }
}

const contents = new ReadText().call('README.md');

Invoke call(...), not forward(...), to keep the tracing boundary. The base replayable = false is right for I/O. Opt into replay only for operations that can safely recompute. For tensor code, extend ModuleOperation or wrap a module with vec.Transform.fromModule. If an operation will be persisted, give it a stable static qualifiedName and a truthful JSON configuration(); see training.