langchain4j ChatLanguageModel(一)
侧边栏壁纸
  • 累计撰写 24 篇文章
  • 累计收到 177 条评论

langchain4j ChatLanguageModel(一)

xiaoyu
2024-05-26 / 22 评论 / 262 阅读 / 正在检测是否收录...

想象一下,如果你能让聊天机器人不仅仅回答通用问题,还能从你自己的数据库或文件中提取信息,并根据这些信息执行具体操作,比如发邮件,那会是什么情况?Langchain 正是为了实现这一目标而诞生的。
lwoquz7l.png

model分类

  • ChatLanguageModel(聊天对话)
  • EmbeddingModel(将文本转换为 Embedding)
  • ImageModel(生成和编辑image)
  • ModerationModel(检查文本是否包含有害内容)
  • ScoringModel(对多段文本进行评分、排名,对 RAG 很有用。)

从今天开始陆续给大家分享这些model的学习记录。并且始终使用百度千帆大模型,langchain4j官方的qianfan.md目前也由本人尽己所能参与编写及维护。

导入依赖

  <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j</artifactId>
      <version>{{version}}</version>
  </dependency>
  <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-qianfan</artifactId>
      <version>{{version}}</version>
  </dependency>

ChatLanguageModel(聊天对话)

ChatLanguageModel 是 LangChain4j 中的低级 API,提供最大的功能和灵活性。

ChatMessage(消息)

  • UserMessage

    • 来自用户的消息。 用户可以是应用程序的最终用户(人),也可以是应用程序本身。
  • AiMessage

    • 这是由 AI 生成的消息,通常用于响应
  • ToolExecutionResultMessage
  • SystemMessage

    • 这是来自系统的消息。 作为开发人员,应该定义此消息的内容。 通常,你会在这里写下关于LLM在这次对话中扮演什么角色的说明, 它应该如何表现,以什么方式回答,等等。 LLM 被训练为比其他类型的消息更关注, 所以要小心,最好不要让最终用户自由访问定义或注入一些输入。 通常,它位于对话的开头

以下是ChatLanguageModel提供的一种极其简便的方法,输入userMessage处理后输出其返回:

public interface ChatLanguageModel {
    default String generate(String userMessage) {
        return ((AiMessage)this.generate(UserMessage.from(userMessage)).content()).text();
    }
}

但实际的聊天,我们采用 一个或多个ChatMessage 作为输入 :


public interface ChatLanguageModel {  
    default Response<AiMessage> generate(ChatMessage... messages) {
        return this.generate(Arrays.asList(messages));
    }
    Response<AiMessage> generate(List<ChatMessage> var1);
}

设想构建一个聊天机器人。想象一下用户和聊天机器人 (AI) 之间的简单多轮对话:

  • User: 你好, 我是小宇。
  • AI: 你好小宇,我能帮你做什么?
  • User: 我叫什么名字?
  • AI: 小宇。

    
    UserMessage firstUserMessage = UserMessage.from("Hello, my name is xiaoyu");
    AiMessage firstAiMessage = model.generate(firstUserMessage).content(); // Hi xiaoyu, how can I help you?
    UserMessage secondUserMessage = UserMessage.from("What is my name?");
    AiMessage secondAiMessage = model.generate(firstUserMessage, firstAiMessage, secondUserMessage).content(); // xiaoyu

    在第二次 model.generate中,我们提供的不仅仅是一个还包含了以前的消息。

手动维护这些消息会很麻烦,因此存在一个概念ChatMemory

ChatMemory(聊天记忆)

我们先来区分一下Memory和History

  • 历史记录使用户和 AI 之间的所有消息保持完整。历史记录是用户在 UI 中看到的内容。它代表了实际所说的内容
  • 记忆(内存)保留了一些信息,这些信息被呈现给 LLM,使其表现得好像它“记住”了对话。 记忆与历史截然不同。根据所使用的内存算法,它可以通过各种方式修改历史记录: 逐出一些消息,汇总多条消息,汇总单独的消息,从消息中删除不重要的细节, 将额外的信息(例如,用于 RAG)或指令(例如,用于结构化输出)注入到消息中,等等。
    LangChain4j 目前只提供“Memory”,不提供“History”。
    ChatMemory有以下特性
  • Eviction policy(驱逐政策,类似于主动丢掉之前说过的话)
  • Persistence(持久化存储)
  • Special treatment of SystemMessage(SystemMessage的特殊处理)
  • Special treatment of tool messages(工具消息的特殊处理)

Eviction polic

  • 适合 LLM 的上下文窗口。

    • LLM 一次可以处理的Token数量是有上限的。 在某些时候,对话可能会超过此限制。在这种情况下,应逐出某些消息。 通常,最旧的消息会被逐出。
  • 控制成本。

    • 每个Token都有成本,这使得每次调用 LLM 的成本都会逐渐增加。 逐出不必要的消息可降低成本。
  • 控制延迟。

    • 发送到 LLM 的Token越多,处理它们所需的时间就越多。

      Persistence

      默认情况下ChatMemory实现存储ChatMessages在内存中

