7527 字
38 分钟
LangChain4j框架入门项目学习

LangChain4j框架入门项目学习#

1. 初始化项目#

创建项目 使用Java21 JDK21(要使用LangChain4j JDK版本不能小于17)

使用local配置文件来防止推送到云端时信息泄露#

创建application.yml和application-local.yml

将application-local.yml加到.gitignore中

并指定actice为local

application.yml:

spring:
application:
name: ai-code-helper
profiles:
active: local

引入Spring Boot LangChain4j的依赖(Qwen)#

这里使用和教程版本一样的1.1.0-beta7版本

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
<version>${latest version here}</version>
</dependency>

这个是langchain4j + 阿里模型 + Spring boot的依赖包

配置大模型#

两种方式

  1. 在yml文件中配置:(配置后springboot会自动创建自动注入)
langchain4j:
community:
dashscope:
chat-model:
api-key: {这里写api key}
model-name: qwen-plus
  1. 通过构造器链式调手动用来创建
ChatModel qwenModel = QwenChatModel.builder()
.apiKey("You API key here")
.modelName("qwen-plus")
.enableSearch(true)
.temperature(0.7)
.maxTokens(4096)
.stops(List.of("Hello"))
.build();

基本使用#

简单对话#

  1. 注入Ai服务(chatModel)
  2. 构建UserMessage 然后传给ChatModel
@Service
@Slf4j
public class AiCodeHelper {
// 注入ai模型的bean类
@Resource
private ChatModel qwenChatModel;
// 基本的对话功能
public String chat(String message){
UserMessage userMessage = UserMessage.from(message);
ChatResponse chatResponse = qwenChatModel.chat(userMessage);
AiMessage aiMessage = chatResponse.aiMessage();
log.info("AI回复:"+aiMessage.toString());
return aiMessage.text();
}
}

下面是测试类:

@SpringBootTest
class AiCodeHelperApplicationTests {
@Resource
private AiCodeHelper aiCodeHelper;
@Test
void chat() {
aiCodeHelper.chat("你好 你是谁?");
}
}

在使用测试用例的时候出现了一个bug,单独运行一个测试用例会报错说没有发现单元测试,但是运行整个测试类时可以成功,发现使用了Spring Boot4以上的版本就会出现这个问题,为了兼容性先换成了4以下版本

多模态#

UserMessage不止能传入文本信息

// 自定义构建userMessage
public String chat(UserMessage userMessage){
ChatResponse chatResponse = qwenChatModel.chat(userMessage);
AiMessage aiMessage = chatResponse.aiMessage();
log.info("AI回复:"+aiMessage.toString());
return aiMessage.text();
}

测试方法:

@Test
void testChatUserMessage() {
UserMessage userMessage = UserMessage.from(
TextContent.from("请描述图片内容"),
ImageContent.from("https://tuchuang-1353351309.cos.ap-guangzhou.myqcloud.com/picture/20260424210247435.png")
);
aiCodeHelper.chat(userMessage);
}

测试的是下面这张图片: image.png|235

但是由于这个框架对多模态兼容性并不是特别好,目前有很多问题 所以这里只是演示下代码,具体运行的话,由于qwen-plus不支持多模态 只会回复说目前读不了图片。

系统提示词#

系统提示词是设置 ⁡⁡⁡⁡⁡⁡⁡AI 模型行为规则和角色定位的隐藏⁠⁠⁠⁠⁠⁠⁠指令,用户通常不能直接看到。系统 ⁠⁠⁠⁠⁠⁠⁠Prompt 相当于给 AI 设定人格和能力边界,也就是告诉 AI ⁡⁡⁡⁡⁡⁡⁡“你是谁?你能做什么?”。

这里根据需求写一段系统提示词:

你是编程领域的小助手,帮助用户解答编程学习和求职面试相关的问题,并给出建议。重点关注 4 个方向:
1. 规划清晰的编程学习路线
2. 提供项目学习建议
3. 给出程序员求职全流程指南(比如简历优化、投递技巧)
4. 分享高频面试题和面试技巧
请用简洁易懂的语言回答,助力用户高效学习与求职。

接下来新建SystemMessage对象,然后传这个对象给chat方法即可。

AI 服务 - AI Service#

在学习更多特性前⁡⁡⁡⁡⁡⁡⁡,需要了解 LangChain⁠⁠⁠⁠⁠⁠⁠4j 最重要的开发模式 —— A⁠⁠⁠⁠⁠⁠⁠I Service,它提供了很多高层抽象的、用起来更方便的 API⁡⁡⁡⁡⁡⁡⁡,可以把 AI 应用当做服务来开发。

使用 AI Service#

首先引入 langchain4j 依赖:

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.1.0</version>
</dependency>

然后创建一个编程助手 AI Service 服务,采用声明式开发方法,编写一个对话方法,然后可以直接通过 @SystemMessage 注解定义系统提示词。

public interface AiCodeHelperService {
@SystemMessage("你是一位编程小助手")
String chat(String userMessage);
}

不过由于我们提示词较长,写到注解里很不优雅,所以单独在 resources 目录下新建文件 system-prompt.txt 来存储系统提示词。

@SystemMessage 注解支持从文件中读取系统提示词:

public interface AiCodeHelperService {
@SystemMessage(fromResource = "system-prompt.txt")
String chat(String userMessage);
}

然后我们需要⁡⁡⁡⁡编写工厂类,用于创建 ⁠⁠⁠⁠AI Service:⁠⁠⁠⁠                      ⁡⁡⁡⁡          

