如何使用Jlama和LangChain4j搭建Java本地AI离线问答系统

人工智能 潘老师 1个月前 (03-17) 59 ℃ (0) 扫码查看

最近在研究Java本地AI推理的时候,发现了一个挺有意思的组合——Jlama和LangChain4j,用它们可以搭建一个离线问答系统。今天就来给大伙分享一下具体咋做,我尽量讲的详细一点。

一、前期准备工作得做好

在开始动手搭建之前,有些东西得提前准备好。

  1. 开发环境相关:你得装上JDK 21,这是基础的运行环境。开发工具的话,推荐使用IDEA 2023.3.6社区版,用起来顺手。构建工具选择Maven,它能帮我们管理项目依赖,可方便了。
  2. 知识储备:还得懂点Spring Boot的基础知识,前端开发也得有点经验,这样后面写代码、调程序的时候才不会抓瞎。
  3. 模型下载:模型要从Hugging Face下载到本地。如果网络状况好,代码运行的时候也能自动下载,但咱保险起见,还是手动下载吧。下载完后,在本地创建一个叫tjake_Llama-3.2-1B-Instruct-JQ4的目录,把下载的文件一股脑全放进去,然后手动创建一个.finished文件,就像下面展示的这样:
finished
config.json
gitattributes
model.safetensors
README.md
tokenizer.json
tokenizer_config.json

二、项目代码

(一)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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>java-ai</groupId>
  <artifactId>io.ai</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>io.ai</name>
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>21</maven.compiler.release>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>3.4.3</version>
    </dependency>
    <dependency>
      <groupId>com.github.tjake</groupId>
      <artifactId>jlama-core</artifactId>
      <version>0.8.4</version>
    </dependency>
    <dependency>
      <groupId>com.github.tjake</groupId>
      <artifactId>jlama-native</artifactId>
      <!-- supports linux-x86_64, macos-x86_64/aarch_64, windows-x86_64
           Use https://github.com/trustin/os-maven-plugin to detect os and arch -->
      <classifier>windows-x86_64</classifier>
      <version>0.8.4</version>
    </dependency>
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j</artifactId>
      <version>1.0.0-beta2</version>
    </dependency>
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-jlama</artifactId>
      <version>1.0.0-beta2</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
        <configuration>
          <source>21</source>
          <target>21</target>
          <encoding>UTF-8</encoding>
            <compilerArgs>--enable-preview</compilerArgs>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

这里面,<dependencies>标签里的内容就是项目依赖的各种库。比如spring-boot-starter-web是Spring Boot的Web开发依赖,jlama-corejlama-native是Jlama相关的核心库,langchain4jlangchain4j-jlama则是LangChain4j相关的库,它们能帮助我们实现和Jlama模型的交互。<build>标签里配置了Maven编译器和Spring Boot Maven插件,保证项目能正常编译和打包。

(二)服务接口代码

服务接口这块用了SSE(Server-Sent Events),当然也可以直接通过ChatLanguageModel模型同步输出结果。代码如下:

package io.ai;

import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.UUID;

@RestController
public class ChatController {

    @Autowired
    private StreamingChatLanguageModel model;

    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter chat(@RequestParam("prompt") String prompt) {
        // 创建一个 SseEmitter 实例,用于服务器向客户端发送事件流数据
        SseEmitter emitter = new SseEmitter();
        model.chat(prompt, new StreamingChatResponseHandler() {
            @Override
            public void onPartialResponse(String partialResponse) {
                //System.out.println("partialResponse\n" + partialResponse);
                try {
                    // 将模型生成的部分响应数据发送给客户端,每个数据块都有唯一的ID,名称为message
                    emitter.send(SseEmitter
                                    .event()
                                    .id(UUID.randomUUID().toString())
                                    .name("message").data(partialResponse));
                } catch (Exception e) {
                    // 如果发送过程中出现错误,结束事件流并返回错误信息
                    emitter.completeWithError(e);
                }
            }
            @Override
            public void onCompleteResponse(ChatResponse completeResponse) {
                //System.out.println("completeResponse\n" + completeResponse.aiMessage().text());
                //emitter.complete();
            }

            @Override
            public void onError(Throwable error) {
                // 当出现错误时,结束事件流并返回错误信息
                emitter.completeWithError(error);
            }
        });

        return emitter;
    }
}

