PaLM と LangChain4J を使用した、Java でユーザーやドキュメントとの生成 AI を活用したチャット

1. はじめに

最終更新日: 2024 年 2 月 5 日

生成 AI とは

生成 AI(Generative Artificial Intelligence)は、AI を活用してテキスト、画像、音楽、音声、動画などの新しいコンテンツを作成することを指します。

生成 AI は、さまざまなタスクに対応した基盤モデル(大規模 AI モデル)をベースにしており、そのままで要約、質疑応答、分類などをすぐに実行できます。 さらに、ほんのわずかなサンプルデータで必要最小限のトレーニングを行うだけで、基盤モデルを対象のユースケースに適応させることができます。

生成 AI の仕組み

生成 AI は、ML(機械学習)モデルを使用して、人間が作成したコンテンツのデータセット内のパターンと関係を学習します。次に、学習したパターンを使用して新しいコンテンツを生成します。

生成 AI モデルをトレーニングする最も一般的な方法は、教師あり学習を使用することです。つまり、人間が作成したコンテンツと対応するラベルのセットがモデルに与えられます。次に、人間が作成したコンテンツと類似した、同じラベルが付けられたコンテンツを生成することを学習します。

一般的な生成 AI 用途とは

生成 AI は膨大な量のコンテンツを処理し、テキストや画像など人間が親しみやすい形式で分析情報と答えを生み出します。生成 AI は、以下のことに使用できます。

  • 強化されたチャット / 検索エクスペリエンスで顧客対応を改善
  • 会話インターフェースと要約を通じて、膨大な量の非構造化データを探索
  • 提案依頼書(RFP)への返信、マーケティング コンテンツの 5 か国語へのローカライズ、顧客契約のコンプライアンス チェックなど、反復的なタスクを支援する

Google Cloud にはどのような生成 AI サービスがありますか?

Vertex AI を使用すると、基盤モデルを操作、カスタマイズし、アプリケーションに埋め込むことができます。ML の専門知識をほとんど必要としません。Model Garden で基盤モデルにアクセスするか、Generative AI Studio のシンプルな UI からモデルをチューニングするか、データ サイエンス ノートブックでモデルを使用します。

Vertex AI Search and Conversation は、生成 AI を活用した検索エンジンと chatbot を構築する最速の手段をデベロッパーに提供します。

また、Duet AI は AI を活用したコラボレーターで、Google Cloud や IDE で利用でき、より多くのことをより速くこなすことができます。

この Codelab で重視していること

この Codelab では、すべての ML プロダクトとサービスを含む Google Cloud Vertex AI でホストされる PaLM 2 大規模言語モデル(LLM)に焦点を当てます。

Java を使用して、LangChain4J LLM フレームワーク オーケストレーターと組み合わせて PaLM API を操作します。LLM を質問応答、アイデアの生成、エンティティと構造化コンテンツの抽出、要約に活用するためのさまざまな具体例を紹介します。

LangChain4J フレームワークについて詳しく教えてください。

LangChain4J フレームワークは、大規模言語モデルを Java アプリケーションに統合するためのオープンソース ライブラリです。LLM 自体などのさまざまなコンポーネントだけでなく、ベクトル データベース(セマンティック検索用)、ドキュメント ローダーとスプリッター(ドキュメントを分析して学習)、出力パーサーなどをオーケストレートします。

c6d7f7c3fd0d2951.png

学習内容

  • PaLM と LangChain4J を使用するように Java プロジェクトを設定する方法
  • 非構造化コンテンツから有用な情報を抽出する方法(エンティティまたはキーワードの抽出、JSON での出力)
  • ユーザーとの会話を作成する方法
  • チャットモデルを使用して自分のドキュメントに質問する方法

必要なもの

  • Java プログラミング言語の知識
  • Google Cloud プロジェクト
  • Chrome や Firefox などのブラウザ

2. 設定と要件

セルフペース型の環境設定

  1. Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列です。いつでも更新できます。
  • プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud コンソールでは一意の文字列が自動生成されます。通常は、この内容を意識する必要はありません。ほとんどの Codelab では、プロジェクト ID(通常は PROJECT_ID と識別されます)を参照する必要があります。生成された ID が好みではない場合は、ランダムに別の ID を生成できます。または、ご自身で試して、利用可能かどうかを確認することもできます。このステップ以降は変更できず、プロジェクトを通して同じ ID になります。
  • なお、3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
  1. 次に、Cloud のリソースや API を使用するために、Cloud コンソールで課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルの終了後に請求が発生しないようにリソースをシャットダウンするには、作成したリソースを削除するか、プロジェクトを削除します。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Cloud Shell の起動

Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では Cloud 上で動作するコマンドライン環境である Cloud Shell を使用します。

