Goブログ
GoによるLLM搭載アプリケーションの構築
過去1年間で、LLM(大規模言語モデル)と埋め込みモデルのような関連ツールの機能が大幅に向上したため、ますます多くの開発者がアプリケーションへのLLMの統合を検討しています。
LLMは多くの場合、専用のハードウェアと膨大な計算リソースを必要とするため、アクセスするためのAPIを提供するネットワークサービスとしてパッケージ化されることが最も一般的です。OpenAIやGoogle Geminiのような主要なLLMのAPIは、このように機能します。Ollamaのような、独自のLLMを実行するツールも、ローカルでの使用のためにLLMをREST APIでラップしています。さらに、アプリケーションでLLMを利用する開発者は、多くの場合、ベクターデータベースなどの補足ツールを必要とします。これらは、ほとんどの場合、ネットワークサービスとしても展開されます。
言い換えれば、LLM搭載アプリケーションは他の最新のクラウドネイティブアプリケーションと非常によく似ています。RESTとRPCプロトコル、並行処理、パフォーマンスに対する優れたサポートが必要です。これらはGoが得意とする分野であり、GoをLLM搭載アプリケーションを作成するための優れた言語にしています。
このブログ記事では、簡単なLLM搭載アプリケーションにGoを使用する例を説明します。デモアプリケーションが解決する問題の説明から始まり、同じタスクを実行するアプリケーションのいくつかのバリエーションを提示します。これらのバリエーションはすべて同じタスクを実行しますが、異なるパッケージを使用して実装されています。この記事のデモのすべてのコードはオンラインで公開されています。
Q&AのためのRAGサーバー
一般的なLLM搭載アプリケーションの手法は、RAG - Retrieval Augmented Generation(検索拡張生成)です。RAGは、特定のドメインとのやり取りのためにLLMのナレッジベースをカスタマイズする最もスケーラブルな方法の1つです。
GoでRAGサーバーを構築します。これは、ユーザーに2つの操作を提供するHTTPサーバーです。
- ナレッジベースにドキュメントを追加する
- このナレッジベースについてLLMに質問する
典型的な現実世界のシナリオでは、ユーザーはサーバーにドキュメントのコレクションを追加し、質問をします。たとえば、企業はRAGサーバーのナレッジベースに社内ドキュメントを格納し、社内ユーザーにLLM搭載のQ&A機能を提供するために使用できます。
こちらが、サーバーと外部世界のやり取りを示す図です。

