demo背景:
作为java程序员,我们如果直接调用springai框架作为深入学习大模型的技术的话,会发现在进行文生图或者文本对话的时候,要么依赖本地的ollama,要么是直接对接官方开放的api接口。哪怕再底层一些,也是写方法调用python或者c语言,最终实现调用第三方功能来完成大模型的调用。但是这种方式并不是非常简便的方法。对于java开发者而言,直接通过springboot项目+大模型文件来实现一个大模型项目是最简单并且可控性最强的。那么这种方式能否实现呢?
答案是肯定的,我们这里介绍其中一种方法:Jlama
本文通过一个demo例子,来完成整个实验的验证。
最终的项目结构如下:
springboot-jlama-demo/
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── jlama
│ │ │ ├── SpringBootJlamaDemoApplication.java
│ │ │ ├── config
│ │ │ │ └── JlamaConfig.java
│ │ │ ├── controller
│ │ │ │ └── ChatController.java
│ │ │ ├── dto
│ │ │ │ └── ChatRequest.java
│ │ │ └── service
│ │ │ └── JlamaService.java
│ │ └── resources
│ │ ├── application.yml
│ │ └── static
│ │ └── index.html
│ └── test
│ └── java
│ └── com
│ └── example
│ └── jlama
│ └── SpringBootJlamaDemoApplicationTests.java
🚀 第一步:准备环境
- JDK 版本:Jlama 需要 JDK 21 及以上的版本。
- 开发工具:一个趁手的 IDE(如 IntelliJ IDEA 或 VS Code)应该已经准备好了。
🏗️ 第二步:构建项目骨架
我们用一个最小化的 Spring Boot 项目就能开始,pom.xml 是关键配置。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-jlama-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-jlama-demo</name>
<description>Demo project for Spring Boot + Jlama with DeepSeek 4B</description>
<properties>
<java.version>21</java.version>
<jlama.version>0.8.4</jlama.version>
<langchain4j.version>1.0.0-beta1</langchain4j.version>
</properties>
<dependencies>
<!-- Spring Boot Web 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Thymeleaf 启动器,用于渲染 HTML 页面 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Boot DevTools,用于热重载 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Jlama 核心库 -->
<dependency>
<groupId>com.github.tjake</groupId>
<artifactId>jlama-core</artifactId>
<version>${jlama.version}</version>
</dependency>
<!-- Jlama 原生库,会自动适应操作系统 -->
<dependency>
<groupId>com.github.tjake</groupId>
<artifactId>jlama-native</artifactId>
<version>${jlama.version}</version>
<classifier>${os.detected.classifier}</classifier>
</dependency>
<!-- LangChain4j 核心库 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- LangChain4j Jlama 集成库 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-jlama</artifactId>
<version>${langchain4j.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven 编译器插件,配置 JVM 参数 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>
<arg>--add-modules=jdk.incubator.vector</arg>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- Spring Boot Maven 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- os-maven-plugin,用于检测操作系统 -->
<plugin>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>detect</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
🖥️ 第三步:编写后端服务
1. 配置模型加载 (JlamaConfig.java)
我们需要一个专门的配置类来加载模型,确保它在应用启动时是单例的,这样能最大程度地节省内存。
// src/main/java/com/example/jlama/config/JlamaConfig.java
package com.example.jlama.config;
import com.github.tjake.jlama.model.AbstractModel;
import com.github.tjake.jlama.model.ModelSupport;
import com.github.tjake.jlama.safetensors.DType;
import com.github.tjake.jlama.util.Downloader;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.jlama.JlamaChatModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
import java.io.IOException;
@Configuration
public class JlamaConfig {
private static final Logger logger = LoggerFactory.getLogger(JlamaConfig.class);
// 从 application.yml 读取模型路径和名称
@Value("${jlama.model.path}")
private String modelPath;
@Bean
public ChatLanguageModel chatLanguageModel() throws IOException {
logger.info("正在加载模型: {}", modelPath);
// 如果本地模型不存在,Jlama 会尝试从 Hugging Face 下载
// 模型路径可以是本地文件夹路径,也可以是 Hugging Face 的模型名称
File localModelPath = new Downloader("./models", modelPath).huggingFaceModel();
// 使用 LangChain4j 提供的 JlamaChatModel 来构建模型实例
// 这里传入模型路径、工作数据类型和量化数据类型
// AbstractModel model = ModelSupport.loadModel(localModelPath, DType.F32, DType.I8);
// 使用 JlamaChatModel 简化配置
ChatLanguageModel model = JlamaChatModel.builder()
.modelName(modelPath) // 使用 modelPath 作为模型名称
.temperature(0.7f) // 设置温度参数,控制随机性
.build();
logger.info("模型加载完成。");
return model;
}
}
2. 定义 API 请求格式 (ChatRequest.java)
定义一个简单的 DTO(数据传输对象)来处理前端发来的请求。
// src/main/java/com/example/jlama/dto/ChatRequest.java
package com.example.jlama.dto;
public class ChatRequest {
private String prompt;
// Getters and Setters
public String getPrompt() {
return prompt;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
}
}
3. 实现核心业务逻辑 (JlamaService.java)
在 Service 层,我们将注入刚刚配置好的 ChatLanguageModel,并编写一个方法来调用它。
// src/main/java/com/example/jlama/service/JlamaService.java
package com.example.jlama.service;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class JlamaService {
private static final Logger logger = LoggerFactory.getLogger(JlamaService.class);
private final ChatLanguageModel model;
// 构造器注入 ChatLanguageModel
public JlamaService(ChatLanguageModel model) {
this.model = model;
}
public String generateResponse(String prompt) {
logger.info("收到请求: {}", prompt);
// 调用模型生成响应
String response = model.generate(prompt);
logger.info("生成完成");
return response;
}
}
4. 创建 REST 控制器 (ChatController.java)
最后,创建一个 Controller 来暴露 API,处理前端的请求。
// src/main/java/com/example/jlama/controller/ChatController.java
package com.example.jlama.controller;
import com.example.jlama.dto.ChatRequest;
import com.example.jlama.service.JlamaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.HashMap;
@RestController
@RequestMapping("/api")
public class ChatController {
@Autowired
private JlamaService jlamaService;
@PostMapping("/chat")
public Map<String, String> chat(@RequestBody ChatRequest request) {
String response = jlamaService.generateResponse(request.getPrompt());
Map<String, String> result = new HashMap<>();
result.put("response", response);
return result;
}
}
🌐 第四步:创建 Web 交互界面
-
创建 HTML 页面 (
index.html) :在src/main/resources/static目录下创建index.html文件。 -
编写前端代码 (
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>DeepSeek 本地助手</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; } .container { max-width: 800px; margin: 0 auto; background: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; } .header { background: #1a1a2e; color: white; padding: 20px; text-align: center; } .chat-box { height: 400px; overflow-y: auto; padding: 20px; background: #f9f9f9; } .message { margin-bottom: 15px; padding: 10px 15px; border-radius: 10px; max-width: 80%; word-wrap: break-word; } .user { background: #007bff; color: white; margin-left: auto; text-align: right; } .assistant { background: #e9ecef; color: #333; margin-right: auto; } .input-area { display: flex; padding: 20px; background: white; border-top: 1px solid #ddd; } .input-area input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; } .input-area button { margin-left: 10px; padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } .input-area button:hover { background: #0056b3; } .loading { text-align: center; color: #666; padding: 10px; display: none; } </style> </head> <body>index.html) :编辑index.html,实现一个简单的聊天界面。🤖 DeepSeek 本地助手
基于 Jlama 和 DeepSeek 4B 模型
你好!我是 DeepSeek 本地助手,有什么我可以帮你的吗?思考中...<button id="send-btn">发送</button></body> </html><script> const chatBox = document.getElementById('chat-box'); const userInput = document.getElementById('user-input'); const sendBtn = document.getElementById('send-btn'); const loading = document.getElementById('loading'); function appendMessage(text, sender) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${sender}`; messageDiv.textContent = text; chatBox.appendChild(messageDiv); chatBox.scrollTop = chatBox.scrollHeight; } async function sendMessage() { const prompt = userInput.value.trim(); if (!prompt) return; appendMessage(prompt, 'user'); userInput.value = ''; loading.style.display = 'block'; try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: prompt }), }); if (!response.ok) { throw new Error('网络响应失败'); } const data = await response.json(); appendMessage(data.response, 'assistant'); } catch (error) { console.error('Error:', error); appendMessage('抱歉,发生了错误。请检查后端服务是否正常运行。', 'assistant'); } finally { loading.style.display = 'none'; } } sendBtn.addEventListener('click', sendMessage); userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendMessage(); } }); </script>
▶️ 第五步:运行与测试
- 编译与构建 :打开终端,进入项目根目录,执行
mvn clean compile,让 Maven 来下载所有依赖。 - 启动应用 :在 IDE 中右键运行
SpringBootJlamaDemoApplication.java的main方法。 - 访问界面 :打开浏览器,访问
http://localhost:8080,就可以看到聊天界面了。
⚙️ 高级配置:使用本地模型
如果你想使用下载好的本地模型,只需修改 src/main/resources/application.yml 文件即可。
# src/main/resources/application.yml
jlama:
model:
# 使用本地路径
path: /absolute/path/to/your/deepseek-4b-model
🔧 常见问题与解决
- Q1: 模型加载失败怎么办?
- A : 如果模型需要从 Hugging Face 下载,你可以手动下载
.safetensors文件和config.json,然后将它们放在项目的./models文件夹下。修改application.yml中的path为本地的模型文件夹路径即可。
- Q2: 为什么我的想法与代码不一致?
- A : 正如你提到的,本地加载其实指向的是 Hugging Face 模型名,这背后是 Jlama 自动处理了下载与缓存。我们可以优化
JlamaConfig.java,让模型真正"离线"。例如,使用ModelSupport.loadModel方式,或自定义模型加载路径。
好了,项目到这里就搭建完毕了。如果在运行中遇到任何问题,随时告诉我,我们一起解决~