LocalMode
Core

Classification & NER

Text classification, zero-shot classification, and named entity recognition.

LocalMode provides provider-agnostic functions for text classification, zero-shot classification, and named entity recognition (NER). Pass any model that implements the corresponding interface — the core functions handle retries, cancellation, and structured results.

See it in action

Try Sentiment Analyzer and Email Classifier for working demos of these APIs.

classify()

Classify a single text using a trained classification model:

import { classify } from '@localmode/core';
import { transformers } from '@localmode/transformers';

const model = transformers.classifier('Xenova/distilbert-base-uncased-finetuned-sst-2-english');

const { label, score, usage, response } = await classify({
  model,
  text: 'I love this product!',
});

console.log('Label:', label); // 'POSITIVE'
console.log('Score:', score); // 0.9998
console.log('Duration:', usage.durationMs); // ~120
console.log('Model:', response.modelId);
const controller = new AbortController();

setTimeout(() => controller.abort(), 5000); // Cancel after 5s

const { label, score } = await classify({
  model,
  text: 'I love this product!',
  abortSignal: controller.signal,
});

ClassifyOptions

Prop

Type

ClassifyResult

Prop

Type

classifyMany()

Classify multiple texts in a single call:

import { classifyMany } from '@localmode/core';
import { transformers } from '@localmode/transformers';

const model = transformers.classifier('Xenova/distilbert-base-uncased-finetuned-sst-2-english');

const { results, usage } = await classifyMany({
  model,
  texts: ['I love this!', 'This is terrible!', 'It is okay.'],
});

results.forEach((r, i) => {
  console.log(`Text ${i}: ${r.label} (${r.score.toFixed(2)})`);
});
// Text 0: POSITIVE (1.00)
// Text 1: NEGATIVE (1.00)
// Text 2: POSITIVE (0.56)

ClassifyManyResult

Prop

Type

ClassificationResultItem

Prop

Type

classifyZeroShot()

Classify text into arbitrary labels without fine-tuning:

import { classifyZeroShot } from '@localmode/core';
import { transformers } from '@localmode/transformers';

const model = transformers.zeroShot('Xenova/mobilebert-uncased-mnli');

const { labels, scores } = await classifyZeroShot({
  model,
  text: 'I just bought a new Tesla Model 3',
  candidateLabels: ['automotive', 'technology', 'finance', 'sports'],
});

console.log(labels[0]); // 'automotive'
console.log(scores[0]); // 0.87
const { labels, scores } = await classifyZeroShot({
  model,
  text: 'The new iPhone uses advanced AI for photography',
  candidateLabels: ['technology', 'photography', 'smartphones'],
  multiLabel: true,
});

// Multiple labels can have high scores simultaneously
labels.forEach((label, i) => {
  console.log(`${label}: ${scores[i].toFixed(2)}`);
});
// technology: 0.92
// smartphones: 0.85
// photography: 0.78

ClassifyZeroShotOptions

Prop

Type

ClassifyZeroShotResult

Prop

Type

extractEntities()

Extract named entities (people, organizations, locations, etc.) from text:

import { extractEntities } from '@localmode/core';
import { transformers } from '@localmode/transformers';

const model = transformers.ner('Xenova/bert-base-NER');

const { entities, usage } = await extractEntities({
  model,
  text: 'John works at Microsoft in Seattle',
});

// entities: [
//   { text: 'John', type: 'PERSON', start: 0, end: 4, score: 0.99 },
//   { text: 'Microsoft', type: 'ORG', start: 14, end: 23, score: 0.98 },
//   { text: 'Seattle', type: 'LOC', start: 27, end: 34, score: 0.97 }
// ]

ExtractEntitiesOptions

Prop

Type

ExtractEntitiesResult

Prop

Type

Entity

Prop

Type

extractEntitiesMany()

Extract entities from multiple texts in a single call:

import { extractEntitiesMany } from '@localmode/core';
import { transformers } from '@localmode/transformers';

const model = transformers.ner('Xenova/bert-base-NER');

const { results, usage } = await extractEntitiesMany({
  model,
  texts: [
    'John works at Microsoft',
    'Apple is based in Cupertino',
    'The Eiffel Tower is in Paris',
  ],
});

results.forEach((r, i) => {
  console.log(`Text ${i}:`, r.entities.map((e) => `${e.text} (${e.type})`));
});
// Text 0: ['John (PERSON)', 'Microsoft (ORG)']
// Text 1: ['Apple (ORG)', 'Cupertino (LOC)']
// Text 2: ['Eiffel Tower (LOC)', 'Paris (LOC)']

Cancellation Support

All classification and NER operations support AbortSignal for cancellation:

const controller = new AbortController();

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

try {
  const { label } = await classify({
    model,
    text: 'I love this product!',
    abortSignal: controller.signal,
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Classification was cancelled');
  }
}

Custom Provider Implementations

Implement the core interfaces to create custom classification, zero-shot, and NER providers.

import type {
  ClassificationModel,
  DoClassifyOptions,
  DoClassifyResult,
} from '@localmode/core';

class MyClassifier implements ClassificationModel {
  readonly modelId = 'custom:my-classifier';
  readonly provider = 'custom';
  readonly labels = ['positive', 'negative', 'neutral'];

  async doClassify(options: DoClassifyOptions): Promise<DoClassifyResult> {
    const { texts } = options;

    // Your classification logic here
    const results = texts.map((text) => ({
      label: 'positive',
      score: 0.95,
      allScores: { positive: 0.95, negative: 0.03, neutral: 0.02 },
    }));

    return {
      results,
      usage: {
        inputTokens: texts.join('').length,
        durationMs: 0,
      },
    };
  }
}
import type {
  ZeroShotClassificationModel,
  DoClassifyZeroShotOptions,
  DoClassifyZeroShotResult,
} from '@localmode/core';

class MyZeroShotClassifier implements ZeroShotClassificationModel {
  readonly modelId = 'custom:zero-shot';
  readonly provider = 'custom';

  async doClassifyZeroShot(
    options: DoClassifyZeroShotOptions
  ): Promise<DoClassifyZeroShotResult> {
    const { texts, candidateLabels } = options;

    // Your zero-shot logic here
    const results = texts.map(() => ({
      labels: candidateLabels,
      scores: candidateLabels.map(() => 1 / candidateLabels.length),
    }));

    return {
      results,
      usage: {
        inputTokens: texts.join('').length,
        durationMs: 0,
      },
    };
  }
}
import type {
  NERModel,
  DoExtractEntitiesOptions,
  DoExtractEntitiesResult,
} from '@localmode/core';

class MyNERModel implements NERModel {
  readonly modelId = 'custom:ner';
  readonly provider = 'custom';
  readonly entityTypes = ['PERSON', 'ORG', 'LOC'];

  async doExtract(
    options: DoExtractEntitiesOptions
  ): Promise<DoExtractEntitiesResult> {
    const { texts } = options;

    // Your NER logic here
    const results = texts.map(() => ({
      entities: [],
    }));

    return {
      results,
      usage: {
        inputTokens: texts.join('').length,
        durationMs: 0,
      },
    };
  }
}

For recommended models, provider-specific options, and practical recipes, see the Transformers.js provider pages: Classification, Zero-Shot Classification, and NER.

Next Steps

Showcase Apps

AppDescriptionLinks
Sentiment AnalyzerClassify text sentiment with sequential batch processingDemo · Source
Email ClassifierZero-shot email categorization into custom labelsDemo · Source

On this page