@Configuration
public class AiCodeHelperServiceFactory {
@Resource
private ChatModel qwenChatModel;
@Bean
public AiCodeHelperService aiCodeHelperService() {
return AiServices.create(AiCodeHelperService.class, qwenChatModel);
}
}

调用 AiServices.create 方法就可以创建出 AI Service 的实现类了,背后的原理是利用 Java 反射机制创建了一个实现接口的代理对象,代理对象负责输入和输出的转换,比如把 String 类型的用户消息参数转为 UserMessage 类型并调用 ChatModel,再将 AI 返回的 AiMessage 类型转换为 String 类型作为返回值。

但我们不用⁡⁡⁡⁡⁡⁡⁡关心这么多⁠⁠⁠,⁠直⁠接⁠写⁠接⁠⁠⁠口和⁠注解⁠来开发⁠⁠就好。

Ai服务测试#

/**
* 测试AI服务是否可用 * * @author ShineAcZ
* @date 2026/4/25
*/@SpringBootTest
class AiCodeHelperServiceTest {
@Resource
public AiCodeHelperService aiCodeHelperService;
@Test
void testChat() {
String result1 = aiCodeHelperService.chat("Java中的stream流如何使用?");
System.out.println(result1);
}
}

Spring Boot 项目中使用#

还能直接引入相关依赖:

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>

直接给AiService加上@AiService注解 就会自动创建出服务实例。

@AiService
public interface AiCodeHelperService {
@SystemMessage(fromResource = "system-prompt.txt")
String chat(String userMessage);
}

这种方式虽然更方⁡⁡⁡⁡⁡⁡⁡便了,但是缺少了自主构建的灵活性⁠⁠⁠⁠⁠⁠⁠(可以自由设置很多参数),所以⁠还是⁠建议采用自主构建。之后的功能特性,我们也会基于这种 AI S⁡⁡⁡⁡⁡⁡⁡ervice 开发模式来实现。

会话记忆 - ChatMemory#

会话记忆是⁡指让⁡⁡⁡⁡⁡⁡ AI 能够记⁠住用户之前的对话⁠⁠⁠⁠⁠⁠内⁠容,并保持上下文连贯性,这⁠⁠⁠⁠⁠⁠是实现 A⁡I 应用的核心特性。                ⁡⁡⁡⁡⁡⁡                

怎么实现对话⁡⁡⁡⁡⁡⁡⁡记忆?最传统的方式是自⁠⁠⁠⁠⁠⁠⁠己维护消息列表,不仅要⁠⁠⁠⁠⁠⁠⁠手动添加消息,消息多了还要考虑淘汰、不同用户⁡⁡⁡⁡⁡⁡⁡的消息还要隔离,想想都头疼!

传统方式:

// 自己实现会话记忆
Map<String, List<Message>> conversationHistory = new HashMap<>();
public String chat(String message, String userId) {
// 获取用户历史记录
List<Message> history = conversationHistory.getOrDefault(userId, new ArrayList<>());
// 添加用户新消息
Message userMessage = new Message("user", message);
history.add(userMessage);
// 构建完整历史上下文
StringBuilder contextBuilder = new StringBuilder();
for (Message msg : history) {
contextBuilder.append(msg.getRole()).append(": ").append(msg.getContent()).append("\n");
}
// 调用 AI API
String response = callAiApi(contextBuilder.toString());
// 保存 AI 回复到历史
Message aiMessage = new Message("assistant", response);
history.add(aiMessage);
conversationHistory.put(userId, history);
return response;
}

使用会话记忆#

LangChain4j 为我们提供了开箱即用的 MessageWindowChatMemory 会话记忆,最多保存 N 条消息,多余的会自动淘汰。创建会话记忆后,在构造 AI Service 设置 chatMemory:

@Configuration
public class AiCodeHelperServiceFactory {
@Resource
private ChatModel qwenChatModel;
@Bean
public AiCodeHelperService aiCodeHelperService() {
// 会话记忆
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
.chatModel(qwenChatModel)
.chatMemory(chatMemory)
.build();
return aiCodeHelperService;
}
}

编写单元测试,测试会话记忆是否生效:

@Test
void chatWithMemory() {
String result = aiCodeHelperService.chat("你好,我是程序员鱼皮");
System.out.println(result);
result = aiCodeHelperService.chat("你好,我是谁来着?");
System.out.println(result);
}

进阶用法#

会话记忆默认是存储在内存的,重启后会丢失,可以通过自定义 ChatMemoryStore 接口的实现类,将消息保存到 MySQL 等其他数据源中。

image.png

如果有多个用户,⁡⁡⁡⁡⁡⁡⁡希望每个用户之间的消息隔离,可⁠⁠⁠⁠⁠⁠⁠以通过给对话方法增加 memo⁠⁠⁠⁠⁠⁠⁠ryId 参数和注解,在调用对话时传入 memoryId 即⁡⁡⁡⁡⁡⁡⁡可(类似聊天室的房间号):

String chat(@MemoryId int memoryId, @UserMessage String userMessage);

构造 AI Service 时,可以通过 chatMemoryProvider 来指定 每个 memoryId 单独创建会话记忆

// 构造 AI Service
AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
.chatModel(qwenChatModel)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
.build();

结构化输出#

结构化输出⁡⁡⁡⁡⁡⁡⁡是指将大模型返回的文⁠⁠⁠⁠⁠⁠⁠本输出转换为结构化的⁠⁠⁠⁠⁠⁠⁠数据格式,比如一段 JSON、一个对象、⁡⁡⁡⁡⁡⁡⁡或者是复杂的对象列表。

