8. LangChain4j + 提示词工程详细说明
toc
docs.langchain4j.dev/tutorials/c...

目前有四种类型的聊天消息,每种对应消息的一个"来源":
UserMessage
:这是来自用户的消息。 用户可以是您应用程序的最终用户(人类)或您的应用程序本身。 根据 LLM 支持的模态,UserMessage
可以只包含文本(String
), 或其他模态。AiMessage
:这是由 AI 生成的消息,通常是对UserMessage
的回应。 正如您可能已经注意到的,generate 方法返回一个包装在Response
中的AiMessage
。AiMessage
可以包含文本响应(String
)或执行工具的请求(ToolExecutionRequest
)。 我们将在另一节中探讨工具。ToolExecutionResultMessage
:这是ToolExecutionRequest
的结果。SystemMessage
:这是来自系统的消息。 通常,您作为开发人员应该定义此消息的内容。 通常,您会在这里写入关于 LLM 角色是什么、它应该如何行为、以什么风格回答等指令。 LLM 被训练为比其他类型的消息更加关注SystemMessage
, 所以要小心,最好不要让最终用户自由定义或在SystemMessage
中注入一些输入。 通常,它位于对话的开始。CustomMessage
:这是一个可以包含任意属性的自定义消息。这种消息类型只能由 支持它的ChatLanguageModel
实现使用(目前只有 Ollama)。
现在我们了解了所有类型的 ChatMessage
,让我们看看如何在对话中组合它们。
在最简单的情况下,我们可以向 chat
方法提供单个 UserMessage
实例。 这类似于第一个版本的 chat
方法,它接受 String
作为输入。 主要区别在于它现在返回的不是 String
,而是 ChatResponse
。 除了 AiMessage
外,ChatResponse
还包含 ChatResponseMetadata
。 ChatResponseMetadata
包含 TokenUsage
,其中包含有关输入包含多少令牌的统计信息 (您提供给 generate 方法的所有 ChatMessages
), 输出(在 AiMessage
中)生成了多少令牌,以及总计(输入 + 输出)。 您需要这些信息来计算给定 LLM 调用的成本。 然后,ChatResponseMetadata
还包含 FinishReason
, 这是一个枚举,包含生成停止的各种原因。 通常,如果 LLM 自己决定停止生成,它将是 FinishReason.STOP
。
创建 UserMessage
有多种方法,取决于内容。 最简单的是 new UserMessage("Hi")
或 UserMessage.from("Hi")
。


源码内容:

提示词可以做什么:
利用LangChain4J框架构建一个专业的法律/医疗/保险/教育等咨询助手。
这个助手将专注于回答中国法律相关问题,对其他领域的咨询则会礼貌地拒绝。
学习角色设定和提示词模板的使用,这是实现这个功能的两个关键要素。
一句话总结:打造专业的限定能力范围和作用边界的A助手。


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

LangChain4j + 提示词工程实战:
-
用SystemMessage明确定义助手的角色和能力范围,将其限定在法律咨询领域。在LangChain4j中,我们主要利用SystemMessage来实现这一点,SystemMessage具有高优先级,能有效地指导模型的整体行为
-
利用提示词模板(@UserMessage设计,@V)精确控制输入和期望的输出格式,确保问题被正确理解和回答
-
创建对应项目的 module 模块内容:
-
导入相关的 pom.xml 的依赖,这里我们采用流式输出的方式,导入
langchain4j-open-ai + langchain4j + langchain4j-reactor
这三件必须存在,这里我们不指定版本,而是通过继承的 pom.xml 当中获取。
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>
- 设置 applcation.yaml / properties 配置文件,其中指明我们的输出响应的编码格式,因为如果不指定的话,存在返回的中文,就是乱码了。
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
- 在 LangChain4j 当中,提示词的编写,有三种方式
- 第一种方式:@SystemMessage+@UserMessage+@V,推荐
- 第二种方式:创建一个实体类,将提示词信息封装到实体类当中,再用
@StructuredPrompt
注解配合{{}}
占位符,动态填充提示词。推荐 - 第三种方式:在LangChain4j中有两个对象 PromptTemplate以及Prompt用来实现提示词相关功能。不推荐,可读性太差了,也扩展性太差了。
第一种方式:@SystemMessage+@UserMessage+@V


java
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);
}
- 编写大模型三件套(大模型 key,大模型 name,大模型 url) 三件套的大模型配置类。
注意:需要用到我们的通义千问的长对话大模型了。


java
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);
}
}
- 编写提示词工程调用的 cutroller

java
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的业务实体类。

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

java
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);
}

java
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 类当中的方法即可。

java
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();
}
}
运行测试:

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