Cloud Shell をアクティブにする

  1. Cloud Console で、[Cloud Shell をアクティブにする] d1264ca30785e435.png をクリックします。

cb81e7c8e34bc8d.png

Cloud Shell を初めて起動する場合は、内容を説明する中間画面が表示されます。中間画面が表示されたら、[続行] をクリックします。

d95252b003979716.png

Cloud Shell のプロビジョニングと接続に少し時間がかかる程度です。

7833d5e1c5d18f54.png

この仮想マシンには、必要なすべての開発ツールが読み込まれます。5 GB の永続的なホーム ディレクトリが用意されており、Google Cloud で稼働するため、ネットワークのパフォーマンスと認証が大幅に向上しています。この Codelab での作業のほとんどはブラウザを使って行うことができます。

Cloud Shell に接続すると、認証が完了し、プロジェクトに各自のプロジェクト ID が設定されていることがわかります。

  1. Cloud Shell で次のコマンドを実行して、認証されたことを確認します。
gcloud auth list

コマンド出力

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project

コマンド出力

[core]
project = <PROJECT_ID>

上記のようになっていない場合は、次のコマンドで設定できます。

gcloud config set project <PROJECT_ID>

コマンド出力

Updated property [core/project].

3. 開発環境の準備

この Codelab では、Cloud Shell ターミナルとコードエディタを使用して Java プログラムを開発します。

Vertex AI API を有効にする

  1. Google Cloud コンソールで、Google Cloud コンソールの上部にプロジェクト名が表示されていることを確認します。選択されていない場合は、[プロジェクトを選択] をクリックして [Project Selector] を開き、目的のプロジェクトを選択します。
  2. Google Cloud コンソールの [Vertex AI] セクションが表示されていない場合は、次の操作を行います。
  3. [検索] に「Vertex AI」と入力して Enter キーを押します。
  4. 検索結果で [Vertex AI] をクリックします。Vertex AI ダッシュボードが表示されます。
  5. Vertex AI ダッシュボードで、[すべての推奨 API を有効化] をクリックします。

これにより複数の API が有効になりますが、この Codelab で最も重要なのは aiplatform.googleapis.com です。これは、コマンドラインの Cloud Shell ターミナルで次のコマンドを実行して有効にすることもできます。

$ gcloud services enable aiplatform.googleapis.com

Gradle でのプロジェクト構造の作成

Java のコードサンプルをビルドするには、Gradle ビルドツールと Java のバージョン 17 を使用します。Gradle でプロジェクトを設定するには、Cloud Shell ターミナルでディレクトリ(ここでは palm-workshop)を作成し、そのディレクトリで gradle init コマンドを実行します。

$ mkdir palm-workshop
$ cd palm-workshop

$ gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3

Split functionality across multiple subprojects?:
  1: no - only one application project
  2: yes - application and library projects
Enter selection (default: no - only one application project) [1..2] 1

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] 

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4] 4

Project name (default: palm-workshop): 
Source package (default: palm.workshop): 

> Task :init
Get more help with your project: https://docs.gradle.org/7.4/samples/sample_building_java_applications.html

BUILD SUCCESSFUL in 51s
2 actionable tasks: 2 executed

アプリケーション(オプション 2)を Java 言語(オプション 3)でビルドします。サブプロジェクトは使用しない(オプション 1)、ビルドファイルに Groovy 構文を使用(オプション 1)、新しいビルド機能を使用しない(オプション番号)、JUnit Jupiter を使用してテストを生成します(オプション 4)。プロジェクト名には、ソース パッケージに palm-workshop を使用できます。

プロジェクトの構造は次のようになります。

├── gradle 
│   └── ...
├── gradlew 
├── gradlew.bat 
├── settings.gradle 
└── app
    ├── build.gradle 
    └── src
        ├── main
        │   └── java 
        │       └── palm
        │           └── workshop
        │               └── App.java
        └── test
            └── ...