结构化输出⁡⁡有 3 种实现方⁠式⁠:      ⁠  ⁠              ⁡    ⁡      

  • 利用大模型的 JSON schema
  • 利用 Prompt + JSON Mode
  • 利用 Prompt

默认是 Prompt 模式,也就是在原本的用户提示词下 拼接一段内容 来指定大模型强制输出包含特定字段的 JSON 文本。

你是一个专业的信息提取助手。请从给定文本中提取人员信息,
并严格按照以下 JSON 格式返回结果:
{
"name": "人员姓名",
"age": 年龄数字,
"height": 身高(米),
"married": true/false,
"occupation": "职业"
}
重要规则:
1. 只返回 JSON 格式,不要添加任何解释
2. 如果信息不明确,使用 null
3. age 必须是数字,不是字符串
4. married 必须是布尔值

可以 阅读这篇文章 了解更多,不过我们开发时无需关心这些,只要修改对话方法的返回值,框架就会自动帮我们实现结构化输出,非常爽!

image.png

比如我们增加一个 让 AI 生成学习报告 的方法,AI 需要输出学习报告对象,包含名称和建议列表:

@SystemMessage(fromResource = "system-prompt.txt")
Report chatForReport(String userMessage);
// 学习报告
record Report(String name, List<String> suggestionList){}
@Test
void testChatForReport() {
String userMessage = "我有Java和C语言的编程基础,我想学习rust这门语言并达到能自己写出来一个带界面的项目的水平,请帮我指定学习报告.";
AiCodeHelperService.Report report = aiCodeHelperService.chatForReport(userMessage);
System.out.println(report.toString());
}

测试结果: image.png

如果你发现 AI⁡⁡⁡⁡⁡⁡⁡ 有时无法生成准确的 JSON,⁠⁠⁠⁠⁠⁠⁠那么可以采用 JSON Sche⁠⁠⁠⁠⁠⁠⁠ma 模式,直接在请求中约束 LLM 的输出格式。这是目前最可靠⁡⁡⁡⁡⁡⁡⁡、精确度最高的结构化输出实现。

ResponseFormat responseFormat = ResponseFormat.builder()
.type(JSON)
.jsonSchema(JsonSchema.builder()
.name("Person")
.rootElement(JsonObjectSchema.builder()
.addStringProperty("name")
.addIntegerProperty("age")
.addNumberProperty("height")
.addBooleanProperty("married")
.required("name", "age", "height", "married")
.build())
.build())
.build();
ChatRequest chatRequest = ChatRequest.builder()
.responseFormat(responseFormat)
.messages(userMessage)
.build();

检索增强生成 - RAG#

RAG(Retr⁡⁡⁡⁡⁡⁡⁡ieval-Augmented ⁠⁠⁠⁠⁠⁠⁠Generation,检索增强生⁠⁠⁠⁠⁠⁠⁠成)是一种结合信息检索技术和 AI 内容生成的混合架构,可以解决⁡⁡⁡⁡⁡⁡⁡大模型的知识时效性限制和幻觉问题。

简单来说,RAG 就像⁡⁡⁡⁡⁡⁡⁡给 AI 配了一个 “小抄本”,让 AI ⁠⁠⁠⁠⁠⁠⁠回答问题前先查一查特定的知识库来获取知识,⁠⁠⁠⁠⁠⁠⁠确保回答是基于真实资料而不是凭空想象。很多企业也基于 RAG 搭建了自己的智能客服,⁡⁡⁡⁡⁡⁡⁡可以用自己积累的领域知识回复用户。

RAG 的完整工作流程如下:

image.png

LangC⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡hain 提供了⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠ ⁠3 种 RAG⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠ 的⁠实现方式,我把它称为:极简版⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡、标准版⁡、进阶版。

极简版 RAG#

极简版适合快速查看效果,首先需要引入额外的依赖,里面包含了内置的离线 Embedding 模型,开箱即用:

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.1.0-beta7</version>
</dependency>

示例代码如下,使用内⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡置的文档加载器读取文档,然后利用内置的⁠⁠⁠⁠⁠⁠⁠⁠ Embe⁠⁠⁠dding 模型将文档转换成⁠⁠⁠⁠⁠⁠⁠⁠向量,并存储在内置的 ⁠⁠⁠Embedding 内存存储中,最后给 AI Serv⁡⁡⁡⁡⁡⁡⁡⁡ice 绑定默认的内容检索器。

// RAG
// 1. 加载文档
List<Document> documents = FileSystemDocumentLoader.loadDocuments("src/main/resources/docs");
// 2. 使用内置的 EmbeddingModel 转换文本为向量,然后存储到自动注入的内存 embeddingStore 中
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
// 构造 AI Service
AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
.chatModel(qwenChatModel)
.chatMemory(chatMemory)
// RAG:从内存 embeddingStore 中检索匹配的文本片段
.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
.build();

极简⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡版的特点是 “一切皆默认”,⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠实际开发中,为了更好的效果,⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠建议采用标准版或进阶版。

标准版 RAG#

下面来试试标准版 RAG 实现,为了更好地效果,我们需要:

  • 加载 Markdown 文档并按需切割
  • Markdown 文档补充文件名信息
  • 自定义 Embedding 模型
  • 自定义内容检索器

在 Spring Boot 配置文件中添加 Embedding 模型配置,使用阿里云提供的 text-embedding-v4 模型:

langchain4j:
community:
dashscope:
chat-model:
model-name: qwen-max
api-key: <You API Key here>
embedding-model:
model-name: text-embedding-v4
api-key: <You API Key here>

