1. 初めに
前回にvLLMを使用してチャットモデルと埋め込みモデルを同時にサーブした内容の記事を書きました。
その際の応用として、RAGシステムをローカルで構築しました。
コードはこちらです。
2. この記事で記載していること
- LangChainを使用した基本的なragシステムの構築
- 埋め込みモデルのサーバーライブラリであるinfinityについて
- reasoningモデルを使用する際のvLLMの設定について
3. ragについて
自分が以前作成した記事を参照していただければと思います。
4. サーバーの構成
詳細は、githubのコードを確認いただけますとよいと思います。
重要な点は、チャットモデルと埋め込みモデルを別々のサーバーライブラリで構築したという点です。
具体的には、下記のライブラリで構成しています。
前回の記事で埋め込みモデルもvLLMで構築していましたが、
- 何故か公式モデルの例と全く異なる検索結果になる
- transformersで推論した際と比較して、検索性能が明らかに悪くなる
といった現象が起きました。
後者については、以下のような候補にたいして、
documents = [
"機械学習には、RandomForestやSVMなど様々なモデルがある",
"CNNやViTは画像をクラス分類する技術です。",
"画像認識には、ResNetが有効",
"yoloは物体検出を行うためのモデルである",
"SwinTransformerは画像分類でかなり性能が高い",
"音声認識には、1DのCNNが有効",
"音声認識には、LSTMが有効",
"データベースは情報を効率的に管理するシステムです。"
]
画像分類に有効なモデルは?
というクエリを投げるとなぜかデータベースは情報を効率的に管理するシステムです。
という文章が上位になり、画像処理関連の解答が得られないという結果がおきました。
そこで、実際にtransformersでの推論結果とvLLMでホストした同じモデルの推論結果を比べると全く異なる結果となっていることが分かりました。
そこで、埋め込みモデルをサーブするために作成された以下のようなライブラリを試しました。その結果として、概ね満足のいく結果となったので現状の構成としました。
infinityを選択した理由は、text-embeddings-inferenceが今回選定した埋め込みモデルに対応していないためです。
5. 使用モデル
チャットモデル, 埋め込みモデルでそれぞれ下記を採用しました。
- チャットモデル:Qwen/Qwen3-4B-Thinking-2507
- 埋め込みモデル:sbintuitions/sarashina-embedding-v2-1b
それぞれのモデルの選択理由を下記に記載します。
Qwen/Qwen3-4B-Thinking-2507
こちらは、Qwen3-4Bの改良版となっております。4B級のモデルなのですが、Qwen3-30B-A3B Thinkingと同等の性能が得られるコストパフォーマンスのよいモデルであるため採用しました(参考)。
sbintuitions/sarashina-embedding-v2-1b
こちらは、sarashina2.2-1Bをベースにしたモデルとなっております。特徴として、
- 日本語特化
- 指示を付与した検索が可能
という点が挙げられます(参考)。
特に、2番目が興味深い内容でしたので採用しました。
6. ragの構成
今回のragの構成を下記にまとめます。
チャットモデル | 埋め込みモデル | DB | 使用ドキュメント |
---|---|---|---|
Qwen/Qwen3-4B-Thinking-2507(vLLMでホスト) | sbintuitions/sarashina-embedding-v2-1b(infinityでホスト) | ChromaDB | テキスト生成AI利活用におけるリスクへの対応ガイドブック |
7. ragの結果
ここからは、ragの結果の例を示したいと思います。
以下のような質問をしました。
task: クエリに関連した文章を検索してください
query: AI開発におけるコスト削減方法は?
下記の例に従って回答してください。
## 朝に散歩をする
- 理由: 脳を活性化させるため
- 説明: 散歩をするとタスクを遂行する能力が向上する研究結果がある
## 毎日早く寝る
- 理由: 早く寝ると集中力がアップする
- 説明: 夜更かしする人とそうでない人とでは、前者の方が集中力がアップするというデータがある
また、
- なるべく多様な方法を紹介してください。
- 意味的に似た内容は避けてください。
取得したドキュメントは、
--- # 1つ目の文章
text: 21
a) 複数のリクエストを一つに集約する
テキスト生成AI の Web API への複数リクエストを一つに集約すればコストカ
ットにつながる可能性がある。ただしこの手法ではリクエスト 1 件あたりのト
ークン数が多くなるため、リクエスト 1 件あたりの平均費用が高くなり、結果
としてコストカット効果に乏しい場合もある。また、テキスト生成AIにとって
の処理難易度が上がることで品質劣化を招く危険性もある。
b) テキスト生成AI以外の手法を採用する
例えば、文中の単語や特定の品詞を抽出するだけであれば形態素解析器で十
分であり、文間の意味的類似度を測るだけであればベクトル表現( 埋め込み表
現、embedding ともいう)の採用で十分な場合もある。このようにテキスト生
成 AI 以外の手法で十分な場合ではテキスト生成 AI 以外の手法をまず検討すべ
きである。またこれらの自然言語処理手法は、テキスト生成AIとの二者択一で
はなく、相互に弱みを補完しながら組み合わせて利用することも十分に考えら
れる。
c) 過去のテキスト生成AIの生成物を再利用する
過去にテキスト生成AIで生成された実績のあるリクエストと同じようなリク
エスト(例えばリクエスト文字列をベクトル表現化し、それと近しいベクトル
表現の場合に、同じようなものとみなす)の場合、キャッシュサーバー等から
過去の生成物を送信するようにし、テキスト生成 AI の Web APIにリクエストを
させない。この手法をより極端にした場合、テキスト生成 AI の Web APIへのア
クセスは全て事前にバッチ処理で行い、テキスト生成AI関連の処理にかかるコ
ストを制御しやすくする手法も考えられる。この手法に関しては、 5.2 4)
テスト済みの生成物のみを用いる場合の工夫 で詳細に述べる
ウ テキスト生成 AI の機械学習モデルが直接提供されるケース
大規模言語モデルの実行にともなう計算コストに直接影響される形でハード
ウェアや運用に関連するコストが増加する。そのため大規模言語モデルの実行
にともなう計算コストが重要であり、その観点や手法は Web API 利用のケース
での検討事項と同様である。
3) 運用業務のコスト削減観点
--- # 2つ目の文章
text: 20
当初想定した大規模言語モデルでは性能不十分であることが発覚するリスクも
十分考えられる点にも注意する。
また、回答精度を上げるためにプロンプトへの記述を手厚くするほ ど、トー
クン数や入力文字数に比例し費用が高くなるため、プロンプトの改良による精
度改善の試行錯誤により初期検討時のプロンプトよりも費用が高くなる可能性
も十分考えられる。想定よりもリクエスト数が多い場合に想定以上の費用が高
くなる可能性もある。
この場合、初期検討時のものと比べてどこまで費用が上振れするものかのデ
ータがまだないため、妥当な見積もり方法が確立していない点に注意が必要。
クラウドサービスによっては従量課金でなく定額プランを用意している場合
もあるが、定額プランは一般的にかなり高価である。しっかりと見積も りをし
たうえで採用することを強く推奨する。
ウ テキスト生成 AI の機械学習モデルが直接提供されるケース
多くのケースで最もコストが高い選択肢になる傾向がある。少なくてもハー
ドウェアの調達から必要な場合は、初期投資が最も高い。他の利用形態で要件
を十分満たすかの検討とコスト試算を行ったうえで、この利用形態を採用する
かを検討する。
2) アプリケーションのコスト削減観点
ア テキスト生成 AI を組み込んだサービスとして提供されるケース
テキスト生成AIで扱われる大規模言語モデルは、ひとつのモデルだけで多様
な自然言語処理(例えば、翻訳や要約、文章作成や固有表現抽出等)が可能で
ある。この特徴から、バラバラに実装・提供されていた複数の自然言語処理関
連の機能や製品をまとめられないかの検討をし、重複投資を避ける。
イ テキスト生成 AI を利活用するための Web API がクラウドサービスと
して提供されるケース
生成AIへのリクエスト1件あたりの平均費用を下げるか、そもそもリクエス
ト量を減らすことでコスト削減が可能である。平均費用の方は 3.1 1)
ハードウェア ・ソフトウェアのコスト削減観点 で述べたため、ここでは生成
AIへのリクエスト量を減らすための検討事項を紹介する。
リクエスト量を大幅に減らすことのできる選択肢として主なものは以下の 3
つである。
--- # 3つ目の文章
text: 32
イアスを抑止できる。例えば、検索拡張生成 (RAG, Retrieval Augmented
Generative) の技法、つまり文章検索で関連文章を抽出し、それを大規模言語モ
デルにプロンプトとして渡す手法を用いる場合は
(1) 質問文に対して適切な関連文章が抽出できているか
(ア) もし想定していた関連文章があれば、 それをちゃんと抽出して
いるか(再現率の観点)
(イ) 抽出した関連文章に不適切なものが含まれていないか(適合性
の観点)
(2) 関連文章に対して大規模言語モデルの出力が適切か
(ア) 出力結果に含まれている固有名詞は関連文章内に含まれている
か
(イ) 出力結果の内容は関連文章内で言及されているものか
(ウ) 複数の関連文章の結果を統合している場合、その論理の繋がり
は自然か
(3) 最終的な生成物は求められる品質をクリアしているか
(ア) 十分読みやすいか(ユースケースに応じてもっと観点を明確に
する)
(イ) 出力形式は意図通りになっているか
等の観点や評価項目が考えられる。分解したところで、主観に基づくバイア
スの影響は完全に除外できないが、分解しない場合と比べて大幅に抑制可能で
ある。
(2) 複数人により独立に実施する
1 つの項目に定性テストを行う人間を複数人アサインし、それぞれが独立に
評価した内容を総合することで主観に基づくバイアスを抑止できる。いわゆる
ダブルチェック、トリプルチェックである。
ただし、その複数人が同じ種類のバイアスをもっている時はリスクが軽減せ
ず、人件費や開発リードタイムは明確に増加するといった欠点も多い。
ウ 費用や開発リードタイムの増加に起因するリスク
定性テストを実施するにあたって、人員をアサインした場合にその人件費が
増加し、また定性テスト自体も時間のかかる工程であるため開発リードタイム
の増加を招きやすい。これはプロジェクト管理の観点から明確なリスクである。
軽減策
(1) 機械的な品質評価手法を導入する
となっています。
1, 2つめの文章に関しては、予算に関する記載がある3章から取得できていました。3つめの文章は5章の「設計・開発時のテキスト生成AI固有の留意点」の文章となっています。
チャットモデルが生成した結果の一例は下記のようになります。
## 過去の生成物をキャッシュで再利用する
- 理由: リクエストの重複を防ぎ、Web APIへのアクセスを削減するため
- 説明: 既に生成された実績のあるリクエストと類似のリクエストが来た場合、キャッシュサーバーから過去の生成物を再利用することで、テキスト生成AIのWeb APIにリクエストを送らない。これにより、リクエスト量が減少し、コストが削減される。
## テキスト生成AI以外のNLP手法を採用する
- 理由: テキスト生成AIのコストを回避し、効率的な処理を実現するため
- 説明: 文中の単語や品詞の抽出には形態素解析器、意味的類似度の測定にはベクトル表現(埋め込み表現)が十分な場合がある。これらの手法を用いることで、テキスト生成AIの利用を避け、コストを削減できる。
## RAG技術を導入する
- 理由: 関連文章の抽出により、大規模言語モデルの生成リクエストを削減するため
- 説明: 検索拡張生成(RAG)の技法では、質問文に対して関連文章を抽出し、それらをプロンプトとして大規模言語モデルに渡す。これにより、生成に必要なリクエストの数が減少し、コストが削減される。
## 機械的品質評価手法を導入する
- 理由: 人間による定性テストのコストと時間の増加を抑制するため
- 説明: 生成物の品質を機械的な手法(例: ベクトル距離の計算や自動チェック)で評価することで、人間のチェックを必要とせず、人件費や開発リードタイムを削減できる。
1, 2つ目については実際に記載がある文章でした。ただ、3, 4に関しては、部分的に正しいものの文中に記載がないことを回答しています。
8. まとめ
- ragを使用するためのサーバーを構築
- 構築したサーバーを使用して、ragシステムの検証を実施
9. 感想
- 埋め込みモデル専用のサービングライブラリがあることを知らなかったので新鮮に感じた
- ローカルLLMでも案外まともなragシステムを構築できることが分かったことは収穫
- 埋め込みモデルで指示が可能なモデルというのは珍しくて可能性を感じた
10. その他いろいろはまった点
infinityを使用した場合に、使用するLangChainクライアント
結論:InfinityEmbeddingsというクラスがあるのでこちらを使用するとよいです
こちら、OpenAI互換のAPIなのでOpenAI公式のクライアントを使用すると上手く動作しますが、何故かlangchainのOpenAIEmbeddingsクラスを使用すると上手くいきません。
回答を制御するのに少し苦労した
結論:意外とfew shotプロンプトは有効
ragの結果で記載した出力形式を得るために少し苦労しました。理由と説明を付与してください
などの指示もしましたが、なかなかうまくいきませんでした。出力例をいくつか記載することで上手く想定通りの出力が得られました。
また、出力例を記載する際は、具体的に記載しないとダメでした。例えば、
以下の例に従って回答してください。
## 回答
- 理由:
- 説明:
と指示すると下記のように出力されてしまいました。
## 回答1
- 理由: リクエストの重複を防ぎ、Web ...
- 説明: 既に生成された実績のあるリクエスト...
## 回答2
- 理由: テキスト生成AIのコストを回避し...
- 説明: 文中の単語や品詞の抽出には形態...
そこで、具体的な例を与えると上手く回答してくれるようになりました。
vLLMの引数
--reasoning-parser引数に関して
の設定に関して、下記のような特徴がありました。
- deepseek_r1に設定した場合:思考過程(
<think></think>
)以外を出力してくれます
思考過程を含めるとコンテキスト長が長くなるので不要、そもそも結果だけ知りたいといった場合はこちらを使用するとよいでしょう。今回は、最終的な結論だけ知りたいのでこの設定にしています - qwen3に設定した場合:思考過程も出力してくれます
思考過程も知りたい場合はこちらを使用するとよいでしょう - 何も設定しない場合:
<think></think>
となったり</think>
だけで終わったり安定しない挙動
モデルサイズによって挙動が異なるのでしょうか?
前回のQwen3の非2507モデルの場合は、デフォルトで思考過程も出力してくれましたので注意が必要かもしれません。
実際の運用では、
- 公式の例をまず試す
- 何も指定しない場合と比較する
などを実施するとよいかもしれまん
--enable-reasoning引数に関して
こちら、公式によるとvLLM version0.10で削除されるフラグのようです。
理由は、下記で議論されているように、「reasoningを有効にするとき際--reasoning-parser
を指定するのであれば、--enable-reasoning
は必要なのか?」という問いの結果です。
なので、vLLM version 0.10以降を使用される方はここではまらないように注意が必要です。