⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 www.baeldung.com/java-langchain-basics 「JavaGuide」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

在 Baeldung 上看到了一篇介绍基于 Java + LangChain 开发大语言模型应用的基础入门文章,写的非常不错,非常适合初学者。

1. 简介

在本教程中,我们将详细探讨 LangChain,一个用于开发基于语言模型的应用程序的框架。我们将首先了解语言模型的基础概念,这些知识将对本教程有所帮助。

尽管 LangChain 主要提供 Python 和 JavaScript/TypeScript 版本,但也可以在 Java 中使用 LangChain。我们会讨论 LangChain 作为框架的构建模块,然后尝试在 Java 中进行实验。

2. 背景

在深入探讨为什么需要一个用于构建基于语言模型的应用程序的框架之前,我们需要弄清楚语言模型是什么,并了解使用语言模型时可能遇到的一些典型复杂性。

2.1. 大型语言模型

语言模型是自然语言的概率模型,可以生成一系列单词的概率。大型语言模型(LLM)则是以其规模庞大而著称,通常包含数十亿参数的人工神经网络。

大型语言模型通常通过在大量未标记数据上进行预训练,使用自监督学习和弱监督学习技术。之后,通过微调和提示词工程等技术将预训练模型适配于特定任务:

大型语言模型

大型语言模型可以执行多种自然语言处理任务,如语言翻译、内容摘要等。此外,它们还具备生成内容的能力,因此在回答问题等应用场景中非常有用。

几乎所有主流云服务提供商都在其服务中引入了大型语言模型。例如,Microsoft Azure 提供了 Llama 2 和 OpenAI GPT-4,Amazon Bedrock 提供了 AI21 Labs、Anthropic、Cohere、Meta 和 Stability AI 的模型。

2.2. 提示词工程

大型语言模型是一种基础模型,经过大规模文本数据的训练后,可以捕捉人类语言的语法和语义。然而,为了让模型执行特定任务,它们需要进一步调整。

提示词工程(Prompt engineering)是让语言模型完成特定任务的最快捷方法之一。它通过结构化文本向模型描述任务目标,使其能够理解并执行任务:

提示词工程

提示词帮助大型语言模型执行上下文学习,这种学习是暂时的。通过提示词工程,我们可以促进大型语言模型的安全使用,并构建新的功能,比如将领域知识和外部工具整合到模型中。

这一领域目前是一个活跃的研究方向,不断涌现新的技术。然而,诸如 链式思维提示 等技术已经变得颇为流行。这种方法的核心是让大型语言模型在给出最终答案之前,将问题分解为一系列中间步骤。

2.3. 词向量

如前所述,大型语言模型能够高效处理自然语言。如果我们将自然语言中的单词表示为词向量(Word Embeddings ),模型的性能将显著提升。词向量是能够编码单词语义的实值向量。

词向量通常通过算法生成,例如 Word2vec 或 GloVe。

GloVe 是一种无监督学习算法,在语料库的全局词共现统计上进行训练:

Word Embedding Illustration

在提示词工程中,我们将提示转换成词向量,这使得模型更容易理解和响应提示。此外,它也对增强我们提供给模型的上下文非常有帮助,从而使模型能够给出更具上下文意义的回答。

例如,我们可以从现有数据集中生成词向量并将其存储在向量数据库中。然后,我们可以使用用户提供的输入在向量数据库中执行语义搜索,并将搜索结果作为附加上下文提供给模型。

3. 使用 LangChain 构建 LLM 技术栈

正如我们已经了解的那样,创建有效的提示词是成功利用 LLM 的关键元素。这包括使与语言模型的交互具有上下文感知能力,并依赖语言模型进行推理。

为此,我们需要执行多项任务,例如为提示词创建模板、调用语言模型,以及从多种来源提供用户特定数据。为了简化这些任务,我们需要一个像 LangChain 这样的框架作为 LLM 技术栈的一部分:

使用 LangChain 构建 LLM 技术栈

该框架还帮助开发需要链式调用多个语言模型的应用程序,并能够回忆起过去与语言模型过去交互的信息。此外,还有更复杂的用例,例如将语言模型用作推理引擎。

最后,我们可以执行日志记录、监控、流式处理以及其他重要的维护和故障排除任务。LLM 技术栈正在快速发展以应对许多此类问题,而 LangChain 正迅速成为 LLM 技术栈的宝贵组成部分。

4. 面向 Java 的 LangChain

LangChain于 2022 年作为开源项目推出,凭借社区支持迅速发展壮大。最初是由 Harrison Chase 开发的 Python 项目,后来成为 AI 领域增长最快的初创企业之一。

随后,JavaScript/TypeScript 版本的 LangChain 于 2023 年初推出,并迅速流行起来,支持多个 JavaScript 环境,如 Node.js、浏览器、CloudFlare workers、Vercel/Next.js、Deno 和 Supabase Edge functions。