新建 rag.RagConfig,编写 RAG 相关的代码,执行 RAG 的初始流程并返回了一个定制的内容检索器 Bean:

/**
* Rag标准版配置类
* @author ShineAcZ
* @date 2026/4/30
*/
@Configuration
public class RagConfig {
@Resource
private EmbeddingModel qwenEmbeddingModel;
// 内存向量数据库(存文档切片)
@Resource
private EmbeddingStore<TextSegment> embeddingStore;
// 内容检索器
@Bean
public ContentRetriever contentRetriever(){
// ---RAG---
// 1. 加载文档 List<Document> documents = FileSystemDocumentLoader.loadDocuments("src/main/resources/docs");
// 2. 切割文档:按照段落切割,最大1000字符,最大重叠200字符(有多种不同的切割策略,但是这里用的是按段落切割)
DocumentByParagraphSplitter documentByParagraphSplitter = new DocumentByParagraphSplitter(1000, 200);
// 3. 定义文档加载器,把文档转化为向量并存到向量数据库中
EmbeddingStoreIngestor embeddingStoreIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(documentByParagraphSplitter)
// 给文档切片加上元数据信息(这里是加上了文件名在前面,可以给ai提供更多可用信息)
.textSegmentTransformer(textSegment -> TextSegment.from(textSegment.metadata().getString("file_name")+"\n"+textSegment.text(),textSegment.metadata()))
// 使用的向量模型
.embeddingModel(qwenEmbeddingModel)
.embeddingStore(embeddingStore)
.build();
// 加载文档
embeddingStoreIngestor.ingest(documents);
// 4. 自定义内容检索器
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(qwenEmbeddingModel)
// 最多五条
.maxResults(5)
// 最低相似度(低于这个值将查不出来,需要结果测试才能得知合适的值)
.minScore(0.75)
.build();
}
}

然后在构建 AI Service 时绑定内容检索器:

@Resource
private ContentRetriever contentRetriever;
@Bean
public AiCodeHelperService aiCodeHelperService() {
// 会话记忆
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
// 构造 AI Service
AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
.chatModel(qwenChatModel)
.chatMemory(chatMemory)
.contentRetriever(contentRetriever) // RAG 检索增强生成
.build();
return aiCodeHelperService;
}

编写单元测试:

@Test
void testChatRAG() {
String chat = aiCodeHelperService.chat("怎么学习Java?有哪些常见的面试题?");
System.out.println(chat);
}

然后接下来看RagConfig的构造过程,可以发现它将四个文档切了150多个切片,然后每个切片匹配一个向量。

查看AI服务类的上下文记忆可以看到,发过去的请求是包含了文档信息的: image.png

说明成功用上了RAG检索。

获取引用源文档#

在 LangChain⁡⁡⁡⁡⁡4j 中,实现这个功能很简单。在 AI Se⁠⁠⁠⁠⁠rvice 中新增方法,在原本的返回类型外封⁠⁠⁠⁠⁠装一层 Result 类,就可以获得封装后的结果,从中能够获取到 RAG 引用的源文档、⁡⁡⁡⁡⁡以及 Token 的消耗情况等等。

@SystemMessage(fromResource = "system-prompt.txt")
Result<String> chatWithRag(String userMessage);

修改单元测试,输出更多信息:

@Test
void chatWithRag() {
Result<String> result = aiCodeHelperService.chatWithRag("怎么学习 Java?有哪些常见面试题?");
String content = result.content();
List<Content> sources = result.sources();
System.out.println(content);
System.out.println(sources);
}

执行效果如图,获取到了引用的源文档信息: image.png

进阶版 RAG#

这就是一套标准的 R⁡⁡⁡⁡⁡AG 实现了,大多数时候,使用标准版就够⁠⁠⁠⁠⁠了。进阶版会更加灵活,额外支持查询转⁠⁠⁠⁠⁠换器、查询路由、内容聚合器、内容注入器等特性,将整个 RAG 的流程流水线化⁡⁡⁡⁡⁡(RAG pipeline)。

image.png

定义好 R⁡⁡⁡⁡⁡AG 流程后,最后⁠⁠⁠⁠⁠通过 Retrie⁠⁠⁠⁠⁠valAugmentor 提供给 A⁡⁡⁡⁡⁡I Service:

AiServices.builder(xxx.class)
...
.retrievalAugmentor(retrievalAugmentor)
.build();

此外,之前我们使用的是内存向量存储,每次启动都要重新加载文档、调用嵌入模型,比较耗时,所以实际开发中建议使用独立的存储,官方支持很多第三方存储,但是比较推荐使用的是 PG Vector,在原有关系库的基础上安装插件来支持向量存储,而且支持的特性很多。

image.png

工具调用 - Tools#

工具调用(Tool Calling)可以理解为让 AI 大模型 借用外部工具 来完成它自己做不到的事情。

跟人类一样⁡⁡⁡⁡⁡,如果只凭⁠手⁠脚⁠完⁠成⁠不⁠了工⁠作,⁠那么⁠就可⁠以利用工⁡具箱来⁡完成。

工具可以是⁡⁡⁡⁡⁡任何东西,比如网⁠⁠⁠⁠页⁠搜索、对外部 ⁠⁠⁠⁠AP⁠I 的调用、访问外部数据、或⁡⁡⁡⁡执行特定⁡的代码等。

比如用户提⁡⁡⁡⁡⁡问 “帮我查询上海最⁠⁠⁠⁠⁠新的天气”,AI 本⁠⁠⁠⁠⁠身并没有这些知识,它就可以调用 “查询天⁡⁡⁡⁡⁡气工具”,来完成任务。

