8. LangChain4j + 提示词工程详细说明


https://docs.langchain4j.dev/tutorials/chat-and-language-models/#types-of-chatmessage

目前有四种类型的聊天消息,每种对应消息的一个"来源":

  • UserMessage:这是来自用户的消息。 用户可以是您应用程序的最终用户(人类)或您的应用程序本身。 根据 LLM 支持的模态,UserMessage 可以只包含文本(String), 或其他模态
  • AiMessage:这是由 AI 生成的消息,通常是对 UserMessage 的回应。 正如您可能已经注意到的,generate 方法返回一个包装在 Response 中的 AiMessageAiMessage 可以包含文本响应(String)或执行工具的请求(ToolExecutionRequest)。 我们将在另一节中探讨工具。
  • ToolExecutionResultMessage:这是 ToolExecutionRequest 的结果。
  • SystemMessage:这是来自系统的消息。 通常,您作为开发人员应该定义此消息的内容。 通常,您会在这里写入关于 LLM 角色是什么、它应该如何行为、以什么风格回答等指令。 LLM 被训练为比其他类型的消息更加关注 SystemMessage, 所以要小心,最好不要让最终用户自由定义或在 SystemMessage 中注入一些输入。 通常,它位于对话的开始。
  • CustomMessage:这是一个可以包含任意属性的自定义消息。这种消息类型只能由 支持它的 ChatLanguageModel 实现使用(目前只有 Ollama)。

现在我们了解了所有类型的 ChatMessage,让我们看看如何在对话中组合它们。

在最简单的情况下,我们可以向 chat 方法提供单个 UserMessage 实例。 这类似于第一个版本的 chat 方法,它接受 String 作为输入。 主要区别在于它现在返回的不是 String,而是 ChatResponse。 除了 AiMessage 外,ChatResponse 还包含 ChatResponseMetadataChatResponseMetadata 包含 TokenUsage,其中包含有关输入包含多少令牌的统计信息 (您提供给 generate 方法的所有 ChatMessages), 输出(在 AiMessage 中)生成了多少令牌,以及总计(输入 + 输出)。 您需要这些信息来计算给定 LLM 调用的成本。 然后,ChatResponseMetadata 还包含 FinishReason, 这是一个枚举,包含生成停止的各种原因。 通常,如果 LLM 自己决定停止生成,它将是 FinishReason.STOP

创建 UserMessage 有多种方法,取决于内容。 最简单的是 new UserMessage("Hi")UserMessage.from("Hi")

源码内容:

提示词可以做什么:

利用LangChain4J框架构建一个专业的法律/医疗/保险/教育等咨询助手。

这个助手将专注于回答中国法律相关问题,对其他领域的咨询则会礼貌地拒绝。

学习角色设定和提示词模板的使用,这是实现这个功能的两个关键要素。

一句话总结:打造专业的限定能力范围和作用边界的A助手

下面这个是 Spring AI 的多角色的类型

LangChain4j + 提示词工程实战:

  1. 用SystemMessage明确定义助手的角色和能力范围,将其限定在法律咨询领域。在LangChain4j中,我们主要利用SystemMessage来实现这一点,SystemMessage具有高优先级,能有效地指导模型的整体行为

  2. 利用提示词模板(@UserMessage设计,@V)精确控制输入和期望的输出格式,确保问题被正确理解和回答

  3. 创建对应项目的 module 模块内容:

  4. 导入相关的 pom.xml 的依赖,这里我们采用流式输出的方式,导入langchain4j-open-ai + langchain4j + langchain4j-reactor 这三件必须存在,这里我们不指定版本,而是通过继承的 pom.xml 当中获取。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--langchain4j-open-ai + langchain4j + langchain4j-reactor-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.22</version>
        </dependency>
  1. 设置 applcation.yaml / properties 配置文件,其中指明我们的输出响应的编码格式,因为如果不指定的话,存在返回的中文,就是乱码了。
server.port=9006

spring.application.name=langchain4j-06chat-memory

# 设置响应的字符编码,避免流式返回输出乱码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

  1. 在 LangChain4j 当中,提示词的编写,有三种方式
  • 第一种方式:@SystemMessage+@UserMessage+@V,推荐
  • 第二种方式:创建一个实体类,将提示词信息封装到实体类当中,再用 @StructuredPrompt注解配合 {{}} 占位符,动态填充提示词。推荐
  • 第三种方式:在LangChain4j中有两个对象 PromptTemplate以及Prompt用来实现提示词相关功能。不推荐,可读性太差了,也扩展性太差了。

第一种方式:@SystemMessage+@UserMessage+@V

package com.rainbowsea.langchain4jchatprompt.service;

import com.rainbowsea.langchain4jchatprompt.entities.LawPrompt;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

/**
 */
public interface LawAssistant
{
    // @SystemMessage+@UserMessage+@V
    @SystemMessage("你是一位专业的中国法律顾问,只回答与中国法律相关的问题。" +
            "输出限制:对于其他领域的问题禁止回答,直接返回'抱歉,我只能回答中国法律相关的问题。'")

    @UserMessage("请回答以下法律问题:{{question}},字数控制在{{length}}以内")

    String chat(@V("question") String question, @V("length") int length);
}

  1. 编写大模型三件套(大模型 key,大模型 name,大模型 url) 三件套的大模型配置类。

注意:需要用到我们的通义千问的长对话大模型了。

package com.rainbowsea.langchain4jchatprompt.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.rainbowsea.langchain4jchatprompt.service.LawAssistant;
/**
 */
