チャットと言語モデル
このページでは低レベルのLLM APIについて説明します。 高レベルのLLM APIについてはAI Servicesをご覧ください。
サポートされているすべてのLLMはこちらで確認できます。
LLMは現在、2種類のAPIタイプで利用可能です:
LanguageModel。そのAPIは非常にシンプルで、入力としてStringを受け取り、出力としてStringを返します。 このAPIは現在、チャットAPI(2番目のAPIタイプ)に取って代わられつつあります。ChatModel。これらは入力として複数のChatMessageを受け取り、出力として単一のAiMessageを返します。ChatMessageは通常テキストを含みますが、一部のLLMは他のモダリティ(画像、音声など)もサポートしています。 そのようなチャットモデルの例には、OpenAIのgpt-4o-miniやGoogleのgemini-1.5-proがあります。
LangChain4jではLanguageModelのサポートはこれ以上拡張されないため、
すべての新機能ではChatModel APIを使用します。
ChatModelはLangChain4jでLLMと対話するための低レベルAPIであり、最も強力で柔軟性を提供します。
また、高レベルAPI(AI Services)もあり、基本を説明した後で後ほど説明します。
ChatModelとLanguageModelの他に、LangChain4jは以下のタイプのモデルをサポートしています:
EmbeddingModel- このモデルはテキストをEmbeddingに変換できます。ImageModel- このモデルはImageを生成および編集できます。ModerationModel- このモデルはテキストに有害なコンテンツが含まれているかどうかを確認できます。ScoringModel- このモデルはクエリに対して複数のテキスト片をスコアリング(またはランク付け)し、 本質的に各テキスト片がクエリにどれだけ関連しているかを判断します。これはRAGに役立ちます。 これらについては後ほど説明します。
では、ChatModel APIをより詳しく見てみましょう。
public interface ChatModel {
String chat(String userMessage);
...
}
ご覧のように、LanguageModelと同様に、入力としてStringを受け取り、出力としてStringを返す単純なchatメソッドがあります。
これは単なる便宜的なメソッドで、StringをUserMessageでラップする必要なく、素早く簡単に試すことができます。
他のチャットAPIメソッドは以下の通りです:
...
ChatResponse chat(ChatMessage... messages);
ChatResponse chat(List<ChatMessage> messages);
...
これらのバージョンのchatメソッドは、1つまたは複数のChatMessageを入力として受け取ります。
ChatMessageはチャットメッセージを表す基本インターフェースです。
次のセクションでチャットメッセージについて詳しく説明します。
リクエストをカスタマイズしたい場合(例:モデル名、temperature、ツール、JSON schemaなどの指定)、
chat(ChatRequest)メソッドを使用できます:
...
ChatResponse chat(ChatRequest chatRequest);
...
ChatRequest chatRequest = ChatRequest.builder()
.messages(...)
.modelName(...)
.temperature(...)
.topP(...)
.topK(...)
.frequencyPenalty(...)
.presencePenalty(...)
.maxOutputTokens(...)
.stopSequences(...)
.toolSpecifications(...)
.toolChoice(...)
.responseFormat(...)
.parameters(...) // you can also set common or provider-specific parameters all at once
.build();
ChatResponse chatResponse = chatModel.chat(chatRequest);
ChatMessageの種類
現在、メッセージの各「ソース」に対応する5種類のチャットメッセージがあります:
UserMessage:ユーザーからのメッセージです。 ユーザーはアプリケーションのエンドユーザー(人間)またはアプリケーション自体のいずれかです。 以下を含めることができます:contents():メッセージの内容。LLMがサポートするモダリティに応じて、 単一のテキスト(String)のみ、 または他のモダリティを含むことができます。name():ユーザーの名前。すべてのモデルプロバイダーがサポートしているわけではありません。attributes():追加属性:これらの属性はモデルに送信されませんが、ChatMemoryに保存されます。
AiMessage:送信されたメッセージに対してAIが生成したメッセージです。 以下を含めることができます:text():テキストコンテンツthinking():思考/推論コンテンツtoolExecutionRequests():ツールを実行するリクエスト。ツールについては 別のセクションで説明します。attributes():追加属性。通常はプロバイダー固有です
ToolExecutionResultMessage:これはToolExecutionRequestの結果です。SystemMessage:システムからのメッセージです。 通常、開発者であるあなたがこのメッセージの内容を定義する必要があります。 通常、ここではLLMのこの会話における役割、 どのように振る舞うべきか、どのようなスタイルで回答するかなどの指示を書きます。 LLMは他の種類のメッセージよりもSystemMessageに注意を払うよう訓練されているため、 注意が必要であり、エンドユーザーにSystemMessageを自由に定義させたり、入力を注入させたりしない方がよいです。 通常、会話の開始位置に配置されます。CustomMessage:任意の属性を含むことができるカスタムメッセージです。このメッセージタイプは、 それをサポートするChatModel実装でのみ使用できます(現在はOllamaのみ)。
すべての種類のChatMessageを理解したので、会話でそれらをどのように組み合わせるかを見てみましょう。
最も単純なシナリオでは、chatメソッドに単一のUserMessageインスタンスを提供できます。
これは、入力としてStringを受け取る最初のバージョンのchatメソッドと似ています。
ここでの主な違いは、StringではなくChatResponseを返すことです。
AiMessageに加えて、ChatResponseにはChatResponseMetadataも含まれます。
ChatResponseMetadataにはTokenUsageが含まれており、入力
(generateメソッドに提供したすべてのChatMessage)にいくつのトークンが含まれていたか、
出力としていくつのトークンが生成されたか(AiMessage内)、および合計(入力 + 出力)に関する統計が含まれています。
この情報は、LLMへの特定の呼び出しのコストを計算するために必要です。
次に、ChatResponseMetadataにはFinishReasonも含まれており、
これは生成が停止したさまざまな理由を持つ列挙型です。
通常、LLMが自ら生成を停止することを決定した場合、FinishReason.STOPになります。
内容に応じて、UserMessageを作成する方法は複数あります。
最も単純なものはnew UserMessage("Hi")またはUserMessage.from("Hi")です。
複数のChatMessage
では、なぜ1つだけでなく、複数のChatMessageを入力として提供する必要があるのでしょうか?
これは、LLMが本質的にステートレスであり、会話の状態を維持しないためです。
したがって、マルチターン会話をサポートしたい場合は、会話の状態を管理する必要があります。
チャットボットを構築したいとしましょう。ユーザーとチャットボット(AI)の間の単純なマルチターン会話を想像してください:
- ユーザー:Hello, my name is Klaus
- AI:Hi Klaus, how can I help you?
- ユーザー:What is my name?
- AI:Klaus
ChatModelとのやり取りは次のようになります:
UserMessage firstUserMessage = UserMessage.from("Hello, my name is Klaus");
AiMessage firstAiMessage = model.chat(firstUserMessage).aiMessage(); // Hi Klaus, how can I help you?
UserMessage secondUserMessage = UserMessage.from("What is my name?");
AiMessage secondAiMessage = model.chat(firstUserMessage, firstAiMessage, secondUserMessage).aiMessage(); // Klaus
ご覧のように、chatメソッドの2回目の呼び出しでは、単一のsecondUserMessageだけでなく、
会話の以前のメッセージも提供しています。
これらのメッセージを手動で維持および管理するのは面倒です。
そのため、ChatMemoryの概念が存在し、次のセクションで説明します。
マルチモダリティ
UserMessageにはテキストだけでなく、他の種類のコンテンツも含めることができます。
UserMessageにはList<Content> contentsが含まれています。
Contentはインターフェースであり、以下の実装があります:
TextContentImageContentAudioContentVideoContentPdfFileContent
どのLLMプロバイダーがどのモダリティをサポートしているかは、比較表こちらで確認できます。
テキストと画像の両方をLLMに送信する例を次に示します:
UserMessage userMessage = UserMessage.from(
TextContent.from("Describe the following image"),
ImageContent.from("https://example.com/cat.jpg")
);
ChatResponse response = model.chat(userMessage);
テキストコンテンツ
TextContentは、プレーンテキストを表し、単一のStringをラップする最も単純な形式のContentです。
UserMessage.from(TextContent.from("Hello!"))はUserMessage.from("Hello!")と同等です。
UserMessage内に1つまたは複数のTextContentを提供できます:
UserMessage userMessage = UserMessage.from(
TextContent.from("Hello!"),
TextContent.from("How are you?")
);
画像コンテンツ
LLMプロバイダーに応じて、ImageContentはリモート画像のURLから作成するか(上記の例を参照)、
Base64エンコードされたバイナリデータから作成できます:
byte[] imageBytes = readBytes("/home/me/cat.jpg");
String base64Data = Base64.getEncoder().encodeToString(imageBytes);
ImageContent imageContent = ImageContent.from(base64Data, "image/jpg");
UserMessage userMessage = UserMessage.from(imageContent);
モデルが画像を処理する方法を制御するために、DetailLevel列挙型(LOW/HIGH/AUTOオプション)を指定することもできます。
詳細はこちらをご覧ください。
音声コンテンツ
AudioContentはImageContentと似ていますが、音声コンテンツを表します。
動画コンテンツ
VideoContentはImageContentと似ていますが、動画コンテンツを表します。
PDFファイルコンテンツ
PdfFileContentはImageContentと似ていますが、PDFファイルのバイナリコンテンツを表します。
Kotlin拡張機能
ChatModelのKotlin拡張機能は、Kotlinのコルーチン機能を活用して、言語モデルとのチャット対話を処理する非同期メソッドを提供します。chatAsyncメソッドは、ChatRequestまたはChatRequest.Builder構成の非ブロッキング処理を可能にし、モデルの応答を含むChatResponseを返します。同様に、generateAsyncはチャットメッセージからの応答の非同期生成を処理します。これらの拡張機能は、Kotlinアプリケーションでチャットリクエストを構築し、会話を効率的に処理することを簡素化します。これらのメソッドは実験的としてマークされており、時間とともに進化する可能性があることに注意してください。
ChatModel.chatAsync(request: ChatRequest):Kotlinコルーチン向けに設計されたこの非同期拡張関数は、Dispatchers.IOを使用するコルーチンスコープ内で同期的なchatメソッドをラップします。これにより非ブロッキング操作が可能 になり、アプリケーションの応答性を維持するために重要です。既存の同期的なchatとの競合を避けるために、特にchatAsyncと名付けられています。関数シグネチャは次のとおりです:suspend fun ChatModel.chatAsync(request: ChatRequest): ChatResponse。キーワードsuspendは、それをコルーチン関数として指定します。
ChatModel.chat(block: ChatRequestBuilder.() -> Unit):このchatのバリアントは、Kotlinの型安全なビルダーDSLを使用することで、より効率的なアプローチを提供します。ChatRequestオブジェクトの構築を簡素化しながら、内部的にchatAsyncを使用して非同期実行を行います。このバージョンは、コルーチンを通じて簡潔さと非ブロッキング動作の両方を提供します。