需要注意的是,工具调用的本质 并不是 AI 服务器自己调用这些工具、也不是把工具的代码发送给 AI 服务器让它执行,它只能提出要求,表示 “我需要执行 XX 工具完成任务”。而真正执行工具的是我们自己的应用程序,执行后再把结果告诉 AI,让它继续工作。

image.png

百度搜索支持通过 URL 参数传入不同的搜索关键词

这里我准备尝试使用Jsoup库,让ai能去百度搜索结果并抓取搜索页面内容返回给我。

先引入 Jsoup 库:

<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.20.1</version>
</dependency>

然后在 tools 包下编写工具,通过 @Tool 注解就能声明工具了,注意 要认真编写工具和工具参数的描述,这直接决定了 AI 能否正确地调用工具。

@Slf4j
public class InterviewQuestionTool {
/**
* 从面试鸭网站获取关键词相关的面试题列表
*
* @param keyword 搜索关键词(如"redis"、"java多线程")
* @return 面试题列表,若失败则返回错误信息
*/
@Tool(name = "interviewQuestionSearch", value = """
Retrieves relevant interview questions from mianshiya.com based on a keyword.
Use this tool when the user asks for interview questions about specific technologies,
programming concepts, or job-related topics. The input should be a clear search term.
"""
)
public String searchInterviewQuestions(@P(value = "the keyword to search") String keyword) {
List<String> questions = new ArrayList<>();
// 构建搜索URL(编码关键词以支持中文)
String encodedKeyword = URLEncoder.encode(keyword, StandardCharsets.UTF_8);
String url = "https://www.mianshiya.com/search/all?searchText=" + encodedKeyword;
// 发送请求并解析页面
Document doc;
try {
doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.timeout(5000)
.get();
} catch (IOException e) {
log.error("get web error", e);
return e.getMessage();
}
// 提取面试题
Elements questionElements = doc.select(".ant-table-cell > a");
questionElements.forEach(el -> questions.add(el.text().trim()));
return String.join("\n", questions);
}
}

给 AI Service 绑定工具:

// 构造 AI Service
AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
.chatModel(qwenChatModel)
.chatMemory(chatMemory)
.contentRetriever(contentRetriever) // RAG 检索增强生成
.tools(new InterviewQuestionTool()) // 工具调用
.build();

编写单元测试

@Test
void chatWithTools() {
String result = aiCodeHelperService.chat("有哪些常见的计算机网络面试题?");
System.out.println(result);
}

Debug⁡⁡⁡⁡⁡⁡ 运行,发现 AI⁠⁠⁠⁠⁠⁠ 调用了工具,工具去网站中搜索了面试题并得到了列表。

image.png

image.png

Debug AICodeHelperService可以发现它加载了工具:

image.png

输出的结果也符合预期。

前面只演示了最⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡简单的工具定义方法 —— 声⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠明式,LangChain4j⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠ 也提供了编程式的工具定义方法 但是编程式创建工具比较麻烦。 image.png

除了联网搜索外,⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡还有一些经典的工具,比如文件读⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠写、PDF 生成、调用终端、输⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠出图表等等。这些工具我们可以自己开发,也可以通过 MCP 直⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡接使用别人开发好的工具。

模型上下文协议 - MCP#

MCP(Model Co⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡ntext Protocol,模型上下文协议)是⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠一种开放标准,目的是增强 AI 与外部系统的交互⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠能力。MCP 为 AI 提供了与外部工具、资源和服务交互的标准化方式,让 AI 能够访问最新数据⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡、执行复杂操作,并与现有系统集成。

可以将 MCP ⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡想象成 AI 应用的 USB 接⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠口。就像 USB 为设备连接各种⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠外设和配件提供了标准化方式一样,MCP 为 AI 模型连接不同的⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡数据源和工具提供了标准化的方法。

image.png

简单来说,通⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡过 MCP 协议,AI ⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠应用可以轻松接入别人提供⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠的服务来实现更多功能,比如查询地理位置、操作数据⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡库、部署网站、甚至是支付等等。

刚刚我们通过工具调用实现了面试题的搜索,下面我们利用 MCP 实现 全网搜索内容,这也是一个典型的 MCP 应用场景了。

首先从 MCP 服务市场搜索 Web Search 服务,推荐 下面这个,因为它提供了 SSE 在线调用服务,不用我们自己在本地安装启动,很方便。

image.png

但也要注意⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡,用别人的⁠⁠⁠⁠⁠⁠⁠服⁠务⁠可⁠能⁠是⁠⁠⁠⁠⁠⁠⁠需要⁠ A⁠PI⁠ K⁠ey 的⁡⁡⁡⁡⁡⁡⁡,一般⁡是按⁡量付费。 需要先去 平台官方获取 API Key,等会儿会用到:

然后引入MCP的依赖

<!-- https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-mcp -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-mcp</artifactId>
<version>1.1.0-beta7</version>
</dependency>

新增key配置

bigmodel:
api-key: <Your Api Key>

新建 mcp.McpConfig,按照官方的开发方式,初始化和 MCP 服务的通讯,并创建 McpToolProvider 的 Bean:

@Configuration
public class McpConfig {
@Value("${bigmodel.api-key}")
private String apiKey;
@Bean
public McpToolProvider mcpToolProvider() {
// 和 MCP 服务通讯
McpTransport transport = new HttpMcpTransport.Builder()
.sseUrl("https://open.bigmodel.cn/api/mcp/web_search/sse?Authorization=" + apiKey)
.logRequests(true) // 开启日志,查看更多信息
.logResponses(true)
.build();
// 创建 MCP 客户端
McpClient mcpClient = new DefaultMcpClient.Builder()
.key("yupiMcpClient")
.transport(transport)
.build();
// 从 MCP 客户端获取工具
McpToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(mcpClient)
.build();
return toolProvider;
}
}

