embed()->one('hello world'); // float[] * $vecs = $ai->embed()->batch(['a', 'b', 'c']); // float[][] * $sim = \Nibiru\Module\Ai\Plugins\Embed::cosine($a, $b); // 0..1 */ class Embed { protected \stdClass $cfg; protected Ollama $ollama; public function __construct(\stdClass $cfg) { $this->cfg = $cfg; $this->ollama = new Ollama($cfg); } /** * Embed a single string. Returns a flat float[]. */ public function one(string $text): array { $model = $this->cfg->embed_model ?? 'nomic-embed-text'; $res = $this->ollama->embed($model, $text); if (!isset($res['embedding']) || !is_array($res['embedding'])) { throw new \RuntimeException('Ollama embed: no `embedding` in response.'); } return array_map('floatval', $res['embedding']); } /** * Embed many strings. Sequential under the hood (Ollama embeddings * endpoint is single-input), but rate-limited by config. */ public function batch(array $texts): array { $out = []; foreach ($texts as $t) { $out[] = $this->one((string) $t); } return $out; } /** * Cosine similarity between two equal-length vectors. Returns 0–1. */ public static function cosine(array $a, array $b): float { $dot = 0.0; $na = 0.0; $nb = 0.0; $len = min(count($a), count($b)); for ($i = 0; $i < $len; $i++) { $dot += $a[$i] * $b[$i]; $na += $a[$i] * $a[$i]; $nb += $b[$i] * $b[$i]; } $denom = sqrt($na) * sqrt($nb); return $denom === 0.0 ? 0.0 : $dot / $denom; } /** * Pack a vector to a base64 string for compact storage in JSON. */ public static function pack(array $vec): string { return base64_encode(pack('f*', ...$vec)); } /** * Inverse of pack(). */ public static function unpack(string $b64): array { $bin = base64_decode($b64, true); if ($bin === false) return []; return array_values(unpack('f*', $bin)); } }