app/build.gradle ファイルを更新して、必要な依存関係を追加しましょう。guava 依存関係が存在する場合はそれを削除し、LangChain4J プロジェクトの依存関係とロギング ライブラリに置き換えることで、ロガー メッセージの欠落を回避できます。

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'

    // Logging library
    implementation 'org.slf4j:slf4j-jdk14:2.0.9'

    // This dependency is used by the application.
    implementation 'dev.langchain4j:langchain4j-vertex-ai:0.24.0'
    implementation 'dev.langchain4j:langchain4j:0.24.0'
}

LangChain4J には 2 つの依存関係があります。

  • 1 つはコアプロジェクト
  • 2 つ目は Vertex AI 専用モジュールです

Java 17 を使用してプログラムのコンパイルと実行を行うには、plugins {} ブロックの下に次のブロックを追加します。

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

さらにもう 1 つ変更を加える必要があります。app/build.gradleapplication ブロックを更新して、ビルドツールを呼び出すときにメインクラスをオーバーライドしてコマンドラインで実行できるようにします。

application {
    mainClass = providers.systemProperty('javaMainClass')
                         .orElse('palm.workshop.App')
}

ビルドファイルでアプリケーションを実行する準備が整っていることを確認するには、デフォルトのメインクラスを実行すると、シンプルな Hello World! メッセージが出力されます。

$ ./gradlew run -DjavaMainClass=palm.workshop.App

> Task :app:run
Hello World!

BUILD SUCCESSFUL in 3s
2 actionable tasks: 2 executed

これで、LangChain4J プロジェクトを使用して PaLM 大規模言語テキストモデルをプログラミングする準備が整いました。

参考までに、app/build.gradle ビルドファイル全体は次のようになります。

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

