Java集成大模型LangChain4j實戰(zhàn)完全指南(推薦)
一、LangChain4j簡介和使用
LangChain4j 的目標是簡化將大語言模型(LLM - Large Language Model)集成到 Java 應(yīng)用程序中的過程。
1.1、官網(wǎng):https://docs.langchain4j.dev
1.2、主要功能
與大型語言模型和向量數(shù)據(jù)庫的便捷交互
通過統(tǒng)一的應(yīng)用程序編程接口(API),可以輕松訪問所有主要的商業(yè)和開源大型語言模型以及向量數(shù)據(jù)庫,使你能夠構(gòu)建聊天機器人、智能助手等應(yīng)用。
專為 Java 打造
借助Spring Boot 集成,能夠?qū)⒋竽P图傻絘va 應(yīng)用程序中。大型語言模型與 Java 之間實現(xiàn)了雙向集成:你可以從 Java 中調(diào)用大型語言模型,同時也允許大型語言模型反過來調(diào)用你的 Java 代碼
智能代理、工具、檢索增強生成(RAG)
為常見的大語言模型操作提供了廣泛的工具,涵蓋從底層的提示詞模板創(chuàng)建、聊天記憶管理和輸出解析,到智能代理和檢索增強生成等高級模式。
二、創(chuàng)建一個springboot項目
2.1、創(chuàng)建Maven項目

(在完成SpringBoot項目的測試和調(diào)試后,我最終將其集成到了若依框架中)
2.2、添加相對應(yīng)的依賴,示例如下:
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.2.6</spring-boot.version>
<knife4j.version>4.3.0</knife4j.version>
<langchain4j.version>1.0.0-beta3</langchain4j.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version>
<!-- 添加 Logback 版本屬性,使用兼容 SLF4J 2.x 的版本 -->
<logback.version>1.4.12</logback.version>
<!-- 添加兼容 Spring Boot 3.2.6 的 Tomcat 版本 -->
<tomcat.version>10.1.24</tomcat.version>
</properties>
<dependencies>
<!-- Pinecone Java 客戶端 -->
<dependency>
<groupId>io.pinecone</groupId>
<artifactId>pinecone-client</artifactId>
<version>5.0.0</version>
</dependency>
<!-- JSON 處理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- web應(yīng)用程序核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除 javax.servlet 依賴,避免與 jakarta.servlet 沖突 -->
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加 jakarta.servlet-api 依賴 -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- 編寫和運行測試用例 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 基于open-ai的langchain4j接口:ChatGPT、deepseek都是open-ai標準下的大模型 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>
<!-- 接入ollama -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
</dependency>
<!-- 前后端分離中的后端接口測試工具 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- 接入阿里云百煉平臺 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>
<!-- langchain4j高級功能 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot Starter Data MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- mybatis-plus 持久層 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 解析pdf文檔 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<!-- 簡單的rag實現(xiàn) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
</dependency>
<!-- 集成pine -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pinecone</artifactId>
</dependency>
<!-- 流式輸出 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--引入SpringBoot依賴管理清單-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--引入langchain4j依賴管理清單-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--引入百煉依賴管理清單-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 覆蓋logback的依賴配置,使用兼容 SLF4J 2.x 的版本 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- 覆蓋tomcat的依賴配置,使用兼容 Spring Boot 3.2.6 的版本 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>
</dependencyManagement>2.3、創(chuàng)建配置文件,示例如下:
# web服務(wù)訪問端口
server.port=8087
#langchain4j測試模型
#langchain4j.open-ai.chat-model.base-url=http://langchain4j.dev/demo/openai/v1
#langchain4j.open-ai.chat-model.api-key=demo
#langchain4j.open-ai.chat-model.model-name=gpt-4o-mini
##DEEP_SEEK_API_KEY=這個是deepseek的apikey
##DeepSeek
#langchain4j.open-ai.chat-model.base-url=https://api.deepseek.com
#langchain4j.open-ai.chat-model.api-key=${DEEP_SEEK_API_KEY}
##DeepSeek-V3
#langchain4j.open-ai.chat-model.model-name=deepseek-chat
DASH_SCOPE_API_KEY=阿里云百煉大模型的apikey
#阿里百煉平臺
langchain4j.community.dashscope.chat-model.api-key=${DASH_SCOPE_API_KEY}
langchain4j.community.dashscope.chat-model.model-name=qwen-max
#集成百煉-deepseek
langchain4j.open-ai.chat-model.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1
langchain4j.open-ai.chat-model.api-key=${DASH_SCOPE_API_KEY}
langchain4j.open-ai.chat-model.model-name=deepseek-v3
#溫度系數(shù):取值范圍通常在 0 到 1 之間。值越高,模型的輸出越隨機、富有創(chuàng)造性;
# 值越低,輸出越確定、保守。這里設(shè)置為 0.9,意味著模型會有一定的隨機性,生成的回復可能會比較多樣化。
langchain4j.open-ai.chat-model.temperature=0.9
#ollama
langchain4j.ollama.chat-model.base-url=http://localhost:11434
langchain4j.ollama.chat-model.model-name=deepseek-r1:1.5b
# 創(chuàng)造性控制
# 0.7到1.2?:適用于需要較高創(chuàng)造性的場景,如詩歌生成或頭腦風暴
# 0.3到1.0?:適用于需要較高穩(wěn)定性和多樣性的場景,如技術(shù)文檔或法律文書的生成
langchain4j.ollama.chat-model.temperature=0.8
# 模型進行通信的超時時間為60秒。
langchain4j.ollama.chat-model.timeout=PT60S
langchain4j.ollama.chat-model.log-requests=true
langchain4j.ollama.chat-model.log-responses=true
#MongoDB連接配置
spring.data.mongodb.uri=mongodb://localhost:27017/chat_memory_db
# 基本數(shù)據(jù)源配置
spring.datasource.url=jdbc:mysql://localhost:3306/guiguxiaozhi?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 開啟 SQL 日志打印
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#集成阿里通義千問-通用文本向量-v3
langchain4j.community.dashscope.embedding-model.api-key=${DASH_SCOPE_API_KEY}
langchain4j.community.dashscope.embedding-model.model-name=text-embedding-v3
#集成阿里通義千問-流式輸出
langchain4j.community.dashscope.streaming-chat-model.api-key=${DASH_SCOPE_API_KEY}
langchain4j.community.dashscope.streaming-chat-model.model-name=qwen-plus2.4、創(chuàng)建啟動類,如下所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RuoyiLangChain4jApplication {
public static void main(String[] args) {
SpringApplication.run(RuoyiLangChain4jApplication.class, args);
}
}2.5、進行相對應(yīng)的測試,如下示例(放在test下面進行測試):
我在編寫AI程序時,總會進行一些測試環(huán)節(jié)。
接入任何一個大模型都需要先去申請apiKey。
如果你暫時沒有密鑰,也可以使用LangChain4j 提供的演示密鑰,這個密鑰是免費的,有使用配額限制,且僅限于 gpt-4o-mini 模型
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class LLMTest {
/**
* gpt-4o-mini語言模型接入測試
*/
@Test
public void testGPTDemo() {
//初始化模型
OpenAiChatModel model = OpenAiChatModel.builder()
//LangChain4j提供的代理服務(wù)器,該代理服務(wù)器會將演示密鑰替換成真實密鑰, 再將請求轉(zhuǎn)發(fā)給OpenAI API
//.baseUrl("http://langchain4j.dev/demo/openai/v1") //設(shè)置模型api地址(如果apiKey="demo",則可省略baseUrl的配置)
.apiKey("demo") //設(shè)置模型apiKey
.modelName("gpt-4o-mini") //設(shè)置模型名稱
.build();
//向模型提問
String answer = model.chat("你好");
//輸出結(jié)果
System.out.println(answer);
}
}2.6、由于之前已經(jīng)提供了完整的依賴配置,現(xiàn)在可以直接進行后續(xù)測試:
測試均在LLMTest測試類中完成
/**
* 整合SpringBoot
*/
@Autowired
private OpenAiChatModel openAiChatModel;
@Test
public void testSpringBoot() {
//向模型提問
String answer = openAiChatModel.chat("你好");
//輸出結(jié)果
System.out.println(answer);
}三、接入其他的大模型
3.1、大語言模型排行榜:https://superclueai.com/
LangChain4j支持接入的大模型:https://docs.langchain4j.dev/integrations/language-models/
3.2、接入deepseek模型(步驟如下):
3.2.1、獲取開發(fā)參數(shù)
訪問官網(wǎng):https://www.deepseek.com/ 注冊賬號,獲取base_url和api_key,充值1元即可
(注意:幾乎不會使用開放平臺的模型,因為在阿里云的百煉大模型中已經(jīng)都給予包含)
3.2.2、為了apikay的安全,建議將其配置在服務(wù)器的環(huán)境變量中。變量名自定義即可,例如DEEP_SEEK_API_KEY,如圖所示:

3.2.3、需要配置模型的參數(shù),之前提供了完整的配置,只需要改apikey即可
3.2.4、進行測試,直接在之前用例測試即可
四、ollama本地部署
4.1、Ollama 是一個本地部署大模型的工具。使用 Ollama 進行本地部署有以下多方面的原因:
- 數(shù)據(jù)隱私與安全:對于金融、醫(yī)療、法律等涉及大量敏感數(shù)據(jù)的行業(yè),數(shù)據(jù)安全至關(guān)重要。
- 離線可用性:在網(wǎng)絡(luò)不穩(wěn)定或無法聯(lián)網(wǎng)的環(huán)境中,本地部署的 Ollama 模型仍可正常運行。
- 降低成本:云服務(wù)通常按使用量收費,長期使用下來費用較高。而 Ollama 本地部署,只需一次性投入硬件成本,對于需要頻繁使用大語言模型且對成本敏感的用戶或企業(yè)來說,能有效節(jié)約成本。
- 部署流程簡單:只需通過簡單的命令 “ollama run < 模型名>”,就可以自動下載并運行所需的模型。
- 靈活擴展與定制:可對模型微調(diào),以適配垂直領(lǐng)域需求。
4.2、在Ollama部署Deepseek,示例如下:
官網(wǎng):https://ollama.com/
4.2.1、(1)下載并安裝ollama:OllamaSetup.exe
(2)查看模型列表,選擇要部署的模型,模型列表: https://ollama.com/search

4.2.2、下載deepseek模型,如下所示:


復制執(zhí)行命令:ollama run deepseek-r1:1.5運行大模型。如果是第一次運行則會先下載大模型
4.2.3、進行測試:
/**
* ollama接入
*/
@Autowired
private OllamaChatModel ollamaChatModel;
@Test
public void testOllama() {
//向模型提問
String answer = ollamaChatModel.chat("你好");
//輸出結(jié)果
System.out.println(answer);
}五、接入阿里百煉平臺
5.1、什么是阿里百煉
- 阿里云百煉是 2023 年 10 月推出的。它集成了阿里的通義系列大模型和第三方大模型,涵蓋文本、圖像、音視頻等不同模態(tài)。
- 功能優(yōu)勢:集成超百款大模型 API,模型選擇豐富;5-10 分鐘就能低代碼快速構(gòu)建智能體,應(yīng)用構(gòu)建高效;提供全鏈路模型訓練、評估工具及全套應(yīng)用開發(fā)工具,模型服務(wù)多元;在線部署可按需擴縮容,新用戶有千萬 token 免費送,業(yè)務(wù)落地成本低。
- 支持接入的模型列表:https://help.aliyun.com/zh/model-studio/models
- 模型廣場:https://bailian.console.aliyun.com/?productCode=p_efm#/model-market

