找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2726

积分

0

好友

352

主题
发表于 2025-12-14 16:14:57 | 查看: 76| 回复: 0

1. 简介

本文将探讨LangChain,这是一个用于开发基于语言模型应用程序的框架。我们将首先了解语言模型的基础知识,这有助于理解后续内容。

虽然LangChain主要提供Python和JavaScript/TypeScript版本,但我们也可以在Java中使用它。我们会讨论LangChain框架的核心构建模块,并通过Java代码示例进行实践。

2. 背景

在深入讨论为何需要构建基于语言模型应用的框架之前,我们需要先理解什么是语言模型,以及使用它们时可能遇到的典型挑战。

2.1. 大型语言模型

语言模型是自然语言的概率模型,能够生成一系列单词的概率分布。大型语言模型则以其庞大的参数量著称,通常是包含数十亿参数的人工神经网络。

LLM通常通过在大量无标注数据上进行预训练来构建,采用自监督学习弱监督学习技术。之后,通过微调提示词工程等技术,将预训练模型适配到特定任务。

大型语言模型示意图

大型语言模型可以执行多种自然语言处理任务,如翻译、摘要等。此外,它们还具备强大的内容生成能力,因此在问答等场景中非常有用。目前,几乎所有主流云服务提供商都集成了大型语言模型服务。

2.2. 提示词工程

大型语言模型是经过海量文本训练的基础模型,能够捕捉人类语言的语法和语义。然而,要让它们执行特定任务,还需要进一步调整。

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

提示词帮助LLM进行上下文学习,这种学习是临时性的。通过提示词工程,我们不仅能促进LLM的安全使用,还能构建新功能,例如将领域知识和外部工具整合到模型中。像链式思维提示这类技术已经变得非常流行,其核心是引导LLM在给出最终答案前,先将问题分解为一系列中间步骤。

2.3. 词向量

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

词向量通常由Word2vecGloVe等算法生成。GloVe是一种无监督学习算法,在语料库的全局词共现统计上进行训练。

在提示词工程中,我们将提示转换为词向量,这使模型更容易理解和响应。此外,它还能有效增强我们提供给模型的上下文,从而获得更符合情境的回答。例如,我们可以从现有数据生成词向量并存入向量数据库。随后,可以使用用户输入在向量数据库中进行语义搜索,并将搜索结果作为附加上下文提供给模型,这正是构建由人工智能驱动的应用的关键步骤之一。

3. 使用 LangChain 构建 LLM 技术栈

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

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

使用 LangChain 构建 LLM 技术栈

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

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

4. 面向 Java 的 LangChain

LangChain2022年作为开源项目推出,在社区支持下迅速发展。它最初是由Harrison Chase开发的Python项目,后来成为AI领域增长最快的初创企业之一。

随后,JavaScript/TypeScript版本的LangChain于2023年初推出,并迅速流行起来。然而,目前并没有官方的Java版本LangChain。不过,社区开发了一个Java版本,称为LangChain4j,它支持Java 8或更高版本,并对JavaSpring Boot开发者友好。

LangChain4j的依赖可以在Maven Central上找到。根据所需功能,我们可能需要在应用中添加一个或多个依赖项。

LangChain4j依赖示意图

<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 模型 I/O

提示模板是用于生成语言模型提示的预定义配方,可以包含指令、少量示例和特定上下文。

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)

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

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

LangChain 检索

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

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将文档分割成更小的段落。

为提高检索效率,这些文档通常被转换为嵌入向量,并存储在向量数据库中。LangChain支持多种嵌入模型,并与主流向量存储集成。

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

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

当外部数据以嵌入形式存储后,就可以从中进行检索。LangChain支持多种检索算法,从简单的语义搜索到更复杂的集成检索器。

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还提供了构建更复杂应用的组件,例如链和代理,可用于构建更加自适应和功能增强的应用。

6.1. 链(Chains)

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

链还可以组合多个子链来构建更复杂的应用程序,这些应用可能需要与多个语言模型交互。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还提供了更强大的结构,例如代理。与链不同,代理将语言模型用作推理引擎,以确定应该执行哪些操作以及执行的顺序。我们还可以为代理提供访问合适工具的权限,以执行必要的操作。

在LangChain4j中,代理以AI服务的形式提供,用于声明式地定义复杂的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的一些核心组件。随着这些库的快速发展,开发语言模型驱动的应用正变得更加成熟和高效。




上一篇:嵌入式C语言面试题精选:10大必考知识点与实战代码解析
下一篇:货拉拉大型PHP服务Java化迁移:高并发场景下的工程实践与效能提升
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-3 06:36 , Processed in 0.290661 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表