注意,上面我们⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡是通过 SSE 的方式调用 M⁠⁠⁠⁠⁠⁠⁠⁠⁠C⁠⁠P。如果你是通过 npx⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠ 或 uvx 本地启动 MCP 服务,需要先安装对应的工⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡具,并且利用下面的配置建立通讯:

McpTransport transport = new StdioMcpTransport.Builder()
.command(List.of("/usr/bin/npm", "exec", "@modelcontextprotocol/server-everything@0.6.2"))
.logEvents(true) // only if you want to see the traffic in the log
.build();

在 AI ⁡⁡⁡⁡⁡⁡⁡Service 中应⁠⁠⁠⁠⁠⁠⁠用 MCP 工具:

@Resource
private McpToolProvider mcpToolProvider;
// 构造 AI Service
AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
.chatModel(qwenChatModel)
.chatMemory(chatMemory)
.contentRetriever(contentRetriever) // RAG 检索增强生成
.tools(new InterviewQuestionTool()) // 工具调用
.toolProvider(mcpToolProvider) // MCP 工具调用
.build();

书写单元测试

@Test
void chatWithMcp() {
String result = aiCodeHelperService.chat("什么是程序员鱼皮的编程导航?");
System.out.println(result);
}

测试结果有包括网址 并且日志也有显示出搜索的日志 说明MCP生效了 image.png

护轨 - Guardrail#

可以理解为拦截器。分为输入护轨⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠(input guardrails)和输出护轨(ou⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠tput guardrails),可以在请求 AI 前和接收到 AI 的响应后执行一些额外操作,比如调用⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡ AI 前鉴权、调用 AI 后记录日志。

让我们小试⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡一把,在调用 ⁠⁠⁠⁠⁠⁠⁠⁠⁠A⁠I⁠ 前进行敏⁠⁠⁠⁠⁠⁠⁠⁠⁠感词⁠检测⁠,如果用户提示词包含⁡⁡⁡⁡⁡⁡⁡⁡⁡敏感词,则直⁡接拒⁡绝。 新建 guardrail.SafeInputGuardrail,实现 InputGuardrail 接口:

实现接口可以看到有什么方法:

image.png

这里实现validate 可以获取用户信息来校验,它返回一个护轨结果InputGuardrailResult

/**
* 安全检测输入护轨
*/
public class SafeInputGuardrail implements InputGuardrail {
private static final Set<String> sensitiveWords = Set.of("kill", "evil");
/**
* 检测用户输入是否安全
*/
@Override
public InputGuardrailResult validate(UserMessage userMessage) {
// 获取用户输入并转换为小写以确保大小写不敏感
String inputText = userMessage.singleText().toLowerCase();
// 使用正则表达式分割输入文本为单词
String[] words = inputText.split("\\W+");
// 遍历所有单词,检查是否存在敏感词
for (String word : words) {
if (sensitiveWords.contains(word)) {
return fatal("Sensitive word detected: " + word);
}
}
return success();
}
}

LangCh⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡ain4j 提供了几种⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠快速返回的方法,简单来⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠说,想继续调用 AI 就返回 success⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡、否则就返回 fatal。 image.png

修改 AI Service,使用输入护轨:

@InputGuardrails({SafeInputGuardrail.class})
public interface AiCodeHelperService {
@SystemMessage(fromResource = "system-prompt.txt")
String chat(String userMessage);
@SystemMessage(fromResource = "system-prompt.txt")
Report chatForReport(String userMessage);
// 学习报告
record Report(String name, List<String> suggestionList) {
}
}

编写单元测⁡⁡⁡⁡⁡⁡试,写一个包含敏感⁠⁠⁠⁠⁠⁠词的提示词:

@Test
void chatWithGuardrail() {
String result = aiCodeHelperService.chat("kill the game");
System.out.println(result);
}

运行并查看效果,会触发输入检测,直接抛出异常:

image.png

如果不包含敏感词,则会顺利通过。

当然,除了⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡输入护轨,⁠⁠⁠⁠⁠也⁠可⁠以⁠编⁠⁠⁠写⁠⁠⁠⁠⁠输出⁠护轨⁠,对⁠ ⁠⁠A⁠I 的响⁡⁡⁡⁡⁡应结果进⁡行检⁡测。

日志和可观测性#

之前我们都⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡是通过 Deb⁠⁠⁠⁠u⁠g⁠ 查看运⁠⁠⁠⁠⁠行⁠⁠⁠⁠信息⁠,不⁠仅不便于调⁠⁠⁠⁠⁠试,而且生⁡⁡⁡⁡产环境肯⁡定不⁡能这么做。                ⁡⁡⁡⁡⁡                

官方提供了 日志可观测性,来帮我们更好地调试程序、发现问题。

日志#

开启日志的方⁡⁡⁡法⁡⁡⁡⁡⁡⁡⁡⁡很简单,直接构造模型⁠⁠⁠时指定开启、或⁠⁠⁠⁠⁠⁠⁠⁠者直接编⁠⁠⁠写 Spring Bo⁠⁠⁠⁠⁠⁠⁠⁠ot 配置,支持打印 ⁡⁡⁡AI 请求和响应日志。

