喜欢看视频的小伙伴直接去看博主讲解的。
一、开始前
1、去阿里大模型开放平台注册并创建一个密钥
(老样子的别泄露咯!)https://bailian.console.aliyun.com/
https://bailian.console.aliyun.com/
(1)这里的模型调用如果产生费用会直接扣你阿里云账号上面的钱钱!!所有调用模型要注意看有没有免费token。
(2)查看是否有免费token方式


2、langchain4j中文文档路径
https://docs.langchain4j.info/get-started
https://docs.langchain4j.info/get-started
二、简单使用
1、导入依赖
<!-- 阿里社区langchain4j包 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
2、配置application.yml
langchain4j:
community:
dashscope: # 阿里社区
chat-model:
model-name: deepseek-v3.1 # 有免费token数
api-key: 你的密钥
embedding-model: # RAG
model-name: text-embedding-v4 # 有免费token数
api-key: 你的密钥
streaming-chat-model: # 流式输出
model-name: qwen-max # qwen-max 这个模型要钱!慎用!!!
api-key: 你的密钥
3、实现代码
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class AiCodeHelper {
private final ChatModel qwenChatModel;
public static final String SYSTEM_MESSAGE = "你叫攀小黑,是一名程序员";
// 简单对话
public String chat(String message) {
UserMessage userMessage = UserMessage.from(message);
ChatResponse chat = qwenChatModel.chat(userMessage);
AiMessage aiMessage = chat.aiMessage();
log.info("AI输出 : "+ aiMessage.toString());
return aiMessage.text();
}
// 简单对话 - 用户自定义消息
public String chatWithMessage(UserMessage userMessage) {
ChatResponse chat = qwenChatModel.chat(userMessage);
AiMessage aiMessage = chat.aiMessage();
log.info("AI输出 : "+ aiMessage.toString());
return aiMessage.text();
}
// 简单对话 - 系统提示词
public String chatSystemMessage(String message) {
SystemMessage systemMessage = SystemMessage.from(SYSTEM_MESSAGE);
UserMessage userMessage = UserMessage.from(message);
ChatResponse chat = qwenChatModel.chat(userMessage, systemMessage);
AiMessage aiMessage = chat.aiMessage();
log.info("AI输出 : "+ aiMessage.toString());
return aiMessage.text();
}
}
因为涉及到spring Bean注入,这里测试需要用@SpringBootTest 去测试
4、测试代码
@SpringBootTest
public class AiUnitTest {
@Autowired
private AiCodeHelper aiCodeHelper;
@Test
public void test() {
String message = "你是谁";
aiCodeHelper.chat(message);
}
@Test
public void testWithMessage() {
UserMessage userMessage = UserMessage.from(
TextContent.from("描述图片"),
ImageContent.from("https://admin.panxiaohei.cn/...")
);
aiCodeHelper.chatWithMessage(userMessage);
}
@Test
public void testSystemMessage() {
String message = "你是谁";
aiCodeHelper.chatSystemMessage(message);
}
}
三、整合spring boot
1、按需求导入依赖
<!-- 阿里langchain4j包 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
<!--反射机制-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
<!--AiService-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.1.0</version>
</dependency>
<!-- RAG -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.1.0-beta7</version>
</dependency>
<!-- 响应式返回 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.1.0-beta7</version>
</dependency>
<!-- 网页爬取 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.20.1</version>
</dependency>
2、AiCodeHelperService 服务类
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.Result;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.guardrail.InputGuardrails;
import org.dromara.pxhai.guardrail.SafeInputGuardrail;
import reactor.core.publisher.Flux;
import java.util.List;
//@AiService # 封装的服务类不太灵活,不建议使用
@InputGuardrails(SafeInputGuardrail.class)
public interface AiCodeHelperService {
// @SystemMessage("你叫攀小黑,是一名程序员")
@SystemMessage(fromResource = "system-prompt.txt")
String chat(String userMessage);
// // 指定一个会话的记忆ID
// @SystemMessage(fromResource = "system-prompt.txt")
// String chat(@MemoryId int memoryId, String userMessage);
// 指定对象返回
@SystemMessage(fromResource = "system-prompt.txt")
Report chatForReport(String userMessage);
// 学习报告类(快速实现实体)
record Report(String name, List<String> suggestionList){};
// 返回引用文档
@SystemMessage(fromResource = "system-prompt.txt")
Result<String> chatWithReference(String userMessage);
// 流式输出 - 需要修改配置文件
@SystemMessage(fromResource = "system-prompt.txt")
Flux<String> chatSSE(@MemoryId int memoryId, @UserMessage String userMessage);
}
3、工厂类(接口反射实现继承)
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.service.AiServices;
import lombok.RequiredArgsConstructor;
import org.dromara.pxhai.apply.AiCodeHelperService;
import org.dromara.pxhai.domain.application.PXHAiConfig;
import org.dromara.pxhai.tools.RealTimeNewsTool;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class AicodeHelperServiceFactory {
private final ChatModel myQwenChatModel;
private final ContentRetriever contentRetriever; // RAG
private final StreamingChatModel qwenStreamingChatModel; // 流式
private final PXHAiConfig pxhAiConfig;
/**
* 工程模式-反射机制实现接口类
* 可以导入 下面依赖 实现自动生成实现类
* <dependency>
* <groupId>dev.langchain4j</groupId>
* <artifactId>langchain4j-spring-boot-starter</artifactId>
* <version>1.1.0-beta7</version>
* </dependency>
* 在service中使用@AiService
* 注意,
* 1、如果使用注解方式就必须把AicodeHelperServiceFactory类@Configuration注释,不然会引发重复bean
* 2、不建议使用注解方式,不灵活
* @return
*/
@Bean
public AiCodeHelperService aiCodeHelperService() {
// 会话记忆
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
// 持久化会话记忆
// ChatMemory chatMemory = MessageWindowChatMemory.builder()
// .id("12345")
// .maxMessages(10)
// .chatMemoryStore(new PersistentChatMemoryStore())
// .build();
// // 响应格式
// ResponseFormat responseFormat = ResponseFormat.builder()
// .type(JSON) // 类型可以是 TEXT(默认)或 JSON
// .jsonSchema(JsonSchema.builder()
// .name("Person") // OpenAI 要求为 schema 指定名称
// .rootElement(JsonObjectSchema.builder() // 见下面的 [1]
// .addStringProperty("name")
// .addIntegerProperty("age")
// .addNumberProperty("height")
// .addBooleanProperty("married")
// .required("name", "age", "height", "married") // 见下面的 [2]
// .build())
// .build())
// .build();
// ChatRequest chatRequest = ChatRequest.builder()
// .responseFormat(responseFormat)
// .messages(new UserMessage("你叫攀小黑,是一名程序员"))
// .build();
// ChatResponse chatResponse = qwenChatModel.chat(chatRequest);
// String output = chatResponse.aiMessage().text();
// System.out.println(output); // {"name":"John","age":42,"h
AiServices<AiCodeHelperService> aiServices = AiServices.builder(AiCodeHelperService.class)
.chatModel(myQwenChatModel) // 模型
.streamingChatModel(qwenStreamingChatModel) // 流式输出
.chatMemory(chatMemory) // 会话记忆
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) // 会话记忆提供者
.tools(new RealTimeNewsTool());// 工具调用 (需要定义工具方法,给定提示词和返回)
// .chatMemoryProvider( memoryId -> MessageWindowChatMemory.withMaxMessages(10)) // 会话记忆提供者(需要在接口类 String chat(@MemoryId int memoryId, String userMessage);)
if (pxhAiConfig.getOpenRag()) {
aiServices.contentRetriever(contentRetriever); // RAG
}
AiCodeHelperService aiCodeHelperService = aiServices.build();
// 构造 AI 服务
// AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
// .chatModel(myQwenChatModel) // 模型
// .streamingChatModel(qwenStreamingChatModel) // 流式输出
// .chatMemory(chatMemory) // 会话记忆
// .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) // 会话记忆提供者
// .contentRetriever(contentRetriever) // RAG
// .tools(new RealTimeNewsTool()) // 工具调用 (需要定义工具方法,给定提示词和返回)
//// .chatMemoryProvider( memoryId -> MessageWindowChatMemory.withMaxMessages(10)) // 会话记忆提供者(需要在接口类 String chat(@MemoryId int memoryId, String userMessage);)
// .build();
return aiCodeHelperService;
// return AiServices.create(AiCodeHelperService.class, qwenChatModel);
}
这里引用的一些其他方法在后面。
4、我自定义的配置开关
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component("pxhAiConfig")
public class PXHAiConfig {
@Value("${pxh.ai.open-rag}")
private Boolean openRag;
}
.yml配置
pxh:
ai:
open-rag: false
5、RAG配置
调用ai前对本地文章进行检索,并将匹配值较高的内容一起发送给AI。
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.dromara.pxhai.domain.application.PXHAiConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 加载 RAG
*/
@Configuration
@RequiredArgsConstructor
public class RagConfig {
private final EmbeddingModel qwenEmbeddingModel;
@Resource
private EmbeddingStore<TextSegment> embeddingStore;
@Bean
@ConditionalOnExpression("@pxhAiConfig.openRag") // 动态注入
public ContentRetriever contentRetriever() {
// RAG
// 1.加载文档(只会扫码当前目录,不会扫码子目录,需要自己在配置)
List<Document> documents = FileSystemDocumentLoader.loadDocuments("E:\\");
// 2.文档切割:每个文档按照段落进行切割,最大1000字符,每次最多重叠200字符()
DocumentByParagraphSplitter documentByParagraphSplitter =
new DocumentByParagraphSplitter(1000, 200);
// 3。自定义文档加载器,把文档转换成向量并保存到向量数据库中
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(documentByParagraphSplitter)
// 为了提高文档质量:为给每个切割后的文档碎片 TextSegment 添加文档名作为元信息
.textSegmentTransformer(textSegment -> {
return TextSegment.from(textSegment.metadata().getString("file_name")
+ "\n" + textSegment.text());
})
// 使用指定的向量模型
.embeddingModel(qwenEmbeddingModel)
.embeddingStore(embeddingStore) // 保存向量
.build();
// 加载文档
ingestor.ingest(documents);
// 4.自定义内容加载器
EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(qwenEmbeddingModel)
.maxResults(5) // 最多返回5个结果
.minScore(0.75) // 过滤掉分数小于0.75的结果
.build();
return contentRetriever;
}
}
6、工具调用(MCP)
(1)工具调用主要是用来给AI去调用的,当你提出的问题需要调用某些工具时,Ai会主动向我们的程序发出请求并调用这个工具并将结果返回给用户。如果你是要生成dpf等工具直接返回给前端,那么工具调用成功后就不用再去调用Ai了
(2)mcp广场,具体教程这里不多说,看官方文档
https://mcp.so/zh/servers?tag=featured
https://mcp.so/zh/servers?tag=featured
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* 工具调用
*/
@Slf4j
public class RealTimeNewsTool {
/**
* 一个简单的工具调用,主要实现网页爬取,并返回列表数据
* P注解 是AI提示词传入的参数 required = true为必填
* 给AI的提示词最后不用中文,使用英文
* @param keyword 搜索关键词
* @return 面试题列表,若失败返回失败信息。
*/
@Tool(name = "realTimeNewsTool", value = """
This is a tool that mainly returns the latest real-time news data.
It can give you the latest news list.
""")
public String searchRealTimeNewsTool(@P(value = "the keyword to search" , required = true) String keyword) {
List<String> questions =new ArrayList<>();
//构建搜索URL(编码关键词以支持中文)
String encodedKeyword = URLEncoder.encode(keyword, StandardCharsets.UTF_8);
String url = "https://ww.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(element -> questions.add(element.text().trim()));
return String.join("\n", questions);
}
}
7、自定义日志
用于和Ai对话时打印日志,虽然官网上有,但是有一些不生效。毕竟还没有成熟。
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import jakarta.annotation.Resource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 自定义模型配置,用于日志打印
*/
@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()
.modelName(modelName)
.apiKey(apiKey)
.listeners(List.of(chatModelListener))
.build();
}
}
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 聊天模型监听器(日志监听)
*/
@Configuration
public class ChatModelListenersConfiguration {
@Bean
ChatModelListener chatModelListener() {
return new ChatModelListener() {
private static final Logger log = LoggerFactory.getLogger(ChatModelListener.class);
@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());
}
};
}
}
8、安全过滤器
(1)访问Ai前过滤
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.guardrail.InputGuardrail;
import dev.langchain4j.guardrail.InputGuardrailResult;
import java.util.Set;
/**
* 输入安全过滤
* 使用:在接口类中添加 @InputGuardrails(SafeInputGuardrail.class)
*/
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();
}
}
(2)访问Ai后过滤器
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.guardrail.OutputGuardrail;
import dev.langchain4j.guardrail.OutputGuardrailResult;
/**
* 输出安全过滤
*/
public class SafeOutputGuardrail implements OutputGuardrail {
@Override
public OutputGuardrailResult validate(AiMessage responseFromLLM) {
return OutputGuardrail.super.validate(responseFromLLM);
}
}
9、控制层
import cn.dev33.satoken.annotation.SaIgnore;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.pxhai.apply.AiCodeHelperService;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* Ai测试接口
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@SaIgnore
@RequestMapping("/pxhAi/test")
public class TestChatController {
@Resource
private ChatModel qwenChatModel;
@Resource
private AiCodeHelperService aiCodeHelperService;
/**
* 等待全部回复后返回
* @param msg
* @return
*/
@GetMapping("/one/{msg}")
public String redirect(@PathVariable String msg){
UserMessage userMessage = UserMessage.from(msg);
ChatResponse chat = qwenChatModel.chat(userMessage);
AiMessage aiMessage = chat.aiMessage();
log.info("AI输出 : "+ aiMessage.toString());
return aiMessage.text();
}
/**
* 流式返回
*/
@GetMapping("/stream")
public Flux<ServerSentEvent<String>> redirectStream(int memoryId, String message){
return aiCodeHelperService.chatSSE(memoryId, message)
.map(chunk -> ServerSentEvent.<String>builder()
.data(chunk)
.build()
);
}
}
四、看下效果

五、目录结构

六、作者有话说
一直以来,对技术的渴望一直是作者的通病。
AI身为当前主流应用,几乎大一点的都会植入AI。
刚开始没接触以为AI对接是一个非常麻烦和反锁的事情,是要大佬才能去弄。
但是当你真正去了解了,才发现!哎哟,无非还是一堆AI问话,就是加了一些约束条件。
所谓的MCP协议也就是一个大家约定俗成的东西,只要按照一定的访问方式就可以了。
最后AI搞到手,就可以去实现自己心里的小想法啦!
冲冲冲!家人们!

4319





