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

langchain4j ChatLanguageModel(二)

xiaoyu
2024-05-27 / 0 评论 / 100 阅读 / 正在检测是否收录...

接上篇文章,上次提到了ChatMessage、以及ChatMemory和一些大家看不懂的example,现在我们来到最重要的地方

AI Service

到目前为止,我们已经涵盖了底层组件,如聊天语言模型、聊天消息、聊天记忆等。在这个级别上工作是非常灵活的,给你完全的自由,但它也迫使你编写大量的样板代码。由于LLM驱动的应用程序通常不仅需要单个组件,而且需要多个组件协同工作(例如,提示模板、聊天内存、LLM、输出解析器、RAG组件:嵌入模型和存储),并且通常涉及多个交互,编排它们变得更加繁琐。

我们希望您关注业务逻辑,而不是低层的实现细节。因此,目前在Langchain4j中有两个高级概念可以帮助解决这个问题AI Services and Chains(Chains被遗弃了,所以只剩AI Services).
lworjj4j.png

AI Service的一些处理操作

  • 格式化大语言模型的输入
  • 解析大语言模型输出

AI Service更高级的的处理操作

AI Services可用于构建有状态聊天机器人,以促进来回交互,以及自动化流程,其中每个对LLM的调用都是隔离的。

最简单的AI Service

首先,我们用一个方法chat定义一个接口,它接受一个String作为输入并返回一个String。


interface IAiService {
    String chat(String userMessage);
}

创建了我们的底层组件,这些组件将在我们的AI服务中使用。在这种情况下,我们只需要ChatLanguageModel:(下方所用的qianfanChatModel均来自此处,后边不再重复写)


QianfanChatModel qianfanChatModel = QianfanChatModel.builder()
                  .apiKey("apiKey")
                  .secretKey("secretKey")
                  .modelName("modelName")
                  .build();

最后,我们可以使用AiServices去create 一个我们的AI Service的一个实例:

IAiService aiService = AiService .create(IAiService .class, qianfanChatModel);

现在我们可以使用aiService:

String answer = aiService.chat("Hello");
System.out.println(answer); // Hello, how can I help you?

AI Service是如何工作的

你向AiServices提供接口的Class以及底层组件,AiServices创建一个实现这个接口的代理对象。目前,它使用反射,此代理对象处理输入和输出的所有转换。在本例中,输入是一个String,但我们使用的是一个以ChatMessage为输入的ChatLanguageModel。因此,AiService会自动将其转换为UserMessage并且反射到ChatLanguageModel。由于chat方法的输出类型是String,ChatLanguageModel处理后返回的是AiMessage,在从chat方法返回之前,它将被转换成一个字符串。

@SystemMessage与@UserMessage

在上一篇文章中,我们有提到ChatMessage,UserMessage和SystemMessage也被介绍到了。

我们这里所用到的是两个注解。

我们使用@SystemMessage注解,并添加了我们希望使用的系统提示符,这将被转换为幕后的SystemMessage,并与UserMessage一起发送到大语言模型中。


interface Chinese {
    @SystemMessage("Please answer in Chinese. {{it}}")
    String chat(String userMessage);
}

Chinese chinese = AiServices.create(Chinese.class, qianfanChatMode);

String answer = chinese.chat("Hello"); // 你好,***(中文回复)

上文中也有提到过,有的模型他并没有SystemMessage这个概念,或许我们可以使用@UserMessage来达到这个目的


interface Chinese {
    @UserMessage("Please answer in Chinese. {{message}}")
    String chat(@V("message") String userMessage);
}

Chinese chinese = AiServices.create(Chinese.class, qianfanChatMode);

String answer = chinese.chat("Hello"); // 你好,***(中文回复)

很明显,在@UserMessaged的实例中,我并不是简单的将SystemMessage改成了UserMessage,当然,也不是必须要改,只是说一下{{it}}我们可以自定义名称。

自定义实体类对象为返回类型

如果你想接收来自LLM的结构化输出,你可以将你的AlService方法的返回类型从String改为其他类型。目前,AIService支持以下返回类型:
lworp5j5.png

class Person {
    String firstName;
    String lastName;
    LocalDate birthDate;
       @Override
    public String toString() {
        return "Person {" +
                " firstName = \"" + firstName + "\"" +
                ", lastName = \"" + lastName + "\"" +
                ", birthDate = " + birthDate +
                " }";
    }
}


