こんにちは、かつコーチです。
「関連する記事を自動でおすすめしたい」「類似の問い合わせを検索したい」といった要望を実現する鍵になるのが、embeddingです。
このシリーズもいよいよRAG(検索拡張生成)実装の入り口に差し掛かりました。
この記事では、embeddingという概念を比喩を使ってわかりやすく解説し、実際にLaravelからAPIを呼び出すところまで実装します。
embeddingとは?
文章の「意味」を数値化した地図上の座標
embeddingとは、文章や単語を、意味の近さを反映した数値の配列(ベクトル)に変換する技術です。
イメージしやすい比喩として、embeddingは「文章の意味を数値化した地図上の座標」だと考えてください。
「犬」と「子犬」は地図上で近い場所に置かれ、「犬」と「株価」は遠く離れた場所に置かれる、というイメージです。
この座標同士の距離を計算することで、コンピュータは「意味が近い文章」を数学的に見つけ出せるようになります。
なぜ検索に使えるのか
通常のキーワード検索は、文字列が一致するかどうかでしか判定できません。
「Laravelのバグを直したい」と「Laravelでエラーが出て困っている」は、キーワード検索では別物として扱われがちです。
しかしembeddingを使えば、この2つの文章は意味的に近い座標に配置されるため、「似た内容の質問」として検索にヒットさせられます。
これが、社内FAQ検索やレコメンド機能でembeddingが活用される理由です。
基本の書き方 / 実装手順
embeddingモデルの概要
代表的なembeddingモデルに、OpenAIの text-embedding-3-small があります。
このモデルは1536次元(座標の軸の数が1536本あるということ)のベクトルを出力し、比較的安価でありながら検索性能が良いとされています。
次元数が多いほど表現力は高くなりますが、その分ストレージや計算コストも増えるため、text-embedding-3-small は精度とコストのバランスが取れたモデルとしてよく選ばれます。
LaravelからembeddingAPIを呼び出す基本コード
PrismPHPを使うと、embeddingの取得もシンプルなコードで書けます。
// app/Services/EmbeddingService.php
namespace App\Services;
use Prism\Prism\Prism;
use Prism\Prism\Enums\Provider;
class EmbeddingService
{
/**
* 文章をembedding(数値ベクトル)に変換する
*
* @return float[] 1536次元のベクトル
*/
public function embed(string $text): array
{
$response = Prism::embeddings()
->using(Provider::OpenAI, 'text-embedding-3-small')
->fromInput($text)
->asEmbeddings();
return $response->embeddings[0]->embedding;
}
}
// 使用例
use App\Services\EmbeddingService;
$service = new EmbeddingService();
$vector = $service->embed('Laravelでエラーが出て困っている');
// $vector は [0.0123, -0.0456, 0.0789, ...] のような1536個の数値の配列
このベクトルをDBに保存しておき、あとで別の文章のベクトルとの距離(コサイン類似度:ベクトル同士の向きの近さを測る指標)を計算すれば、意味的な検索が実現できます。
記事同士の類似度を試しに計算してみる
コサイン類似度の計算自体は、シンプルな数式で書けます。
// app/Support/VectorMath.php
namespace App\Support;
class VectorMath
{
public static function cosineSimilarity(array $a, array $b): float
{
$dotProduct = 0.0;
$normA = 0.0;
$normB = 0.0;
foreach ($a as $i => $value) {
$dotProduct += $value * $b[$i];
$normA += $value ** 2;
$normB += $b[$i] ** 2;
}
return $dotProduct / (sqrt($normA) * sqrt($normB));
}
}
値が1に近いほど「意味が近い」、0に近いほど「意味が遠い」と判断できます。
よくあるつまずきポイント・エラー対処
次元数の不一致で計算エラーが出た話
実際に試作していたとき、以前 text-embedding-ada-002(1536次元)で保存しておいた古いデータと、新しく text-embedding-3-large(3072次元)で取得したベクトルを比較しようとしてしまいました。
その結果、VectorMath::cosineSimilarity() を呼び出した瞬間、次のような警告とともに配列の範囲外アクセスが発生しました。
Warning: Undefined array key 1536 in /app/Support/VectorMath.php on line 14
原因はシンプルで、モデルが違えば出力される次元数も異なるため、単純に比較できなかったのです。
「同じembeddingモデルで生成したベクトルでなければ比較できない」という、言われてみれば当たり前のことを、実際にエラーを踏んで初めて体に染み込ませました。
❌Before → ✅After
❌Before:異なるembeddingモデルで生成したベクトルを混在させ、次元数の不一致でエラーになった。
✅After:使用したembeddingモデル名をDBのカラムに記録し、同一モデルのベクトル同士でのみ比較するように統一した。
// database/migrations/2026_09_05_000001_add_embedding_model_to_articles_table.php
Schema::table('articles', function (Blueprint $table) {
$table->json('embedding')->nullable();
$table->string('embedding_model')->nullable(); // 例: text-embedding-3-small
});
モデルのバージョンアップ時は、既存データを一括で再embedding(作り直し)する移行作業が必要になる、という運用上の注意点も学びました。
応用・一歩先の使い方
ベクトルデータベースへの道
今回はPHPの配列同士でコサイン類似度を計算しましたが、データ件数が増えると、この総当たり計算はすぐに重くなります。
数百件を超えたあたりから、検索のたびに毎回全件との類似度を計算するのは現実的ではなくなってきます。
そこで登場するのが、ベクトル専用の検索インデックスを備えたデータベースです。
次回予告:pgvectorでベクトル検索の環境を構築する
次回は、PostgreSQLの拡張機能である pgvector を使い、embeddingを効率的に検索できる環境を構築していきます。
今回学んだ「意味を座標に変換する」という考え方が、そのままRAG(検索拡張生成)の土台になります。
まとめ
この記事のポイント
- embeddingは文章の意味を数値化した地図上の座標であり、意味の近さを距離として計算できる
text-embedding-3-smallは1536次元で、コストと性能のバランスが良いモデル- 異なるembeddingモデルのベクトルは次元数が違うため、単純比較できない点に注意する
次に読むべき記事
- 前の記事「ツール呼び出し(Function Calling)でAIにアプリの操作を行わせる」も合わせてどうぞ
- 次は「pgvectorでベクトル検索の環境を構築する」で、実際にベクトル検索を動かしてみましょう
コメント