Langchain4j - 基础工程的构建以及两套API测试案例

Langchain4j 简介

生态定位

在 Java 庞大的企业级生态中,LangChain4j 是 “Java 领域大模型应用开发的第一基础设施”,它就是 Java 版的 LangChain(Python)。在 Java 开发中,如果作一个类比,它就类似于 JDBC 或 Spring Data。大模型厂商(OpenAI、智谱、阿里、DeepSeek、以及本地 Ollama 下的各类大模型)就像各种不同的关系型数据库(MySQL、Oracle、PostgreSQL),其原生 API 各不相同。LangChain4j 抹平了这些差异,提供了一套统一的 Java 抽象接口。它不自己做模型,它只做大模型与企业级 Java 架构之间的 “粘合剂” 与 “立交桥”。

简而言之,LangChain4j 的出现,让 Java 工程师不需要再去卷底层的 Python 算法,而是用最熟悉的面向接口编程、依赖注入、POJO 映射这套标准组合拳,快速把 AI 能力组装进现有的微服务体系中。


开发套路

  • Low-Level API:类似于 JDBC,适用于动态网关与路由。直接操控原始报文、Token 计数,适合做企业级 LLM 统一网关、多模型动态降级熔断。
  • High-Level API:类似于 Mybatis 或 Spring Data,日常业务主要用它开发。通过编写 Java 接口加注解,把 Prompt 隐藏在方法之上,像调用普通 RPC 服务一样调用大模型。
  • Streaming API:利用 WebFlux 或 WebSocket 配合流式接口,实现前端类似 ChatGPT 那样逐字蹦出(SSE)的打字机流式体验。


常见落地应用

在实际生产中,目前阶段围绕 LangChain4j 的玩法主要集中在以下三个方向:

  • 智能客服与知识库(RAG 架构):这是目前企业落地最多的套路。利用 LangChain4j 的 Embedding 模块,把企业的本地 PDF/Word 文档切片、向量化,存入向量数据库(如 Milvus, Pgvector)。用户提问时,LangChain4j 先去向量库捞出相关片段,再把片段和问题一起喂给大模型,让大模型 “看书答题”。
  • 结构化数据提取与数据看板(Structured Outputs):利用 LangChain4j 的 AiServices 强类型绑定能力。你可以定义一个 Java POJO(如 Invoice),让大模型去阅读一封杂乱的求职邮件或发票扫描文本,大模型会自动精准抽取出 JSON 并直接映射为 Java 对象。
  • 自动化代理工具箱(Agent + Tool Calling):将 Java 本地的 Service 方法通过 @Tool 注解暴露给 LangChain4j。大模型在理解用户意图后,会主动决定去调用你的 Java 方法(例如 checkInventory(String itemId)),拿到结果后,再组织语言回复给用户。让大模型拥有了执行具体业务代码的能力。


父项目 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<spring-boot.version>3.5.16</spring-boot.version>
<spring-ai.version>1.1.8</spring-ai.version>
<langchain4j.version>1.17.2</langchain4j.version>
<langchain4j-community.version>1.17.2-beta27</langchain4j-community.version>
<langgraph4j.version>1.8.20</langgraph4j.version>
<lombok.version>1.18.32</lombok.version>