如需開通,請點擊"免費開通"按鈕。完成開通后,創(chuàng)建API密鑰并粘貼至指定位置即可。
5.2、測試通義千問
/**
* 通義千問大模型
*/
@Autowired
private QwenChatModel qwenChatModel;
@Test
public void testDashScopeQwen() {
//向模型提問
String answer = qwenChatModel.chat("你好");
//輸出結(jié)果
System.out.println(answer);
}5.3、測試通義萬象
圖片生成可能無法順利完成
@Test
public void testDashScopeWanx(){
WanxImageModel wanxImageModel = WanxImageModel.builder()
.modelName("wanx2.1-t2i-plus")
.apiKey(System.getenv("DASH_SCOPE_API_KEY"))
.build();
Response<Image> response = wanxImageModel.generate("奇幻森林精靈:在一片彌漫著輕柔薄霧的古老森林深處,陽光透過茂密枝葉灑下金色光斑。一位身材嬌小、長著透明薄翼的精靈少女站在一朵碩大的蘑菇上。她有著海藻般的綠色長發(fā),發(fā)間點綴著藍色的小花,皮膚泛著珍珠般的微光。身上穿著由翠綠樹葉和白色藤蔓編織而成的連衣裙,手中捧著一顆散發(fā)著柔和光芒的水晶球,周圍環(huán)繞著五彩斑斕的蝴蝶,腳下是鋪滿苔蘚的地面,蘑菇和蕨類植物叢生,營造出神秘而夢幻的氛圍。");
System.out.println(response.content().url());
}5.4、測試DeepSeek
使用之前的測試用例即可
六、人工服務(wù)AiService
6.1、什么是AiService
AIService使用面向接口和動態(tài)代理的方式完成程序的編寫,更靈活的實現(xiàn)高級功能。
6.2、人工智能服務(wù) AIService
在LangChain4j中我們使用AIService完成復雜操作。底層組件將由AIService進行組裝。
AIService可處理最常見的操作:
- 為大語言模型格式化輸入內(nèi)容
- 解析大語言模型的輸出結(jié)果
它們還支持更高級的功能:
- 聊天記憶 Chat memory
- 工具 Tools
- 檢索增強生成 RAG
6.3、創(chuàng)建assistant(不是在test下面創(chuàng)建):
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
@AiService(
wiringMode = EXPLICIT,
streamingChatModel = "qwenStreamingChatModel",
chatMemoryProvider = "chatMemoryProviderXiaozhi",
tools = "appointmentTools",
contentRetriever = "contentRetrieverXiaozhiPincone" )
public interface XiaozhiAgent {
@SystemMessage(fromResource = "zhaozhi-prompt-template.txt")
Flux<String> chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}6.4、測試用例中,我們可以直接注入Assistant對象
@Autowired
private Assistant assistant;
@Test
public void testAssistant() {
String answer = assistant.chat("Hello");
System.out.println(answer);
}七、進行聊天記憶ChatMemory
7.1、測試模型有沒有聊天記憶(你可以選擇繼續(xù)在test下面進行測試,也可以單獨創(chuàng)建一個測試):
@SpringBootTest
public class ChatMemoryTest {
@Autowired
private Assistant assistant;
@Test
public void testChatMemory() {
String answer1 = assistant.chat("我是華仔");
System.out.println(answer1);
String answer2 = assistant.chat("我是誰");
System.out.println(answer2);
}
}測試完很顯然模型是沒有記憶的
7.2、聊天記憶的簡單實現(xiàn):
import com.guigu.ai.langchain4j.assistant.Assistant;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.response.ChatResponse;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
@SpringBootTest
public class ChatMemoryTest {
@Autowired
private Assistant assistant;
@Autowired
private QwenChatModel qwenChatModel;
@Test
public void testChatMemory2() {
UserMessage userMessage1 = UserMessage.userMessage("我是華仔");
ChatResponse chatResponse1 = qwenChatModel.chat(userMessage1);
AiMessage aiMessage1 = chatResponse1.aiMessage();
System.out.println(aiMessage1.text());
UserMessage userMessage2 = UserMessage.userMessage("我是誰");
ChatResponse chatResponse2= qwenChatModel.chat(Arrays.asList(userMessage1,aiMessage1,userMessage2));
AiMessage aiMessage2 = chatResponse2.aiMessage();
System.out.println(aiMessage2.text());
}
}7.3、使用ChatMemory進行聊天記憶
使用AIService可以封裝多輪對話的復雜性,使聊天記憶功能的實現(xiàn)變得簡單
@Test
public void testChatMemory3() {
//創(chuàng)建chatMemory
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
//創(chuàng)建AIService
Assistant assistant = AiServices
.builder(Assistant.class)
.chatLanguageModel(qwenChatModel)
.chatMemory(chatMemory)
.build();
//調(diào)用service的接口
String answer1 = assistant.chat("我是華仔");
System.out.println(answer1);
String answer2 = assistant.chat("我是誰");
System.out.println(answer2);
}7.4、使用AiService進行聊天記憶
當AIService由多個組件(大模型,聊天記憶,等)組成的時候,我們就可以稱他為智能體了
由于在之前的時候我已經(jīng)提供了完整的AIService類直接可以使用
7.4.1、創(chuàng)建config包(注意不是在test下面創(chuàng)建):
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MemoryChatAssistantConfig {
@Bean
ChatMemory chatMemory(){
//設(shè)置聊天記憶記錄的message數(shù)量
return MessageWindowChatMemory.withMaxMessages(10);
}
}7.4.2、測試
@Autowired
private MemoryChatAssistant memoryChatAssistant;
@Test
public void testChatMemory4() {
String answer1 = memoryChatAssistant.chat("我是華仔");
System.out.println(answer1);
String answer2 = memoryChatAssistant.chat("我是誰");
System.out.println(answer2);
}7.5、實現(xiàn)隔離聊天記憶
在config包下創(chuàng)建SeparateChatAssistantConfig類
import com.ruoyi.langchain4j.store.MongoChatMemoryStore;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SeparateChatAssistantConfig {
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
@Bean
ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(10)
.chatMemoryStore(mongoChatMemoryStore)//配置持久化對象
.build();
}
}7.5.1、測試聊天記憶
@Autowired
private SeparateChatAssistant separateChatAssistant;
@Test
public void testChatMemory5() {
String answer1 = separateChatAssistant.chat(1,"我是華仔");
System.out.println(answer1);
String answer2 = separateChatAssistant.chat(1,"我是誰");
System.out.println(answer2);
String answer3 = separateChatAssistant.chat(2,"我是誰");
System.out.println(answer3);
}八、持久化聊天記憶 Persistence
8.1、存儲介質(zhì)選擇
大模型中聊天記憶的存儲選擇哪種數(shù)據(jù)庫,需要綜合考慮數(shù)據(jù)特點、應(yīng)用場景和性能要求等因素,以下是一些常見的選擇及其特點:
- MySQL
- 特點:關(guān)系型數(shù)據(jù)庫。支持事務(wù)處理,確保數(shù)據(jù)的一致性和完整性,適用于結(jié)構(gòu)化數(shù)據(jù)的存儲和查詢。
- 適用場景:如果聊天記憶數(shù)據(jù)結(jié)構(gòu)較為規(guī)整,例如包含固定的字段如對話 ID、用戶 ID、時間戳、消息內(nèi)容等,且需要進行復雜的查詢和統(tǒng)計分析,如按用戶統(tǒng)計對話次數(shù)、按時間范圍查詢特定對話等,MySQL 是不錯的選擇。
- Redis
- 特點:內(nèi)存數(shù)據(jù)庫,讀寫速度極高。它適用于存儲熱點數(shù)據(jù),并且支持多種數(shù)據(jù)結(jié)構(gòu),如字符串、哈希表、列表等,方便對不同類型的聊天記憶數(shù)據(jù)進行處理。
- 適用場景:對于實時性要求極高的聊天應(yīng)用,如在線客服系統(tǒng)或即時通訊工具,Redis 可以快速存儲和獲取最新的聊天記錄,以提供流暢的聊天體驗。
- MongoDB
- 特點:文檔型數(shù)據(jù)庫,數(shù)據(jù)以 JSON - like 的文檔形式存儲,具有高度的靈活性和可擴展性。它不需要預先定義嚴格的表結(jié)構(gòu),適合存儲半結(jié)構(gòu)化或非結(jié)構(gòu)化的數(shù)據(jù)。
- 適用場景:當聊天記憶中包含多樣化的信息,如文本消息、圖片、語音等多媒體數(shù)據(jù),或者消息格式可能會頻繁變化時,MongoDB 能很好地適應(yīng)這種靈活性。例如,一些社交應(yīng)用中用戶可能會發(fā)送各種格式的消息,使用 MongoDB 可以方便地存儲和管理這些不同類型的數(shù)據(jù)。
- Cassandra
- 特點:是一種分布式的 NoSQL 數(shù)據(jù)庫,具有高可擴展性和高可用性,能夠處理大規(guī)模的分布式數(shù)據(jù)存儲和讀寫請求。適合存儲海量的、時間序列相關(guān)的數(shù)據(jù)。
- 適用場景:對于大型的聊天應(yīng)用,尤其是用戶量眾多、聊天數(shù)據(jù)量巨大且需要分布式存儲和處理的場景,Cassandra 能夠有效地應(yīng)對高并發(fā)的讀寫操作。例如,一些面向全球用戶的社交媒體平臺,其聊天數(shù)據(jù)需要在多個節(jié)點上進行分布式存儲和管理,Cassandra 可以提供強大的支持。
8.2、MongoDB
8.2.1、
MongoDB 是一個基于文檔的 NoSQL 數(shù)據(jù)庫,由 MongoDB Inc. 開發(fā)。
NoSQL,指的是非關(guān)系型的數(shù)據(jù)庫。NoSQL有時也稱作Not Only SQL的縮寫,是對不同于傳統(tǒng)的關(guān)系型數(shù)據(jù)庫的數(shù)據(jù)庫管理系統(tǒng)的統(tǒng)稱。
MongoDB 的設(shè)計理念是為了應(yīng)對大數(shù)據(jù)量、高性能和靈活性需求。
MongoDB使用集合(Collections)來組織文檔(Documents),每個文檔都是由鍵值對組成的。
- 數(shù)據(jù)庫(Database):存儲數(shù)據(jù)的容器,類似于關(guān)系型數(shù)據(jù)庫中的數(shù)據(jù)庫。
- 集合(Collection):數(shù)據(jù)庫中的一個集合,類似于關(guān)系型數(shù)據(jù)庫中的表。
- 文檔(Document):集合中的一個數(shù)據(jù)記錄,類似于關(guān)系型數(shù)據(jù)庫中的行(row),以 BSON 格式存儲。
8.2.2、安裝
下載網(wǎng)址:https://www.mongodb.com/try/download/shell

