利用生成式 AI 通过 PaLM 和 LangChain4J 以 Java 与用户和文档聊天

1. 简介

上次更新日期:2024 年 2 月 5 日

什么是生成式 AI

生成式 AI(生成式人工智能)是指使用 AI 来创作新内容,例如文本、图片、音乐、音频和视频。

生成式 AI 基于可以执行多任务处理和执行开箱即用任务(包括摘要、问答、分类等)的基础模型(大型 AI 模型)。此外,只需少量训练,即可针对使用场景调整基础模型,所需示例数据极少。

生成式 AI 如何运作?

生成式 AI 的工作原理是利用机器学习 (ML) 模型,学习人工创建的内容数据集中的模式和关系。然后,它会利用学到的模式生成新内容。

如需训练生成式 AI 模型,最常见的方式是使用监督学习,即系统会为该模型提供一组人工创建的内容和相应的标签。然后,它会学习如何生成类似于人工创建的内容,并使用相同的标签进行标记。

生成式 AI 的常见应用有哪些?

生成式 AI 可处理大量内容,并通过文本、图片和方便用户使用的格式提供数据洞见和答案。生成式 AI 可用于:

  • 通过改善聊天和搜索体验来提高客户互动度
  • 通过对话界面和摘要探索大量非结构化数据
  • 协助处理重复性任务,例如回复提案请求 (RFP)、将营销内容本地化为 5 种语言,以及检查客户合同是否合规等

Google Cloud 提供哪些生成式 AI 产品?

借助 Vertex AI,您可以自定义基础模型、与其交互、将其嵌入到应用中,而无需具备机器学习专业知识。在 Model Garden 上访问基础模型,通过 生成式 AI Studio 上的简单界面微调模型,或者在数据科学笔记本中使用模型。

Vertex AI Search and Conversation 为开发者提供了构建由生成式 AI 提供支持的搜索引擎和聊天机器人的最快方式。

Duet AI 是依托 AI 技术的协作工具,可在 Google Cloud 和 IDE 中使用,帮助您更快地完成更多工作。

本 Codelab 重点介绍哪些内容?

此 Codelab 重点介绍 PaLM 2 大语言模型 (LLM),该模型托管在 Google Cloud Vertex AI 上,涵盖所有机器学习产品和服务。

您将使用 Java 与 PaLM API 进行交互,同时使用 LangChain4J LLM 框架编排器。您将通过不同的具体示例,了解如何利用 LLM 进行问答、构思、提取实体和结构化内容,以及总结内容。

请详细介绍 LangChain4J 框架!

LangChain4J 框架是一个开源库,可通过编排各种组件(例如 LLM 本身,以及矢量数据库 [用于语义搜索]、文档加载器和拆分器 [用于分析文档并从中学习]、输出解析器等其他工具)将大语言模型集成到 Java 应用中。

c6d7f7c3fd0d2951.png

学习内容

  • 如何设置 Java 项目以使用 PaLM 和 LangChain4J
  • 如何从非结构化内容中提取有用信息(实体或关键字提取,以 JSON 格式输出)
  • 如何与用户展开对话
  • 如何使用聊天模型就您自己的文档提出问题

所需条件

  • 了解 Java 编程语言
  • Google Cloud 项目
  • 浏览器,例如 Chrome 或 Firefox

2. 设置和要求

自定进度的环境设置

  1. 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串。您可以随时对其进行更新。
  • 项目 ID 在所有 Google Cloud 项目中是唯一的,并且是不可变的(一经设置便无法更改)。Cloud 控制台会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(通常用 PROJECT_ID 标识)。如果您不喜欢生成的 ID,可以再随机生成一个 ID。或者,您也可以尝试自己的项目 ID,看看是否可用。完成此步骤后便无法更改该 ID,并且此 ID 在项目期间会一直保留。
  • 此外,还有第三个值,即部分 API 使用的项目编号,供您参考。如需详细了解所有这三个值,请参阅文档
  1. 接下来,您需要在 Cloud 控制台中启用结算功能,以便使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有的话)。若要关闭资源以避免产生超出本教程范围的结算费用,您可以删除自己创建的资源或删除项目。Google Cloud 新用户符合参与 300 美元免费试用计划的条件。