然而,目前没有官方的 Java 版本 LangChain 可供 Java 或 Spring 应用使用。不过,社区开发了 Java 版本 LangChain,称为 LangChain4j ,支持 Java 8 或更高版本,并兼容 Spring Boot 2 和 3。

LangChain 的各种依赖项可以在 Maven Central 上找到。

根据我们使用的功能,可能需要在应用程序中添加一个或多个依赖项:

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.23.0</version>
</dependency>

5. LangChain 的构建模块

LangChain 为我们的应用程序提供了多种价值主张,这些功能以模块化组件的形式提供。模块化组件不仅提供了有用的抽象,还包含了一系列操作语言模型的实现。接下来,我们将通过 Java 示例来讨论其中的一些模块。

5.1. 模型输入/输出(Models I/O)

在使用任何语言模型时,我们需要具备与其交互的能力。LangChain 提供了必要的构建模块,例如模板化提示的能力,以及动态选择和管理模型输入的能力。此外,我们还可以使用输出解析器从模型输出中提取信息:

LangChain 模型

提示模板(Prompt Templates)是用于生成语言模型提示的预定义配方,可以包括指令、少样本示例[16]和特定上下文:

PromptTemplate promptTemplate = PromptTemplate
.from("Tell me a {{adjective}} joke about {{content}}..");
Map<String, Object> variables = new HashMap<>();
variables.put("adjective", "funny");
variables.put("content", "computers");
Prompt prompt = promptTemplate.apply(variables);

5.2. 内存

通常,一个利用大型语言模型(LLM)的应用程序会有一个对话界面。对话的一个重要方面是能够引用对话中先前的信息。这种存储过去交互信息的能力称为内存:

LangChain 内存

LangChain 提供了一些关键功能,可以为应用程序添加内存。例如,我们需要能够从内存中读取信息以增强用户输入,同时还需要将当前运行的输入和输出写入内存:

ChatMemory chatMemory = TokenWindowChatMemory
.withMaxTokens(300, new OpenAiTokenizer(GPT_3_5_TURBO));
chatMemory.add(userMessage("你好,我叫 Kumar"));
AiMessage answer = model.generate(chatMemory.messages()).content();
System.out.println(answer.text()); // 你好 Kumar!今天我能为您做些什么?
chatMemory.add(answer);
chatMemory.add(userMessage("我叫什么名字?"));
AiMessage answerWithName = model.generate(chatMemory.messages()).content();
System.out.println(answerWithName.text()); // 您的名字是 Kumar。
chatMemory.add(answerWithName);

在这里,我们使用 TokenWindowChatMemory 实现了固定窗口聊天内存,它允许我们读取和写入与语言模型交换的聊天消息。

LangChain 还提供更复杂的数据结构和算法,以便从内存中返回选定的消息, 而不是返回所有内容。例如,它支持返回过去几条消息的摘要,或者仅返回与当前运行相关的消息。

5.3. 检索(Retrieval)

大型语言模型通常是在大量的文本语料库上进行训练的。因此,它们在处理通用任务时表现得非常高效,但在处理特定领域任务时可能效果不佳。为了解决这一问题,我们需要在生成阶段检索相关的外部数据,并将其传递给语言模型。

这个过程被称为检索增强生成(Retrieval Augmented Generation,RAG)。RAG 有助于将模型的生成过程与相关且准确的信息结合,同时也让我们更深入地了解模型的生成过程。LangChain 提供了构建 RAG 应用程序所需的核心组件:

LangChain Retrieval

首先,LangChain 提供了文档加载器 FileSystemDocumentLoader,用于从存储位置检索文档。然后,LangChain 还提供了转换器,用于进一步处理文档,例如将大型文档分割成更小的块:

Document document = FileSystemDocumentLoader.loadDocument("simpson's_adventures.txt");
DocumentSplitter splitter = DocumentSplitters.recursive(100, 0,
new OpenAiTokenizer(GPT_3_5_TURBO));
List<TextSegment> segments = splitter.split(document);

在这里,我们使用 FileSystemDocumentLoader 从文件系统中加载文档。然后使用 OpenAiTokenizer 将文档分割成更小的段落。

为了提高检索效率,这些文档通常会被转换成嵌入(embeddings),并存储在向量数据库中。LangChain 支持多种嵌入提供商和方法,并与几乎所有主流的向量存储集成:

EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
embeddingStore.addAll(embeddings, segments);

在这里,我们使用 AllMiniLmL6V2EmbeddingModel 为文档段落创建嵌入,然后将嵌入存储在内存中的向量存储中。

现在,我们的外部数据已经以嵌入的形式存储在向量存储中,可以从中进行检索。LangChain 支持多种检索算法,例如简单的语义搜索和更复杂的集成检索器(ensemble retriever):

String question = "Who is Simpson?";
// 假设该问题的答案包含在我们之前处理的文档中。
Embedding questionEmbedding = embeddingModel.embed(question).content();
int maxResults = 3;
double minScore = 0.7;
List<EmbeddingMatch<TextSegment>> relevantEmbeddings = embeddingStore
.findRelevant(questionEmbedding, maxResults, minScore);