@Configuration
public class LLMConfig
{
    @Bean
    public ChatModel chatModel()
    {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQwen_api"))  // 根据自身系统变量当中配置的变量名
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public LawAssistant lawAssistant(ChatModel chatModel) {
        return AiServices.create(LawAssistant.class, chatModel);
    }
}

  1. 编写提示词工程调用的 cutroller


import cn.hutool.core.date.DateUtil;
import com.rainbowsea.langchain4jchatprompt.entities.LawPrompt;
import com.rainbowsea.langchain4jchatprompt.service.LawAssistant;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

/**
 */
@RestController
@Slf4j
public class ChatPromptController
{
    @Resource
    private LawAssistant lawAssistant;


    // http://localhost:9007/chatprompt/test1
    @GetMapping(value = "/chatprompt/test1")
    public String test1()
    {
        String chat = lawAssistant.chat("什么是知识产权?",2000);
        System.out.println(chat);

        String chat2 = lawAssistant.chat("什么是java?",2000);
        System.out.println(chat2);

        String chat3 = lawAssistant.chat("介绍下西瓜和芒果",2000);
        System.out.println(chat3);

        String chat4 = lawAssistant.chat("飞机发动机原理",2000);
        System.out.println(chat4);

        return "success : "+ DateUtil.now()+"<br> \n\n chat: "+chat+"<br> \n\n chat2: "+chat2;
    }

}

运行测试:


第二种方式:创建一个实体类,将提示词信息封装到实体类当中,再用 @StructuredPrompt注解配合 {{}} 占位符,动态填充提示词。

编写,提示词的实体类,新建带着@structuredPrompt的业务实体类。

接口当中将操作大模型聊天的参数添加为我们的提示词的实体类


import com.rainbowsea.langchain4jchatprompt.entities.LawPrompt;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

/**
 */
public interface LawAssistant
{



    //案例 新建带着@StructuredPrompt的业务实体类,比如LawPrompt
    @SystemMessage("你是一位专业的中国法律顾问,只回答与中国法律相关的问题。" +
            "输出限制:对于其他领域的问题禁止回答,直接返回'抱歉,我只能回答中国法律相关的问题。'")
    String chat(LawPrompt lawPrompt);


}



import cn.hutool.core.date.DateUtil;
import com.rainbowsea.langchain4jchatprompt.entities.LawPrompt;
import com.rainbowsea.langchain4jchatprompt.service.LawAssistant;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

import java.util.Map;

/**
 *
 */
@RestController
@Slf4j
public class ChatPromptController {
    @Resource
    private LawAssistant lawAssistant;


    @Resource
    private ChatModel chatModel;

    /**
     * TRIPS协议(与贸易有关的知识产权协议):
     * 这是世界贸易组织(WTO)成员间的一个重要协议,
     * 它规定了最低标准的知识产权保护要求,并适用于所有WTO成员。
     *
     * @return
     */
    @GetMapping(value = "/chatprompt/test2")
    public String test2() {
        LawPrompt prompt = new LawPrompt();

        prompt.setLegal("知识产权");
        prompt.setQuestion("TRIPS协议?");

        String chat = lawAssistant.chat(prompt);
        System.out.println(chat);

        LawPrompt prompt2 = new LawPrompt();

        prompt2.setLegal("不知道");
        prompt2.setQuestion("什么是Java?");
        chat = lawAssistant.chat(prompt2);

        System.out.println(chat);

        return "success : " + DateUtil.now() + "<br> \n\n chat: " + chat;
    }
}

运行测试:


第三张种方式:在LangChain4j中有两个对象 PromptTemplate以及Prompt用来实现提示词相关功能。

该方式,只需编写一个普通的 ChatModel ,在 Controller 层,使用 PromptTemplate 类当中的方法即可。


import cn.hutool.core.date.DateUtil;
import com.rainbowsea.langchain4jchatprompt.entities.LawPrompt;
import com.rainbowsea.langchain4jchatprompt.service.LawAssistant;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

import java.util.Map;

/**
 *
 */
@RestController
@Slf4j
public class ChatPromptController {


    @Resource
    private ChatModel chatModel;


    /**
     * @Description: 单个参数可以使用{{it}》”占位符或者”{{参数名}”,如果为其他字符,系统不能自动识别会报错。
     * http://localhost:9007/chatprompt/test3
     */
    @GetMapping(value = "/chatprompt/test3")
    public String test3() {
        // 看看源码,默认 PromptTemplate 构造使用 it 属性作为默认占位符

        /*String role = "外科医生";
        String question = "牙疼";*/

        String role = "财务会计";
        String question = "人民币大写";

        //1 构造PromptTemplate模板
        PromptTemplate template = PromptTemplate.from("你是一个{{it}}助手,{{question}}怎么办");
        //2 由PromptTemplate生成Prompt
        Prompt prompt = template.apply(java.util.Map.of("it", role, "question", question));
        //3 Prompt提示词变成UserMessage
        UserMessage userMessage = prompt.toUserMessage();
        //4 调用大模型
        ChatResponse chatResponse = chatModel.chat(userMessage);

        //4.1 后台打印
        System.out.println(chatResponse.aiMessage().text());
        //4.2 前台返回
        return "success : " + DateUtil.now() + "<br> \n\n chat: " + chatResponse.aiMessage().text();
    }

}

运行测试:

最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

在这里插入图片描述

Logo

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

更多推荐