这段代码定义了一个ChatController类,它是一个Spring的RestControllerchat方法接收一个prompt参数,也就是用户的提问。通过StreamingChatLanguageModelStreamingChatResponseHandler,模型会对用户的提问进行处理,然后把生成的响应数据通过SseEmitter发送给前端。

(三)启动类代码

注意,启动时需要配置IDEA Edit Configurations 增加参数VM参数 --add-modules jdk.incubator.vector

启动类代码如下:

package io.ai;

import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.jlama.JlamaStreamingChatModel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.nio.file.Path;

@SpringBootApplication
public class App {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(App.class);
    }

    @Bean
    public StreamingChatLanguageModel chatLanguageMode(){
        return JlamaStreamingChatModel.builder()
                .modelCachePath(Path.of("D:\\dev_llm\\jlama\\models"))
                .modelName("tjake/Llama-3.2-1B-Instruct-JQ4")
                .temperature(0.7f)
                .build();
    }
}

这个App类是Spring Boot的启动类。main方法启动Spring Boot应用。chatLanguageMode方法是一个Spring的Bean定义,它配置了JlamaStreamingChatModel,指定了模型的缓存路径、模型名称和温度参数。温度参数可以理解为控制模型生成文本的随机性,数值越高,生成的文本越随机;数值越低,生成的文本越保守。

(四)前端界面代码

前端界面虽然简单,但也能满足基本的交互需求。代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>在线问答Demo</title>
    <style>
        #answer {
            height: 300px;
            overflow-y: auto;
            border: 1px solid #ccc;
            margin-top: 10px;
            padding: 10px;
            white-space: pre-wrap;
        }
    </style>
</head>
<body>
<h1>在线问答</h1>
<input type="text" id="question" style="width:700px;" placeholder="输入您的问题">
<button onclick="askQuestion()">提问</button>
<div id="answer"></div>

<script>
    function askQuestion() {
        const question = document.getElementById('question').value;
        const answerDiv = document.getElementById('answer');
        // 清空之前的回答,保证每次提问后答案区域都是干净的
        answerDiv.innerHTML = '';

        // 创建 EventSource 连接到 SSE 端点,把用户的问题作为参数传过去
        const eventSource = new EventSource(`/chat?prompt=${question}`);

        // 监听消息事件,接收到服务器推送的消息后,把数据添加到答案区域
        eventSource.onmessage = (event) => {
            answerDiv.textContent += event.data + ' ';
        };

        // 错误处理,如果连接出错,在答案区域显示错误信息并关闭连接
        eventSource.onerror = (err) => {
            console.error("EventSource failed:", err);
            answerDiv.innerHTML += "Error: 与服务器通信时发生错误。";
            eventSource.close();
        };

        // 关闭处理,连接关闭时在答案区域显示连接关闭的提示
        eventSource.onclose = () =>{
             answerDiv.innerHTML += "连接关闭";
        }
    }
</script>
</body>
</html>

这段HTML代码创建了一个简单的页面,有一个输入框让用户输入问题,一个按钮用于提交问题,还有一个区域用来显示答案。JavaScript代码负责实现交互逻辑,通过EventSource与后端的SSE端点建立连接,接收并展示模型的回答。

三、最终效果展示

目前这个界面做得还比较简陋,不过核心的离线问答功能已经跑通了。你在输入框里输入问题,比如“Help me write a java version of the palindromic algorithm”,点击提问按钮后,就能看到模型给出的回答。虽然界面不咋美观,但咱先把功能实现了,后面有时间再慢慢优化界面。

用Jlama和LangChain4j搭建Java本地AI离线问答系统就是这么个流程。要是你在搭建过程中遇到啥问题,欢迎在评论区留言,咱一起讨论解决。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/ai/15905.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】