启动 Cloud Shell

虽然可以通过笔记本电脑对 Google Cloud 进行远程操作,但在此 Codelab 中,您将使用 Cloud Shell,这是一个在云端运行的命令行环境。

激活 Cloud Shell

  1. 在 Cloud Console 中,点击激活 Cloud Shelld1264ca30785e435.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 控制台的顶部。如果未显示,请点击选择项目以打开项目选择器,然后选择您的预期项目。
  2. 如果您不在 Google Cloud 控制台的 Vertex AI 部分中,请执行以下操作:
  3. 搜索中输入 Vertex AI,然后按 Return 键。
  4. 在搜索结果中,点击 Vertex AI 系统会显示 Vertex AI 信息中心。
  5. 在 Vertex AI 信息中心内,点击启用所有推荐的 API

这会启用多个 API,但对于此 Codelab 而言,最重要的 API 是 aiplatform.googleapis.com,您也可以在 Cloud Shell 终端中通过命令行启用该 API,只需运行以下命令:

$ 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

您将使用 Java 语言(选项 3)构建应用(选项 2),不使用子项目(选项 1),使用构建文件的 Groovy 语法(选项 1),不使用新的 build 功能(选项否),使用 JUnit Jupiter(选项 4)生成测试,项目名称可以使用 palm-workshop,同样,源软件包可以使用 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 个依赖项:

  • 一个位于核心项目上,
  • 一个用于专用 Vertex AI 模块。

为了使用 Java 17 编译和运行我们的程序,请在 plugins {} 代码块下方添加以下代码块:

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

还需要进行一项更改:更新 app/build.gradleapplication 块,以便用户在调用 build 工具时能够替换要在命令行上运行的主类:

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

如需检查 build 文件是否已准备好运行应用,您可以运行默认的主类,该类会输出一条简单的 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 build 文件现在的样子:

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 会知道在对话上下文中,“them”是指 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 个 token(3 个 token 大致相当于 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(可移植游戏表示法)文件。不过,这个聊天机器人可能无法战胜 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 方法的特别之处在于,您可以使用强类型对象。您不会直接与聊天 LLM 进行互动。相反,您使用的是具体类,例如用于表示提取的个人信息的 Person 类,并且您有一个包含 extractPerson() 方法的 PersonExtractor 类,该方法会返回 Person 实例。LLM 的概念被抽象出来,作为 Java 开发者,您只需操作常规类和对象。

7. 检索增强生成:与文档对话

让我们回到对话。这次,您可以就文档内容提问。您将构建一个聊天机器人,该机器人能够从文档摘要数据库中检索相关信息,并且模型会使用这些信息来依托其回答,而不是尝试根据其训练数据生成回答。这种模式称为 RAG,即检索增强生成

简而言之,在检索增强生成中,有两个阶段:

  1. 提取阶段 - 加载文档,将其拆分为更小的块,并将其向量表示形式(即“向量嵌入”)存储在能够进行语义搜索的“向量数据库”中。

6c5bb5cb2e3b8088.png

  1. 查询阶段 - 用户现在可以向您的聊天机器人询问有关文档的问题。问题也会转换为向量,并与数据库中的所有其他向量进行比较。最相似的向量通常在语义上相关,并由向量数据库返回。然后,系统会向 LLM 提供对话的上下文、与数据库返回的向量对应的文本片段,并要求 LLM 通过查看这些片段来确定答案。

2c279c506d7606cd.png

准备文档

在此新演示中,您将询问有关 “Transformer” 神经网络架构的问题。该架构由 Google 开创,是目前所有现代大语言模型的实现方式。

您可以使用 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

实现对话式检索链

让我们逐步探索如何构建两阶段方法,首先是文档提取,然后是用户询问文档相关问题时的查询时间。

文档注入

文档提取阶段的第一步是找到我们下载的 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 变量中)和嵌入模型。其任务是通过计算用户查询的向量嵌入来查询向量数据库,以查找数据库中的相似向量:

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:

深入阅读

参考文档