服務(wù)器:mongodb-windows-x86_64-8.0.6-signed.msi https://www.mongodb.com/try/download/community
下載并安裝
8.2.3、使用mongosh
啟動 MongoDB Shell:
在命令行中輸入 mongosh 命令,啟動 MongoDB Shell,如果 MongoDB 服務(wù)器運行在本地默認端口(27017),則可以直接連接。
mongosh
連接到 MongoDB 服務(wù)器:
如果 MongoDB 服務(wù)器運行在非默認端口或者遠程服務(wù)器上,可以使用以下命令連接:
mongosh --host <hostname>:<port>
其中 <hostname> 是 MongoDB 服務(wù)器的主機名或 IP 地址,<port> 是 MongoDB 服務(wù)器的端口號。
8.2.4、創(chuàng)建bean包(注意不是在test下面創(chuàng)建):
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document("chat_messages")
public class ChatMessages {
@Id
private ObjectId id;
private int messageId;
//存聊天記錄的
private String content;
}8.2.5、創(chuàng)建測試類
@SpringBootTest
public class MongoCrudTest {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 插入文檔
*/
/* @Test
public void testInsert() {
mongoTemplate.insert(new ChatMessages(1L, "聊天記錄"));
}*/
/**
* 插入文檔
*/
@Test
public void testInsert2() {
ChatMessages chatMessages = new ChatMessages();
chatMessages.setContent("聊天記錄列表");
mongoTemplate.insert(chatMessages);
}
/**
* 根據(jù)id查詢文檔
*/
@Test
public void testFindById() {
ChatMessages chatMessages = mongoTemplate.findById("6801ead733ba9c4a0d9b6c7b", ChatMessages.class);
System.out.println(chatMessages);
}
/**
* 修改文檔
*/
@Test
public void testUpdate() {
Criteria criteria = Criteria.where("_id").is("6801ead733ba9c4a0d9b6c7b");
Query query = new Query(criteria);
Update update = new Update();
update.set("content", "新的聊天記錄列表");
//修改或新增
mongoTemplate.upsert(query, update, ChatMessages.class);
}
/**
* 新增或修改文檔
*/
@Test
public void testUpdate2() {
Criteria criteria = Criteria.where("_id").is("100");
Query query = new Query(criteria);
Update update = new Update();
update.set("content", "新的聊天記錄列表");
//修改或新增
mongoTemplate.upsert(query, update, ChatMessages.class);
}
/**
* 刪除文檔
*/
@Test
public void testDelete() {
Criteria criteria = Criteria.where("_id").is("100");
Query query = new Query(criteria);
mongoTemplate.remove(query, ChatMessages.class);
}
}
8.3、創(chuàng)建持久化類
創(chuàng)建一個store包(注意不是在test下面)
import com.ruoyi.langchain4j.bean.ChatMessages;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;
@Component
public class MongoChatMemoryStore implements ChatMemoryStore {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public List<ChatMessage> getMessages(Object memoryId) {
Criteria memoryId1 = Criteria.where("memoryId").is(memoryId);
Query query = new Query(memoryId1);
ChatMessages one = mongoTemplate.findOne(query, ChatMessages.class);
if(one == null){
return new LinkedList<>();
}
return ChatMessageDeserializer.messagesFromJson(one.getContent());
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
Criteria criteria = Criteria.where("memoryId").is(memoryId);
Query query = new Query(criteria);
Update update = new Update();
update.set("content", ChatMessageSerializer.messagesToJson(messages));
//根據(jù)query條件能查詢出文檔,則修改文檔;否則新增文檔
mongoTemplate.upsert(query, update, ChatMessages.class);
}
@Override
public void deleteMessages(Object memoryId) {
Criteria criteria = Criteria.where("memoryId").is(memoryId);
Query query = new Query(criteria);
mongoTemplate.remove(query, ChatMessages.class);
}
}8.4、提示詞Prompt
8.4.1、系統(tǒng)提示詞
@SystemMessage 設(shè)定角色,塑造AI助手的專業(yè)身份,明確助手的能力范圍
在SeparateChatAssistant類的chat方法上添加@SystemMessage注解
之前有提供完整的方法,所以不需要進行這一步測試
九、創(chuàng)建智能助手(是根據(jù)硅谷小智來更改)
9.1、提供提示詞模板
我的項目涉及智能車聯(lián)網(wǎng)領(lǐng)域,因此撰寫的提示詞主要圍繞車聯(lián)網(wǎng)相關(guān)內(nèi)容展開。
你的名字是“智能車聯(lián)的助理”,你是幫助人們答惑解疑的智能客服。
你是一個擁有海量智能車聯(lián)的智能小助手。
你態(tài)度友好、溫柔、禮貌且言辭簡潔。
1、請僅在用戶發(fā)起第一次會話時,和用戶打個招呼,并介紹你是誰。
一、基本信息
角色名稱:智能車聯(lián)系統(tǒng)智能助理
核心定位:專注于智能車聯(lián)領(lǐng)域的服務(wù)型助理,為用戶解決智能車聯(lián)系統(tǒng)使用中的各類問題,提供專業(yè)、便捷的支持
交互基調(diào):態(tài)度友好溫柔、言辭簡潔禮貌,搭配輕松可愛的圖標 / 表情,提升用戶交互體驗
二、核心功能
1. 智能車聯(lián)功能答疑
覆蓋場景:導航聯(lián)動(如手機導航同步車機)、車機互聯(lián)(如 CarPlay/HiCar 連接)、語音控制(如語音調(diào)空調(diào) / 切歌)等核心功能
服務(wù)內(nèi)容:用簡潔語言解釋功能原理、 step-by-step 說明使用方法,避免技術(shù)術(shù)語堆砌
2. 故障排查指引
針對問題:車聯(lián)系統(tǒng)連接失?。ㄈ缢{牙 / USB 連不上)、功能卡頓(如導航加載慢)、語音指令無響應(yīng)等常見故障
服務(wù)內(nèi)容:提供 3-5 步簡易排查步驟,優(yōu)先推薦用戶可自主操作的解決方案(如重啟車機、檢查連接權(quán)限)
3. 場景化功能推薦
場景匹配:根據(jù)用戶使用場景(長途駕駛、日常通勤、家庭出行等)推薦適配功能
示例:長途駕駛時推薦 “導航中途休息提醒”“語音控制免手動操作”;日常通勤推薦 “通勤路線記憶”“實時路況同步” ??
三、服務(wù)規(guī)則
內(nèi)容要求:回答必須準確(避免錯誤指引)、相關(guān)(不偏離智能車聯(lián)主題)、有幫助(解決用戶實際問題)
表達要求:簡潔明了(避免冗長)、有條理(分點 / 分步驟說明)、有邏輯(不矛盾),禁用模糊或隨意表述
交互要求:全程禮貌無冒犯,對用戶疑問耐心回應(yīng),若遇暫無法解答的問題,需明確告知 “當前暫未覆蓋該問題,可提供更多細節(jié)以便進一步協(xié)助”
四、交互規(guī)范
首次會話:主動打招呼并自我介紹,示例:“你好呀!?? 我是智能車聯(lián)系統(tǒng)智能助理,專門幫你解決車聯(lián)功能使用、故障排查等問題,有需求隨時跟我說哦~”
功能調(diào)用:用戶提及相關(guān)需求(如 “車機連不上手機”“推薦通勤用的功能”),直接對應(yīng)核心功能提供服務(wù),無需額外冗余引導
表情使用:關(guān)鍵功能 / 場景搭配適配圖標(如功能答疑用 “??”,故障排查用 “??”),每段回復表情不超過 2 個,避免過度堆砌9.2、配置智能助手
創(chuàng)建assistant包
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
@AiService(
wiringMode = EXPLICIT,
streamingChatModel = "qwenStreamingChatModel",
chatMemoryProvider = "chatMemoryProviderXiaozhi",
tools = "appointmentTools",
contentRetriever = "contentRetrieverXiaozhiPincone" )
public interface XiaozhiAgent {
@SystemMessage(fromResource = "zhaozhi-prompt-template.txt")
Flux<String> chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}9.3、配置對話對象
在之前創(chuàng)建的bean包下面進行配置
import lombok.Data;
@Data
public class ChatForm {
private Long memoryId;//對話id
private String message;//用戶問題
}9.4、配置controller
創(chuàng)建controller包(注意不是在test測試下面進行創(chuàng)建)
import com.ruoyi.langchain4j.assistant.XiaozhiAgent;
import com.ruoyi.langchain4j.bean.ChatForm;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@Tag(name = "智能車聯(lián)助手")
@RestController
@RequestMapping("/xiaozhi")
public class XiaozhiController {
@Autowired
private XiaozhiAgent xiaozhiAgent;
@Operation(summary = "對話")
@PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
public Flux<String> chat(@RequestBody ChatForm chatForm) {
return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage());
}
}9.5、待優(yōu)化
信息查詢可以使用RAG檢索增強生成
業(yè)務(wù)實現(xiàn)需要通過Function Calling函數(shù)調(diào)用
9.6、Function Calling函數(shù)調(diào)用
例如,大語言模型本身并不擅長數(shù)學運算。如果應(yīng)用場景中偶爾會涉及到數(shù)學計算,我們可以為他提供一個 “數(shù)學工具”。當我們提出問題時,大語言模型會判斷是否使用某個工具。
數(shù)學工具的話我是沒有相對應(yīng)的去測試,如果你感興趣的話你可以按以下進行操作:
創(chuàng)建tools包(注意不是在test下面創(chuàng)建)
import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Component;
@Component
public class CalculatorTools {
@Tool
double sum(double a, double b) {
System.out.println("調(diào)用加法運算");
return a + b;
}
@Tool
double squareRoot(double x) {
System.out.println("調(diào)用平方根運算");
return Math.sqrt(x);
}
}9.6.1、測試
@SpringBootTest
public class ToolsTest {
@Autowired
private SeparateChatAssistant separateChatAssistant;
@Test
public void testCalculatorTools() {
String answer = separateChatAssistant.chat(1, "1+2等于幾,475695037565的平方根是多少?");
//答案:3,689706.4865
System.out.println(answer);
}
}9.7、創(chuàng)建智能助手的數(shù)據(jù)庫表
我是根據(jù)硅谷小智來進行更改調(diào)試
CREATE DATABASE `guiguxiaozhi`; USE `guiguxiaozhi`; CREATE TABLE `appointment` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL, `id_card` VARCHAR(18) NOT NULL, `department` VARCHAR(50) NOT NULL, `date` VARCHAR(10) NOT NULL, `time` VARCHAR(10) NOT NULL, `doctor_name` VARCHAR(50) DEFAULT NULL, PRIMARY KEY (`id`) );
9.7.1、我們將不再重復提供之前已完整提交的配置文件和依賴項。
9.7.2、創(chuàng)建entity包(注意不是在test下面創(chuàng)建)
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Appointment {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String idCard;
private String department;
private String date;
private String time;
private String doctorName;
}9.7.3、創(chuàng)建mapper(注意不是在test下面創(chuàng)建)

