【Laravel】Laravel 13のセマンティック検索(ベクトル検索)を試す

laravelアイキャッチ Laravel

こんにちは、かつコーチです。
Laravel 13の新機能の中でも、RAGアプリケーションを作りたいエンジニアから特に注目を集めているのがセマンティック検索(ベクトル検索)です。
今回はwhereVectorSimilarTo()を実際に動かしながら、PostgreSQL + pgvectorでの環境構築から検索クエリの書き方まで解説します。
前提として、EloquentやPostgreSQLの基本操作は理解している読者を想定しています。

セマンティック検索(ベクトル検索)とは

キーワード検索との違い

従来のLIKE検索やフルテキスト検索は、文字列の一致度で結果を絞り込みます。
一方セマンティック検索は、文章をembedding(文章の意味をベクトル空間上の数値配列に変換したもの)に変換し、そのベクトル同士の距離の近さで「意味が近い文章」を検索します。

たとえば「値上げに困っている」というクエリで検索したとき、キーワード検索では「値上げ」という文字列を含む記事しかヒットしませんが、セマンティック検索では「価格改定で悩んでいる」といった、表現は違っても意味が近い記事もヒットします。
このため、社内FAQ検索やRAGアプリの検索基盤として相性がよい仕組みです。

なぜLaravel 13で必要になるのか

前回の記事で紹介したAI SDKと組み合わせることで、社内ドキュメントを検索してAIに回答させるRAG構成が組みやすくなる、というのが今回の機能追加の背景です。
これまではpgvector拡張を直接SQLで叩くか、サードパーティパッケージを使うしかありませんでしたが、Laravel 13ではEloquentのクエリビルダに標準で組み込まれました。

実装手順:環境構築からクエリまで

手順1:PostgreSQL + pgvector拡張のセットアップ

whereVectorSimilarTo()はPostgreSQL + pgvector拡張が必須です。
MySQLやSQLiteでは利用できないため、まずはデータベース側の対応が必要です。

# pgvector拡張をインストール済みのPostgreSQLコンテナを使う場合の例
docker run -d \
  --name laravel13-pgvector \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=laravel13_app \
  -p 5432:5432 \
  pgvector/pgvector:pg16

.envのデータベース接続もPostgreSQLに向けておきます。

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel13_app
DB_USERNAME=postgres
DB_PASSWORD=secret

手順2:pgvector拡張の有効化とベクトルカラムの追加

