とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

AnyThingLLM APIでドキュメント検索(RAG)を試してみた

このブログではAnyThingLLMについて何回か取り上げましたが、今回はAnyThingLLM APIを使ってRAGによるドキュメント検索を試してみます。

なお、LLM ServerはOllamaを使っていますが、基本的にAnyThingLLMが利用できるLLM Serverであれば何でも構いません。

AnyThingLLMとRAG

AnyThingLLMにはRAGを取り扱う機能があります。 GUIベースでRAGを試したい場合は、以前書いた記事を参考にしてください。

devops-blog.virtualtech.jp

今回はAPIベースでAnyThingLLMのRAGを体験します。 APIを利用すれば、自分で作成したプログラムやWebアプリケーション、チャットボットなどからAnyThingLLMのRAG機能を利用できるようになります。

AnyThingLLMのAPIを使えば、単なるLLMモデルの知識をベースにデータを引き出すだけでは無く、用意した情報ベースでデータを引き出すことが出来るようになります。

GUIでRAGを設定した時に任意のワークスペースにデータを登録して、そのデータを元に検索をしたと思います。 ワークスペースには任意の名前を付けられるので、今回はgovernmentというワークスペースを作成し、以前作成した「歴代内閣の総理大臣の一覧」のデータを登録しておきます。RAGに登録するデータはプレーンテキスト形式のデータの方が様々なモデルで取り扱えるのでおすすめです。

そして次にAPIにアクセスします。 実はAnyThingLLMを起動すると、3001番ポートでAPIがリンクします。このポートにリクエストを投げるとAPIにアクセスできます。

% lsof -i:3001 |grep LISTEN 

AnythingL 88074 ytooyama   30u  IPv4 0x8f742366420d85b1      0t0  TCP localhost:redwood-broker (LISTEN)

デフォルトの設定ではlocalhost:3001としてアクセスできますが、「設定→一般的な設定→Enable network discovery」をオンに設定してAnyThingLLMを再起動すると、外部からサーバーに対してアクセス出来るようになります。

この設定をすると、スペックの優れたマシンでLLMサーバー(Ollama, LM Studio, llama.cpp, vLLM etc.)とAnyThingLLMを動かすことで、別のマシンからその「LLM検索マシン」にアクセスできるようになります。

ブラウザーで次のアドレスにアクセスすると、色々なAPIエンドポイントのアドレスが確認できます。今回はWorkspace APIの中に用意されているChat APIを利用します。

アクセスすると、次のようなページが表示されます。スクロールしていくと、AnythingLLMの色々なAPIエンドポイントを確認できます(今回は詳細は省略)。

今回のテストには「開発者APIキー」というものが必要です。AnyThingLLMを起動して「設定→ツール→開発者API」を開いて、事前にAPIキーを作成してください。

最後にワークスペースgovernmentのチャットモード設定を確認してください。デフォルトは「代理人」モードで、LLMモデルのネイティブなツール呼び出しを元に自動的に様々なソースから情報を入手する設定になっています。外的要因は排除したいので、「クエリ」モードにします。このモードは必要な情報が見つかった場合にのみ、回答を提供するというモードです。今回のようなテストには適したモードです。

ためしてみよう

ここまで完了したら、あとはアクセスするだけです。curlを使ってクエリーをAnythingLLM APIにリクエストしてみましょう。アドレスのgovernmentの所がワークスペース名です。

検索に使われるLLMモデルは「ワークスペース設定→チャット設定」で設定しているワークスペースチャットモデルが使われます。APIから利用した場合でも、この設定が利用されます。

適切なものを設定すると良いでしょう。色々設定してみると楽しいですが、私は「qwen3-vl:4b」や「lfm2.5-thinking:1.2b」というモデルを使いました。どちらのモデルも軽量モデルの分類になりますが、今回の内容を試すには十分です。

curl -X POST "http://localhost:3001/api/v1/workspace/government/chat" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "検索結果の中から「第1代」で始まる行だけをそのまま返してください。説明は不要です。", "mode": "query" }'|jq -r '.textResponse'

すると、Thinkingの内容も含めた結果が返ってくるはずです。 Thinking対応モデルでは、APIのレスポンスにも思考過程が含まれます。最終回答だけ取得したい場合はさらにgrepやsedなどで加工すると扱いやすくなります。

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100 11013  100 10848  100   165    346      5  0:00:33  0:00:31  0:00:02  2700

The user is asking me to return only the line that starts with "第1代" from the search results, without any explanation.

Let me check through all the provided context to find if there's a line starting with "第1代":

From the context provided in [CONTEXT 0], I can see:

  • The list of Japanese prime ministers (or regents) goes from 第80代 down to earlier entries.

  • The earliest entry shown is 第26代|田中義一... and so on.