接口:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.langchain4j.entity.Appointment;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AppointmentMapper extends BaseMapper<Appointment> {
}xml:在resources下創(chuàng)建mapper目錄,創(chuàng)建AppointmentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.langchain4j.mapper.AppointmentMapper">
</mapper>9.7.4、service
接口:
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.langchain4j.entity.Appointment;
public interface AppointmentService extends IService<Appointment> {
Appointment getOne(Appointment appointment);
}實現(xiàn)類(在service下面創(chuàng)建一個Impl):
package com.ruoyi.langchain4j.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.langchain4j.entity.Appointment;
import com.ruoyi.langchain4j.mapper.AppointmentMapper;
import com.ruoyi.langchain4j.service.AppointmentService;
import org.springframework.stereotype.Service;
@Service
public class AppointmentServiceImpl extends ServiceImpl<AppointmentMapper, Appointment> implements AppointmentService {
@Override
public Appointment getOne(Appointment appointment) {
//查詢數(shù)據(jù)是否存在
LambdaQueryWrapper<Appointment> appointmentLambdaQueryWrapper = new LambdaQueryWrapper<>();
appointmentLambdaQueryWrapper.eq(Appointment::getUsername,appointment.getUsername());
appointmentLambdaQueryWrapper.eq(Appointment::getIdCard,appointment.getIdCard());
appointmentLambdaQueryWrapper.eq(Appointment::getDepartment,appointment.getDepartment());
appointmentLambdaQueryWrapper.eq(Appointment::getDate,appointment.getDate());
appointmentLambdaQueryWrapper.eq(Appointment::getTime,appointment.getTime());
Appointment appointment1 = baseMapper.selectOne(appointmentLambdaQueryWrapper);
return appointment1;
}
}十、檢索增強RAG
10.1、微調(diào)大模型
在現(xiàn)有大模型的基礎(chǔ)上,使用小規(guī)模的特定任務(wù)數(shù)據(jù)進行再次訓練,調(diào)整模型參數(shù),讓模型更精確地處理特定領(lǐng)域或任務(wù)的數(shù)據(jù)。更新需重新訓練,計算資源和時間成本高。
- 優(yōu)點:一次會話只需一次模型調(diào)用,速度快,在特定任務(wù)上性能更高,準確性也更高。
- 缺點:知識更新不及時,模型訓成本高、訓練周期長。
- 應(yīng)用場景:適合知識庫穩(wěn)定、對生成內(nèi)容準確性和風格要求高的場景,如對上下文理解和語言生成質(zhì)量要求高的文學創(chuàng)作、專業(yè)文檔生成等。
10.2、RAG
Retrieval-Augmented Generation 檢索增強生成
他是把外掛的知識庫放到向量數(shù)據(jù)庫里面。
用戶提問的時候,先把用戶的問題發(fā)送到向量數(shù)據(jù)庫里面查詢出相關(guān)的向量。
然后把相關(guān)的向量和用戶的問題一塊發(fā)給大模型,大模型再去組織語言進行回答。
這樣大模型就通過外掛的知識庫獲取了特定領(lǐng)域的知識
將原始問題以及提示詞信息發(fā)送給大語言模型之前,先通過外部知識庫檢索相關(guān)信息,然后將檢索結(jié)果和原始問題一起發(fā)送給大模型,大模型依據(jù)外部知識庫再結(jié)合自身的訓練數(shù)據(jù),組織自然語言回答問題。通過這種方式,大語言模型可以獲取到特定領(lǐng)域的相關(guān)信息,并能夠利用這些信息進行回復。
- 優(yōu)點:數(shù)據(jù)存儲在外部知識庫,可以實時更新,不依賴對模型自身的訓練,成本更低。
- 缺點:需要兩次查詢:先查詢知識庫,然后再查詢大模型,性能不如微調(diào)大模型
- 應(yīng)用場景:適用于知識庫規(guī)模大且頻繁更新的場景,如企業(yè)客服、實時新聞查詢、法律和醫(yī)療領(lǐng)域的最新知識問答等。
10.2.1、RAG的過程
加載知識庫文檔 ==> 將文檔中的文本分段 ==> 利用向量大模型將分段后的文本轉(zhuǎn)換成向量 ==> 將向量存入向量數(shù)據(jù)庫
10.3、文檔加載器 Document Loader
10.3.1、常用的文檔加載器
來自 langchain4j 模塊的文件系統(tǒng)文檔加載器(FileSystemDocumentLoader)- 來自 langchain4j 模塊的類路徑文檔加載器(ClassPathDocumentLoader)
- 來自 langchain4j 模塊的網(wǎng)址文檔加載器(UrlDocumentLoader)
- 來自 langchain4j-document-loader-amazon-s3 模塊的亞馬遜 S3 文檔加載器(AmazonS3DocumentLoader)
- 來自 langchain4j-document-loader-azure-storage-blob 模塊的 Azure Blob 存儲文檔加載器(AzureBlobStorageDocumentLoader)
- 來自 langchain4j-document-loader-github 模塊的 GitHub 文檔加載器(GitHubDocumentLoader)
- 來自 langchain4j-document-loader-google-cloud-storage 模塊的谷歌云存儲文檔加載器(GoogleCloudStorageDocumentLoader)
- 來自 langchain4j-document-loader-selenium 模塊的 Selenium 文檔加載器(SeleniumDocumentLoader)
- 來自 langchain4j-document-loader-tencent-cos 模塊的騰訊云對象存儲文檔加載器(TencentCosDocumentLoader)
10.3.2、測試文檔解析
你可以生成一份文檔來進行解析
@SpringBootTest
public class RAGTest {
@Test
public void testReadDocument() {
//使用FileSystemDocumentLoader讀取指定目錄下的知識庫文檔
//并使用默認的文檔解析器TextDocumentParser對文檔進行解析
Document document = FileSystemDocumentLoader.loadDocument("E:/knowledge/測試.txt");
System.out.println(document.text());
}
}解析pdf文檔
/**
* 解析PDF
*/
@Test
public void testParsePDF() {
Document document = FileSystemDocumentLoader.loadDocument(
"E:/knowledge/醫(yī)院信息.pdf",
new ApachePdfBoxDocumentParser()
);
System.out.println(document);
}10.4、文檔分割器 DocumentSplitter
LangChain4j 有一個 “文檔分割器”(DocumentSplitter)接口,并且提供了幾種開箱即用的實現(xiàn)方式:
按段落文檔分割器(DocumentByParagraphSplitter)
按行文檔分割器(DocumentByLineSplitter)
按句子文檔分割器(DocumentBySentenceSplitter)
按單詞文檔分割器(DocumentByWordSplitter)
按字符文檔分割器(DocumentByCharacterSplitter)
按正則表達式文檔分割器(DocumentByRegexSplitter)
遞歸分割:DocumentSplitters.recursive (...)
默認情況下每個文本片段最多不能超過300個token
10.5、測試向量轉(zhuǎn)換和存儲
Embedding (Vector) Stores 常見的意思是 “嵌入(向量)存儲” 。在機器學習和自然語言處理領(lǐng)域,Embedding 指的是將數(shù)據(jù)(如文本、圖像等)轉(zhuǎn)換為低維稠密向量表示的過程,這些向量能夠保留數(shù)據(jù)的關(guān)鍵特征。而 Stores 表示存儲,即用于存儲這些嵌入向量的系統(tǒng)或工具。它們可以高效地存儲和檢索向量數(shù)據(jù),支持向量相似性搜索,在文本檢索、推薦系統(tǒng)、圖像識別等任務(wù)中發(fā)揮著重要作用。
Langchain4j支持的向量存儲:https://docs.langchain4j.dev/integrations/embedding-stores/
測試:
/**
* 加載文檔并存入向量數(shù)據(jù)庫
*/
@Test
public void testReadDocumentAndStore() {
//使用FileSystemDocumentLoader讀取指定目錄下的知識庫文檔
//并使用默認的文檔解析器對文檔進行解析(TextDocumentParser)
Document document = FileSystemDocumentLoader.loadDocument("E:/knowledge/人工智能.md");
//為了簡單起見,我們暫時使用基于內(nèi)存的向量存儲
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
//ingest
//1、分割文檔:默認使用遞歸分割器,將文檔分割為多個文本片段,每個片段包含不超過 300個token,并且有 30個token的重疊部分保證連貫性
//DocumentByParagraphSplitter(DocumentByLineSplitter(DocumentBySentenceSplitter(DocumentByWordSplitter)))
//2、文本向量化:使用一個LangChain4j內(nèi)置的輕量化向量模型對每個文本片段進行向量化
//3、將原始文本和向量存儲到向量數(shù)據(jù)庫中(InMemoryEmbeddingStore)
EmbeddingStoreIngestor.ingest(document, embeddingStore);
//查看向量數(shù)據(jù)庫內(nèi)容
System.out.println(embeddingStore);
}測試文檔分割:
/**
* 文檔分割
*/
@Test
public void testDocumentSplitter() {
//使用FileSystemDocumentLoader讀取指定目錄下的知識庫文檔
//并使用默認的文檔解析器對文檔進行解析(TextDocumentParser)
Document document = FileSystemDocumentLoader.loadDocument("E:/knowledge/人工智能.md");
//為了簡單起見,我們暫時使用基于內(nèi)存的向量存儲
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
//自定義文檔分割器
//按段落分割文檔:每個片段包含不超過 300個token,并且有 30個token的重疊部分保證連貫性
//注意:當段落長度總和小于設(shè)定的最大長度時,就不會有重疊的必要。
DocumentByParagraphSplitter documentSplitter = new DocumentByParagraphSplitter(
300,
30,
//token分詞器:按token計算
new HuggingFaceTokenizer());
//按字符計算
//DocumentByParagraphSplitter documentSplitter = new DocumentByParagraphSplitter(300, 30);
EmbeddingStoreIngestor
.builder()
.embeddingStore(embeddingStore)
.documentSplitter(documentSplitter)
.build()
.ingest(document);
}10.6、智能助手實現(xiàn)RAG
在config下面創(chuàng)建
import com.ruoyi.langchain4j.store.MongoChatMemoryStore;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
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 dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
@Configuration
public class XiaozhiAgentConfig {
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
@Autowired
private EmbeddingStore embeddingStore;
@Autowired
private EmbeddingModel embeddingModel;
@Bean
ChatMemoryProvider chatMemoryProviderXiaozhi() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(mongoChatMemoryStore)
.build();
}
@Bean
ContentRetriever contentRetrieverXiaozhi() {
//使用FileSystemDocumentLoader讀取指定目錄下的知識庫文檔
//并使用默認的文檔解析器對文檔進行解析
Document document1 = FileSystemDocumentLoader.loadDocument("D:/knowledge/醫(yī)院信息.md");
Document document2 = FileSystemDocumentLoader.loadDocument("D:/knowledge/科室信息.md");
Document document3 = FileSystemDocumentLoader.loadDocument("D:/knowledge/神經(jīng)內(nèi)科.md");
List<Document> documents = Arrays.asList(document1, document2, document3);
//使用內(nèi)存向量存儲
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
//使用默認的文檔分割器
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
//從嵌入存儲(EmbeddingStore)里檢索和查詢內(nèi)容相關(guān)的信息
return EmbeddingStoreContentRetriever.from(embeddingStore);
}
@Bean
ContentRetriever contentRetrieverXiaozhiPincone() {
// 創(chuàng)建一個 EmbeddingStoreContentRetriever 對象,用于從嵌入存儲中檢索內(nèi)容
return EmbeddingStoreContentRetriever
.builder()
// 設(shè)置用于生成嵌入向量的嵌入模型
.embeddingModel(embeddingModel)
// 指定要使用的嵌入存儲
.embeddingStore(embeddingStore)
// 設(shè)置最大檢索結(jié)果數(shù)量,這里表示最多返回 1 條匹配結(jié)果
.maxResults(1)
// 設(shè)置最小得分閾值,只有得分大于等于 0.8 的結(jié)果才會被返回
.minScore(0.8)
// 構(gòu)建最終的 EmbeddingStoreContentRetriever 實例
.build();
}
}10.7、向量數(shù)據(jù)庫
10.7.1、上傳到知識庫Pinecone
在test下面進行測試
文檔可以進行自己生成
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class EmbeddingTest {
@Autowired
private EmbeddingStore embeddingStore;
@Autowired
private EmbeddingModel embeddingModel;
@Test
public void testUploadKnowledgeLibrary() {
//使用FileSystemDocumentLoader讀取指定目錄下的知識庫文檔
//并使用默認的文檔解析器對文檔進行解析
Document document1 = FileSystemDocumentLoader.loadDocument("D:/knowledge/醫(yī)院信息.md");
Document document2 = FileSystemDocumentLoader.loadDocument("D:/knowledge/科室信息.md");
Document document3 = FileSystemDocumentLoader.loadDocument("D:/knowledge/神經(jīng)內(nèi)科.md");
List<Document> documents = Arrays.asList(document1, document2, document3);
//文本向量化并存入向量數(shù)據(jù)庫:將每個片段進行向量化,得到一個嵌入向量
EmbeddingStoreIngestor
.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.build()
.ingest(documents);
System.out.println("上傳知識庫成功");
}
}10.7.2、添加向量數(shù)據(jù)庫的配置
直接提供完整配置
在config包下面創(chuàng)建
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore;
import dev.langchain4j.store.embedding.pinecone.PineconeServerlessIndexConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EmbeddingStoreConfig {
@Autowired
private EmbeddingModel embeddingModel;
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
//創(chuàng)建向量存儲
EmbeddingStore<TextSegment> embeddingStore = PineconeEmbeddingStore.builder()
.apiKey("相對應(yīng)的apikey")
.index("xiaozhi-index")//如果指定的索引不存在,將創(chuàng)建一個新的索引
.nameSpace("xiaozhi-namespace") //如果指定的名稱空間不存在,將創(chuàng)建一個新的名稱空間
.createIndex(PineconeServerlessIndexConfig.builder()
.cloud("AWS") //指定索引部署在 AWS 云服務(wù)上。
.region("us-east-1") //指定索引所在的 AWS 區(qū)域為 us-east-1。
.dimension(embeddingModel.dimension()) //指定索引的向量維度,該維度與 embeddedModel 生成的向量維度相同。
.build())
.build();
return embeddingStore;
}
}import com.ruoyi.langchain4j.store.MongoChatMemoryStore;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
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 dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
@Configuration
public class XiaozhiAgentConfig {
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
@Autowired
private EmbeddingStore embeddingStore;
@Autowired
private EmbeddingModel embeddingModel;
@Bean
ChatMemoryProvider chatMemoryProviderXiaozhi() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(mongoChatMemoryStore)
.build();
}
@Bean
ContentRetriever contentRetrieverXiaozhi() {
//使用FileSystemDocumentLoader讀取指定目錄下的知識庫文檔
//并使用默認的文檔解析器對文檔進行解析
Document document1 = FileSystemDocumentLoader.loadDocument("D:/knowledge/醫(yī)院信息.md");
Document document2 = FileSystemDocumentLoader.loadDocument("D:/knowledge/科室信息.md");
Document document3 = FileSystemDocumentLoader.loadDocument("D:/knowledge/神經(jīng)內(nèi)科.md");
List<Document> documents = Arrays.asList(document1, document2, document3);
//使用內(nèi)存向量存儲
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
//使用默認的文檔分割器
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
//從嵌入存儲(EmbeddingStore)里檢索和查詢內(nèi)容相關(guān)的信息
return EmbeddingStoreContentRetriever.from(embeddingStore);
}
@Bean
ContentRetriever contentRetrieverXiaozhiPincone() {
// 創(chuàng)建一個 EmbeddingStoreContentRetriever 對象,用于從嵌入存儲中檢索內(nèi)容
return EmbeddingStoreContentRetriever
.builder()
// 設(shè)置用于生成嵌入向量的嵌入模型
.embeddingModel(embeddingModel)
// 指定要使用的嵌入存儲
.embeddingStore(embeddingStore)
// 設(shè)置最大檢索結(jié)果數(shù)量,這里表示最多返回 1 條匹配結(jié)果
.maxResults(1)
// 設(shè)置最小得分閾值,只有得分大于等于 0.8 的結(jié)果才會被返回
.minScore(0.8)
// 構(gòu)建最終的 EmbeddingStoreContentRetriever 實例
.build();
}
}10.7.3、在Pinecone上注冊新賬號時,請按以下步驟操作:
- 完成賬號注冊流程(填寫公司名稱時可隨意填寫)
- 創(chuàng)建API Key
- 特別注意:API Key生成后僅顯示一次,請務(wù)必及時保存