マイグレーションでpgvector拡張を有効化し、embeddingを格納するカラムを追加します。

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
    public function up(): void
    {
        DB::statement('CREATE EXTENSION IF NOT EXISTS vector');

        Schema::table('articles', function (Blueprint $table) {
            $table->vector('embedding', dimensions: 1536)->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('articles', function (Blueprint $table) {
            $table->dropColumn('embedding');
        });
    }
};

dimensions(ベクトルの次元数)は、embeddingを生成するAIモデルに合わせて指定します。

手順3:embeddingを生成して保存する

前回紹介したLaravel AI SDKを使って、記事本文からembeddingを生成し保存します。

use Illuminate\Support\Facades\Ai;

class Article extends Model
{
    protected $fillable = ['title', 'body', 'embedding'];

    public function generateEmbedding(): void
    {
        $embedding = Ai::make()->embedding($this->title . "\n" . $this->body);

        $this->update([
            'embedding' => $embedding->vector,
        ]);
    }
}

記事の作成・更新時にモデルイベントで自動生成しておくと、保存漏れを防げます。

protected static function booted(): void
{
    static::saved(function (Article $article) {
        $article->generateEmbedding();
    });
}

手順4:whereVectorSimilarTo()で検索する

準備が整ったら、実際に検索クエリを書きます。

use Illuminate\Support\Facades\Ai;

class ArticleSearchController extends Controller
{
    public function search(Request $request)
    {
        $queryEmbedding = Ai::make()->embedding($request->input('q'));

        $articles = Article::query()
            ->whereVectorSimilarTo('embedding', $queryEmbedding->vector, threshold: 0.75)
            ->limit(10)
            ->get();

        return response()->json($articles);
    }
}

threshold(類似度のしきい値)を調整することで、どこまで意味が離れた結果まで許容するかをコントロールできます。
値を高くするほど、より意味が近い結果だけに絞り込まれます。

よくあるつまずきポイント・エラー対処

つまずき1:pgvector拡張が入っていない環境でのエラー

筆者が最初にこの機能を試したとき、ローカルのPostgreSQLにpgvector拡張が入っておらず、マイグレーション実行時にextension "vector" does not existというエラーが出ました。

// ❌ Before:pgvector拡張が入っていない通常のPostgreSQLに対して
// いきなりCREATE EXTENSIONを実行してしまう
DB::statement('CREATE EXTENSION IF NOT EXISTS vector');
// → SQLSTATE[42704]: extension "vector" does not exist
# ✅ After:pgvector拡張が同梱されたイメージを使うか、
# 既存のPostgreSQLにpgvectorを別途インストールしてから実行する
docker run -d -p 5432:5432 pgvector/pgvector:pg16

通常のpostgres公式イメージにはpgvectorは同梱されていないため、pgvector/pgvectorイメージを使うか、サーバーに手動でビルド・インストールする必要があります。

つまずき2:embeddingの次元数の不一致

embeddingを生成するAIモデルを途中で変更すると、ベクトルの次元数が変わり、既存データと検索できなくなることがあります。

// ❌ Before:モデルを変更したのにマイグレーションのdimensionsを直さず、
// 新旧のembeddingが混在した状態で検索してしまう
$table->vector('embedding', dimensions: 1536)->nullable();
// モデル変更後のembeddingは768次元 → 挿入時にエラーまたは検索結果が不正確に
// ✅ After:モデルを変更する場合は、
// マイグレーションでdimensionsを合わせたうえで、
// 既存データのembeddingを全件再生成する
Article::chunk(100, function ($articles) {
    foreach ($articles as $article) {
        $article->generateEmbedding();
    }
});

embeddingを生成するモデルを変更する際は、次元数の変更とデータの再生成をセットで行いましょう。

応用・一歩先の使い方

AI SDKと組み合わせたRAG構成

セマンティック検索単体でも便利ですが、真価を発揮するのは前回紹介したAI SDKとの組み合わせです。
検索でヒットした記事本文をプロンプトに埋め込み、AI SDKに要約・回答させることで、社内ドキュメントに基づいた回答生成(RAG)が実現できます。

$relatedArticles = Article::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding->vector, threshold: 0.75)
    ->limit(3)
    ->get();

$context = $relatedArticles->pluck('body')->implode("\n\n---\n\n");

$response = Ai::make()->prompt(
    "以下の記事を参考に、質問に答えてください。\n\n参考記事:\n{$context}\n\n質問: {$request->input('q')}"
);

キーワード検索との併用

セマンティック検索は万能ではなく、固有名詞や型番のような完全一致が重要な検索には向きません。
実務では、キーワード検索とセマンティック検索を併用し、両方の結果をマージして提示する設計がよく使われます。

まとめ

この記事のポイント

  • Laravel 13のセマンティック検索はwhereVectorSimilarTo()で実現する
  • 利用にはPostgreSQL + pgvector拡張が必須。MySQL/SQLiteでは使えない
  • embeddingの生成にはLaravel AI SDKのembedding()メソッドを使う
  • pgvector拡張の未インストールと、embeddingの次元数不一致がよくあるつまずきポイント
  • キーワード検索と組み合わせることで、実務での検索精度を高められる

次に読むべき記事

  • 「Laravel 13とは?Laravel 12からの変更点まとめ」
  • 「Laravel 13のAI SDKを徹底解説(テキスト・画像・音声生成)」

タグ: #Laravel #Laravel13 #上級者向け #データベース

タイトルとURLをコピーしました