ユーザーがHTTPリクエストを送信する(上記で説明した2つの操作)ことに加えて、サーバーは以下とやり取りします。
- 送信されたドキュメントとユーザーの質問のベクター埋め込みを計算するための埋め込みモデル。
- 埋め込みを効率的に保存および検索するためのベクターデータベース。
- ナレッジベースから収集されたコンテキストに基づいて質問をするためのLLM。
具体的に、サーバーはユーザーに2つのHTTPエンドポイントを公開します。
/add/: POST {"documents": [{"text": "..."}, {"text": "..."}, ...]}
:ナレッジベースに追加するテキストドキュメントのシーケンスを送信します。このリクエストに対して、サーバーは
- 埋め込みモデルを使用して各ドキュメントのベクター埋め込みを計算します。
- ベクター埋め込みとともにドキュメントをベクターDBに保存します。
/query/: POST {"content": "..."}
:サーバーに質問を送信します。このリクエストに対して、サーバーは
- 埋め込みモデルを使用して質問のベクター埋め込みを計算します。
- ベクターDBの類似性検索を使用して、ナレッジデータベース内の質問に最も関連するドキュメントを見つけます。
- 簡単なプロンプトエンジニアリングを使用して、ステップ(2)で見つかった最も関連性の高いドキュメントをコンテキストとして質問を再構成し、LLMに送信して、その回答をユーザーに返します。
デモで使用されているサービスは次のとおりです。
- LLMと埋め込みモデルにはGoogle Gemini API。
- ローカルホストのベクターDBにはWeaviate。Weaviateは、Goで実装されたオープンソースのベクターデータベースです。
これらを他の同等のサービスに置き換えるのは非常に簡単です。実際、これがサーバーの2番目と3番目のバリエーションのすべてです!これらのツールを直接使用する最初のバリエーションから始めましょう。
Gemini APIとWeaviateの直接使用
Gemini APIとWeaviateの両方には便利なGo SDK(クライアントライブラリ)があり、最初のサーバーのバリエーションではこれらを直接使用します。このバリエーションの完全なコードはこのディレクトリにあります。
このブログ記事では完全なコードを再現しませんが、コードを読む際に考慮すべきいくつかの点があります。
構造:コード構造は、GoでHTTPサーバーを作成したことがある人なら誰でも馴染みのあるものです。GeminiとWeaviateのクライアントライブラリは初期化され、クライアントはHTTPハンドラーに渡される状態値に格納されます。
ルート登録:Go 1.22で導入されたルーティングの機能強化を使用して、サーバーのHTTPルートは簡単に設定できます。
mux := http.NewServeMux()
mux.HandleFunc("POST /add/", server.addDocumentsHandler)
mux.HandleFunc("POST /query/", server.queryHandler)
並行処理:サーバーのHTTPハンドラーは、ネットワーク経由で他のサービスにアクセスし、応答を待ちます。各HTTPハンドラーは独自のゴルーチンで同時に実行されるため、Goではこれは問題になりません。このRAGサーバーは多数の同時リクエストを処理でき、各ハンドラーのコードは線形かつ同期です。
バッチAPI:/add/
リクエストは、ナレッジベースに追加する多数のドキュメントを提供する場合があるため、サーバーは埋め込み(embModel.BatchEmbedContents
)とWeaviate DB(rs.wvClient.Batch
)の両方にバッチAPIを活用して効率性を高めます。
Go用LangChainの使用
2番目のRAGサーバーのバリエーションでは、LangChainGoを使用して同じタスクを実行します。
LangChainは、LLM搭載アプリケーションを構築するための一般的なPythonフレームワークです。LangChainGoは、そのGo対応版です。このフレームワークには、モジュール式コンポーネントからアプリケーションを構築するためのツールがいくつかあり、多くのLLMプロバイダーとベクターデータベースを共通のAPIでサポートしています。これにより、開発者は、任意のプロバイダーで動作し、プロバイダーを非常に簡単に変更できるコードを記述できます。
このバリエーションの完全なコードはこのディレクトリにあります。コードを読むと、2つのことに気付くでしょう。
まず、前のバリエーションよりも短くなっています。LangChainGoはベクターデータベースの完全なAPIを共通のインターフェースでラップする役割を果たし、Weaviateの初期化と処理に必要なコードが少なくなります。
第二に、LangChainGo APIを使用すると、プロバイダーの切り替えが非常に簡単になります。Weaviateを別のベクターDBに置き換えたいとしましょう。前のバリエーションでは、新しいAPIを使用するために、ベクターDBとインターフェースするすべてのコードを書き直す必要がありました。LangChainGoのようなフレームワークを使用すると、それを行う必要がなくなります。LangChainGoが対象の新しいベクターDBをサポートしている限り、サーバーのコードを数行置き換えるだけで済みます。すべてのDBが共通のインターフェースを実装しているためです。
type VectorStore interface {
AddDocuments(ctx context.Context, docs []schema.Document, options ...Option) ([]string, error)
SimilaritySearch(ctx context.Context, query string, numDocuments int, options ...Option) ([]schema.Document, error)
}
Go用Genkitの使用
今年初めに、GoogleはGo用Genkitを発表しました。これは、LLM搭載アプリケーションを構築するための新しいオープンソースフレームワークです。GenkitはLangChainと共通の特性を共有していますが、他の点では異なります。
LangChainと同様に、異なるプロバイダー(プラグインとして)によって実装できる共通のインターフェースを提供するため、一方から他方への切り替えが簡単になります。ただし、異なるLLMコンポーネントがどのように相互作用するかを規定しようとはしません。代わりに、プロンプト管理とエンジニアリング、統合開発ツールを使用したデプロイなどの運用機能に重点を置いています。
3番目のRAGサーバーのバリエーションでは、Go用Genkitを使用して同じタスクを実行します。完全なコードはこのディレクトリにあります。
このバリエーションはLangChainGoのものとかなり似ています。LLM、エンベダー、ベクターDBの共通インターフェースが直接のプロバイダーAPIの代わりに使用されるため、一方から他方への切り替えが容易になります。さらに、Genkitを使用すると、LLM搭載アプリケーションの運用へのデプロイがはるかに容易になります。このバリエーションでは実装していませんが、興味があればドキュメントを参照してください。
まとめ - LLM搭載アプリケーションのためのGo
この記事のサンプルは、GoでLLM搭載アプリケーションを構築できることのほんの一部を示しています。比較的少ないコードで強力なRAGサーバーを構築する容易さを示しています。最も重要なのは、いくつかの基本的なGo機能により、サンプルにはかなりの量の運用準備が備わっていることです。
LLMサービスを使用するということは、多くの場合、ネットワークサービスにRESTまたはRPCリクエストを送信し、応答を待って、それに基づいて他のサービスに新しいリクエストを送信するなどすることを意味します。Goはこれらすべてにおいて優れており、並行処理とネットワークサービスのジャグリングの複雑さを管理するための優れたツールを提供します。
さらに、Goの高いパフォーマンスと信頼性により、クラウドネイティブ言語として、LLMエコシステムのより基本的な構成要素を実装するための自然な選択肢となります。例として、Ollama、LocalAI、Weaviate、またはMilvusなどのプロジェクトを参照してください。