如果需要持久性,则自定义ChatMemoryStore可以实施 存储ChatMessages在您选择的任何持久性存储中

public class MyChatMemoryStore implements ChatMemoryStore {

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        /*
      每当Chat Memory的用户请求所有消息时,都会调用getMessages方法。这通常在每次与LLM交互期间发生一次。 Object memoryId 值对应于id在创建聊天存储器期间指定。它可以用来区分多个用户和/或对话。getMessages()方法应该返回与给定内存ID相关联的所有消息。
      */
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        /*
        每次向ChatMemory添加新的ChatMessage时都会调用updateMessages。这通常在每次与LLM交互时发生两次:-次是添加新的UserMessage时另一次是添加新的AiMessage时。
        updateMessages()方法应该更新与给定内存ID相关联的所有消息。聊天信息可以单独存储(例如,每个消息一个记录/行/对象)或一起存储(例如,整个聊天存储器一个记录/行/对象)。
        */
    }

    @Override
    public void deleteMessages(Object memoryId) {
      // 当调用ChatMemory.clear()是会调用该方法,可以不重写此方法/leave this method empty 。
    }
}


// 使用:
ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .id("12345")
        .maxMessages(10) // 包括ai的回复
        .chatMemoryStore(new PersistentChatMemoryStore())
        .build();

lwor79og.png

Special treatment of SystemMessage

SystemMessage是message的一种特殊类型,因此,它与其他消息类型的处理方式不同

  • 一旦添加SystemMessage,将永远保留
  • 一次只能持有一个SystemMessage
  • If a new SystemMessage with the same content is added, it is ignored.
  • If a new SystemMessage with different content is added, it replaces the previous one.

Special treatment of tool messages

lwor8gkp.png
大概意思好像就是:如果一个包含ToolExecutionRequests 的AiMessage被驱除,他的ToolExecutionResultMessage(s) 也会被自动驱除,以避免一些问题

Example(一些代码示例)

定义一个通用的IAiService,后续会多次用到。

public interface IAiService {
    /**
     * AI Service提供了一种更简单、更灵活的替代方案。 您可以定义自己的 API(具有一个或多个方法的 Java 接口), 并将为其提供实现
     * @param userMessage
     * @return String
     */
    String chat(String userMessage);
}

一次极其简单的对话


QianfanChatModel qianfanChatModel = QianfanChatModel.builder()
                .apiKey("apiKey")
                .secretKey("secretKey")
                .modelName("Yi-34B-Chat") // 千帆大模型目前暂时免费的一个modelName
                .build();

String answer = model.generate("雷军");

System.out.println(answer)


// 当然不止apiKey、secretKey、modelName还有以下
QianfanChatModel qianfanChatModel = QianfanChatModel.builder()
    .baseUrl(...)
    .apiKey(...)
    .secretKey(...)
    .temperature(...)
    .maxRetries(...)
    .topP(...)
    .modelName(...)
    .endpoint(...)
    .responseFormat(...)
    .penaltyScore(...)
    .logRequests(...)
    .logResponses()
    .build();

带有一个人的记忆的聊天


 QianfanChatModel qianfanChatModel = QianfanChatModel.builder()
          .apiKey("apiKey")
          .secretKey("secretKey")
          .modelName("Yi-34B-Chat")
          .build();
  /* MessageWindowChatMemory
     functions as a sliding window, retaining the N most recent messages and evicting older ones that no longer fit.
     However, because each message can contain a varying number of tokens, MessageWindowChatMemory is mostly useful for fast prototyping.
     我的理解:保留最新的n条消息(包括回复)
   */
  /* TokenWindowChatMemory
    which also operates as a sliding window but focuses on keeping the N most recent tokens, evicting older messages as needed. Messages are indivisible.
    If a message doesn't fit, it is evicted completely.
    MessageWindowChatMemory requires a Tokenizer to count the tokens in each ChatMessage.
    我的理解:更专注于保留N个最新的Token,根据需要逐出较旧的消息
   */
  ChatMemory chatMemory = MessageWindowChatMemory.builder()
          .maxMessages(10)
          .build();

  IAiService assistant = AiServices.builder(IAiService.class)
          .chatLanguageModel(qianfanChatModel) // the model
          .chatMemory(chatMemory)  // memory
          .build();
        String answer = assistant.chat("Hello,my name is xiaoyu");
        System.out.println(answer); // Hello xiaoyu!******

        String answerWithName = assistant.chat("What's my name?");
        System.out.println(answerWithName); // Your name is xiaoyu.******

        String answer1 = assistant.chat("I like playing football.");
        System.out.println(answer1); // The answer

        String answer2 = assistant.chat("I want to go eat delicious food.");
        System.out.println(answer2); // The answer

        String answerWithLike = assistant.chat("What I like to do?");
        System.out.println(answerWithLike);//Playing football.******