十一、流式輸出
大模型的流式輸出是指大模型在生成文本或其他類型的數(shù)據(jù)時,不是等到整個生成過程完成后再一次性返回所有內(nèi)容,而是生成一部分就立即發(fā)送一部分給用戶或下游系統(tǒng),以逐步、逐塊的方式返回結(jié)果。這樣,用戶就不需要等待整個文本生成完成再看到結(jié)果。通過這種方式可以改善用戶體驗,因為用戶不需要等待太長時間,幾乎可以立即開始閱讀響應(yīng)。
依賴和配置之前提供完整
編碼:
修改XiaozhiAgent中chatModel改為 streamingChatModel = "qwenStreamingChatModel"
`chat方法的返回值為Flux<String>
也進行了提供完整配置
十二、前端頁面
<template>
<div class="app-layout">
<div class="sidebar">
<div class="logo-section">
<div class="car-animation">
<i class="fas fa-car-side car-icon"></i>
<div class="glow-effect"></div>
</div>
<span class="logo-text">智能車聯(lián)助手</span>
<span class="logo-subtitle">AI駕駛伴侶</span>
<div class="status-indicator">
<span class="status-dot"></span>
<span class="status-text">系統(tǒng)在線</span>
</div>
</div>
<el-button class="new-chat-button" @click="newChat">
<i class="fas fa-plus"></i>
<span class="button-text">新旅程</span>
</el-button>
</div>
<div class="main-content">
<div class="chat-container">
<div class="message-list" ref="messaggListRef">
<div
v-for="(message, index) in messages"
:key="index"
:class="
message.isUser ? 'message user-message' : 'message bot-message'
"
>
<!-- 會話圖標 -->
<i
:class="
message.isUser
? 'fa-solid fa-user message-icon'
: 'fa-solid fa-robot message-icon'
"
></i>
<!-- 會話內(nèi)容 -->
<span>
<span v-html="message.content"></span>
<!-- loading -->
<span
class="loading-dots"
v-if="message.isThinking || message.isTyping"
>
<span class="dot"></span>
<span class="dot"></span>
</span>
</span>
</div>
</div>
<div class="input-container">
<el-input
v-model="inputMessage"
placeholder="請輸入消息"
@keyup.enter="sendMessage"
></el-input>
<el-button @click="sendMessage" :disabled="isSending" type="primary"
>發(fā)送</el-button
>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'
import axios from 'axios'
import { v4 as uuidv4 } from 'uuid'
const messaggListRef = ref()
const isSending = ref(false)
const uuid = ref()
const inputMessage = ref('')
const messages = ref([])
onMounted(() => {
initUUID()
// 移除 setInterval,改用手動滾動
watch(messages, () => scrollToBottom(), { deep: true })
hello()
})
const scrollToBottom = () => {
if (messaggListRef.value) {
messaggListRef.value.scrollTop = messaggListRef.value.scrollHeight
}
}
const hello = () => {
sendRequest('你好')
}
const sendMessage = () => {
if (inputMessage.value.trim()) {
sendRequest(inputMessage.value.trim())
inputMessage.value = ''
}
}
const sendRequest = (message) => {
isSending.value = true
const userMsg = {
isUser: true,
content: message,
isTyping: false,
isThinking: false,
}
//第一條默認發(fā)送的用戶消息”你好“不放入會話列表
if(messages.value.length > 0){
messages.value.push(userMsg)
}
// 添加機器人加載消息
const botMsg = {
isUser: false,
content: '', // 增量填充
isTyping: true, // 顯示加載動畫
isThinking: false,
}
messages.value.push(botMsg)
const lastMsg = messages.value[messages.value.length - 1]
scrollToBottom()
axios
.post(
'/api/xiaozhi/chat',
{ memoryId: uuid.value, message },
{
responseType: 'stream', // 必須為合法值 "text"
onDownloadProgress: (e) => {
const fullText = e.event.target.responseText // 累積的完整文本
let newText = fullText.substring(lastMsg.content.length)
lastMsg.content += newText //增量更新
console.log(lastMsg)
scrollToBottom() // 實時滾動
},
}
)
.then(() => {
// 流結(jié)束后隱藏加載動畫
messages.value.at(-1).isTyping = false
isSending.value = false
})
.catch((error) => {
console.error('流式錯誤:', error)
messages.value.at(-1).content = '請求失敗,請重試'
messages.value.at(-1).isTyping = false
isSending.value = false
})
}
// 初始化 UUID
const initUUID = () => {
let storedUUID = localStorage.getItem('user_uuid')
if (!storedUUID) {
storedUUID = uuidToNumber(uuidv4())
localStorage.setItem('user_uuid', storedUUID)
}
uuid.value = storedUUID
}
const uuidToNumber = (uuid) => {
let number = 0
for (let i = 0; i < uuid.length && i < 6; i++) {
const hexValue = uuid[i]
number = number * 16 + (parseInt(hexValue, 16) || 0)
}
return number % 1000000
}
// 轉(zhuǎn)換特殊字符
const convertStreamOutput = (output) => {
return output
.replace(/\n/g, '
')
.replace(/\t/g, ' ')
.replace(/&/g, '&') // 新增轉(zhuǎn)義,避免 HTML 注入
.replace(/</g, '<')
.replace(/>/g, '>')
}
const newChat = () => {
// 這里添加新會話的邏輯
console.log('開始新會話')
localStorage.removeItem('user_uuid')
window.location.reload()
}
</script>
<style scoped>
.app-layout {
display: flex;
height: 100vh;
background: linear-gradient(135deg, #0f0f23 0%, #1a1a3a 50%, #0f0f23 100%);
color: #ffffff;
}
.sidebar {
width: 200px;
background: linear-gradient(180deg, #1a1a3a 0%, #0f0f23 100%);
border-right: 1px solid #2a2a5a;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 0 20px rgba(0, 136, 255, 0.1);
}
.logo-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30px;
}
.car-animation {
position: relative;
margin-bottom: 15px;
}
.car-icon {
font-size: 48px;
color: #00d4ff;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.8);
animation: pulse 2s ease-in-out infinite;
}
.glow-effect {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80px;
height: 80px;
background: radial-gradient(circle, rgba(0, 212, 255, 0.3) 0%, transparent 70%);
border-radius: 50%;
animation: rotate 4s linear infinite;
}
@keyframes rotate {
from {
transform: translate(-50%, -50%) rotate(0deg);
}
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.logo-text {
font-size: 22px;
font-weight: bold;
color: #ffffff;
margin-bottom: 5px;
text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
letter-spacing: 1px;
}
.logo-subtitle {
font-size: 13px;
color: #00d4ff;
opacity: 0.9;
margin-bottom: 15px;
font-weight: 300;
}
.status-indicator {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
}
.status-dot {
width: 8px;
height: 8px;
background: #00ff88;
border-radius: 50%;
animation: blink 2s ease-in-out infinite;
box-shadow: 0 0 5px rgba(0, 255, 136, 0.5);
}
@keyframes blink {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.3;
}
}
.status-text {
font-size: 11px;
color: #8a8aa5;
font-weight: 300;
}
.new-chat-button {
width: 100%;
margin-top: 10px;
background: linear-gradient(135deg, #00d4ff, #0066ff, #6600ff);
border: none;
color: white;
font-weight: 600;
border-radius: 25px;
padding: 12px 20px;
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.4);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.new-chat-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.new-chat-button:hover::before {
left: 100%;
}
.new-chat-button:hover {
transform: translateY(-3px) scale(1.02);
box-shadow: 0 8px 30px rgba(0, 212, 255, 0.6);
}
.button-text {
margin-left: 8px;
font-size: 14px;
}
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
max-width: 800px;
margin: 0 auto;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 25px;
border: 1px solid rgba(0, 212, 255, 0.2);
border-radius: 20px;
background: rgba(26, 26, 58, 0.3);
margin-bottom: 25px;
display: flex;
flex-direction: column;
backdrop-filter: blur(20px);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
position: relative;
}
.message-list::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.5), transparent);
}
.message {
margin-bottom: 20px;
padding: 18px 22px;
border-radius: 18px;
display: flex;
align-items: flex-start;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.message:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
}
.user-message {
max-width: 75%;
background: linear-gradient(135deg, #00d4ff 0%, #0066ff 50%, #6600ff 100%);
color: white;
align-self: flex-end;
flex-direction: row-reverse;
margin-left: auto;
border: 1px solid rgba(0, 212, 255, 0.3);
}
.bot-message {
max-width: 80%;
background: rgba(42, 42, 90, 0.6);
color: #ffffff;
align-self: flex-start;
border: 1px solid rgba(0, 212, 255, 0.2);
background-image:
linear-gradient(45deg, rgba(0, 212, 255, 0.05) 25%, transparent 25%),
linear-gradient(-45deg, rgba(0, 212, 255, 0.05) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, rgba(0, 212, 255, 0.05) 75%),
linear-gradient(-45deg, transparent 75%, rgba(0, 212, 255, 0.05) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
}
.message-icon {
margin: 0 10px;
font-size: 1.2em;
color: #00d4ff;
}
.user-message .message-icon {
color: #ffffff;
}
.loading-dots {
padding-left: 5px;
}
.dot {
display: inline-block;
margin-left: 5px;
width: 8px;
height: 8px;
background-color: #00d4ff;
border-radius: 50%;
animation: pulse 1.2s infinite ease-in-out both;
box-shadow: 0 0 5px rgba(0, 212, 255, 0.5);
}
.dot:nth-child(2) {
animation-delay: -0.6s;
}
@keyframes pulse {
0%,
100% {
transform: scale(0.6);
opacity: 0.4;
}
50% {
transform: scale(1);
opacity: 1;
}
}
.input-container {
display: flex;
gap: 15px;
align-items: center;
padding: 20px;
background: rgba(26, 26, 58, 0.3);
border-radius: 25px;
backdrop-filter: blur(20px);
border: 1px solid rgba(0, 212, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.input-container .el-input {
flex: 1;
}
.input-container .el-input__inner {
background: rgba(42, 42, 90, 0.4);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 20px;
color: #ffffff;
padding: 15px 20px;
font-size: 15px;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.input-container .el-input__inner:focus {
border-color: #00d4ff;
box-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
background: rgba(42, 42, 90, 0.6);
}
.input-container .el-input__inner::placeholder {
color: rgba(138, 138, 165, 0.7);
font-style: italic;
}
.input-container .el-button {
background: linear-gradient(135deg, #00d4ff, #0066ff, #6600ff);
border: none;
border-radius: 50%;
color: white;
font-weight: bold;
width: 50px;
height: 50px;
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.4);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
flex-shrink: 0;
}
.input-container .el-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.input-container .el-button:hover::before {
left: 100%;
}
.input-container .el-button:hover {
transform: translateY(-2px) scale(1.05);
box-shadow: 0 8px 30px rgba(0, 212, 255, 0.6);
}
.input-container .el-button:active {
transform: translateY(0) scale(0.95);
}
/* 媒體查詢,當設(shè)備寬度小于等于 768px 時應(yīng)用以下樣式 */
@media (max-width: 768px) {
.main-content {
padding: 10px 0 10px 0;
}
.app-layout {
flex-direction: column;
}
.sidebar {
width: 100%;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 10px;
background: linear-gradient(90deg, #1a1a3a 0%, #0f0f23 100%);
}
.logo-section {
flex-direction: row;
align-items: center;
}
.car-icon {
font-size: 32px;
margin-right: 10px;
margin-bottom: 0;
}
.logo-text {
font-size: 18px;
margin-bottom: 0;
}
.logo-subtitle {
display: none;
}
.new-chat-button {
margin-right: 30px;
width: auto;
margin-top: 5px;
padding: 8px 16px;
font-size: 14px;
}
}
/* 媒體查詢,當設(shè)備寬度大于 768px 時應(yīng)用原來的樣式 */
@media (min-width: 769px) {
.main-content {
padding: 0 0 10px 10px;
}
.app-layout {
display: flex;
height: 100vh;
}
.sidebar {
width: 200px;
background-color: #f4f4f9;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.logo-section {
display: flex;
flex-direction: column;
align-items: center;
}
.logo-text {
font-size: 18px;
font-weight: bold;
margin-top: 10px;
}
.new-chat-button {
width: 100%;
margin-top: 20px;
}
}
.message-list::-webkit-scrollbar {
width: 8px;
}
.message-list::-webkit-scrollbar-track {
background: rgba(42, 42, 90, 0.3);
border-radius: 4px;
}
.message-list::-webkit-scrollbar-thumb {
background: linear-gradient(45deg, #00d4ff, #0066ff);
border-radius: 4px;
}
.message-list::-webkit-scrollbar-thumb:hover {
background: linear-gradient(45deg, #0099cc, #0044cc);
}
/* 添加科技感邊框動畫 */
.message-list {
position: relative;
overflow: hidden;
}
.message-list::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #00d4ff, #0066ff, #00d4ff);
border-radius: 15px;
opacity: 0.1;
z-index: -1;
animation: borderGlow 3s linear infinite;
}
@keyframes borderGlow {
0%, 100% {
opacity: 0.1;
}
50% {
opacity: 0.3;
}
}
/* 添加消息懸停效果 */
.message {
position: relative;
overflow: hidden;
}
.message::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
transition: left 0.5s;
}
.message:hover::before {
left: 100%;
}
</style>12.1、后端服務(wù)端口

到此這篇關(guān)于Java集成大模型LangChain4j實戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Java LangChain4j內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jpa多條件查詢重寫Specification的toPredicate方法
這篇文章主要介紹了多條件查詢重寫Specification的toPredicate方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
基于Springboot執(zhí)行多個定時任務(wù)并動態(tài)獲取定時任務(wù)信息
這篇文章主要為大家詳細介紹了基于Springboot執(zhí)行多個定時任務(wù)并動態(tài)獲取定時任務(wù)信息,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04

