1.走进LangChain

1.1.什么是LangChain4J?

The goal of LangChain4J is to simplify integrating LLMs into Java applications.

​ -- 《LangChain4J Doc》

LangChain4J就像Java的Spring框架一样,为LLM的接入提供了一套标准的接入能力。LangChain4J是LangChain在Java生态下的具体实现,用于构建基于LLM的应用和Agent系统。

LangChain4J为不同的LLM提供了一个统一的API,屏蔽了底层API供应商的实现和调用差异,开发者可以使用LangChain无缝切换不同的LLM。

2.Architecture of LangChain4J

LangChain4J的架构可以分为两层,分别为高层和底层。

高层AI服务屏蔽了底层实现的细节,开发者可以实现一些业务服务的API和LLM进行交互。

低层具有更高的自由度,你可以任意定义组件的具体实现,控制组件的组合方式等等。

下面,就LangChain4J的核心组件,一一详细介绍。

2.1.Chat and Language Model

一般地,LLMs的API可以分为两类,

  • LanguageModel:输入输出都为String类型,现在使用的越来越少了
  • ChatModel:应用广泛的一类API,接收多个ChatMessage作为输入,输出一个AiMessage,除了文本类型之外,还支持图片、音频、视频等其他模式。

ChatModel是LangChain4J的低层API,提供强大的灵活性和可扩展性。

根据消息来源进行分类,LangChain4J支持以下五种消息类型:

消息类型 描述 主要方法
UserMessage 用户输入的消息数据 content(), name(), attributes()
AiMessage AI根据输入数据生成的消息 text(), thinking(), toolExceptionRequests(), attributes()
ToolExecutionResultMessage 工具执行结果消息
SystemMessage 系统发送的消息,可以由开发者自定义
CustomMessage 自定义消息
2.2.Chat Memory

我们知道,大模型是无状态的,模型本身并不会记录对话上下文。因此,如果你想要在对话中使用上下文的内容,就必须主动维护管理ChatMessage。但是手动维护比较麻烦,而LangChain4J提供了ChatMemory用来维护管理chatMessage.

2.2.1.History VS Memory

历史记录和记忆听起来似乎是同一种东西,但是对于LLM Agent来说,history和memory具有两种截然不同的语义:

  • history保留了用户和AI的所有会话记录,用户可以在界面中看到
  • memory记录的是专门呈现给大模型使用的信息,使其表现得好像是“记住了”对话的内容,实际底层实现是截然不同的算法。

LangChain4J目前只提供Memroy,而不提供History

下面介绍关于Memory的几点高级特性。

2.2.2.Eviction Policy 淘汰策略

为什么要使用淘汰策略?

  • 存储空间受限:Memory组件可以理解为缓存,当存放的内容超过容量大小的时候,需要使用一定的策略淘汰一些记忆;
  • Token成本昂贵:更多的上下文记忆就代表使用更多的Token,而token的增加会增加每次调用LLM的成本;
  • 控制延迟:发送的LLM的token越多,处理他们所需要的时长越长,则用户等待输出的时间越长

LangChain4J目前支持两种开箱即用的记忆淘汰策略:

  1. 基于消息滑动窗口:使用MessageWindowChatMemory作为滑动窗口,仅保留最近的N条信息,淘汰窗口之外的旧消息;
  2. 基于token的滑动窗口:TokenWindowChatMemory只保留最近的N个token。但是消息是不可再分的,因此如果消息不合适的话,整条消息会直接被丢弃。
2.2.3.Persistence 持久化

默认情况下Memory是存放在内存中的,如果需要保留消息,可以自定义实现MessageStore,将chatMessage存储在数据库中。

class PersistentChatMemroyStore implements ChatMemoryStore {
    @Override
    public List<ChatMessage> getMessages(Object memoryId) {

    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {

    }

    @Override
    public void deleteMessages(Object memoryId) {
      // TODO: Implement deleting all messages in the persistent store by memory ID.
    }
}
2.2.4.SystemMessage

SystemMessage是一种特殊的消息,具有如下的特性:

  • 一旦添加,就会一直被保留,不会被淘汰策略淘汰
  • 一次只能持有一个SystemMessage
  • SystemMessage具有唯一性,重复的SystemMessage会被忽略,如果不一致,则会覆盖掉之前的SystemMessage
2.2.5.Tool 消息配对

AIMessage 中的 ToolExecutionRequestToolExecutionResponse 总是成对出现的。如果 ToolExecutionRequest 被淘汰了,则其对应的 ToolExecutionResponse 消息也会被自动淘汰。

2.3.Tools(Function Calling)

工具(Tools)允许LLM在必要的时候调用开发者定义的一个或者多个可用工具,工具是一个泛称,可以表示任何东西,例如网页搜索、外部API、或者执行特定的代码等等。

实际上LLM并不会去主动调用工具,而是通过开发人员在业务层接收LLM的回复,根据LLM的意愿进行工具的调用并将执行结果反馈给LLM。

一个良好的工具应当具有以下几个属性:

  1. 清晰明确的工具名称
  2. 工具的具体描述,以及应该何时使用工具;
  3. 每个工具的具体参数;

如果这个工具对于人来说一眼就能理解应该如何使用,那么这个工具对于LLM也是易于使用的。

Tools的定义和使用可以参考文档:LangChain4J Documents

2.4.Agent

3.实战

3.1.ChatModel
3.1.1.基本聊天
public static void main(String[] args) {
    // 1.创建 chatModel
    OpenAiChatModel chatModel = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .baseUrl(System.getenv("OPENAI_BASE_URL"))
            .modelName(System.getenv("OPENAI_MODEL"))
            .build();

    // 2.使用 chat 方法发起对话
    ChatResponse response = chatModel.chat(ChatRequest.builder()
            .messages(UserMessage.from("Hello, could you introduce yourself ?"))
            .build());


    // 3.控制台打印
    System.out.println(response.aiMessage().text());
}
3.1.2.多轮对话

多轮对话和基本对话的实现方式相同,只不过构建对话请求的时候,需要将前文传入到messages中,LLM会参考message得出合理的响应。

private static void chatWithMultipleMessages() {
    // 1.构建 User 和AI 的多轮对话
    UserMessage firstUserMessage = UserMessage.from("Hello, My name is shepi, could you introduce yourself ?");
    AiMessage firstAiMessage = CHAT_MODEL.chat(
            ChatRequest.builder()
                    .messages(firstUserMessage)
                    .build())
            .aiMessage();
    UserMessage secondUserMessage = UserMessage.from("Please tell me my name");
    AiMessage secondAiMessage = CHAT_MODEL.chat(
            ChatRequest.builder()
                    .messages(firstUserMessage, firstAiMessage, secondUserMessage)
                    .build()).aiMessage();

    // 2.控制台打印
    System.out.println(secondAiMessage.text());
}
3.2.ChatMemory

ChatMemory可以帮助我们维护上下文,LangChain4J提供了两种开箱即用的窗口聊天记忆