OpenAiChatModel.builder()
...
.logRequests(true)
.logResponses(true)
.build();
langchain4j.open-ai.chat-model.log-requests = true
langchain4j.open-ai.chat-model.log-responses = true
logging.level.dev.langchain4j = DEBUG

但并不是所有⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡的 ChatModel⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠ 都支持,比如测试下⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠来 QwenChatModel 就不支持。这⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡时只能把希望交给可观测性了。

可观测性#

可以通过自⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡定义 List⁠⁠⁠⁠⁠⁠⁠⁠⁠e⁠n⁠er 获取⁠⁠⁠⁠⁠⁠⁠⁠⁠ C⁠ha⁠tModel 的调用⁡⁡⁡⁡⁡⁡⁡⁡⁡信息,⁡比较灵活。

新建 listener.ChatModelListenerConfig,输出请求、响应、错误信息:

@Configuration
@Slf4j
public class ChatModelListenerConfig {
@Bean
ChatModelListener chatModelListener() {
return new ChatModelListener() {
@Override
public void onRequest(ChatModelRequestContext requestContext) {
log.info("onRequest(): {}", requestContext.chatRequest());
}
@Override
public void onResponse(ChatModelResponseContext responseContext) {
log.info("onResponse(): {}", responseContext.chatResponse());
}
@Override
public void onError(ChatModelErrorContext errorContext) {
log.info("onError(): {}", errorContext.error().getMessage());
}
};
}
}

但是只定义 ⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡Listener 好像对⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠ QwenChatMod⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠el 不起作用,所以我们需要手动构造自定义的 Q⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡wenChatModel。

新建 model.QwenChatModelConfig,构造 ChatModel 对象并绑定 Listener:

@Configuration
@ConfigurationProperties(prefix = "langchain4j.community.dashscope.chat-model")
@Data
public class QwenChatModelConfig {
private String modelName;
private String apiKey;
@Resource
private ChatModelListener chatModelListener;
@Bean
public ChatModel myQwenChatModel() {
return QwenChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.listeners(List.of(chatModelListener))
.build();
}
}

然后,可以将原本引用 ChatModel 的名称改为 myQwenChatModel,防止和 Spring Boot 自动注入的 ChatModel 冲突。

再次调用 ⁡⁡⁡⁡⁡⁡⁡AI,就能看到很多⁠⁠⁠⁠⁠⁠⁠信息了:     ⁠⁠⁠⁠⁠⁠⁠ image.png

AI 服务化#

至此,AI⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡ 的能力基本开发完成⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠,但是目前只支持本地⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠运行,需要编写一个接口提供给前端调用,让⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡ AI 能够成为一个服务。

我们平时开发的大多数接口都⁡⁡⁡⁡是⁡⁡⁡⁡⁡⁡⁡同步接口,也就是等后端处理完再返回。但是对于 A⁠⁠⁠⁠I 应用,特别⁠⁠⁠⁠⁠⁠⁠是响应时间较长的对话类应用,可能会让⁠⁠⁠⁠用户失去耐心等待,因此推荐⁠⁠⁠⁠⁠⁠⁠使用 SSE(Server-Sent Events)技术实现实时流式输出,⁡⁡⁡⁡类似打字机效果,大幅提升用户体验         ⁡⁡⁡⁡⁡⁡⁡                       

SSE 流式接口开发#

LangC⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡hain 提供⁠⁠⁠⁠⁠⁠⁠⁠⁠了⁠ ⁠2 种方式⁠⁠⁠⁠⁠⁠⁠⁠⁠来支⁠持流⁠式响应(注意,流式响⁡⁡⁡⁡⁡⁡⁡⁡⁡应不支持⁡结构⁡化输出)。

一种方法是 TokenStream,先让 AI 对话方法返回 TokenStream,然后创建 AI Service 时指定流式对话模型 StreamingChatModel:

interface Assistant {
TokenStream chat(String message);
}
StreamingChatModel model = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
Assistant assistant = AiServices.create(Assistant.class, model);
TokenStream tokenStream = assistant.chat("Tell me a joke");
tokenStream.onPartialResponse((String partialResponse) -> System.out.println(partialResponse))
.onRetrieved((List<Content> contents) -> System.out.println(contents))
.onToolExecuted((ToolExecution toolExecution) -> System.out.println(toolExecution))
.onCompleteResponse((ChatResponse response) -> System.out.println(response))
.onError((Throwable error) -> error.printStackTrace())
.start();

我个人会更喜欢另一种方法,使用 Flux 代替 TokenStream,熟悉响应式编程的同学应该对 Flux 不陌生吧?让 AI 对话方法返回 Flux 响应式对象即可。示例代码:

interface Assistant {
Flux<String> chat(String message);
}

首先需要引入依赖

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.1.0-beta7</version>
</dependency>

然后给 A⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡I Serv⁠⁠⁠⁠⁠⁠⁠⁠i⁠c⁠e⁠ 增加⁠⁠⁠⁠⁠⁠⁠⁠流式⁠对话⁠方法⁠,这里顺便支持⁡⁡⁡⁡⁡⁡⁡⁡下多用⁡户的会话⁡记忆:

// 流式对话
Flux<String> chatStream(@MemoryId int memoryId, @UserMessage String userMessage);

由于要用到流式模型,需要增加流式模型配置:

langchain4j:
community:
dashscope:
streaming-chat-model:
model-name: qwen-max
api-key: <Your Api Key>

构造 AI⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡ Servic⁠⁠⁠⁠⁠⁠⁠⁠⁠e⁠ ⁠时指定流式⁠⁠⁠⁠⁠⁠⁠⁠⁠对话⁠模型⁠(自动注入即可),并⁡⁡⁡⁡⁡⁡⁡⁡⁡且补充会⁡话记忆提⁡供者:

