PaLM 및 LangChain4J를 사용하여 Java로 사용자 및 문서와 함께 생성형 AI 기반 채팅

1. 소개

최종 업데이트: 2024년 2월 5일

생성형 AI란 무엇인가요?

생성형 AI 또는 생성형 인공지능은 AI를 사용하여 텍스트, 이미지, 음악, 오디오, 동영상과 같은 새로운 콘텐츠를 만드는 것을 의미합니다.

생성형 AI는 요약, Q&A, 분류 등의 바로 사용 가능한 태스크를 멀티태스킹하고 수행할 수 있는 기반 모델(대규모 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 기반 검색엔진과 챗봇을 가장 빠르게 빌드할 수 있는 방법을 제공합니다.

또한 Google Cloud와 IDE 전반에서 사용할 수 있는 AI 기반 공동작업 도구인 Duet AI를 사용하면 더 많은 작업을 더 빠르게 수행할 수 있습니다.

이 Codelab에서 중점을 두는 부분은 무엇인가요?

이 Codelab에서는 모든 머신러닝 제품과 서비스를 포괄하는 Google Cloud Vertex AI에서 호스팅되는 PaLM 2 대규모 언어 모델 (LLM)에 중점을 둡니다.

Java를 사용하여 LangChain4J LLM 프레임워크 조정자와 함께 PaLM API와 상호작용합니다. 질의 응답, 아이디어 도출, 항목 및 구조화된 콘텐츠 추출, 요약에 LLM을 활용할 수 있도록 구체적인 여러 사례를 살펴봅니다.

LangChain4J 프레임워크에 대해 자세히 알아보기

LangChain4J 프레임워크는 LLM 자체와 같은 다양한 구성요소뿐만 아니라 벡터 데이터베이스 (시맨틱 검색용), 문서 로더 및 스플리터 (문서 분석 및 학습용), 출력 파서 등의 다른 도구도 조정하여 Java 애플리케이션에 대규모 언어 모델을 통합하기 위한 오픈소스 라이브러리입니다.

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를 생성할 수 있습니다. 또는 직접 시도해 보고 사용 가능한지 확인할 수도 있습니다. 이 단계 이후에는 변경할 수 없으며 프로젝트 기간 동안 유지됩니다.
  • 참고로 세 번째 값은 일부 API에서 사용하는 프로젝트 번호입니다. 이 세 가지 값에 대한 자세한 내용은 문서를 참고하세요.
  1. 다음으로 Cloud 리소스/API를 사용하려면 Cloud 콘솔에서 결제를 사용 설정해야 합니다. 이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 튜토리얼이 끝난 후에 요금이 청구되지 않도록 리소스를 종료하려면 만든 리소스 또는 프로젝트를 삭제하면 됩니다. Google Cloud 신규 사용자는 300달러(USD) 상당의 무료 체험판 프로그램에 참여할 수 있습니다.

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

가상 머신에는 필요한 개발 도구가 모두 들어 있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 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 콘솔 상단에 표시되는지 확인합니다. 그렇지 않은 경우 프로젝트 선택을 클릭하여 프로젝트 선택기를 열고 원하는 프로젝트를 선택합니다.
  2. Google Cloud 콘솔의 Vertex AI 부분이 아닌 경우 다음을 수행합니다.
  3. 검색에서 Vertex AI를 입력한 다음
  4. 검색 결과에서 Vertex AI를 클릭합니다. Vertex AI 대시보드가 표시됩니다.
  5. Vertex AI 대시보드에서 모든 권장 API 사용 설정을 클릭합니다.

이렇게 하면 여러 API가 사용 설정되지만 Codelab에서 가장 중요한 API는 aiplatform.googleapis.com로, 명령줄의 Cloud Shell 터미널에서 다음 명령어를 실행하여 사용 설정할 수도 있습니다.

$ gcloud services enable aiplatform.googleapis.com

Gradle로 프로젝트 구조 만들기

자바 코드 예를 빌드하려면 Gradle 빌드 도구 및 자바 버전 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)을 자바 언어 (옵션 3)를 사용하여 하위 프로젝트를 사용하지 않고 (옵션 1), 빌드 파일에 Groovy 문법 (옵션 1)을 사용하고, 새로운 빌드 기능을 사용하지 않음 (옵션 1), JUnititer로 테스트를 생성하고 (옵션 4), 프로젝트 이름으로 palm-workshop.palm 패키지를 사용할 수 있습니다.

프로젝트 구조는 다음과 같습니다.

├── 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에는 두 가지 종속 항목이 있습니다.

  • 하나는 핵심 프로젝트에 있고
  • 하나는 전용 Vertex AI 모듈용입니다

프로그램을 컴파일하고 실행하는 데 자바 17을 사용하려면 plugins {} 블록 아래에 다음 블록을 추가합니다.

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

한 가지 더 변경할 사항이 있습니다. 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은 '그들'을 인지하고 있습니다. 대화의 맥락에서 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~40) 중에서 무작위로 단어를 선택합니다.
  • .topP(0.95): 총 확률의 합이 부동 소수점 수 (0과 1 사이)가 되는 가능한 단어를 선택합니다.
  • .maxRetries(3): 시간별 요청 할당량을 초과하여 실행 중인 경우 모델이 호출을 3회 재시도하도록 할 수 있습니다. 예를 들면 다음과 같습니다.

5. 개성이 있는 유용한 챗봇

이전 섹션에서는 특별한 맥락을 제공하지 않고 즉시 LLM 챗봇에 질문하기 시작했습니다. 그러나 이러한 챗봇을 전문화하여 특정 작업이나 특정 주제에 대한 전문가가 될 수 있습니다.