  • MessageWindowChatMemory
  • TokenWindowChatMemory

下面是一个基于消息数量的窗口对话记忆:

public static void main(String[] args) {

    // 1.新建 messageWindow,滑动窗口最大长度设置为 3
    MessageWindowChatMemory chatWindow = MessageWindowChatMemory.builder()
            .maxMessages(4)
            .build();
    // tryChatWithCommonMessages(chatWindow);
    // 测试特殊的系统消息 systemMessage
    tryChatWithSystemMessage(chatWindow);
}

/**
 * 系统消息永远不会被驱逐
 * @param chatWindow
 */
private static void tryChatWithSystemMessage(MessageWindowChatMemory chatWindow) {
    // 创建系统消息
    SystemMessage systemMessage = SystemMessage.from("The user talking with you is shepi");
    chatWindow.add(systemMessage);
    System.out.println(systemMessage.text());
    for (int i = 0; i < 4; i++) {
        UserMessage userMessage = null;
        if (i == 3) {
            userMessage = UserMessage.from("Please tell me my name");
        } else {
            userMessage = UserMessage.from("let's talk about something interesting");
        }
        System.out.println(userMessage.contents());
        chatWindow.add(userMessage);
        // 使用 chatMemory 对话
        AiMessage aiMessage = CHAT_MODEL.chat(ChatRequest.builder().messages(chatWindow.messages()).build()).aiMessage();
        System.out.println(aiMessage.text());
        chatWindow.add(aiMessage);
    }
}

private static void tryChatWithCommonMessages(MessageWindowChatMemory chatWindow) {
    // 2.模拟进行多轮对话
    // 第一轮给出自己的名字,第四轮询问AI我的名字,其他两轮随便聊点什么
    for (int i = 0; i < 4; i++) {
        UserMessage userMessage = null;
        if (i == 0) {
            userMessage = UserMessage.from("Hello, My name is shepi, could you introduce yourself ?");
        } else if (i == 3) {
            userMessage = UserMessage.from("Please tell me my name");
        } else {
            userMessage = UserMessage.from("let's talk about something interesting");
        }
        System.out.println(userMessage.contents());
        chatWindow.add(userMessage);
        // 使用 chatMemory 对话
        AiMessage aiMessage = CHAT_MODEL.chat(ChatRequest.builder().messages(chatWindow.messages()).build()).aiMessage();
        System.out.println(aiMessage.text());
        chatWindow.add(aiMessage);
    }
}
3.3.Tools

LangChain中定义Tool非常简单,只需要在工具接口方法上使用注解 @Tool("Tool description")即可,下面是一个简答的例子。

public class WeatherService {
    @Tool("获取指定城市的天气情况")
    public String getWeather(String city) {
        // 模拟天气数据
        return MessageFormat.
                format("Today's weather in {0} is sunny with a temperature of 22 degrees.", city);
    }
}
3.4.AiService (Agent 封装)

AiService 是什么?

AiService 是 LangChain4J 提供的高层 API,用于快速构建 Agent 应用。它屏蔽了工具调用的底层细节(如检测 LLM 返回的函数调用请求、执行工具、将结果反馈给 LLM),让开发者可以专注于业务逻辑。

AiService 与 Agent 的关系:

  • AiService 是 LangChain4J 对 Agent 概念的具体实现
  • 它自动完成了 Agent Loop 中的:接收输入 → 判断是否需要 Tool → 调用 Tool → 观察结果 → 生成响应
  • 开发者只需定义接口和 Tool,AiService 会自动完成工具调用的编排

如果想要使用上面创建的 Tool,我们需要在定义 AiService 时,传入 Tool 对象。

private static final OpenAiChatModel CHAT_MODEL = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .baseUrl(System.getenv("OPENAI_BASE_URL"))
            .modelName(System.getenv("OPENAI_MODEL"))
            .build();

public static void main(String[] args) {

    // 根据CHAT_MODEL创建一个 chatService
    ChatAssistant chatService = AiServices.builder(ChatAssistant.class)
            .chatLanguageModel(CHAT_MODEL)
            .tools(new WeatherService())
            .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
            .build();

    // 问题
    String question = "今天北京什么天气?";
    String answer = chatService.chat(question);
    System.out.println(answer);
}

4.流式响应 (Streaming)

LangChain4J 支持流式响应,即 LLM 在生成内容时实时返回每个 token,而不是等待完整响应后再返回。

使用场景
  • 需要实时显示生成过程的场景(如 ChatGPT 的打字机效果)
  • 生成较长内容时提升用户体验
  • 需要提前终止不满意的输出
实现方式

使用 StreamingAiServices 代替 AiServices

private static final OpenAiChatModel CHAT_MODEL = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .baseUrl(System.getenv("OPENAI_BASE_URL"))
            .modelName(System.getenv("OPENAI_MODEL"))
            .build();

public static void main(String[] args) {

    // 根据CHAT_MODEL创建一个 chatService
    ChatAssistant chatService = AiServices.builder(ChatAssistant.class)
            .chatLanguageModel(CHAT_MODEL)
            .tools(new WeatherService())
            .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
            .build();

    // 问题
    String question = "今天北京什么天气?";
    String answer = chatService.chat(question);
    System.out.println(answer);
}
流式 vs 非流式
特性 非流式 (AiServices) 流式 (StreamingAiServices)
响应方式 等待完整响应后返回 实时返回每个 token
用户体验 有等待感 实时反馈,体验更好
实现复杂度 简单 稍复杂(需要处理回调)
适用场景 短文本、后台任务 长文本、交互式对话


 

  学习资源推荐

如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!​

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示

​因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

四、AI大模型商业化落地方案

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