第2326篇:LangChain4j 1.0新特性解析——架构变化和迁移要点
第2326篇:LangChain4j 1.0新特性解析——架构变化和迁移要点
适读人群:使用LangChain4j构建Java AI应用的工程师,或对比Spring AI与LangChain4j选型的开发者 | 阅读时长:约16分钟 | 核心价值:掌握LangChain4j 1.0核心架构变化,了解与Spring AI的定位差异
有个读者私信我,说他们团队在Spring AI和LangChain4j之间选了后者,理由很简单:LangChain4j的AI服务接口注解方式太方便了,几行代码就能定义一个带类型安全的AI调用接口,测试也方便。
这个选择没错。两个框架的定位有细微差别:Spring AI更像是Spring生态的原生扩展,适合已经深度使用Spring Boot的团队;LangChain4j则更独立,对Python LangChain社区的用户更友好,迁移概念更直接。
LangChain4j在2024年底到2025年初经历了一次较大的架构重组,1.0版本正式发布。这篇文章整理1.0的核心变化,以及从0.x迁移需要注意的地方。
最核心的变化:模块拆分和依赖精简
0.x版本的LangChain4j是个"大单体"——你引入一个依赖,所有功能都进来了,包括大量你用不到的LLM适配器和工具。
1.0把依赖拆得更细了:
<!-- 1.0的依赖方式 -->
<dependencies>
<!-- 核心模块(必须) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Spring Boot集成(可选) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- OpenAI适配器(按需引入) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 或者使用Spring Boot Starter方式 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- PGVector向量存储(按需) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>这个变化对大项目影响很大——以前编译出来的jar因为引入了所有适配器,体积虚胖;1.0按需引入后,jar瘦了不少。
AI服务接口:LangChain4j最独特的能力
这是LangChain4j相比Spring AI最有特色的地方——通过接口注解定义AI服务,框架自动生成实现:
// 定义AI服务接口
@AiService
interface TechAdvisor {
// 简单问答
String answer(String question);
// 带系统提示
@SystemMessage("你是一位专业的Java架构师,回答简洁精准,不超过200字")
String quickAnswer(String question);
// 结构化输出(1.0增强)
@SystemMessage("分析这段Java代码,返回JSON格式的分析结果")
CodeAnalysis analyzeCode(@UserMessage String code);
// 带记忆的对话(需要配置MemoryId)
@SystemMessage("你是用户的专属技术助手,记住用户的背景和偏好")
String chat(@MemoryId String userId, @UserMessage String message);
}
// 结构化输出的数据类
record CodeAnalysis(
int qualityScore,
List<String> issues,
List<String> suggestions,
String complexity // LOW / MEDIUM / HIGH
) {}// Spring Boot中的配置(1.0推荐方式)
@Configuration
public class AiServiceConfig {
@Bean
public TechAdvisor techAdvisor(ChatLanguageModel chatModel, ChatMemoryStore memoryStore) {
return AiServices.builder(TechAdvisor.class)
.chatLanguageModel(chatModel)
.chatMemory(MessageWindowChatMemory.builder()
.maxMessages(20)
.chatMemoryStore(memoryStore)
.build())
.build();
}
}1.0对结构化输出的支持增强了,CodeAnalysis这种record类型可以直接作为返回值,框架会自动要求LLM以JSON格式返回并进行反序列化:
// 测试结构化输出
@SpringBootTest
class TechAdvisorTest {
@Autowired
private TechAdvisor advisor;
@Test
void testCodeAnalysis() {
String code = """
public String buildUrl(String host, String path) {
return "http://" + host + "/" + path;
}
""";
CodeAnalysis result = advisor.analyzeCode(code);
assertThat(result.qualityScore()).isBetween(0, 100);
assertThat(result.issues()).isNotEmpty();
// 这段代码应该被发现的问题:没有输入校验、没有处理path前缀斜杠等
System.out.println("问题:" + result.issues());
System.out.println("建议:" + result.suggestions());
}
}工具调用(Tools)的1.0变化
0.x里工具调用的注解是@Tool,这个在1.0里没有改变,但工具的注册方式有调整:
// 工具类定义(0.x和1.0写法一致)
public class DatabaseTools {
private final UserRepository userRepository;
public DatabaseTools(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Tool("根据用户ID查询用户信息,返回用户名、邮箱和注册时间")
public UserInfo getUserById(
@P("用户ID,整数类型") long userId) {
return userRepository.findById(userId)
.map(u -> new UserInfo(u.getName(), u.getEmail(), u.getCreatedAt()))
.orElseThrow(() -> new RuntimeException("用户不存在: " + userId));
}
@Tool("搜索用户,支持按用户名模糊搜索,返回匹配的用户列表")
public List<UserInfo> searchUsers(
@P("搜索关键词") String keyword,
@P("返回数量限制,默认10") int limit) {
return userRepository.findByNameContaining(keyword, PageRequest.of(0, limit))
.stream()
.map(u -> new UserInfo(u.getName(), u.getEmail(), u.getCreatedAt()))
.toList();
}
public record UserInfo(String name, String email, String createdAt) {}
}// 1.0推荐:在AiServices.builder中注册工具
@Bean
public AssistantService assistantService(
ChatLanguageModel chatModel,
DatabaseTools dbTools,
FileTools fileTools) {
return AiServices.builder(AssistantService.class)
.chatLanguageModel(chatModel)
.tools(dbTools, fileTools) // 1.0:支持多个工具实例
.build();
}1.0里一个重要的工具调试增强——可以通过回调监听工具调用过程:
@Bean
public AssistantService assistantService(ChatLanguageModel chatModel, DatabaseTools dbTools) {
return AiServices.builder(AssistantService.class)
.chatLanguageModel(chatModel)
.tools(dbTools)
// 1.0新增:工具调用监听
.toolExecutionExceptionHandler(exception -> {
log.error("工具调用异常:{}", exception.getMessage());
return "工具调用失败:" + exception.getMessage() + ",请换种方式尝试";
})
.build();
}RAG流水线:1.0的重构
LangChain4j 1.0对RAG流水线做了比较彻底的重构,引入了RetrievalAugmentor的概念:
// 0.x的RAG方式(直接在AiServices中指定retrievalAugmentor)
// 1.0:显式构建RetrievalAugmentor,更灵活
@Bean
public RetrievalAugmentor retrievalAugmentor(EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) {
// 内容检索器
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.7)
.build();
// 构建完整的增强器
return DefaultRetrievalAugmentor.builder()
.contentRetriever(retriever)
// 1.0新增:内容注入策略
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("""
参考以下资料回答问题:
{{contents}}
用户问题:{{userMessage}}
如果资料不足以回答,请明确说明。
"""))
.build())
.build();
}
@Bean
public KnowledgeBaseAdvisor knowledgeBaseAdvisor(RetrievalAugmentor retrievalAugmentor,
ChatLanguageModel chatModel) {
return AiServices.builder(KnowledgeBaseAdvisor.class)
.chatLanguageModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.build();
}流式输出:1.0的API
LangChain4j 1.0的流式输出接口设计比0.x更清晰:
// 在AI服务接口中声明流式方法
@AiService
interface StreamingChatService {
// 流式输出(返回TokenStream)
TokenStream chatStream(@UserMessage String message);
}
// 使用
@Service
@RequiredArgsConstructor
public class ChatController {
private final StreamingChatService streamingService;
// Spring WebFlux的SSE接口
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(@RequestParam String message) {
return Flux.create(sink -> {
streamingService.chatStream(message)
.onNext(token -> sink.next(token))
.onComplete(response -> sink.complete())
.onError(error -> sink.error(error))
.start();
});
}
}从0.x迁移的关键清单
最常见的迁移问题:
- ChatMemory接口变化:0.x的
MessageWindowChatMemory构造函数签名在1.0变成了Builder模式 - EmbeddingStore泛型参数:1.0对泛型参数更严格,
EmbeddingStore<TextSegment>不能再简写为EmbeddingStore - Document和TextSegment的区别:1.0更明确区分了文档(Document)和分块后的文本段(TextSegment),不要混用
// 正确的文档入库流程(1.0)
@Service
@RequiredArgsConstructor
public class DocumentIngestionService {
private final EmbeddingModel embeddingModel;
private final EmbeddingStore<TextSegment> embeddingStore;
public void ingest(String content, String source) {
// 1. 创建Document
Document document = Document.from(content, Metadata.from("source", source));
// 2. 分割为TextSegment
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
List<TextSegment> segments = splitter.split(document);
// 3. 生成Embedding
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
// 4. 存入向量数据库
embeddingStore.addAll(embeddings, segments);
log.info("文档入库完成:{},共{}个分块", source, segments.size());
}
}和Spring AI的选型参考
这个问题很多人问,简单说几个选型维度:
选LangChain4j的场景:
- 团队有Python LangChain背景,概念迁移更顺畅
- 偏好接口注解方式定义AI服务,代码更简洁
- 不深度依赖Spring生态(比如用Micronaut或纯JavaSE)
选Spring AI的场景:
- 团队已有Spring Boot微服务体系,生态集成优先
- 需要和Spring Security、Spring Cloud等深度集成
- 团队更熟悉Spring的编程模型(Bean、Advisor、AutoConfiguration)
两者在功能覆盖上已经趋同,选哪个主要看团队背景和已有技术栈。不建议在一个项目里混用——两套框架的抽象模型不同,混用会让代码很难维护。