java {
    toolchain {
        // Ensure we compile and run on Java 17
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'

    // This dependency is used by the application.
    implementation 'dev.langchain4j:langchain4j-vertex-ai:0.24.0'
    implementation 'dev.langchain4j:langchain4j:0.24.0'
    implementation 'org.slf4j:slf4j-jdk14:2.0.9'
}

application {
    mainClass = providers.systemProperty('javaMainClass').orElse('palm.workshop.App')
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

4. PaLM のチャットモデルに最初の呼び出しを行う

プロジェクトを正しく設定したので、PaLM API を呼び出します。

app/src/main/java/palm/workshop ディレクトリに(デフォルトの App.java クラスとともに)ChatPrompts.java という新しいクラスを作成し、次の内容を入力します。

package palm.workshop;

import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.chain.ConversationalChain;

public class ChatPrompts {
    public static void main(String[] args) {
        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(400)
            .maxRetries(3)
            .build();

        ConversationalChain chain = ConversationalChain.builder()
            .chatLanguageModel(model)
            .build();

        String message = "What are large language models?";
        String answer = chain.execute(message);
        System.out.println(answer);

        System.out.println("---------------------------");

        message = "What can you do with them?";
        answer = chain.execute(message);
        System.out.println(answer);

        System.out.println("---------------------------");

        message = "Can you name some of them?";
        answer = chain.execute(message);
        System.out.println(answer);
    }
}

この最初の例では、VertexAiChatModel クラスと LangChain4J の ConversationalChain をインポートして、会話のマルチターンの側面を簡単に処理できるようにする必要があります。

次に、main メソッドでチャット言語モデルを構成します。VertexAiChatModel のビルダーを使用して、以下を指定します。

  • エンドポイント
  • プロジェクト、
  • リージョン
  • パブリッシャー
  • とモデルの名前(chat-bison@001)。

言語モデルの準備ができたら、ConversationalChain を準備します。これは、LangChain4J が提供する上位レベルの抽象化で、チャット言語モデル自体のような会話を処理するさまざまなコンポーネントを一緒に構成しますが、チャットの会話の履歴を処理する、またはベクトル データベースから情報を取得するリトリーバーなどの他のツールをプラグインする場合もあります。でも、心配はいりません。これについては、この Codelab で後ほど説明します。

次に、チャットモデルとマルチターンの会話を行い、相互に関連する質問をします。まず LLM について不思議に思った場合、LLM で何ができるのか、その例にはどのようなものがあるのかを質問します。繰り返し行う必要がないことに注目してください。LLM は「彼ら」という言葉を認識しています。LLM を意味します。

マルチターンの会話を受けるには、チェーンで execute() メソッドを呼び出すだけで、それが会話のコンテキストに追加されます。チャットモデルは返信を生成し、チャット履歴にも追加します。

このクラスを実行するには、Cloud Shell ターミナルで次のコマンドを実行します。

./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts

出力は次のようになります。

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts
Starting a Gradle Daemon, 2 incompatible and 2 stopped Daemons could not be reused, use --status for details

> Task :app:run
Large language models (LLMs) are artificial neural networks that are trained on massive datasets of text and code. They are designed to understand and generate human language, and they can be used for a variety of tasks, such as machine translation, question answering, and text summarization.
---------------------------
LLMs can be used for a variety of tasks, such as:

* Machine translation: LLMs can be used to translate text from one language to another.
* Question answering: LLMs can be used to answer questions posed in natural language.
* Text summarization: LLMs can be used to summarize text into a shorter, more concise form.
* Code generation: LLMs can be used to generate code, such as Python or Java code.
* Creative writing: LLMs can be used to generate creative text, such as poems, stories, and scripts.

LLMs are still under development, but they have the potential to revolutionize a wide range of industries. For example, LLMs could be used to improve customer service, create more personalized marketing campaigns, and develop new products and services.
---------------------------
Some of the most well-known LLMs include:

* GPT-3: Developed by OpenAI, GPT-3 is a large language model that can generate text, translate languages, write different kinds of creative content, and answer your questions in an informative way.
* LaMDA: Developed by Google, LaMDA is a large language model that can chat with you in an open-ended way, answering your questions, telling stories, and providing different kinds of creative content.
* PaLM 2: Developed by Google, PaLM 2 is a large language model that can perform a wide range of tasks, including machine translation, question answering, and text summarization.
* T5: Developed by Google, T5 is a large language model that can be used for a variety of tasks, including text summarization, question answering, and code generation.

These are just a few examples of the many LLMs that are currently being developed. As LLMs continue to improve, they are likely to play an increasingly important role in our lives.

BUILD SUCCESSFUL in 25s
2 actionable tasks: 2 executed

PaLM が 3 つの関連する質問に回答しました。

VertexAIChatModel ビルダーを使用すると、オーバーライド可能なデフォルト値がすでに存在するオプションのパラメータを定義できます。次に例を示します。

  • .temperature(0.2) - レスポンスに求めるクリエイティブを定義します(0 は創造性が低く、多くの場合は事実に即したもので、1 はよりクリエイティブな出力を表します)。
  • .maxOutputTokens(50) - この例では、生成される回答の長さに応じて 400 個のトークンがリクエストされています(3 個のトークンはほぼ 4 語に相当します)。
  • .topK(20) - テキスト補完のために、最大数の単語の中からランダムに 1 つの単語を選択します(1 ~ 40)。
  • .topP(0.95) - 合計確率がその浮動小数点数(0 ~ 1)の合計になる単語を選択します。
  • .maxRetries(3) - たとえば、リクエストの時間あたりの割り当てを超過している場合、モデルに呼び出しを 3 回再試行させることができます。

5. 個性ある便利な chatbot です。

前のセクションでは、特別なコンテキストを与えることなく、すぐに LLM chatbot に質問しました。しかし、そのような chatbot を専門にすることで、特定のタスクや特定のトピックのエキスパートになれます。

どのようにすればよいでしょうか。まず LLM に、当面のタスクとコンテキストを説明して、実行する内容、どのようなペルソナを持つべきか、回答を得たい形式、chatbot が特定の動作をさせたい場合のトーンの例をいくつか示します。

プロンプトの作成に関する記事では、この図を使用してこのアプローチをわかりやすく説明しています。

8a4c67679dcbd085.png

https://medium.com/@eldatero/master-the-perfect-chatgpt-prompt-formula-c776adae8f19

この点を説明するために、prompts.chat ウェブサイトを参考にしてみましょう。このサイトには、次のようなカスタムメイドの chatbot に関する素晴らしいアイデアが多数掲載されています。

LLM chatbot をチェス プレーヤーに変える例が 1 つあります。実装しましょう。

ChatPrompts クラスを次のように更新します。

package palm.workshop;

import dev.langchain4j.chain.ConversationalChain;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;

public class ChatPrompts {
    public static void main(String[] args) {
        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(7)
            .maxRetries(3)
            .build();

        InMemoryChatMemoryStore chatMemoryStore = new InMemoryChatMemoryStore();

        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
            .chatMemoryStore(chatMemoryStore)
            .maxMessages(200)
            .build();

        chatMemory.add(SystemMessage.from("""
            You're an expert chess player with a high ELO ranking.
            Use the PGN chess notation to reply with the best next possible move.
            """
        ));


        ConversationalChain chain = ConversationalChain.builder()
            .chatLanguageModel(model)
            .chatMemory(chatMemory)
            .build();

        String pgn = "";
        String[] whiteMoves = { "Nf3", "c4", "Nc3", "e3", "Dc2", "Cd5"};
        for (int i = 0; i < whiteMoves.length; i++) {
            pgn += " " + (i+1) + ". " + whiteMoves[i];
            System.out.println("Playing " + whiteMoves[i]);
            pgn = chain.execute(pgn);
            System.out.println(pgn);
        }
    }
}

順を追って説明します。

  • チャットのメモリを処理するには、新しいインポートが必要です。
  • チャットモデルをインスタンス化します。ただし、少数の最大トークン(ここでは 7 個)を使用して、チェスに関する学位全体ではなく、次の動きを生成するだけです。
  • 次に、チャットのメモリストアを作成して、チャットの会話を保存します。
  • 最後のムーブを保持するために、実際のチャットのウィンドウ処理メモリを作成します。
  • チャットのメモリには、メッセージで、誰であるか(エキスパート チェス プレーヤー)をチャットモデルに指示します。「システム」メッセージはコンテキストを追加しますが、「user」は「AI」メッセージは実際のディスカッションです。
  • メモリとチャットモデルを組み合わせた会話チェーンを作成します。
  • 次に白のムーブのリストがあります。これを繰り返します。その都度次の白の動きでチェーンが実行され、チャットモデルはその次の最適な動きで応答します。

これらの移動でこのクラスを実行すると、次の出力が表示されます。

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :app:run
Playing Nf3
1... e5
Playing c4
2... Nc6
Playing Nc3
3... Nf6
Playing e3
4... Bb4
Playing Dc2
5... O-O
Playing Cd5
6... exd5 

うわ!PaLM はチェスのプレイ方法を知っているでしょうか。不正解ですが、モデルはトレーニング中にチェスゲームの解説や、過去のゲームの PGN(Portable Game Notation)ファイルさえ見たことがあるはずです。ただし、この chatbot は AlphaZero(囲みゲーム、将軍、チェスの最強プレーヤーを倒す AI)にはおそらく勝てません。また、モデルはゲームの実際の状態をあまり覚えていないため、会話はさらに道筋を外れてしまう可能性があります。

チャットモデルは非常に強力で、ユーザーとの豊富なインタラクションを生み出し、さまざまなコンテキスト タスクを処理できます。次のセクションでは、便利なタスクであるテキストから構造化データを抽出する方法について説明します。

6. 非構造化テキストからの情報の抽出

前のセクションでは、ユーザーとチャット言語モデルとの会話を作成しました。しかし、LangChain4J では、チャットモデルを使用して非構造化テキストから構造化された情報を抽出することもできます。

略歴または説明から、その人物の名前と年齢を抽出するとします。プロンプトを細かく調整して JSON データ構造を生成するように大規模言語モデルに指示できます(これは一般に「プロンプト エンジニアリング」と呼ばれます)。

ChatPrompts クラスを次のように更新します。

package palm.workshop;

import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;

public class ChatPrompts {

    static class Person {
        String name;
        int age;
    }

    interface PersonExtractor {
        @UserMessage("""
            Extract the name and age of the person described below.
            Return a JSON document with a "name" and an "age" property, \
            following this structure: {"name": "John Doe", "age": 34}
            Return only JSON, without any markdown markup surrounding it.
            Here is the document describing the person:
            ---
            {{it}}
            ---
            JSON: 
            """)
        Person extractPerson(String text);
    }

    public static void main(String[] args) {
        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(300)
            .build();
        
        PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);

        Person person = extractor.extractPerson("""
            Anna is a 23 year old artist based in Brooklyn, New York. She was born and 
            raised in the suburbs of Chicago, where she developed a love for art at a 
            young age. She attended the School of the Art Institute of Chicago, where 
            she studied painting and drawing. After graduating, she moved to New York 
            City to pursue her art career. Anna's work is inspired by her personal 
            experiences and observations of the world around her. She often uses bright 
            colors and bold lines to create vibrant and energetic paintings. Her work 
            has been exhibited in galleries and museums in New York City and Chicago.    
            """
        );

        System.out.println(person.name);
        System.out.println(person.age);
    }
}

このファイルのさまざまなステップを見てみましょう。

  • Person クラスは、人物の詳細情報(名前と年齢)を表すために定義されます。
  • PersonExtractor インターフェースは、非構造化テキスト文字列を指定すると、インスタンス化された Person インスタンスを返すメソッドで作成されます。
  • extractPerson() には、プロンプトを関連付ける @UserMessage アノテーションが付けられます。これは、モデルが情報を抽出し、詳細を JSON ドキュメント形式で返すためにモデルが使用するプロンプトです。ドキュメントは自動で解析され、Person インスタンスにマーシャリング解除されます。

次に、main() メソッドの内容を見てみましょう。

  • チャットモデルがインスタンス化されます。
  • PersonExtractor オブジェクトは、LangChain4J の AiServices クラスのおかげで作成されます。
  • 次に、Person person = extractor.extractPerson(...) を呼び出すだけで、非構造化テキストから人物の詳細を抽出し、名前と年齢を含む Person インスタンスを取得できます。

次のコマンドを使用して、このクラスを実行します。

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts

> Task :app:run
Anna
23

はい。アンナが 23 歳。

この AiServices アプローチで特に興味深い点は、厳密に型指定されたオブジェクトを操作することです。チャット LLM と直接やり取りしていません。代わりに、抽出した個人情報を表す Person クラスなどの具象クラスを使用し、Person インスタンスを返す extractPerson() メソッドを含む PersonExtractor クラスを使用します。LLM の概念は抽象化されており、Java デベロッパーは通常のクラスとオブジェクトを操作しているだけです。

7. 検索拡張生成: ドキュメントとのチャット

会話に戻りましょう。今回は、ドキュメントについて質問できるようになります。ドキュメントの抽出のデータベースから関連情報を取得できる chatbot を構築します。モデルは、トレーニングからレスポンスを生成しようとするのではなく、その情報を使用して回答の「根拠づけ」を行います。このパターンは、RAG(検索拡張生成)と呼ばれます。

検索拡張生成には、簡潔に言うと 2 つのフェーズがあります。

  1. 取り込みフェーズ - ドキュメントが読み込まれて小さなチャンクに分割され、そのベクトル表現(ベクトル エンベディング)がセマンティック検索が可能な「ベクトル データベース」に保存されます。

6c5bb5cb2e3b8088.png

  1. クエリフェーズ - ユーザーはドキュメントについて chatbot に質問できます。質問もベクトルに変換され、データベース内の他のすべてのベクトルと比較されます。通常、最も類似したベクトルは意味的に関連しており、ベクトル データベースによって返されます。次に、LLM に会話のコンテキスト、すなわちデータベースから返されたベクトルに対応するテキストのスニペットが与えられます。LLM はそのスニペットを確認して回答の根拠を示すように求められます。

2c279c506d7606cd.png

書類の準備

この新しいデモでは、Google が開発した「Transformer」 ニューラル ネットワーク アーキテクチャ(最新のあらゆる大規模言語モデルの実装方法)について質問します。

wget コマンドを使用してインターネットから PDF をダウンロードすることで、このアーキテクチャについて説明した研究論文(「Attention is all you need」)を取得できます。

wget -O attention-is-all-you-need.pdf \
    https://proceedings.neurips.cc/paper_files/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf

会話型取得チェーンの実装

2 フェーズのアプローチを構築する方法を 1 つずつ見ていきましょう。まずはドキュメントの取り込みで、次にユーザーがドキュメントについて質問したときのクエリ時間です。

ドキュメントの取り込み

ドキュメントの取り込みフェーズの最初の手順は、ダウンロードする PDF ファイルを特定し、それを読み取る PdfParser を準備することです。

PdfDocumentParser pdfParser = new PdfDocumentParser();
Document document = pdfParser.parse(
    new FileInputStream(new File("/home/YOUR_USER_NAME/palm-workshop/attention-is-all-you-need.pdf")));

通常のチャット言語モデルを作成する代わりに、その前に「エンベディング」モデルのインスタンスを作成します。これは、テキスト部分(単語、文、さらには段落)のベクトル表現を作成する役割を持つ特定のモデルおよびエンドポイントです。

VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
    .endpoint("us-central1-aiplatform.googleapis.com:443")
    .project("YOUR_PROJECT_ID")
    .location("us-central1")
    .publisher("google")
    .modelName("textembedding-gecko@001")
    .maxRetries(3)
    .build();

次に、共同作業を行うクラスがいくつか必要になります。

  • PDF ドキュメントをチャンクに読み込んで分割します。
  • これらのチャンクのすべてに対してベクトル エンベディングを作成します。
InMemoryEmbeddingStore<TextSegment> embeddingStore = 
    new InMemoryEmbeddingStore<>();

EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
    .documentSplitter(DocumentSplitters.recursive(500, 100))
    .embeddingModel(embeddingModel)
    .embeddingStore(embeddingStore)
    .build();
storeIngestor.ingest(document);

EmbeddingStoreRetriever retriever = EmbeddingStoreRetriever.from(embeddingStore, embeddingModel);

インメモリ ベクトル データベースである InMemoryEmbeddingStore のインスタンスが、ベクトル エンベディングを保存するために作成されます。

ドキュメントは DocumentSplitters クラスのおかげでチャンクに分割されます。PDF ファイルのテキストを、100 文字の重複で 500 文字のスニペットに分割します(次のチャンクは、単語や文が断片に分割されるのを避けるため)。

ストアの「ingestor」ドキュメント スプリッター、ベクトルを計算するエンベディング モデル、メモリ内ベクトル データベースをリンクします。その後、ingest() メソッドが取り込みを行います。

最初のフェーズが終わり、ドキュメントが関連するベクトル エンベディングによってテキスト チャンクに変換され、ベクトル データベースに格納されました。

質問する

それでは、質問を投げかける準備をしましょう。通常のチャットモデルを作成して会話を開始できます。

VertexAiChatModel model = VertexAiChatModel.builder()
    .endpoint("us-central1-aiplatform.googleapis.com:443")
    .project("YOUR_PROJECT_ID")
    .location("us-central1")
    .publisher("google")
    .modelName("chat-bison@001")
    .maxOutputTokens(1000)
    .build();

また、ベクトル データベース(embeddingStore 変数内)とエンベディング モデルをリンクする "retriever" クラスも必要です。ユーザーのクエリに対するベクトル エンベディングを計算してベクトル データベースをクエリし、データベース内で類似のベクトルを見つけます。

EmbeddingStoreRetriever retriever = 
    EmbeddingStoreRetriever.from(embeddingStore, embeddingModel);

この時点で、ConversationalRetrievalChain クラスをインスタンス化できます(これは、検索拡張生成パターンの単なる別の名前です)。

ConversationalRetrievalChain rag = ConversationalRetrievalChain.builder()
    .chatLanguageModel(model)
    .retriever(retriever)
    .promptTemplate(PromptTemplate.from("""
        Answer to the following query the best as you can: {{question}}
        Base your answer on the information provided below:
        {{information}}
        """
    ))
    .build();

この「チェーン」は互いにバインド:

  • 前の手順で構成したチャット言語モデル。
  • 取得ツールは、ベクトル エンベディングのクエリをデータベース内のベクトルと比較します。
  • プロンプト テンプレートは、提供された情報(つまり、ベクトル エンベディングがユーザーの質問のベクトルに類似しているドキュメントの関連する抜粋)に基づいてチャットモデルが応答する必要があることを明示的に指定します。

いよいよ、質問を投げかける準備が整いました。

String result = rag.execute("What neural network architecture can be used for language models?");
System.out.println(result);
System.out.println("------------");

result = rag.execute("What are the different components of a transformer neural network?");
System.out.println(result);
System.out.println("------------");

result = rag.execute("What is attention in large language models?");
System.out.println(result);
System.out.println("------------");

result = rag.execute("What is the name of the process that transforms text into vectors?");
System.out.println(result);

次のコマンドでプログラムを実行します。

$ ./gradlew run -DjavaMainClass=palm.workshop.ChatPrompts

出力に質問の答えが表示されます。

The Transformer is a neural network architecture that can be used for 
language models. It is based solely on attention mechanisms, dispensing 
with recurrence and convolutions. The Transformer has been shown to 
outperform recurrent neural networks and convolutional neural networks on 
a variety of language modeling tasks.
------------
The Transformer is a neural network architecture that can be used for 
language models. It is based solely on attention mechanisms, dispensing 
with recurrence and convolutions. The Transformer has been shown to 
outperform recurrent neural networks and convolutional neural networks on a 
variety of language modeling tasks. The Transformer consists of an encoder 
and a decoder. The encoder is responsible for encoding the input sequence 
into a fixed-length vector representation. The decoder is responsible for 
decoding the output sequence from the input sequence. The decoder uses the 
attention mechanism to attend to different parts of the input sequence when 
generating the output sequence.
------------
Attention is a mechanism that allows a neural network to focus on specific 
parts of an input sequence. In the context of large language models, 
attention is used to allow the model to focus on specific words or phrases 
in a sentence when generating output. This allows the model to generate 
more relevant and informative output.
------------
The process of transforming text into vectors is called word embedding. 
Word embedding is a technique that represents words as vectors in a 
high-dimensional space. The vectors are typically learned from a large 
corpus of text, and they capture the semantic and syntactic relationships 
between words. Word embedding has been shown to be effective for a variety 
of natural language processing tasks, such as machine translation, question 
answering, and sentiment analysis.

完全なソリューション

簡単にコピー&ペーストできるように、ChatPrompts クラスの内容全体を次に示します。

package palm.workshop;

import dev.langchain4j.chain.ConversationalRetrievalChain;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.parser.PdfDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment; 
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.vertexai.VertexAiChatModel;
import dev.langchain4j.model.vertexai.VertexAiEmbeddingModel;
import dev.langchain4j.retriever.EmbeddingStoreRetriever;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class ChatPrompts {
    public static void main(String[] args) throws IOException {
        PdfDocumentParser pdfParser = new PdfDocumentParser();
        Document document = pdfParser.parse(new FileInputStream(new File("/ABSOLUTE_PATH/attention-is-all-you-need.pdf")));

        VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("YOUR_PROJECT_ID")
            .location("us-central1")
            .publisher("google")
            .modelName("textembedding-gecko@001")
            .maxRetries(3)
            .build();

        InMemoryEmbeddingStore<TextSegment> embeddingStore = 
            new InMemoryEmbeddingStore<>();

        EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
            .documentSplitter(DocumentSplitters.recursive(500, 100))
            .embeddingModel(embeddingModel)
            .embeddingStore(embeddingStore)
            .build();
        storeIngestor.ingest(document);

        EmbeddingStoreRetriever retriever = EmbeddingStoreRetriever.from(embeddingStore, embeddingModel);

        VertexAiChatModel model = VertexAiChatModel.builder()
            .endpoint("us-central1-aiplatform.googleapis.com:443")
            .project("genai-java-demos")
            .location("us-central1")
            .publisher("google")
            .modelName("chat-bison@001")
            .maxOutputTokens(1000)
            .build();

        ConversationalRetrievalChain rag = ConversationalRetrievalChain.builder()
            .chatLanguageModel(model)
            .retriever(retriever)
            .promptTemplate(PromptTemplate.from("""
                Answer to the following query the best as you can: {{question}}
                Base your answer on the information provided below:
                {{information}}
                """
            ))
            .build();

        String result = rag.execute("What neural network architecture can be used for language models?");
        System.out.println(result);
        System.out.println("------------");

        result = rag.execute("What are the different components of a transformer neural network?");
        System.out.println(result);
        System.out.println("------------");

        result = rag.execute("What is attention in large language models?");
        System.out.println(result);
        System.out.println("------------");

        result = rag.execute("What is the name of the process that transforms text into vectors?");
        System.out.println(result);
    }
}

8. 完了

これで、LangChain4J と PaLM API を使用して、Java で最初の生成 AI チャット アプリケーションを構築できました。その過程で、大規模言語チャットモデルは非常にパワフルであり、質問や応答などのさまざまなタスクを処理できることを発見しました。独自のドキュメントの作成やデータ抽出も可能です。また、チェスをプレイすることもできることがわかりました。

次のステップ

Java で PaLM をさらに活用するには、次の Codelab をご覧ください。

参考資料

リファレンス ドキュメント