그 방법은 무엇일까요? 단계를 설정: LLM에 당면한 작업과 맥락을 설명하고 어떤 작업이 필요한지, 어떤 캐릭터가 필요한지, 어떤 형식으로 응답을 받고 싶은지, 챗봇이 특정한 방식으로 작동하기를 원하는 경우 어조를 알려주는 몇 가지 예를 제공합니다.

프롬프트 작성에 관한 도움말은 다음 그래픽을 통해 이 접근 방식을 잘 보여줍니다.

8a4c67679dcbd085.png

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

이 점을 설명하기 위해 prompts.chat 웹사이트에서 몇 가지 아이디어를 얻어 보겠습니다. 이 웹사이트에는 다음과 같은 역할을 하는 맞춤형 챗봇에 대한 멋지고 재미있는 아이디어가 다양하게 나와 있습니다.

LLM 챗봇을 체스 플레이어로 변신시키는 한 가지 예가 있습니다. 이를 구현해 보겠습니다.

다음과 같이 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개)을 사용하여 체스에 관한 전체 논문이 아닌 다음 동작을 생성하려고 합니다.
  • 다음으로 채팅 대화를 저장하기 위해 채팅 메모리 저장소를 만듭니다.
  • 마지막 이동을 유지하기 위해 실제 창이 설정된 채팅 메모리를 만듭니다.
  • 채팅 메모리에서 '시스템'을 추가합니다. 메시지를 반환합니다. '시스템' 메시지는 맥락을 추가하지만 '사용자'는 및 '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) 파일까지 보았을 것입니다. 하지만 이 챗봇은 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() 메서드의 콘텐츠를 살펴보겠습니다.

  • 채팅 모델이 인스턴스화됩니다.
  • LangChain4J의 AiServices 클래스 덕분에 PersonExtractor 객체가 생성됩니다.
  • 그런 다음 간단히 Person person = extractor.extractPerson(...)를 호출하여 구조화되지 않은 텍스트에서 사람의 세부정보를 추출하고 이름과 나이가 포함된 Person 인스턴스를 반환할 수 있습니다.

이제 다음 명령어를 사용하여 이 클래스를 실행합니다.

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

> Task :app:run
Anna
23

예. Anna입니다. 23세입니다.

AiServices 접근 방식에서 특히 흥미로운 점은 강타입(strongly typed) 객체로 작업한다는 것입니다. 채팅 LLM과 직접 상호작용하고 있지 않습니다. 대신 추출된 개인 정보를 나타내는 Person 클래스와 같은 구체적인 클래스를 사용하고 Person 인스턴스를 반환하는 extractPerson() 메서드가 있는 PersonExtractor 클래스를 사용합니다. LLM의 개념은 추상화되며 Java 개발자는 일반 클래스와 객체를 조작할 뿐입니다.

7. 검색 증강 생성: 문서와 채팅

대화로 돌아가서 이번에는 문서에 관해 질문할 수 있습니다. 문서 추출 데이터베이스에서 관련 정보를 검색할 수 있는 챗봇을 빌드해 보겠습니다. 이 정보는 모델이 학습을 통해 생성된 응답을 생성하려고 시도하는 대신 답변의 '그라운딩'을 위해 사용됩니다. 이 패턴을 RAG 또는 검색 증강 생성이라고 합니다.

검색 증강 생성은 간단히 말해 두 단계로 나눌 수 있습니다.

  1. 수집 단계 - 문서가 로드되어 더 작은 청크로 분할되고, 문서의 벡터 표현 ('벡터 임베딩')이 시맨틱 검색을 수행할 수 있는 '벡터 데이터베이스'에 저장됩니다.

6c5bb5cb2e3b8088.png

  1. 쿼리 단계 - 이제 사용자가 챗봇에 문서에 대해 질문할 수 있습니다. 이 질문은 벡터로도 변환되고 데이터베이스의 다른 모든 벡터와 비교됩니다. 가장 유사한 벡터는 일반적으로 의미론적으로 관련되며 벡터 데이터베이스에서 반환합니다. 그런 다음 LLM에는 대화의 컨텍스트, 데이터베이스가 반환한 벡터에 해당하는 텍스트 스니펫이 제공되고, 이러한 스니펫을 확인하여 답변의 근거를 제시하라는 요청을 받습니다.

2c279c506d7606cd.png

서류 준비

이 새로운 데모에서는 Google이 개척한 'transformer' 신경망 아키텍처에 대해 질문합니다. 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단계 접근 방식을 구축하는 방법을 차근차근 살펴보겠습니다. 먼저 문서 수집에 대해 알아본 다음, 사용자가 문서에 대해 질문할 때의 쿼리 시간을 살펴보겠습니다.

문서 수집

문서 처리 단계의 첫 번째 단계는 다운로드한 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 파일의 텍스트를 500자(영문 기준)의 스니펫으로 분할하고 100자가 중복되도록 합니다(단어나 문장을 조금씩 자르지 않도록 다음 덩어리로).

매장 '수집' 문서 분할기, 벡터 계산을 위한 임베딩 모델, 인메모리 벡터 데이터베이스를 연결합니다. 그러면 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();

이 '체인'은 결합:

  • 이전에 구성한 채팅 언어 모델입니다.
  • Retriever는 벡터 임베딩 쿼리를 데이터베이스의 벡터와 비교합니다.
  • 프롬프트 템플릿에는 채팅 모델이 제공된 정보 (즉, 벡터 임베딩이 사용자 질문의 벡터와 유사한 문서의 관련 발췌 부분)를 기반으로 응답해야 한다고 명시적으로 표시되어 있습니다.

이제 드디어 질문할 준비가 되었습니다.

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을 확인하세요.

추가 자료

참조 문서