目录
LangChain官网:LangChain4j,官方英文文档:Get Started | LangChain4j,API 文档:Overview (LangChain4j)
非官方中文文档:快速开始 | LangChain4j 中文文档
官方示例代码仓库:langchain4j/langchain4j-examples
LangChain4j版本:1.14.0-beta24
一、响应流式传输(Streaming Response)
1.1、介绍
响应流式传输(Streaming Response)是指服务器在处理请求时,将生成的数据逐步、连续地推送给客户端,而不是等全部结果生成完毕后再一次性返回。
在与大语言模型(LLM)对话时,流式传输允许模型以逐个 Token 的形式输出文本,客户端接收数据并实时渲染,从而实现将回答文本一个字一个字“打出来”的效果,好处是当模型的推理回答所消耗时间的太长时,用户实时就能看到大模型的回答,不会让用户一直处于等待中。
目前大模型流式传输的主流实现是 SSE,以 “text/event-stream” 的格式持续推送数据,其整个过程的大致步骤是:
-
客户端发送一个普通的 HTTP 请求,并在 Header 中声明接收数据的 MIME 类型为:
Accept: text/event-stream -
服务器保持连接打开,以 “text/event-stream” 格式持续推送数据:
data: {"choices":[{"delta":{"content":"你"}}]} data: {"choices":[{"delta":{"content":"好"}}]} data: [DONE]每一条
data:行代表一个事件(一个 token 或状态标记),客户端通过EventSourceAPI 逐条接收并处理。 -
当模型生成完毕之后,服务器发送
[DONE]信号并关闭连接。
1.2、实现
添加 Maven 依赖坐标:
<!-- WebFlux 流式输出,提供响应式 Web 支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.4.13</version>
</dependency>
<!-- 提供 TokenStream 到 Flux 的直接转换支持 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.14.0-beta24</version>
</dependency>
配置类 ChatModelConfig,返回的是 OpenAiStreamingChatModel 对象:
@Configuration
public class ChatModelConfig {
@Bean("deepseekFluxChatModel")
public OpenAiStreamingChatModel deepseekFluxChatModel() {
return OpenAiStreamingChatModel.builder()
.baseUrl("https://api.deepseek.com")
.apiKey(System.getenv("Key_Deepseek"))
.modelName("deepseek-v4-flash")
.build();
}
}
方法一:AI Service 接口直接返回 Flux<String>
在代理接口 Assistant 中使用 AiService.streamingChatModel 指定使用的模型
/**
* 代理接口 Assistant
*/
@AiService(wiringMode = EXPLICIT, streamingChatModel = "deepseekFluxChatModel")
public interface Assistant {
Flux<String> chatFlux(String userMessage);
}
/**
* Controller 接口
*/
@RestController
public class ChatController {
@Autowired
private Assistant assistant;
/**
* 最简洁的方式:直接返回 Flux<String> 类型数据
*/
// 请求接口预览流式传输效果时可以用 text/stream
// @GetMapping(value = "/chatFlux", produces = "text/stream;charset=UTF-8")
// 正式返回给前端的数据用 text/event-stream
@GetMapping(value = "/chatFlux", produces = "text/event-stream;charset=UTF-8")
public Flux<String> chatFlux(@RequestParam(name = "question", defaultValue = "你是谁") String question) {
return assistant.chatFlux(question);
}
}
方法二:使用 TokenStream 与 Flux.create 桥接
这种方法不用导入 langchain4j-reactor 依赖包,而是手动处理 Token 与 Flux 的转换
/**
* 代理接口 Assistant
*/
@AiService(wiringMode = EXPLICIT, streamingChatModel = "deepseekFluxChatModel")
public interface Assistant {
TokenStream chatStreaming(String userMessage);
}
/**
* Controller 接口
*/
@RestController
public class ChatController {
@Autowired
private Assistant assistant;
/**
* 精细控制:使用 TokenStream 与 Flux.create 桥接
*/
// 请求接口预览流式传输效果时可以用 text/stream
// @GetMapping(value = "/chatStreaming", produces = "text/stream;charset=UTF-8")
// 正式返回给前端的话用 text/event-stream
@GetMapping(value = "/chatStreaming", produces = "text/event-stream;charset=UTF-8")
public Flux<String> chatStreaming(@RequestParam(name = "question", defaultValue = "你是谁") String question) {
return Flux.create((FluxSink<String> sink) -> {
// 从模型的回答中获取 TokenStream
TokenStream tokenStream = assistant.chatStreaming(question);
// 注册流式回调
tokenStream.onPartialResponse(token -> sink.next(token)) // 收到 token,推送到 Flux
.onToolExecuted(toolExecution -> { // 工具被执行时的回调
System.out.println("工具执行完成: " + toolExecution.request().name());
})
.onCompleteResponse(response -> {
System.out.println("\n完成");
sink.complete(); // 流结束
}) // 推送完成
.onError(error -> {
System.err.println("Stream error: " + error.getMessage());
sink.error(error); // 发生错误,通知Flux
})
.start(); // 调用 start() 触发请求
});
}
}
二、提示词
提示词(Prompt) 是给大模型下达的指令或输入的文本,提示词的质量,直接决定了输出的上限。在大模型后端开发中,提示词不可能是固定的,因此 LangChain4j 将提示词做成了一种可拼装、可复用的组件,支持自定义模板、动态参数绑定、Java对象对象映射成提示词等功能。
LangChain4j 中主要由四种消息类型:
| 类型 | 说明 | 使用场景 |
|---|---|---|
SystemMessage |
系统消息,定义角色、规则、输出格式等全局指令 | 每次对话的“剧本” |
UserMessage |
用户消息,承载用户输入或具体问题 | 对话中的问句或指令 |
AiMessage |
模型回复的消息 | 多轮对话的历史记录 |
ToolExecutionResultMessage |
工具调用的结果 | 在 function calling 流程中反馈工具执行信息 |
每一次大模型调用时 LangChain4j 都会将这些消息对象按顺序放入一个列表并发送给模型,过程类似于:
List<ChatMessage> messageList = new ArrayList<>();
// 系统提示词 SystemMessage
messageList.add(SystemMessage.from("你是一个经验丰富的英语翻译"));
// 用户提示词 UserMessage
messageList.add(UserMessage.from("翻译:Hello World"));
// AI 之前的回复消息 AiMessage
messageList.add(AiMessage.from("上次AI的回复内容"));
// 如果本次调用了工具,加上调用工具的返回结果 ToolExecutionResultMessage
messageList.add(ToolExecutionResultMessage.from(toolExecutionRequest, "工具执行结果文本"));
// 将合并的所有消息类型发送给模型
openAiChatModel.chat(messageList);
2.1、提示词注解
系统提示词 @SystemMessage
@SystemMessage 为当前 AI 服务方法设置系统提示词。系统提示词是发给大模型的最高优先级指令,用来定义 AI 的全局人设、行为边界、回答风格等。
生效范围:当标注在方法上时,表示仅对该方法生效,标注在接口上时,则对该接口中的所有方法生效。如果同时存在,方法上的提示词会覆盖接口上的提示词。
支持模板变量占位符 “{{变量名}}”,配合方法参数上的 @V 注解进行变量的动态填充。例如:
@SystemMessage("你是一个经验丰富的{{role}}")
String chat(@V("role") String role, String question);
@V("变量名") 用于将方法参数绑定到模板中的 {{变量名}} 占位符,@V 标注的参数不会参与 {} 的顺序填充,未标注 @V 的 String 参数默认对应 {} 占位符。
从 resources 资源路径下读取文本文件:
@SystemMessage(fromResource = "system-prompt.txt")
用户提示词 @UserMessage
@UserMessage 定义用户消息模板,只能标注在方法上,框架会将方法的参数填充到这个模板中。
除了与 @SystemMessage 一样支持模板变量占位符 “{{变量名}}”,@UserMessage 也支持一种特有的简化占位符 “{}”,存在多个参数时,多个 “{}” 按照实际参数顺序对应:
@UserMessage("请用{{language}}语言回答:{}")
String chat(@V("language") String language, String question);
不过,“{}” 是按顺序填充的,如果参数较多,可读性会比较差。因此,推荐全部使用模板变量占位符 “{{变量名}}”。
@UserMessage 也支持读取 resources 资源路径下的文本文件:
@UserMessage(fromResource = "user-prompt.txt")
需要说明的是,当 AI Service 方法没有标注 @UserMessage 时,框架的默认行为是:将方法的第一个 String 类型参数作为用户消息。例如以下两种写法等价:
// 以下两种写法等价
String chat(String question);
@UserMessage("{}")
String chat(String question);
@SystemMessage 与 @UserMessage 这两个注解组成完整的提示词结构:
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel")
public interface Assistant {
@SystemMessage("你是一个经验丰富的{{role}}")
@UserMessage("请用{{language}}回答问题:{{question}}")
String chat(@MemoryId String memoryId, @V("role") String role, @V("language") String language, @V("question") String question);
}
多轮对话 中需要 ChatMemory 来管理历史消息,@MemoryId 注解可以自动注入当前会话的 ID。
2.2、提示词模板 PromptTemplate
PromptTemplate 是依赖原始的字符串和键值对(如 Map)来传递动态数据的文本模板工具。
通过调用静态方法 PromptTemplate.from(..) 初始化提示词模板,模板中使用双花括号 {{变量名}} 作为变量占位符,然后将存放变量映射的 Map 传入到 apply(Map map) 方法中,即可根据变量灵活构造出提示词。
PromptTemplate template = PromptTemplate.from("请将以下文本翻译成{{language}}:\n{{text}}");
Prompt prompt = template.apply(Map.of(
"language", "中文",
"text", "Hello world"
));
/**
* 构造好的提示词为:
* 请将以下文本翻译成中文:
* Hello world
*/
String promptStr = prompt.text();
构造出来的 Prompt 对象可以转换成多种消息类型:
SystemMessage systemMessage = prompt.toSystemMessage();
UserMessage userMessage = prompt.toUserMessage();
AiMessage aiMessage = prompt.toAiMessage();
2.3、结构化提示词注解 @StructuredPrompt
@StructuredPrompt 将Map 传参升级为Java 对象传参,把这个类的实例传给 StructuredPromptProcessor.toPrompt() 方法时,框架会自动利用反射机制,将这个结构化的数据对象转换成自然语言形式的提示词。
使用 @StructuredPrompt 注解将一个类标记为结构化提示词模板,每一个字段可以用 @Description() 注解来解释字段的含义
@StructuredPrompt("你是一个经验丰富的{{role}},请将以下内容翻译为{{targetLanguage}}:\n{{translateText}}")
public class TranslationPrompt {
private String role;
private String targetLanguage;
private String translateText;
public TranslationPrompt(String role, String targetLanguage, String translateText) {
this.role = role;
this.targetLanguage = targetLanguage;
this.translateText = translateText;
}
}
根据提示词模板构造出提示词文本:
TranslationPrompt translationPrompt = new TranslationPrompt("翻译家", "汉语", "Good health is over wealth");
Prompt prompt = StructuredPromptProcessor.toPrompt(translationPrompt);
String promptText = prompt.text();
System.out.println(promptText);
/**
* 输出:
* 你是一个经验丰富的翻译家,请将以下内容翻译为汉语:
* Good health is over wealth
*/
@StructuredPrompt("你是一个经验丰富的{{role}},请将以下内容翻译为{{targetLanguage}}:\n{{translateText}}") 中的占位符最终会调用 TranslationPrompt 类中字段值的 toString() 方法,如果类中包含集合、数组、对象,那么需要重写该类的 toString() 方法。
除了手动调用 StructuredPromptProcessor.toPrompt() 方法,还可以直接作为 AI Service 方法参数类型使用 @StructuredPrompt 标记的类:
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel")
public interface Assistant {
String chat(TranslationPrompt prompt); // 框架自动将TranslationPrompt对象转为提示词
}
三、多模态输入
模态(Modality)指信息存在或被感知的一种形式,例如常见的有:文本(Text)、图像(Image)、音频(Audio)、视频(Video)、传感器数据(Sensor Data)、3D点云(Point Cloud)、脑电信号(EEG)、深度图(Depth Map)等。
多模态输入(Multimodal Input)特指在大模型(如多模态大语言模型,MLLM)的推理过程中,能够同时接收、编码并理解多种模态的数据形式,并且作为前置指令或上下文素材。
多模态支持的广度取决于使用的模型提供商和具体模型,对于不支持的模型,传入 ImageContent 会导致 API 报错,因此务必确认所用模型的文档。
而在 LangChain4j 中,将多模态数据构造成提示词的核心是 UserMessage + Content,其中 Content 常见的子类有:TextContent、ImageContent、AudioContent、VideoContent。
3.1、同时包含文本和图片
最简单的情况:一张网络图片 + 一个文本提问。
UserMessage userMessage = UserMessage.from(
TextContent.from("请描述这张图片的内容。"),
ImageContent.from("https://img.peapix.com/55da8d0d85b4456d902449f0cfaf2c2b_UHD.jpg")
);
// 多张图片
UserMessage userMessage = UserMessage.from(
TextContent.from("这两张图分别是什么动物?请用 JSON 格式回答。"),
ImageContent.from("https://img.peapix.com/d1fcb066dfcc43cdba3bbdde61b9944c_UHD.jpg"),
ImageContent.from("https://img.peapix.com/d073970bf88a437faa59b2d0c5b7c0cd_UHD.jpg")
);
也可以使用 UserMessage.userMessage(Content... contents) 方法,效果相同。
3.2、使用 Base64 编码图片
当图片以 Base64 字符串形式存在时,需要指定 MIME 类型:
String base64Image = "iVBORw0KGgoAAAANSUhEUgAA...";
ImageContent image = ImageContent.from(base64Image, "image/png");
UserMessage userMessage = UserMessage.from(
TextContent.from("这张截图中有什么错误?"),
image
);
3.3、从本地文件或输入流构造图片
LangChain4j 没有直接提供从 File 或 InputStream 创建 ImageContent 的工厂方法,但你可以很轻松地自行转换:
public class ImageUtils {
public static ImageContent fromFile(Path filePath) throws IOException {
byte[] bytes = Files.readAllBytes(filePath);
String mimeType = Files.probeContentType(filePath); // 如 image/jpeg
String base64 = Base64.getEncoder().encodeToString(bytes);
return ImageContent.from(base64, mimeType);
}
}
// 使用
UserMessage userMessage = UserMessage.from(
TextContent.from("提取这张图片中的文字"),
ImageUtils.fromFile("path/image.png")
);
注意:确保 MIME 类型与图片实际格式一致,否则模型可能拒绝处理。
3.4、与SystemMessage组合
完整的请求通常包含一个设定全局规则的 SystemMessage,再拼接上多模态的 UserMessage。下面以 Qwen 模型为例:
OpenAiChatModel qwenChatModel = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(System.getenv("ApiKey_AliBaiLian"))
.modelName("qwen3-vl-235b-a22b-thinking")
.build();
SystemMessage systemMsg = SystemMessage.from(
"你是一个经验丰富的图片分析助手。对于每张图片,请用中文给出简要描述。"
);
UserMessage userMsg = UserMessage.from(
TextContent.from("请描述下面这张图片的内容。"), ImageContent.from("https://img.peapix.com/d073970bf88a437faa59b2d0c5b7c0cd_UHD.jpg")
);
ChatResponse chatResponse = qwenChatModel.chat(systemMsg, userMsg);
System.out.println(chatResponse.aiMessage().text());
参考资料:
官方英文文档:Get Started | LangChain4j,API 文档:Overview (LangChain4j)
非官方中文文档:快速开始 | LangChain4j 中文文档
评论 (0)