---
title: Embed plugin
description: Turn text into vectors. Cosine similarity. Compact storage. The plumbing under the RAG plugin, also useful on its own.
---

The Embed plugin is a thin wrapper around Ollama's `/api/embeddings` plus three useful helpers: cosine similarity, compact base64 packing, and inverse unpacking. The [RAG plugin](/en/ai/module/rag/) uses it internally, but it's also useful on its own — for clustering, deduplication, semantic search, anomaly detection.

## API

```php
$embed = (new \Nibiru\Module\Ai\Ai())->embed();

$vec     = $embed->one('controller');                  // float[]
$vectors = $embed->batch(['a', 'b', 'c']);             // float[][]

$score   = \Nibiru\Module\Ai\Plugin\Embed::cosine($a, $b);     // 0..1
$packed  = \Nibiru\Module\Ai\Plugin\Embed::pack($vec);          // base64 string
$vec     = \Nibiru\Module\Ai\Plugin\Embed::unpack($packed);     // back to float[]
```

## Pattern: deduplicate near-duplicate strings

```php
$embed = $ai->embed();
$candidates = ['How do I create a module?',
               'How can I make a new module?',
               'What is MMVC?'];

$vecs = $embed->batch($candidates);
foreach ($vecs as $i => $a) {
    foreach ($vecs as $j => $b) {
        if ($i >= $j) continue;
        $sim = \Nibiru\Module\Ai\Plugin\Embed::cosine($a, $b);
        if ($sim > 0.9) {
            echo "Near-dup: {$candidates[$i]} ≈ {$candidates[$j]} ($sim)\n";
        }
    }
}
```

## Pattern: semantic tagging

```php
$tags = ['authentication', 'forms', 'database', 'modules'];
$tagVecs = array_combine($tags, $embed->batch($tags));

function bestTag(string $text, array $tagVecs, $embed): string {
    $tv = $embed->one($text);
    $best = ['_unknown', -INF];
    foreach ($tagVecs as $tag => $vec) {
        $s = \Nibiru\Module\Ai\Plugin\Embed::cosine($tv, $vec);
        if ($s > $best[1]) $best = [$tag, $s];
    }
    return $best[0];
}

echo bestTag('User::isAuthorized', $tagVecs, $embed);  // → 'authentication'
echo bestTag('Pageination::setTable', $tagVecs, $embed); // → 'database' (probably)
```

## Storage

Embeddings are float arrays — typically 768 floats for `nomic-embed-text`, 1024 for `mxbai-embed-large`. That's 3 KB or 4 KB per vector raw.

Use `Embed::pack()` to base64-encode them as 4-byte floats:

```php
$compact = Embed::pack($vec);    // ~4 KB → ~5.3 KB base64 string
$vec     = Embed::unpack($compact);
```

The RAG plugin uses this format internally for its JSON files.

## Embedding model choices

Pull on neuronetz.ai once:

```bash
curl https://api.neuronetz.ai/api/pull -d '{"name":"nomic-embed-text"}'   # 768 dim, default
curl https://api.neuronetz.ai/api/pull -d '{"name":"mxbai-embed-large"}'  # 1024 dim, higher quality
```

In `ai.ini`:

```ini
[AI]
embed.model = "nomic-embed-text"
embed.dim   = 768
```

:::caution[Don't mix models]
Vectors from `nomic-embed-text` and `mxbai-embed-large` live in different geometric spaces — cosine between them is meaningless. If you change `embed.model`, **reset** any RAG collection or saved embeddings before mixing.
:::

## Common pitfalls

- **Calling `one()` in a tight loop.** Each call is one HTTP round-trip. For >100 items, prefer `batch()` (still serial under the hood, but with consistent error handling).
- **Storing raw float arrays in JSON.** Use `pack()` for ~5x smaller files and faster parse.
- **Comparing cosine to a fixed threshold.** Different embedding models have different "similar" baselines. Don't hard-code 0.85 — calibrate per model.
