Rails×pgvector実践記録|Embedding生成で詰まった私が理解したベクトル検索の正体【PostgreSQL・OpenAI・ローカルLLM】

New Challenge

多くの開発者は「AI検索エンジンを作りたい」と考えたとき、
まずスクレイピングやデータベース構築から始めます。

しかし実際に壁になるのは、その後に登場する
「Embedding(埋め込みベクトル)」です。

私もRailsとPostgreSQLを用いて意味検索エンジンを構築する過程で、

  • OpenAIライブラリが読み込めない
  • API認証エラーが出る
  • pgvectorの型が認識されない
  • cosine類似度がどこに保存されているのかわからない

といった数多くの問題に直面しました。
特に興味深かったのは、単なるエラー修正ではなく、

「OpenAIを使うことは本当にインハウス開発と言えるのか」

という設計思想の問題でした。

本記事では、実際のエラー画面や試行錯誤を交えながら、Rails初心者
にも分かるようにベクトル検索システムの内部構造を解説します。


第1章 Embeddingとは何なのか

今回のトラブルは単なるGem不足ではありませんでした。
本質的には、「文章を意味空間へ変換する仕組み」
そのものを理解する過程だったのです。


Embeddingは文章を数値へ変換する技術

まず最初に理解しなければならないのは、

EmbeddingはAIそのものではないということです。

例えば、

AIはすごい

という文章があったとします。人間は意味を理解できます。
しかしコンピュータは理解できません。

そこでAIモデルを使って、

[0.12, -0.83, 0.55, ...]

のような数値列へ変換します。

これがEmbeddingです。


OpenAIは何をしているのか

今回利用したコードは以下でした。

client = OpenAI::Client.new

response = client.embeddings(
  parameters: {
    model: "text-embedding-3-small",
    input: text
  }
)

ここでOpenAIは、

文章

ベクトル

返却

だけを行っています。

検索はしていません。

比較もしていません。


インハウス開発との違和感

ここで私は違和感を覚えました。

「ローカルAIを作っているつもりなのに、
なぜOpenAIを呼び出しているのか?」

これは非常に自然な疑問です。

実際には、

検索システム
≠
Embedding生成器

です。

今回作っているのは検索基盤。

Embeddingは仮にOpenAIを借りているだけです。


第2章 最初に遭遇したエラーと解決

ベクトル検索で最初に遭遇した壁は、

OpenAIそのものではなくRailsのロード機構でした。


uninitialized constant エラー

最初のエラーはこれです。

uninitialized constant EmbeddingService::OpenAI

見た瞬間、

「OpenAIが壊れた?」

と思います。

しかし違います。

Railsが

OpenAI

というクラスを知らないだけです。


require漏れ

原因は単純でした。

require 'openai'

がありません。

そのため、

client = OpenAI::Client.new

で落ちます。


Gem未導入問題

さらに進むと、

cannot load such file -- openai

が発生。

これは

gem 'ruby-openai'

がGemfileに無かったためです。

そこで、

bundle install

を実行。

ログには、

Installing ruby-openai 8.3.0

が表示されました。

この瞬間、

一歩前進です🚀


第3章 401エラーと「OpenAIを使う意味」

Gem導入後もEmbeddingは生成されませんでした。

しかしここで私は重要なことを理解します。


401 Unauthorized

実行結果。

Embedding failed for CrawledPage 1:
the server responded with status 401

これは認証エラーです。


APIキーが存在しない

確認すると、

echo $OPENAI_API_KEY

が空。

つまりOpenAI側は、

「誰だお前?」

状態です。


なぜEmbeddingがゼロのままなのか

モデル側では、

rescue => e

で例外を握り潰していました。

結果、

embedding: []

のままになります。

初心者が最も混乱するポイントです。


OpenAIは最終形ではない

ここで理解したことがあります。

OpenAIは目的ではありません。

目的は、

crawl
↓
embedding
↓
検索

の流れを完成させること。

将来的には、

LocalEmbeddingModel.encode(text)

へ置き換え可能です。


第4章 cosine類似度とpgvectorの正体

最終的に私は、cosine類似度とは何かを理解することで、
ベクトル検索の本質に到達しました。


cosine類似度は保存されない

最初の疑問。

cosine角の情報はどこにあるのか?

答えは驚くほど単純です。

どこにもありません。


保存されるのはベクトルだけ

DBに保存されるのは、

[0.12, -0.44, 0.87]

だけです。

cosineは、

cosine(A, B)

としてその場で計算されます。


pgvectorエラー

次に遭遇したのが、

unknown OID 123027

です。

これはRailsが

vector

型を理解できない状態。


float[]とvectorの違い

実際には、

t.float :embedding, array: true

で動かすことも可能です。

しかし検索性能は落ちます。

pgvectorの真価は、

vector(1536)

型にあります。


ここで見えた未来

この時点で完成していたものは、

✅ スクレイピング

✅ Rails

✅ PostgreSQL

✅ 非同期処理

✅ Embedding生成

です。

残るのは、

意味検索

だけでした。つまり、Googleのようなキーワード検索ではなく、

「意味で探す検索」

の入口に立ったのです。


全体のまとめ

今回のトラブルは、

require漏れ
↓
Gem不足
↓
API認証
↓
pgvector型

という一連のエラーでした。しかし振り返ると、
本当に得られたものはエラー解決ではありません。

  • Embeddingとは何か
  • OpenAIの役割は何か
  • cosine類似度はどこにあるのか
  • pgvectorは何を高速化しているのか

を理解できたことです。そして最も大きな収穫は、

「検索エンジンはAIモデルではなく、データ構造で決まる」

という事実でした。

OpenAIを外しても検索基盤は残ります。つまり今回作っていたものは、
単なるAPI接続ではなく、ローカルLLM時代にも流用できる
意味検索エンジンの土台だったのです。

〆最後に〆

以上、間違い・ご意見は
以下アドレスまでお願いします。
全て返信できていませんが 見ています。
適時、改定をします。

nowkouji226@gmail.com

全体の纏め記事】に戻る

思想の纏め記事】に戻る

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