interface PersonExtractor {
    @UserMessage("Extract information about a person from {{it}}")
    Person extractPersonFrom(String text);
}

PersonExtractor personExtractor = AiServices.create(PersonExtractor.class, model);

String text = """
            In 1968, amidst the fading echoes of Independence Day,
            a child named John arrived under the calm evening sky.
            This newborn, bearing the surname Doe, marked the start of a new journey.
            He was welcomed into the world at 345 Whispering Pines Avenue
            a quaint street nestled in the heart of Springfield
            an abode that echoed with the gentle hum of suburban dreams and aspirations.
            """;

Person person = personExtractor.extractPersonFrom(text);

System.out.println(person); // // Person { firstName = "John", lastName = "Doe", birthDate = 1968-07-04 }

为模型启用日志记录

ChatLanguageModel model = OpenAiChatModel.builder()
    .apiKey(...)
    .logRequests(true)
    .logResponses(true)
    .build();

在官方文档中logRequests()和logResponses()携带一个参数true即可,可是在我看来没什么两样(可能用法不对,也可能官方还存在缺陷@2024/5/26)

以及官方下面提到的JSON模式,我看到有贡献者给的代码例示中:
lworrpfm.png
他都重写toString了,讨论responseFormat()有意义还重要麽?(小宇轻轻吐槽一下,确实有点搞不懂的说)。

预定义的问候语

让我们考虑一个简单的例子。我想为我的公司做一个聊天机器人。如果用户问候聊天机器人,我希望它使用预定义的问候语进行响应,而不依赖于LLM来生成问候语。如果用户问一个问题,我希望LLM生成使用公司(又名RAG)的内部知识库的响应。以下是如何将这项任务分解为两个独立的AI服务:


interface GreetingExpert {

    @UserMessage("Is the following text a greeting? Text: {{it}}")
    boolean isGreeting(String text);
}

interface ChatBot {

    @SystemMessage("You are a polite chatbot of a company called Miles of Smiles.")
    String reply(String userMessage);
}

class MilesOfSmiles {

    private final GreetingExpert greetingExpert;
    private final ChatBot chatBot;
    
    ...
    
    public String handle(String userMessage) {
        if (greetingExpert.isGreeting(userMessage)) {
            return "Greetings from Miles of Smiles! How can I make your day better?";
        } else {
            return chatBot.reply(userMessage);
        }
    }
}

GreetingExpert greetingExpert = AiServices.create(GreetingExpert.class, llama2);

ChatBot chatBot = AiServices.builder(ChatBot.class)
    .chatLanguageModel(gpt4)
    .contentRetriever(milesOfSmilesContentRetriever)
    .build();

MilesOfSmiles milesOfSmiles = new MilesOfSmiles(greetingExpert, chatBot);

String greeting = milesOfSmiles.handle("Hello");
System.out.println(greeting); // Greetings from Miles of Smiles! How can I make your day better?

String answer = milesOfSmiles.handle("Which services do you provide?");
System.out.println(answer); // At Miles of Smiles, we provide a wide range of services ...

上面是官方给的示例(直接复制粘贴下来的,并不能直接运行,大家需要自行修改),现在我使用的千帆大语言模型貌似并没有这种“情感方面”的判断我使用下面进行了一个小小的判断:(当然也不排除是我使用的情感词有问题)


enum Sentiment {
    POSITIVE, NEUTRAL, NEGATIVE
}

interface SentimentAnalyzer {
    @UserMessage("Analyze sentiment of {{it}}")
    Sentiment analyzeSentimentOf(String text);
    @UserMessage("它是消极的吗? {{it}}")
    boolean isPositive(String text);
}

SentimentAnalyzer sentimentAnalyzer = AiServices.create(SentimentAnalyzer.class, chatLanguageModel);

Sentiment sentiment = sentimentAnalyzer.analyzeSentimentOf("This is good!"); 
System.out.println(sentiment);   // POSITIVE 我男孩中有几个 good,great,bad,,前两个POSITIVE,bad为null,,词穷了,中文也没试几个

boolean positive = sentimentAnalyzer.isPositive("It's terrible!");

lwort3xh.png

0

评论 (0)

取消