我们为用户的问题生成嵌入,然后使用该问题的嵌入从向量存储中检索相关的匹配项。现在,我们可以将检索到的相关内容作为上下文,添加到我们打算发送给模型的提示中。

6. LangChain 的复杂应用

到目前为止,我们已经了解了如何使用单个组件来创建一个基于语言模型的应用程序。LangChain 还提供了构建更复杂应用程序的组件。例如,我们可以使用链(Chains)和代理(Agents)来构建更加自适应、功能增强的应用程序。

6.1. 链(Chains)

通常,一个应用程序需要按特定顺序调用多个组件。在 LangChain 中,这被称为链(Chain)。链简化了开发更复杂应用程序的过程,并使调试、维护和改进更加容易。

链还可以组合多个链来构建更复杂的应用程序,这些应用程序可能需要与多个语言模型交互。LangChain 提供了创建此类链的便捷方式,并内置了许多预构建链:

ConversationalRetrievalChain chain = ConversationalRetrievalChain.builder()
.chatLanguageModel(chatModel)
.retriever(EmbeddingStoreRetriever.from(embeddingStore, embeddingModel))
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.promptTemplate(PromptTemplate
.from("Answer the following question to the best of your ability: {{question}}\n\nBase your answer on the following information:\n{{information}}"))
.build();

在这里,我们使用了预构建的链 ConversationalRetrievalChain,它允许我们将聊天模型与检索器、内存和提示模板结合使用。现在,我们可以简单地使用该链来执行用户查询:

String answer = chain.execute("Who is Simpson?");

该链提供了默认的内存和提示模板,我们可以根据需要进行覆盖。创建自定义链也非常容易。链的能力使我们能够更轻松地实现复杂应用程序的模块化实现。

6.2. 代理(Agents)

LangChain 还提供了更强大的结构,例如代理(Agent)。与链不同,代理将语言模型用作推理引擎,以确定应该采取哪些操作以及操作的顺序。我们还可以为代理提供访问合适工具的权限,以执行必要的操作。

在 LangChain4j 中,代理作为 AI 服务(AI Services)提供,用于声明性地定义复杂的 AI 行为。让我们看看如何通过提供一个计算器工具,为 AI 服务赋能,从而使语言模型能够执行计算。

首先,我们定义一个包含一些基本计算功能的类,并用自然语言描述每个函数,这样模型可以理解:

public class AIServiceWithCalculator {
static class Calculator {
@Tool("Calculates the length of a string")
int stringLength(String s) {
return s.length();
}
@Tool("Calculates the sum of two numbers")
int add(int a, int b) {
return a + b;
}
}

接下来,我们定义一个接口,用于构建我们的 AI 服务。这里的接口相对简单,但也可以描述更复杂的行为:

interface Assistant {
String chat(String userMessage);
}

然后,我们使用 LangChain4j 提供的构建器工厂,通过定义的接口和工具创建一个 AI 服务:

Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(OpenAiChatModel.withApiKey(<OPENAI_API_KEY>))
.tools(new Calculator())
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();

完成了!现在,我们可以向语言模型发送包含计算任务的问题:

String question = "What is the sum of the numbers of letters in the words \"language\" and \"model\"?";
String answer = assistant.chat(question);
System.out.println(answer); // The sum of the numbers of letters in the words "language" and "model" is 13.

运行这段代码后,我们会发现语言模型现在能够执行计算。

需要注意的是,语言模型在执行某些任务时可能会遇到困难,例如需要时间和空间概念的任务或复杂的算术操作。然而,我们可以通过为模型提供必要的工具来解决这些问题。

7. 总结

在本教程中,我们探讨了创建基于大型语言模型的应用程序的一些基本元素。此外,我们讨论了将 LangChain 作为技术栈的一部分对开发此类应用程序的重要价值。

这使得我们能够探索 LangChain 的 Java 版本 —— LangChain4j 的一些核心组件 。这些库未来将快速发展,它们会让开发由语言模型驱动的应用程序的过程变得更成熟和有趣!

翻译:JavaGuide

文章目录
  1. 1. 1. 简介
  2. 2. 2. 背景
    1. 2.0.1. 2.1. 大型语言模型
    2. 2.0.2. 2.2. 提示词工程
    3. 2.0.3. 2.3. 词向量
  • 3. 3. 使用 LangChain 构建 LLM 技术栈
  • 4. 4. 面向 Java 的 LangChain
  • 5. 5. LangChain 的构建模块
    1. 5.0.1. 5.1. 模型输入/输出(Models I/O)
    2. 5.0.2. 5.2. 内存
    3. 5.0.3. 5.3. 检索(Retrieval)
  • 6. 6. LangChain 的复杂应用
    1. 6.0.1. 6.1. 链(Chains)
    2. 6.0.2. 6.2. 代理(Agents)
  • 7. 7. 总结