带有多个人的记忆的聊天

QianfanChatModel qianfanChatModel = QianfanChatModel.builder()
          .apiKey("apiKey")
          .secretKey("secretKey")
          .modelName("Yi-34B-Chat")
          .build();
  // 此处使用chatMemoryProvider,较上方来说他会更简洁
  IAiService assistant = AiServices.builder(IAiService.class)
          .chatLanguageModel(qianfanChatModel)         // the model
          .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) // chatMemoryProvider
          .build();

  String answer = assistant.chat(1,"Hello, my name is xiaoyu");
  System.out.println(answer); // Hello xiaoyu!******
  String answer1 = assistant.chat(2,"Hello, my name is xiaomi");
  System.out.println(answer1); // Hello xiaomi!******

  String answerWithName1 = assistant.chat(1,"What's my name?");
  System.out.println(answerWithName1); // Your name is xiaoyu.
  String answerWithName2 = assistant.chat(2,"What's my name?");
  System.out.println(answerWithName2); // Your name is xiaomi.

持久化聊天记忆

导入新依赖,用于存储

    <dependency>
        <groupId>org.mapdb</groupId>
        <artifactId>mapdb</artifactId>
        <version>3.1.0</version>
    </dependency>
class PersistentChatMemoryStore implements ChatMemoryStore {
    private final DB db = DBMaker.fileDB("chat-memory.db").transactionEnable().make();
    private final Map<String, String> map = db.hashMap("messages", STRING, STRING).createOrOpen();

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String json = map.get((String) memoryId);
        return messagesFromJson(json);
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        String json = messagesToJson(messages);
        map.put((String) memoryId, json);
        db.commit();
    }

    @Override
    public void deleteMessages(Object memoryId) {
        map.remove((String) memoryId);
        db.commit();
    }
}

class PersistentChatMemoryTest{
  public void test(){
    QianfanChatModel qianfanChatModel = QianfanChatModel.builder()
            .apiKey("apiKey")
            .secretKey("secretKey")
            .modelName("Yi-34B-Chat")
            .build();

    ChatMemory chatMemory = MessageWindowChatMemory.builder()
            .maxMessages(10)
            .chatMemoryStore(new PersistentChatMemoryStore())
            .build();

    IAiService assistant = AiServices.builder(IAiService.class)
            .chatLanguageModel(qianfanChatModel)
            .chatMemory(chatMemory)
            .build();

    String answer = assistant.chat("My name is xiaoyu");
    System.out.println(answer);
    // Run it once and then comment the top to run the bottom(运行一次后注释上面运行下面,他会正常输出你的名字)
    // String answerWithName = assistant.chat("What is my name?");
    // System.out.println(answerWithName);
  }
}

流式回复

LLMs generate text one token at a time, so many LLM providers offer a way to stream the response token-by-token instead of waiting for the entire text to be generated. This significantly improves the user experience, as the user does not need to wait an unknown amount of time and can start reading the response almost immediately.(许多LLM提供者提供了一种逐个token地传输响应的方法,而不是等待生成整个文本。这极大地改善了用户体验,因为用户不需要等待未知的时间,几乎可以立即开始阅读响应。)

以下是一个通过StreamingResponseHandler来实现

 QianfanStreamingChatModel qianfanStreamingChatModel = QianfanStreamingChatModel.builder()
          .apiKey("apiKey")
          .secretKey("secretKey")
          .modelName("Yi-34B-Chat")
          .build();
  qianfanStreamingChatModel.generate(userMessage, new StreamingResponseHandler<AiMessage>() {
        @Override
        public void onNext(String token) {
            System.out.print(token);
        }
        @Override
        public void onComplete(Response<AiMessage> response) {
            System.out.println("onComplete: " + response);
        }
        @Override
        public void onError(Throwable throwable) {
            throwable.printStackTrace();
        }
  });

以下是另一个通过TokenStream来实现

 QianfanStreamingChatModel qianfanStreamingChatModel = QianfanStreamingChatModel.builder()
          .apiKey("apiKey")
          .secretKey("secretKey")
          .modelName("Yi-34B-Chat")
          .build();
  IAiService assistant = AiServices.create(IAiService.class, qianfanStreamingChatModel);

  TokenStream tokenStream = assistant.chatInTokenStream("Tell me a story.");
  tokenStream.onNext(System.out::println)
          .onError(Throwable::printStackTrace)
          .start();

增强检索(RAG)

程序自动将匹配的内容与用户问题组装成一个Prompt,向大语言模型提问,大语言模型返回答案