@Resource
private StreamingChatModel qwenStreamingChatModel;
AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
.chatModel(myQwenChatModel)
.streamingChatModel(qwenStreamingChatModel)
.chatMemory(chatMemory)
.chatMemoryProvider(memoryId ->
MessageWindowChatMemory.withMaxMessages(10)) // 每个会话独立存储
.contentRetriever(contentRetriever) // RAG 检索增强生成
.tools(new InterviewQuestionTool()) // 工具调用
.toolProvider(mcpToolProvider) // MCP 工具调用
.build();

最后,编写⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡ Contr⁠⁠⁠⁠⁠⁠⁠⁠o⁠l⁠l⁠er ⁠⁠⁠⁠⁠⁠⁠⁠⁠接口。为⁠了方⁠便测试,这里使⁡⁡⁡⁡⁡⁡⁡⁡用 G⁡et 请⁡求:

@RestController
@RequestMapping("/ai")
public class AiController {
@Resource
private AiCodeHelperService aiCodeHelperService;
@GetMapping("/chat")
public Flux<ServerSentEvent<String>> chat(int memoryId, String message) {
return aiCodeHelperService.chatStream(memoryId, message)
.map(chunk -> ServerSentEvent.<String>builder()
.data(chunk)
.build());
}
}

增加服务器配置,指定后端端口和接口路径前缀:

server:
port: 8081
servlet:
context-path: /api

启动服务器⁡⁡,用 CURL 工⁠⁠具测试调用:   ⁠

curl -G 'http://localhost:8081/api/ai/chat' \
--data-urlencode 'message=我是小明' \
--data-urlencode 'memoryId=1'

可以看到流式的输出结果:

image.png

后端支持跨域#

为了让前端⁡⁡⁡项目⁡⁡⁡⁡⁡⁡⁡⁡能够顺利调用后端⁠⁠⁠接口,我们需要在⁠⁠⁠⁠⁠⁠⁠⁠后端⁠⁠⁠配置跨域支持。在 co⁠⁠⁠⁠⁠⁠⁠⁠nfig 包下创建⁡⁡⁡跨域配置类,代码如下:

/**
* 全局跨域配置
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 覆盖所有请求
registry.addMapping("/**")
// 允许发送 Cookie
.allowCredentials(true)
// 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("*");
}
}

注意,如果 .allowedOrigins("*").allowCredentials(true) 同时配置会导致冲突,因为出于安全考虑,跨域请求不能同时允许所有域名访问和发送认证信息(比如 Cookie)。

AI 生成前端#

由于这个项目不需要很复杂的页面,我们可以利用 AI 来快速生成前端代码,极大提高开发效率。这里鱼皮使用 主流 AI 开发工具 Cursor,挑战不写一行代码,生成符合要求的前端项目。

提示词#

首先准备一段详细⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡的 Prompt,一般要包括需求⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠、技术选型、后端接口信息,还可以⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠提供一些原型图、后端代码等。

你是一位专业的前端开发,请帮我根据下列信息来生成对应的前端项目代码。
## 需求
应用为《AI 编程小助手》,帮助用户解答编程学习和求职面试相关的问题,并给出建议。
只有一个页面,就是主页:页面风格为聊天室,上方是聊天记录(用户信息在右边,AI 信息在左边),下方是输入框,进入页面后自动生成一个聊天室 id,用于区分不同的会话。通过 SSE 的方式调用 chat 接口,实时显示对话内容。
## 技术选型
1. Vue3 项目
2. Axios 请求库
## 后端接口信息
接口地址前缀:http://localhost:9001/api
## SpringBoot 后端接口代码
@RestController
@RequestMapping("/ai")
public class AiController {
@GetMapping("/chat")
public Flux<ServerSentEvent<String>> chat(int memoryId, String message) {
return aiCodeHelperService.chatStream(memoryId, message)
.map(chunk -> ServerSentEvent.<String>builder()
.data(chunk)
.build());
}
}
在windows系统开发,你应该使用 Windows ⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡支持的命令来完成任务,并且使用pnpm。

开发#

在项目根目录下创建新的前端项目文件夹 ai-code-helper-frontend,使用 Cursor 工具打开该目录,输入 Prompt 执行。注意要选择 Agent 模式、Thinking 深度思考模型(推荐 Claude):

生成出来项目后,使用pnpm dev运行(查看package可知,以及我电脑用的是pnpm) image.png

生成的前端界面如下: image.png 测试提问:

image.png

会话记忆功能测试:

image.png image.png

总结#

实际开发中应该如何选择 AI 开发框架呢?

就拿 Spring AI 和 LangC⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡hain4j 来说,不知道大家更喜欢哪个框架?我其实会更喜欢 Spring AI⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠ 的开发模式,而且 Spring AI 目前支持的能力更多,还有国内 Sprin⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠g AI Alibaba 的巨头加持,生态更好,遇到问题更容易解决;LangChain4j 的优势在于可以独立于 Spring 项目使用,更自由灵活一些。   ⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡

不过这类框⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡⁡架重⁠⁠⁠⁠⁠⁠点⁠学⁠习⁠一⁠个⁠⁠⁠⁠⁠⁠⁠就⁠好了⁠,很⁠多概⁠念和⁠用⁡⁡⁡⁡⁡⁡法是相⁡通的:

image.png

LangChain4j框架入门项目学习
http://www.shineacz.top/posts/langchain4j框架入门项目学习/
作者
shineAcZ
发布于
2026-04-24
许可协议
CC BY 4.0