Looking further, in the later part of the text (after some truncation), I can find:

"第1代|伊藤博文|明治18年12月22日~明治21年4月30日|861日"

This is a line that starts with "第1代". 

However, there seems to be some confusion in the provided text. Earlier parts of the context show entries starting from 第80代 and going backward, but I don't see any specific entry labeled as "第1代" in the visible part of the document.

Wait, let me check again more carefully...

Actually, looking at the very end of the context:

The user's provided text shows:

  • A list that starts with 第26代|田中義一...

  • Then continues through various entries

  • And finally, there is a line: "第1代|伊藤博文|明治18年12月22日~明治21年4月30日|861日"

This seems to be the entry that starts with "第1代".

Given this information, I should return exactly this line as requested by the user: only the line starting with "第1代", without any explanation.

Let me make sure it's correctly formatted:

"第1代|伊藤博文|明治18年12月22日~明治21年4月30日|861日"

Yes, this is exactly what the user wants me to return. 第1代|伊藤博文|明治18年12月22日~明治21年4月30日|861日

上記は「qwen3-vl:4b」の結果です。「lfm2.5-thinking:1.2b」では、次のように表示されます。いずれも正しい結果を表示できました。

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100 10146  100  9981  100   165   1915     31  0:00:05  0:00:05 --:--:--  2490

Okay, let's see what the user is asking here. They want me to return only the lines from the search results that start with "第1代", without any explanations. From the previous interaction, I need to check the context provided earlier.

Looking back at the context given, there's a section where the entry for 第1代|伊藤博文 is present: "第1代|伊藤博文|明治18年12月22日~明治21年4月30日|861日". This line starts with "第1代", so that's the one to pick. The user emphasized not adding any extra info, just return that line. I should make sure there are no other lines starting with "第1代" elsewhere. Scanning through the context again confirms that this is the only occurrence. So the correct response is just that single line. 第1代|伊藤博文|明治18年12月22日~明治21年4月30日|861日

なお、Chat APIでリクエストした場合、結果はAnythingLLM側にも表示されます。

表示はリアルタイムではありませんが、これまで送信したプロンプトや回答、使用したLLMモデルなどが履歴として表示されます。

今回試したlfm2.5-thinking:1.2bでは、1.8s (116.93 tok/s)という非常に良いパフォーマンスで応答していました。RAG用途ではこのような軽量モデルでも十分実用的な速度が得られます。

RAGではLLM自身が大量の知識を持っていることよりも、検索によって取得したドキュメントを正しく理解し、その中から必要な情報を抽出・整理して回答する能力の方が重要になります。そのため、一般的なチャット用途のように大規模モデルが必ずしも有利とは限らず、このようなドキュメント検索用途では軽量LLMが高いコストパフォーマンスを発揮する場面も少なくありません。

ベクター検索を試す

ベクター検索も試してみましょう。

curl -X POST "http://localhost:3001/api/v1/workspace/government/vector-search" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "第1代|伊藤博文"
  }'| jq -r '.results[0].text'| grep '^第1代|'

Chat APIではLLMによる推論が行われますが、Vector Search APIはベクトル検索のみを行うため、応答は非常に高速です。

実行例:

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100  8759  100  8718  100    41   256k   1233 --:--:-- --:--:-- --:--:--  259k

第1代|伊藤博文|明治18年12月22日~明治21年4月30日|861日

ベクター検索も上手くいきました。検索結果も一瞬で返ってきました。

なおAnythingLLMのバージョンによってAPIが変わったり、出力されるクエリーの構成が変わる可能性があります。思った結果が出ない場合は、jqのオプションを適宜確認してください。

まとめ

今回、AnythingLLMのRAG機能をAPI経由で利用してみました。 事前にデータを登録しておけば、あとは外部からAPI経由でリクエストを投げるだけで目的の情報を結構スムーズに取得することが出来ました。

上手く使えば、店舗内の売り場検索とか、図書館の蔵書検索とかに色々活用できそうですね。大規模な利用であればまだしも小規模な検索システムであれば、AnyThingLLMだけでも十分構築できそうです。

GUIから利用するだけでなくAPI経由で利用できるようになることで、自分で作成したアプリケーションや社内システムからRAG機能を呼び出せるようになります。ローカルLLMと組み合わせれば、インターネットへデータを送信することなく独自の検索システムを構築できる点も大きな魅力です。

注意点

Enable network discoveryを有効にした環境ではIPアドレスとAPIキーが流出すると、第三者がAnythingLLM APIへアクセスできる可能性があります。 APIキーの管理やファイアウォールなどのネットワーク設定を適切に行うよう気をつけましょう。