LangChain4j has an "Easy RAG" feature that makes it as easy as possible to get started with RAG. You don't have to learn about embeddings, choose a vector store, find the right embedding model, figure out how to parse and split documents, etc. Just point to your document(s), and LangChain4j will do its magic.

导入依赖langchain4j-easy-rag


<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-easy-rag</artifactId>
    <version>{{version}}</version>
</dependency>

使用

QianfanChatModel chatLanguageModel = QianfanChatModel.builder()
        .apiKey(API_KEY)
        .secretKey(SECRET_KEY)
        .modelName("Yi-34B-Chat")
        .build();
  // All files in a directory, txt seems to be faster
  List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j/documentation");
  // for simplicity, we will use an in-memory one:
  InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
  EmbeddingStoreIngestor.ingest(documents, embeddingStore);

  IAiService assistant = AiServices.builder(IAiService.class)
          .chatLanguageModel(chatLanguageModel)
          .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
          .contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
          .build();

  String answer = assistant.chat("The Question");
  System.out.println(answer);
0

评论 (22)

取消
  1. 头像
    tnvahvtuwr
    Windows 10 · Google Chrome

    案例丰富,数据详实,论证扎实可信。

    回复
  2. 头像
    gsjvwlbemm
    Windows 10 · Google Chrome

    喜剧效果背后暗含深刻社会观察。

    回复
  3. 头像
    ixmpnrxmwn
    Windows 10 · Google Chrome

    文章紧扣主题,观点鲜明,展现出深刻的思考维度。

    回复
  4. 头像
    vlyrkjitad
    Windows 10 · Google Chrome

    《自古英雄出少年之岳飞》喜剧片高清在线免费观看:https://www.jgz518.com/xingkong/47475.html

    回复
  5. 头像
    pcmgbicjjd
    Windows 10 · Google Chrome

    你的文章让我感受到了不一样的视角,非常精彩。 https://www.yonboz.com/video/63558.html

    回复
  6. 头像
    ppinmrusoy
    Windows 10 · Google Chrome

    《神雕侠侣(完整版)》国产剧高清在线免费观看:https://www.jgz518.com/xingkong/162728.html

    回复
  7. 头像
    jrkcvbugjp
    Windows 10 · Google Chrome

    《小象守护者》记录片高清在线免费观看:https://www.jgz518.com/xingkong/95272.html

    回复
  8. 头像
    niaywjzyod
    Windows 10 · Google Chrome

    你的文章充满了创意,真是让人惊喜。 http://www.55baobei.com/6SNCV1UOuJ.html

    回复
  9. 头像
    ynatlkioqc
    Windows 10 · Google Chrome

    《神雕侠侣(完整版)》国产剧高清在线免费观看:https://www.jgz518.com/xingkong/162728.html

    回复
  10. 头像
    dwanmqpbay
    Windows 10 · Google Chrome

    你的文章让我感受到了不一样的风景,谢谢分享。 https://www.yonboz.com/video/17933.html

    回复
  11. 头像
    ivxjuskdcp
    Windows 10 · Google Chrome

    《陆小凤传奇之剑神一笑》剧情片高清在线免费观看:https://www.jgz518.com/xingkong/90990.html

    回复
  12. 头像
    fnbbbboqqe
    Windows 10 · Google Chrome

    真棒!

    回复
  13. 头像
    lkmmyqsrqs
    Windows 10 · Google Chrome

    你的文章充满了智慧,让人敬佩。 https://www.yonboz.com/video/69966.html

    回复
  14. 头像
    fbsnmlvulj
    Windows 10 · Google Chrome

    你的文章充满了智慧,让人敬佩。 https://www.yonboz.com/video/69966.html

    回复
  15. 头像
    bgzfvmimsl
    Windows 10 · Google Chrome

    你的文章充满了智慧,让人敬佩。 https://www.yonboz.com/video/69966.html

    回复
  16. 头像
    qtqkqcnjbp
    Windows 10 · Google Chrome

    文章的确不错啊https://www.cscnn.com/

    回复
  17. 头像
    fjysaoowko
    Windows 10 · Google Chrome

    文章的确不错啊https://www.cscnn.com/

    回复
  18. 头像
    wdsujgmbzr
    Windows 10 · Google Chrome

    想想你的文章写的特别好https://www.237fa.com/

    回复
  19. 头像
    mrbudkhiel
    Windows 10 · Google Chrome

    怎么收藏这篇文章?

    回复
  20. 头像
    VirtuesGlow
    Android · Google Chrome

    画图

    回复
    1. 头像
      VirtuesGlow
      Android · Google Chrome
      @ VirtuesGlow

      画图

      回复
      1. 头像
        VirtuesGlow
        Android · Google Chrome
        @ VirtuesGlow

        画图

        回复