はじめに
検索機能に「意味の近さ」まで反映させたいと思ったことはありませんか?キーワードが一致しないと結果に出てこない従来の全文検索では、「ノイキャンヘッドホン」と検索しても「周囲の音を遮断するイヤホン」がヒットしないことがあります。かといって、そのためだけにベクトルDBやElasticsearchを構築するのは、規模によっては過剰投資です。
そこで注目したいのが、全文検索・ベクトル検索・ハイブリッド検索を1つのライブラリでこなす「Orama」です。ブラウザでもサーバーでもエッジでも動き、しかもコアの実装はわずか2kb程度という軽さで、キーワード検索と意味検索を両立させられます。
Oramaとは
Oramaは、TypeScriptで書かれた検索エンジンライブラリで、npmパッケージ@orama/oramaとして配布されています。全文検索エンジンでありながらベクトル検索にも対応し、両者を組み合わせたハイブリッド検索、さらにはRAG(検索拡張生成)向けのチャットセッション機能まで備えているのが大きな特徴です。
主な特徴
- 軽量かつマルチランタイム対応 - コア部分は2kb程度のフットプリントで、ブラウザ・Node.js・Deno・Bun・エッジ環境のいずれでも同じAPIで動作します
- 全文検索とベクトル検索の両立 - BM25アルゴリズムによるキーワード検索に加え、埋め込みベクトルを使った意味検索、両者を組み合わせたハイブリッド検索が可能です
- タイポ耐性と多言語対応 - 検索ワードのタイプミスを吸収するタイポトレランスや、30以上の言語のステミング・トークナイズに標準対応しています
- ファセット・地理空間検索 - 絞り込み用のファセット集計や、位置情報を使った検索もオプションなしで利用できます
インストール
npm、yarn、pnpm、bunのいずれでも導入できます。
npm install @orama/orama
yarn add @orama/orama
pnpm add @orama/orama
ブラウザから直接読み込みたい場合は、CDN経由でもインポートできます。
<script type="module">
import { create, insert, search } from 'https://cdn.jsdelivr.net/npm/@orama/orama@latest/+esm'
</script>
基本的な使い方
まずはスキーマを定義してデータベースを作成し、ドキュメントを挿入して検索してみましょう。
import { create, insert, search } from '@orama/orama'
// スキーマを定義してデータベースを作成
const db = create({
schema: {
name: 'string',
description: 'string',
price: 'number',
meta: {
rating: 'number',
},
},
})
// ドキュメントを挿入
insert(db, {
name: 'ノイズキャンセリングヘッドホン',
description: '周囲の騒音を強力にカットする上位モデル',
price: 29800,
meta: { rating: 4.5 },
})
insert(db, {
name: 'オープン型イヤホン',
description: '装着したまま周囲の音も聞こえる開放型',
price: 12800,
meta: { rating: 4.1 },
})
// 全文検索を実行
const results = search(db, {
term: 'ヘッドホン',
})
console.log(results.hits.map((hit) => hit.document.name))
// ['ノイズキャンセリングヘッドホン']
create()でフィールドの型を宣言し、insert()で追加したドキュメントに対してsearch()を呼ぶだけで、日本語を含む全文検索がすぐに動きます。件数が多い場合はinsertMultiple()でまとめて登録すると効率的です。
実践的なユースケース
ベクトル検索とハイブリッド検索
embeddingを扱えるスキーマを定義すれば、意味の近さで検索するベクトル検索や、キーワード一致とスコアを組み合わせたハイブリッド検索も実現できます。
import { create, insert, search } from '@orama/orama'
const db = create({
schema: {
name: 'string',
description: 'string',
embedding: 'vector[4]', // 実際は使用する埋め込みモデルの次元数に合わせる
},
})
insert(db, {
name: 'ノイズキャンセリングヘッドホン',
description: '周囲の騒音を強力にカットする上位モデル',
embedding: [0.24, 0.94, 0.53, 0.42], // 事前に生成しておいた埋め込みベクトル
})
// キーワード検索とベクトル検索を組み合わせたハイブリッド検索
const hybridResults = search(db, {
mode: 'hybrid',
term: '静かに音楽を聴きたい',
vector: {
value: [0.23, 0.91, 0.5, 0.4], // クエリ文を埋め込みモデルに通した結果を渡す
property: 'embedding',
},
similarity: 0.8,
})
console.log(hybridResults.hits.map((hit) => hit.document.name))
mode: 'hybrid'を指定すると、キーワードが完全一致しなくても、意味的に近いドキュメントをスコアに反映して検索結果に含められます。埋め込みベクトルの生成自体はOramaの範囲外なので、OpenAIなどの埋め込みモデルで事前に変換したベクトルを渡す形で組み合わせるのが一般的です。
ブログ記事のクライアントサイド検索
静的サイトの記事検索であれば、ビルド時に生成したメタデータをそのままインデックスに投入するだけで完結します。
import { create, insertMultiple, search } from '@orama/orama'
const db = create({
schema: {
title: 'string',
tags: 'string',
},
})
const posts = [
{ title: 'Oramaで作る検索機能', tags: 'JavaScript 検索' },
{ title: 'Zustandで状態管理', tags: 'React 状態管理' },
{ title: 'XStateで状態遷移を設計する', tags: '状態管理 設計' },
]
await insertMultiple(db, posts)
function handleSearchInput(query) {
if (query.length === 0) return []
return search(db, { term: query, properties: ['title', 'tags'] }).hits
}
console.log(handleSearchInput('検索'))
propertiesで検索対象のフィールドを絞り込めるほか、boostオプションを使えばタイトル一致を本文一致より優先させるといった調整も可能です。外部の検索SaaSを契約せずとも、ビルド成果物とOramaだけでレスポンシブな検索体験を構築できます。
まとめ
Oramaは、軽量な全文検索エンジンでありながら、ベクトル検索やハイブリッド検索、RAG向けのチャットセッションまでカバーする守備範囲の広さが魅力のライブラリです。キーワード検索だけで十分な場面ではシンプルに、意味検索まで踏み込みたい場面では同じAPIのまま拡張できるため、プロジェクトの成長に合わせて使い方を変えていけます。検索機能の実装を検討しているなら、まずは@orama/oramaをインストールして、その手軽さを試してみてください。