<spring-boot-maven-plugin.version>3.4.5</spring-boot-maven-plugin.version>
<spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
<spring-ai-alibaba-extensions.version>1.1.2.1</spring-ai-alibaba-extensions.version>
<maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- spring ai -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- spring ai alibaba -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${spring-ai-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-extensions-bom</artifactId>
<version>${spring-ai-alibaba-extensions.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- langchain4j -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>${langchain4j-community.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- langgraph4j -->
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-bom</artifactId>
<version>${langgraph4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<excludeDevtools>true</excludeDevtools>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<repositories>
<repository>
<id>aliyun-maven</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun-plugin</id>
<name>aliyun plugin</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>


原始低阶和高阶API的使用

依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<!-- 大模型通用模型API -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!-- Langchain4j 高阶 API -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>

<!--日志支持-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>


原始低阶API示例

测试调用本地 ollama 安装的模型

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void test01() {
OpenAiChatModel model = OpenAiChatModel.builder()
// 如果是原生 Ollama 驱动只需要配置到端口,它底层自动拼接 /api/chat。apiKey 这咯可以随便给一个。
.baseUrl("http://localhost:11434/v1") // Ollama OpenAI 的兼容端点
.apiKey("xxx")
.modelName("qwen3:4b")
.timeout(Duration.ofSeconds(300))
.logRequests(true)
.logResponses(true)
.build();
String response = model.chat("介绍你自己,100字以内");
log.info("LLM response: {}", response);
}

测试调用本地 ollama 安装的模型(流式响应)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void test02() throws IOException {
// 模型参数配置参考:https://docs.langchain4j.dev/tutorials/model-parameters
OpenAiStreamingChatModel model = OpenAiStreamingChatModel.builder()
.baseUrl("http://localhost:11434/v1")
.apiKey("xxx")
.modelName("qwen3:4b")
.timeout(Duration.ofSeconds(300))
.logRequests(true)
.logResponses(true)
.build();

model.chat("介绍你自己,100字以内", new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String token) {
System.out.print(token);
}

@Override
public void onCompleteResponse(ChatResponse chatResponse) {
System.out.println(chatResponse.aiMessage().text());
}

@Override
public void onError(Throwable ex) {
System.err.println(ex.getMessage());
}
});

System.in.read(); // 保持main线程
}

测试调用远程千问模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void test03() {
String apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1";
String apiKey = System.getenv("QWEN_API_KEY"); //// 保密
String modelName = "qwen3.7-plus";
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl(apiUrl) // 阿里云的 OpenAI 兼容网关
.apiKey(apiKey)
.modelName(modelName)
.logRequests(true)
.logResponses(true)
.build();

ChatRequest request = ChatRequest.builder()
.messages(List.of(
SystemMessage.from("你是一个资深 Java 架构师,回答要简洁"),
UserMessage.from("什么是 LangChain4j?"),
AiMessage.from("LangChain4j 是 Java 生态的 LLM 应用框架"),
UserMessage.from("它和 Spring AI 有什么区别?")
))
.build();
ChatResponse response = model.chat(request);
log.info("QWEN response: {}", response);
}


原始高阶API示例

定义接口:

1
2
3
public interface MyChatService {
String chat(String question);
}

测试调用大模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void test01() {
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(System.getenv("QWEN_API_KEY"))
.modelName("qwen-plus")
.logRequests(true)
.logResponses(true)
.build();

// https://docs.langchain4j.dev/tutorials/ai-services
MyChatService chatService = AiServices.builder(MyChatService.class)
.chatModel(chatModel) ////
.build();

String answer = chatService.chat("介绍 LangChain4j,回答请在100字之内");
log.info("AI: {}", answer);
}


低阶方式整合spring

依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<!-- langchain4j 低阶 API 整合 spring -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>

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


启动项

1
2
3
4
5
6
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}


配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
import dev.langchain4j.model.openai.OpenAiChatModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.time.Duration;
import java.util.UUID;

@Slf4j
@Configuration
public class LLMConfig {

/**
* 所有大模型聊天的总的父接口
* 还有个兄弟接口 StreamingChatModel
*/
@Bean("local")
@Primary
public ChatModel chatModelLocalQwen() {
return OpenAiChatModel.builder()
.baseUrl("http://localhost:11434/v1")
.apiKey("api_key_xxx")
.modelName("qwen3:4b")
.timeout(Duration.ofSeconds(300)) // 超时参数
.maxRetries(3) // 最大重试次数,默认为3
.logRequests(true)
.logResponses(true)
.build();
}

@Bean("qwen") // 带监听器
public ChatModel chatModelQwen() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("QWEN_API_KEY"))
.modelName("qwen3.7-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.logRequests(true)
.logResponses(true)
// 可以增加多个监听器 https://docs.langchain4j.dev/tutorials/observability
.listeners(new ChatModelListener() {
@Override
public void onRequest(ChatModelRequestContext requestContext) {
String traceId = UUID.randomUUID().toString();
requestContext.attributes().put("traceId", traceId);
log.info("[{}] 请求参数 requestContext:{}", traceId, requestContext);
}

@Override
public void onResponse(ChatModelResponseContext responseContext) {
Object traceId = responseContext.attributes().get("traceId");
log.info("[{}] 响应结果 response text:{}", traceId, responseContext.chatResponse().aiMessage().text());
}

@Override
public void onError(ChatModelErrorContext errorContext) {
Object traceId = errorContext.attributes().get("traceId");
log.info("[{}] 出现异常 error content:{}", traceId, errorContext.error().getMessage());
}
})
.build();
}

@Bean("deepseek")
public ChatModel chatModelDeepseek() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("DEEPSEEK_API_KEY"))
.modelName("deepseek-v4-flash")
.baseUrl("https://api.deepseek.com")
.logRequests(true)
.logResponses(true)
.build();
}
}


业务测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RestController
@RequestMapping("/chat")
public class HelloController {

@Resource
@Qualifier("local")
private ChatModel localChatModel;

@Resource
@Qualifier("qwen")
private ChatModel qwenChatModel;

@Resource
@Qualifier("deepseek")
private ChatModel deepseekChatModel;


@RequestMapping("faq01")
public String faq01(@RequestParam(defaultValue = "10个字以内介绍你自己") String userContent) {
return localChatModel.chat(userContent);
}

@RequestMapping("faq02")
public String faq02(@RequestParam(defaultValue = "10个字以内介绍你自己") String userContent) {
ChatResponse response = qwenChatModel.chat(UserMessage.from(userContent));
return "响应结果:" + response.aiMessage().text() + "\n\ntoken 的用量:" + response.tokenUsage();
}

@RequestMapping("faq03")
public String faq03(@RequestParam(defaultValue = "10个字以内介绍你自己") String userContent) {
return deepseekChatModel.chat(userContent);
}
}


