在项目中通过LangChain4j框架接入AI大模型
在java项目中如何接入AI大模型及其基本使用。
1.LangChain4j
langchain4j 是一个针对 Java 生态的开源框架,专为构建基于大语言模型(LLM)的应用程序设计,提供了统一的 API 来集成各类大模型(如 OpenAI、GPT-4、LLaMA 等),并支持链式调用、对话记忆、工具调用(如数据库查询、API 调用)、文档加载与检索等核心功能,简化了复杂 LLM 应用的开发流程,同时兼容响应式编程(如 Reactor)和 Spring 生态,方便在 Java 项目中快速实现智能对话、内容生成、知识问答等场景。
2.大模型接入
引入依赖:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
需要获取Deepseek的 API Key:首次调用 API | DeepSeek API Docs
chatModel负责和AI大模型交互。
在配置文件application.yml中填写Chat Model配置,并且为了调试方便,开启详细的日志记录。
# AI
langchain4j:
open-ai:
chat-model:
base-url: https://api.deepseek.com
api-key: <Your API Key>
model-name: deepseek-chat
log-requests: true
log-responses: true
为了保护敏感信息也可以创建application-local.yml,填入配置信息,并在主配置文件中激活本地环境。
3. 开发AI服务
3.1 定义接口(以生成HTML代码为例):
public interface AiCodeGeneratorService {
/**
* 生成HTML代码
*
* @param userMessage 用户提示词
* @return 生成的代码结果
*/
@SystemMessage(fromResource = "prompt/codegen-html-system-prompt.txt")
HtmlCodeResult generateHTMLCode(String userMessage);
}
系统提示词比较长,将他们维护到资源文件中:
系统提示词示例:
你是一位资深的 Web 前端开发专家,精通 HTML、CSS 和原生 JavaScript。
你的任务是根据用户提供的网站描述,生成一个完整、独立的单页面网站。你需要一步步思考,并最终将所有代码整合到一个 HTML 文件中。
约束:
1. 技术栈: 只能使用 HTML、CSS 和原生 JavaScript。
2. 禁止外部依赖: ......
3. 独立文件: ......
4. 响应式设计: ......
5. 内容填充: ......
6. 代码质量: 代码必须结构清晰、有适当的注释,易于阅读和维护。
7. 交互性: .......
8. 输出格式: 你的最终输出必须包含 HTML 代码块,可以在代码块之外添加解释、标题或总结性文字。格式如下:
```html
... HTML 代码 ...
3.2创建工厂类来初始化AI服务:
@Configuration
public class AiCodeGeneratorServiceFactory {
@Resource
private ChatModel chatModel;
@Bean
public AiCodeGeneratorService aiCodeGeneratorService() {
return AiServices.create(AiCodeGeneratorService.class, chatModel);
}
}
3.3 编写一个单元测试验证功能:
@SpringBootTest
class AiCodeGeneratorServiceTest {
@Resource
private AiCodeGeneratorService aiCodeGeneratorService;
@Test
void generateHtmlCode() {
String result = aiCodeGeneratorService.generateHtmlCode("做个程序员的工作记录小工具");
Assertions.assertNotNull(result);
}
}
3.4 结构化输出
在调用AI输出代码后,我们需要将AI的输出转换为结构化的对象以便于后续解析代码并保存为文件,利用LangChain4j的结构化输出特性。
创建结构类HtmlCodeResult:
/**
* HTML代码生成结果
*/
@Description("HTML代码生成结果")
@Data
public class HtmlCodeResult {
/**
* HTML代码
*/
@Description("HTML代码")
private String htmlCode;
/**
* 描述
*/
@Description("生成代码的描述")
private String description;
}
为结果类和属性添加详细的描述信息,告诉AI每个属性应该存入什么信息,@Data注解让AI可以将值写入属性。
3.5 优化技巧
1.设置max_tokens参数,防止JSON字符串被中途截断。
在项目配置文件中添加:
langchain4j:
open-ai:
chat-model:
max-tokens: 8192
2. JSON Schema配置
设置 response-format 参数为json_object,可以严格确保结构化输出生效,可以在项目中使用这个配置 :
langchain4j:
open-ai:
chat-model:
strict-json-schema: true
response-format: json_object
3.在系统提示词中明确要求输出JSON格式,进一步提高成功率。
4. 程序处理写入
接下来将生成的代码保存到本地文件系统。
4.1 定义生成类型枚举
我们可以有多种生成类型,比如说HTML,多文件,还有Vue工程化等等。在调用AI服务时将生成类型传入,AI生成对应的代码。
@Getter
public enum CodeGenTypeEnum {
HTML("原生 HTML 模式", "html"),
MULTI_FILE("原生多文件模式", "multi_file");
private final String text;
private final String value;
CodeGenTypeEnum(String text, String value) {
this.text = text;
this.value = value;
}
/**
* 根据 value 获取枚举
*
* @param value 枚举值的value
* @return 枚举值
*/
public static CodeGenTypeEnum getEnumByValue(String value) {
if (ObjUtil.isEmpty(value)) {
return null;
}
for (CodeGenTypeEnum anEnum : CodeGenTypeEnum.values()) {
if (anEnum.value.equals(value)) {
return anEnum;
}
}
return null;
}
}
4.2 创建文件写入工具类
假设将文件存入目录tmp下。每次生成都对应一个目录下的文件夹,使用业务类型+雪花ID的命名方式来确保唯一性。
public class CodeFileSaver {
// 文件保存根目录
private static final String FILE_SAVE_ROOT_DIR = System.getProperty("user.dir") + "/tmp/";
/**
* 保存 HtmlCodeResult
*/
public static File saveHtmlCodeResult(HtmlCodeResult result) {
String baseDirPath = buildUniqueDir(CodeGenTypeEnum.HTML.getValue());
writeToFile(baseDirPath, "index.html", result.getHtmlCode());
return new File(baseDirPath);
}
/**
* 构建唯一目录路径:tmp/bizType_雪花ID
*/
private static String buildUniqueDir(String bizType) {
String uniqueDirName = StrUtil.format("{}_{}", bizType, IdUtil.getSnowflakeNextIdStr());
String dirPath = FILE_SAVE_ROOT_DIR + File.separator + uniqueDirName;
FileUtil.mkdir(dirPath);
return dirPath;
}
/**
* 写入单个文件
*/
private static void writeToFile(String dirPath, String filename, String content) {
String filePath = dirPath + File.separator + filename;
FileUtil.writeString(content, filePath, StandardCharsets.UTF_8);
}
}
5. SSE流式输出
如果AI生成的速度比较慢,用户需要等待一段时间才能得到结果,体验不好。所以引入SSE(Server-Sent Events)流式输出,效果为AI生成一个字,就将其返回给前端。
目前的流式输出不支持结构化输出,所以我们在流式返回的过程中拼接AI的返回结果,可以实时返回给前端,等全部输出完以后,再对拼接结果进行解析和保存。
这里使用 LangChain4j + Reactor 实现。Reactor 通过 Flux 和 Sinks 将 langchain4j 的异步流式响应转换为符合 SSE 规范的持续输出流,实现服务器到客户端的高效实时数据推送。
5.1 引入依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.1.0-beta7</version>
</dependency>
5.2 开发实现
1. 配置流式模型
langchain4j:
open-ai:
streaming-chat-model:
base-url: https://api.deepseek.com
api-key: <Your API Key>
model-name: deepseek-chat
max-tokens: 8192
log-requests: true
log-responses: true
2. 在AiCodeGeneratorServiceFactory工厂中注入流式模型
@Configuration
public class AiCodeGeneratorServiceFactory {
@Resource
private ChatModel chatModel;
@Resource
private StreamingChatModel streamingChatModel;
@Bean
public AiCodeGeneratorService aiCodeGeneratorService() {
return AiServices.builder(AiCodeGeneratorService.class)
.chatModel(chatModel)
.streamingChatModel(streamingChatModel)
.build();
}
}
3. 在AI Service中新增流式方法,返回Flux对象:
/**
* 生成 HTML 代码(流式)
*
* @param userMessage 用户消息
* @return 生成的代码结果
*/
@SystemMessage(fromResource = "prompt/codegen-html-system-prompt.txt")
Flux<String> generateHtmlCodeStream(String userMessage);
4. 编写解析逻辑
由于模型返回的流式数据通常是碎片化、非结构化的原始内容(如单词、短句、标记片段等),而前端或下游系统需要结构化、可理解的对象(如完整 JSON、实体类等)。所以需要代码解析器,将零散的流式数据实时拼接、校验、转换为目标格式的对象,同时处理可能的格式错误(如未闭合的括号、不完整的字段),确保最终输出符合预期结构,避免下游因数据不完整或格式混乱而解析失败。
代码解析器相当于一个工具类,不用搞懂内部逻辑(也是用AI生成的)。
/**
* 代码解析器
* 提供静态方法解析不同类型的代码内容
*/
public class CodeParser {
private static final Pattern HTML_CODE_PATTERN = Pattern.compile("```html\\s*\\n([\\s\\S]*?)```", Pattern.CASE_INSENSITIVE);
private static final Pattern CSS_CODE_PATTERN = Pattern.compile("```css\\s*\\n([\\s\\S]*?)```", Pattern.CASE_INSENSITIVE);
private static final Pattern JS_CODE_PATTERN = Pattern.compile("```(?:js|javascript)\\s*\\n([\\s\\S]*?)```", Pattern.CASE_INSENSITIVE);
/**
* 解析 HTML 单文件代码
*/
public static HtmlCodeResult parseHtmlCode(String codeContent) {
HtmlCodeResult result = new HtmlCodeResult();
// 提取 HTML 代码
String htmlCode = extractHtmlCode(codeContent);
if (htmlCode != null && !htmlCode.trim().isEmpty()) {
result.setHtmlCode(htmlCode.trim());
} else {
// 如果没有找到代码块,将整个内容作为HTML
result.setHtmlCode(codeContent.trim());
}
return result;
}
/**
* 提取HTML代码内容
*
* @param content 原始内容
* @return HTML代码
*/
private static String extractHtmlCode(String content) {
Matcher matcher = HTML_CODE_PATTERN.matcher(content);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
/**
* 根据正则模式提取代码
*
* @param content 原始内容
* @param pattern 正则模式
* @return 提取的代码
*/
private static String extractCodeByPattern(String content, Pattern pattern) {
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
}
5. 在 AiCodeGeneratorFacade 中添加流式调用AI的方法。
核心逻辑为:拼接AI实时响应的字符串,并在流式返回完成后解析字符串并保存代码文件。
/**
* 生成 HTML 模式的代码并保存(流式)
*
* @param userMessage 用户提示词
* @return 保存的目录
*/
private Flux<String> generateAndSaveHtmlCodeStream(String userMessage) {
Flux<String> result = aiCodeGeneratorService.generateHtmlCodeStream(userMessage);
// 当流式返回生成代码完成后,再保存代码
StringBuilder codeBuilder = new StringBuilder();
return result
.doOnNext(chunk -> {
// 实时收集代码片段
codeBuilder.append(chunk);
})
.doOnComplete(() -> {
// 流式返回完成后保存代码
try {
String completeHtmlCode = codeBuilder.toString();
HtmlCodeResult htmlCodeResult = CodeParser.parseHtmlCode(completeHtmlCode);
// 保存代码到文件
File savedDir = CodeFileSaver.saveHtmlCodeResult(htmlCodeResult);
log.info("保存成功,路径为:" + savedDir.getAbsolutePath());
} catch (Exception e) {
log.error("保存失败: {}", e.getMessage());
}
});
}
更多推荐
所有评论(0)