高阶方式整合 spring

依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependencies>
<!-- langchain4j 低阶 API 整合 spring -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>

<!-- langchain4j 高阶 API 整合 spring(比如 @AiService)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
</dependency>

<!--流式响应依赖-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>

<!-- spring boot webflux -->
<!-- 因为要测试流式响应,这里索性就直接引入 spring boot webflux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>


启动项

1
2
3
4
5
6
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}


配置文件

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8080

# https://docs.langchain4j.dev/tutorials/spring-boot-integration
langchain4j:
open-ai:
# 向容器中注入了一个 chatModel 对象
chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${QWEN_API_KEY}
model-name: qwen3.7-plus
log-requests: true
log-responses: true
# 向容器中注入了一个 openAiStreamingChatModel 对象
streaming-chat-model:
base-url: http://localhost:11434/v1
api-key: api-key-xxx
model-name: "qwen3:4b"


使用原始 ChatModel

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class ChatController {

private ChatModel chatModel;
public ChatController(ChatModel chatModel) {
this.chatModel = chatModel;
}

@GetMapping("/chat")
public String model(@RequestParam(value = "message", defaultValue = "Hello") String message) {
return chatModel.chat(message);
}
}


原始 StreamingChatModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
public class StreamingChatController {

@Resource
private StreamingChatModel streamingChatModel;

@GetMapping(value = "/chat5", produces = MediaType.TEXT_PLAIN_VALUE)
public Flux<String> model(@RequestParam(value = "message", defaultValue = "Hello") String message) {
return Flux.<String>create(fluxSink -> {
streamingChatModel.chat(message, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String token) {
if (!fluxSink.isCancelled()) {
fluxSink.next(token);
}
}

@Override
public void onCompleteResponse(ChatResponse chatResponse) {
fluxSink.complete();
}

@Override
public void onError(Throwable throwable) {
fluxSink.error(throwable);
}
});
});
}
}


使用 @AiService

声明一个 AiService 代理类

1
2
3
4
5
6
7
8
9
10
11
12
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;

/**
* 声明一个 AI Service 接口:参考 https://docs.langchain4j.dev/tutorials/spring-boot-integration#spring-boot-starter-for-declarative-ai-services
* @AiService(wiringMode = AiServiceWiringMode.EXPLICIT, chatModel = "qwen3.7-plus", streamingChatModel = "deepseek"),一般默认即可
*/
@AiService
public interface MyChatService2 {
String handleMyChat(String prompt);
Flux<String> handleMyStreamingChat(String prompt);
}

业务测试类 ChatController2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class ChatController2 {

@Resource
private MyChatService2 myChatService2;

@GetMapping("/chat21")
public String model(@RequestParam(value = "message", defaultValue = "Hello") String message) {
return myChatService2.handleMyChat(message);
}

@GetMapping(value = "/chat22", produces = MediaType.TEXT_PLAIN_VALUE)
public Flux<String> model22(@RequestParam(value = "message", defaultValue = "Hello") String message) {
return myChatService2.handleMyStreamingChat(message);
}
}

以上 MyChatService2 代理类除了使用高阶的 @AiService 创建,也可以使用如下方式进行创建,效果是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
public class LLMConfig {

// dev.langchain4j.service.IllegalConfigurationException: Conflict: multiple beans
// 因为我们在 application.yml 中已经配置了一个 streaming-chat-model(bean 名称为 openAiStreamingChatModel)
// LangChain4j 在容器启动时,使用的是它自己的一套名为 AiServicesRegisteringBeanFactoryPostProcessor 的后置处理器。
// 它会检查整个 Spring 容器,一旦发现类型为 StreamingChatModel 的 Bean 数量超过 1 个,
// 为了防止高阶声明式服务“指鹿为马”连错大模型,它会直接选择拒绝启动。

/*@Bean("localStreamingChatModel")
public StreamingChatModel localStreamingChatModel() {
return OpenAiStreamingChatModel.builder()
.baseUrl("http://localhost:11434/v1")
.apiKey("api_key_xxx")
.modelName("qwen3:4b")
.timeout(Duration.ofSeconds(300))
.build();
}*/

@Bean
public MyChatService2 myChatService3(StreamingChatModel localStreamingChatModel) {
return AiServices.create(MyChatService2.class, localStreamingChatModel);
}
}