跳至主要內容

LangChain4j

HeChuangJun约 3706 字大约 12 分钟

LangChain4j

https://docs.langchain4j.dev/open in new window

功能Spring AILangChain4j
Chat支持支持
Function支持支持
RAG支持支持
对话模型15+15+
向量模型10+15+
向量数据库15+20+
多模态模型5+1
JDK178

大模型部署

自己部署
》云服务器部署
优势:前期成本低,维护简单

劣势:数据不安全,长期使用成本高
本地机器部署
优势:数据安全;长期成本

低劣势:初期成本高,维护困难

他人部署
百度智能云 硅基流动 火山引擎 阿里云百炼 API接口
优势:无需部署
劣势:数据不安全,长期成本高

大模型部署-自己部署(本机)

0llama、LM Studio一键下载、运行大模型

ollama是一种用于快速下载、部署、管理大模型的工具,官网地址:https://ollama.comopen in new window

ollama run qwen3:0.6b

0llama默认端口11434

大模型使用

Example using Ollama's chat APl with thinking enabled
curl http://localhost:11434/api/chat -d '{
	model":"qwen3:6.6b"
	"messages":[
		{
		"role": "user"
		"content": "how many r in the word strawberry?"
		}
    ],
    "think”: true,
    "stream": false
}'

Qutput
["model":"deepseek-r1","created at":"2025-05-29T09:35:56.836222Z""message :
{"role": "assistant","content": "The word \"strawberry\" contains **three** instances of the letter "R'"thinking":"First, the question is: i"how manyr in the word strawberry?\" Ineed to count the number of times the letter 'r' appears in the word \"strawberry\"Let me write down the word:...”,
"done reason":"stop",

使用APIFOX的json发送和自动合并即可看到结果

使用大模型需要传递的参数,在访问大模型时都需要在请求体中以json的形式进行传递
{
	"model": "qwen-plus"
	"messages":[
		{
		"role":"system",
		"content":"你是东哥的助手小月月’,
		},
		{
		"role": "user",
        "content":"你是谁?",
		},
		{
		"role": "assistant",
		"content":"您好,有什么可以帮助您?"
		}
	],
    "stream": true,
    "enable search": true
}

model:告诉平台,当前调用哪个模型
messages:发送给模型的数据,模型会根据这些数据给出合适的响应
content:消息内容
role:消息角色(类型)
- user:用户消息
- system:系统消息
- assistant:模型响应消息
stream:调用方式
true:非阻塞调用(流式调用)
- false:阻塞调用(一次性响应),默认值
enable_search:联网搜索,启用后,模型会将搜索结果作为参考信息
true:开启
- false:关闭(默认)

在大语言模型中,Token是大模型处理文本的基本单位
可以理解为模型“看得懂”的最小文本片段
用户输入的内容都需要转换成token,才能让大模型更好的处理
英文:-个tokenx4个字符
中文:一个汉字≈1~2个token

在与大模型交互的过程中,大模型响应的数据是json格式的数据
{
    "choices":[
    "message":{
        "role":"assistant"
        “content”:“我是通义千问,阿里巴巴.….”
    },
    "finish reason":"stop"
    "index": @
    }
    "object":"chat.completion",
    "usage":{
    "prompt tokens": 22,
    'completion_tokens": 80,
    "total tokens": 102,
    },
    'created": 1748068508,
    "system _fingerprint": nul1,
    "model":"qwen-plus",
    "id":"chatcmpl-99f8d040-0f49-955b-943a-21c83'
}

choices:模型生成的内容数组,可以包含一条或多条内容
message:本次调用模型输出的消息
finish_reason:自然结束(stop),生成内容过长(length)
index:当前内容在choices数组中的索引

object:始终为chat.completion,无需关注
usage:本次对话过程中使用的token信息
prompt_tokens:用户的输入转换成token的个数
completion_tokens:模型生成的回复转换成token的个数
total tokens:用户输入和模型生成的总token个数
created:本次会话被创建时的时间戳
system_fingerprint:固定为null,无需关注
model:本次会话使用的模型名称
id:本次调用的唯一标识符

会话功能-快速入门

1.引入Langchian4j依赖
<dependency>
<groupld>dev.langchain4j</groupld>
<artifactld>langchain4j-open-ai</artifactld>
<version>1.0.1</version>
</dependency>
2.构建0penAichatModel对象
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apikey(System.getenv("API-KEY"))# API-KEY配置在计算机的属性那里的话需要重启一下IDE
.modelName("qwen-plus")
.build();
3.调用chat方法与大模型交互
String result = model.chat("东哥帅不帅”)
System.out.printin(result);

打印日志信息
<dependency>
<groupld>ch.qos.logback</groupld>
<artifactld>logback-classic</artifactld>
<version>1.5.18</version>
</dependency>
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(System.getenv("API-KEY"))
.modelName("qwen-plus")
.logRequests(true)# 设置
.logResponses(true)# 设置
.build();

会话功能-Spring整合Langchain4j

1.构建springboot项目
2.引入起步依赖
3.application.yml中配置大模型
4.开发接口,调用大模型
<dependency>
<groupld>dev.langchain4j</groupld>
<artifactld>langchain4j-open-ai-spring-boot-starter</artifactld>
<version>1.0.1-beta6</version>
</dependency>

langchain4j:
	open-ai:
		chat-model:
			base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
			api-key: ${API-KEY}
			model-name: qwen-plus
			log-requests: true
			log-responses: true
logging:
	level:
		dev.langchain4j: debug

@Configuration
public class CommonConfig {
    @Autowired
    private OpenAiChatModel model;
    @Bean
    public ConsultantService consultantService(){
    	ConsultantService cs =  AiServices.builder(ConsultantService.class).chatModel(model).build():
    	return cs;
    }
}

@RestController
public class ChatController {
    @Autowired
    private OpenAiChatModel model;
    
    @RequestMapping("/chat")
    public String chat(String message){
        String result = model.chat(message)
        return result;
    }
}

package com.itheima.consultant.aiservice;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
@AiService(
	wiringMode =AiserviceWiringMode.EXPLICIT,//手动装配
	chatModel ="openAichatModel"//指定模型
)I
public interface ConsultantService {
	//用于聊天的方法
	public string chat(string message);
}

会话功能-流式调用

1.引入依赖
2.配置流式模型对象
3.切换接口中方法的返回值类型
4.修改Controller中的代码

<dependency>
<groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-webflux</artifactld>
</dependency>
<dependency>
<groupld>dev.langchain4j</groupld>
<artifactld>langchain4j-reactor</artifactld>
<version>1.0.1-beta6</version>
</dependency>

langchain4j:
	open-ai:
		streaming-chat-model:
			base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
			api-key: ${API-KEY}
            model-name: qwen-plus
            log-requests: true
            log-responses: true

@AiService(
wkingMode =AiServiceWiringMode.EXPLICIT
chatModel = "openAiChatModel"
streamingChatModel ="openAistreamingChatMode"
}
public interface ConsultantService {
	public Flux<String> chat(String message);
}

@RestController
public class ChatController {
	@Autowired
	private ConsultantService consultantService;
	@RequestMapping(value = "/chat",produces = "text/html;charset=utf-8")
	public Flux<String> chat(string message){
		Flux<String>result =consultantService.chat(message);
		return result;
    }
}

会话功能-消息注解

@SystemMessage(“你是东哥的助手小月月,温柔貌美又多金。")
public Flux<String> chat(String message);

@SystemMessage(fromResource ="system.txt")
public Flux<String> chat(String message);

@AiService( 
wiringMode =AiServiceWiringMode.EXPLICIT,//手动装配
chatModel ="openAiChatModel",//指定模型
streamingChatModel="openAiStreamingChatModel"
//@AiService
public interface ConsultantService {
    //用于聊天的方法
    //public string chat(string message);
    //@SystemMessage("你是东哥的助手小月月,人美心善又多金!")
    @SystemMessage(fromResource="system.txt")
    public Flux<String>chat(String message);
    
    @UserMessage("你是东哥的助手小月月,温柔貌美又多金。{{it)}”)
    //这个花括号内的it它是固定的
    public Flux<String> chat(String message);
    
    @UserMessage("你是东哥的助手小月月,温柔貌美又多金。{{msg}}")
    public Flux<String> chat(@V("msg") String message);
}

会话功能-会话记忆

大模型是不具备记忆能力的,要想让大模型记住之前聊天的内容,唯一的办法就是把之前聊天的内容与新的提示词一起发给大模型,

public interface ChatMemory {
	Object id();/记忆存储对象的唯一标识
	void add(ChatMessage var1)://添加一条会话记记
	List<ChatMessage>messages();/获取所有会话记忆
	void clear();/清除所有会话记忆
}

@Bean
public ChatMemory chatMemory(){
	return MessageWindowChatMemory.builder().maxMessages(20).build();
}

@AiService(
	wiringMode =AiServiceWiringMode.EXPLICIT,
	chatModel = "openAiChatModel",
	streamingChatModel ="openAiStreamingChatMode",
	chatMemory="chatMemory" # 手动设置
)
public interface ConsultantService {
	@SystemMessage(fromResource ="system.txt")
	public Flux<String> chat(String message);
}

会话功能-会话记忆隔离

刚才我们做的会话记忆,所有会话使用的是同一个记忆存储对象,因此不同会话之间的记忆并没有做到隔离

1.定义会话记忆对象提供者
2.配置会话记忆对象提供者
3.ConsultantService接口方法中添加参数
memoryId
4.Controller中chat接囗接收memoryId
5.前端页面请求时传递memoryId

@Bean
public ChatMemoryProvider chatMemoryProvider() {
	ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
		@Override
        public ChatMemory get(Object memoryld) {
            return MessageWindowChatMemory.builder().id(memoryld).maxMessages(20)
            .build();
        }
    };
	return chatMemoryProvider;
}
@AiService(
wiringMode =AiServiceWiringMode.EXPLICIT
chatModel = "openAiChatModel",
streamingChatModel ="openAiStreamingChatModel"
//chatMemory="chatMemory" # 手动设置 注释掉
chatMemoryProvider="chatMemoryProvider" //新提供
)

public interface ConsultantService {
	@SystemMessage(fromResource = "system.txt")
	public Flux<String> chat(
        @Memoryld String memoryld,//前端传过来
        @UserMessage String message
    );
}

会话功能-会话记忆持久化

刚才我们做的会话记忆,只要后端重启,会话记忆就没有了

public interface ChatMemoryStore {
	List<ChatMessage>getMessages(Object memoryld);
	void updateMessages(Object memoryld,List<ChatMessage> messages)
	void deleteMessages(Object memoryld);
}

class SingleSlotChatMemoryStore implements ChatMemoryStore{
    private List<ChatMessage>messages = new ArrayList();
    public List<ChatMessage> getMessages(Object memoryld){
    	return this.messages;
    }
	public void updateMessages(Object memoryld,List<ChatMessage>messages){
		this.messages =messages
    }
	public yoid deleteMessages(Obiect memoryld){
		this.messages = new ArraysList();
	}
}

return MessageWindowChatemory.builder().id(memoryld)
.maxMessages(20)
.build();

public class MessageWindowChatMemory implements ChatMemory {
    private final String id;
    private final ChatMemoryStore store;
    public void add(ChatMessage message) {
    	this.store.updateMessages(this.id, messages);
    }
    public List<ChatMessage> messages(){
    	return this.store.getMessages(this.id);
    }
    public void clear() {
    	this.store.deleteMessages(this.id)
    }
}

会话功能-会话记忆持久化-redis

public interface ChatMemoryStore {
	List<ChatMessage>getMessages(Object memoryld);
	void updateMessages(Object memoryld,List<ChatMessage> messages)
	void deleteMessages(Object memoryld);
}

public class RedisChatMemoryStore implements ChatMemoryStore {
	@Override
    public List<ChatMessage> getMessages(Object o) {
    	//TODO 从redis中获取数据return List.of();
    }
    @Override
    public void updateMessages(Object o, List<ChatMessage> list){
    	//TODO 更新redis 中数据
    }
    @Override
    public void deleteMessages(Object o) {
    	//TODO 删除redis中数据
    }
}
1.准备redis环境
2.引入redis起步依赖
3.配置redis连接信息
4.提供ChatMemoryStore实现类
5.配置ChatMemoryStore

docker run--name redis-d-p6379:6379 redis

<dependency>
	<groupld>org.springframework.boot</groupld>
	<artifactld>spring-boot-starter-data-redis</artifactld>
</dependency>

spring:
	data:
		redis:
			host: localhost
			port:6379

@Repository
public class RedisChatMemoryStore implements ChatMemoryStore {
	//TODO重写getMessages()、updateMessages()、deleteMessages()方法
	//注入RedisTemplate
    @Autowired
    private stringRedisTemplate redisTemplate;
    @0verride
    public List<ChatMessage> getMessages(0bject memoryId){
        //获取会话消息
        String json =redisTemplate.opsForValue().get(memoryId);
        //把json字符电转化成List<ChatMessage>
        List<ChatMessage> list =ChatMessageDeserializer.messagesFromJson(json);
        return list;
    }
    @0verride
    public void updateMessages(0bject memoryId, List<chatMessage> list) {
    	//更新会话消息
    	//1.把list转换成json数据
    	String json =ChatMessageSerializer.messagesToJson(list);//
    	2.把json数据存储到redis中
    	redisTemplate.opsForValue().set(memoryId.toString(),json, Duration.ofDays(1));
    }
    @0verride no usages
	public void deleteMessages(0bject memoryId){
		redisTemplate.delete(memoryId.tostring())
	}
}

public ChatMemory get(Object memoryld) {
	return MessageWindowChatMemory.builder().id(memoryld).maxMessages(20)
.chatMemoryStore(store)# 设置持久化
.build();
}

RAG知识库-原理

RAG,RetrievalAugmented Generation,检索增强生成。通过检索外部知识库的方式增强大模型的生成能力

通用训练数据2023/10 、专业领域数据

1.两个向量的余弦相似度越高,说明向量对应的文本相似度越高
2.向量数据库使用流程
借助于向量模型,把文档知识数据向量化后存储到向量数据库用户输入的内容,借助于向量模型转化为向量后,与数据库中的向量通过计算余弦相似度的方式,找出相似度比较高的文本片段

1.png
1.png
2.png
2.png

向量数据库:
Milvus、Chroma、pineconeRedisSearch(Reids)、pgvector(PostgresQL)

3.png
3.png
4.png
4.png
5.png
5.png
6.png
6.png
7.png
7.png
8.png
8.png
9.png
9.png

RAG知识库-快速入门

1.存储(构建向量数据库操作对象)
·引入依赖
加载知识数据文档
·构建向量数据库操作对象
·把文档切割、向量化并存储到向量数据库中
2.检索(构建向量数据库检索对象)
构建向量数据库检索对象
配置向量数据库检索对象

<dependency>
<groupld>dev.langchain4j</groupld>
<artifactld>langchain4j-easy-rag</artifactld>
<version>1.0.1-beta6</version>
</dependency>
public EmbeddingStore embeddingstore(){
    List<Document> documents = ClassPathDocumentLoader.loadDocuments(“文档路径");

    InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();

    EmbeddingStoreingestor ingestor = EmbeddingStoreingestor.builder().embeddingStore(store)
    .build();
    ingestor.ingest(documents);
    return store;
}

//构建向量数据库检索对象远
@Bean
public ContentRetriever contentRetriever(EmbeddingStore store){
	return EmbeddingStorecontentRetriever.builder.embeddingStore(store).minScore(0.5).maxResults(3).build();
}

@AiService(
	wiringMode =AiServiceWiringMode.EXPLICIT
	contentRetriever = "contentRetriever" # 设置
}

RAG知识库-核心API

11.png
11.png

文档加载器,用于把磁盘或者网络中的数据加载进程序
●FileSystemDocumentLoader,根据本地磁盘绝对路径加载

●ClassPathDocumentLoader,相对于类路径加载
●UrlDocumentLoader,根据url路径加载

List<Document> documents = FileSystemDocumentLoader.loadDocuments( C:\\UsersllAdministratonllideaProjects\\)

文档解析器,用于解析使用文档加载器加载进内存的内容,把非纯文本数据转化成纯文本●TextDocumentParser,解析纯文本格式的文件

●ApachePdfBoxDocumentParser,解析pdf格式文件

ApachePoiDocumentParser,解析微软的office文件,例如DOC、PPT、XLS

ApacheTikaDocumentParser(默认),几乎可以解析所有格式的文件

<dependency>
<groupld>dev.langchain4j</groupld>
<artifactld>langchain4j-document-parser-apache-pdfbox</artifactld>
<version>1.0.1-beta6</version>
</dependency>

List<Document> documents =  ClassPathDocumentLoader.loadDocuments(
"content",new ApachePdfBoxDocumentParser())

文档分割器,用于把一个大的文档,切割成一个一个的小片段
●DocuemntByParagraphSplitter,按照段落分割文本

●DocumentByLineSplitter,按照行分割文本

DocumentBySentenceSplitter,按照句子分割文本

●DocumentByWordSplitter,按照词分割文本

DocumentByCharacterSplitter,按照固定数量的字符分割文本

DocumentByRegexSplitter,按照正则表达式分割文本

DocumentSplitters.recursive(.)(默认),递归分割器,优先段落分割再按照行分割,再按照句子分割,再按照词分割

文本片段:最大300个字符

DocumentSplitter documentSplitter = DocumentSplitters.recursive(
每个片段最大容纳的字符,两个片段之间重叠字符的个数)

EmbeddingStoreIngestor ingestor= 
EmbeddingStorelngestor.builder()
.embeddingStore(store)
documentSplitter(documentSplitter) # 设置RAG
.build();

//1.加载文档进内存
//List<Document> documents = ClassPathDocumentLoader.loadDocuments("content");
List<Document> documents = classPathDocumentLoader.loadDocuments("content",new ApachePdf
//List<Document> documents = FilesystemDocumentLoader.loadDocuments("c:\\Users (AdministratorllideaProjec)
//2.构建向量数据库操作对象
InMemoryEmbeddingStore store =new InMemoryEmbeddingStore();
//构建文档分割器对象
jocumentSplitter ds = Documentsplitters.recursive(
maxSegmentsizelnChars: 500, maxOverlapSizelnChars: 100);
//3.构建一个EmbeddingStoreIngestor对象,完成文本数据切割,向量化,存储
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(store)
.documentSplitter(ds).build():
ingestor.ingest(documents),
return store;
}

向量模型,用于把文档分割后的片段向量化或者查询时把用户输入的内容向量化
1.配置向量模型信息
2.设置EmbeddingModel
向量数据库:
Milvus、Chroma、PineconeRedisSearch(Reids)、pgvector(PostgreSQL)

public interface EmbeddingModel {
	default Response<Embedding> embed(String text) {
		return this.embed(TextSegment.from(text));
    }
	default Response<Embedding>embed(TextSegment textSegment) {
	
	}
	Response<List<Embedding>>embedAll(List<TextSegment> texts);
	default int dimension(){
		return((Embedding)this.embed("test").content()).dimension();
    }
}

langchain4j:
    open-ai:
    	embedding-model:
            base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
            api-key: ${API-KEY}
            model-name: text-embedding-v3
            log-requests: true
            log-responses: true
            
EmbeddingStoreingestor.builder()
.embeddingStore(store) # 设置
.documentSplitter(documentSplitter)  
.embeddingModel(embeddingModel)
.build();

EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(embeddingModel) # 设置
.maxResults(3)
.minScore(0.6)
.build();

EmbeddingStore,用于操作向量数据库(添加、检索)
1.准备向量数据库
2.引入依赖
3.配置向量数据库信息
4.注入RedisEmbeddingstore并使用
public interface EmbeddingStore<Embedded> {
    String add(Embedding embedding);
    void add(String text, Embedding embedding);
    String add(Embedding embedding, Embedded embedded);
    List<String> addAll(List<Embedding> embeddings);
    EmbeddingSearchResult<Embedded> search(EmbeddingSearchRequest request);
}

docker stop redis #停止原有的redis镜像
docker rm redis #删除原有的redis镜像
docker run --name redis-vector -d -p 6379:6379 redislabs/redisearch

<dependency>
    <groupld>dev.langchain4j</groupld>
    <artifactld>langchain4j-community-redis-spring-boot-starter</artifactld>
    <version>1.0.1-beta6</version>
</dependency>

langchain4j:
	community:
		redis:
			host: localhost
			port: 6379
			
EmbeddingStoreingestor.builder()
.embeddingStore(store) # 设置
.documentSplitter(documentSplitter)
.embeddingModel(embeddingModel)
.build();

EmbeddingStoreContentRetriever.builder()
.embeddingStore(store) # 设置
.embeddingModel(embeddingModel)
.maxResults(3)
.minScore(0.6)
.build();

Tools工具-准备工作

准备工作:开发一个预约信息服务,可以读写MySql中预约表中的信息

12.png
12.png
1.准备数据库环境
2.引入依赖
3.配置连接信息
4.准备实体类
5.开发Mapper
6.开发Service
7.完成测试

docker run --name mysql -d -p 3307:3306 -e MYSQL ROOT PASSWORD=1234 mysql
create database if not exists volunteer;
use volunteer;
create table if not exists reservation
(
    id                  bigint      primary key auto_increment not null comment '主键ID',
    name                varchar(50) not null comment '考生姓名',
    gender              varchar(2)  not null comment '考生性别',
    phone               varchar(20) not null comment '考生手机号',
    communication_time  datetime    not null comment '沟通时间',
    province            varchar(32) not null comment '考生所处的省份',
    estimated_score     int         not null comment '考生预估分数'
);

<dependency>
<groupld>org.projectlombok</groupld>
<artifactld>lombok</artifactld>
</dependency>
<dependency>
<groupld>org.mybatis.spring.boot</groupld>
<artifactld>mybatis-spring-boot-starter</artifactld>
<version>3.0.3</version>
</dependency>
<dependency>
<groupld>com.mysql</groupld>
<artifactld>mysql-connector-j</artifactld>
</dependency>

spring:
	datasource:
		username: root
		password:1234url:jdbc:mysql://localhost:3307/gk?
		useUnicode=true&characterEncoding=utf-
		8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
		driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
	configuration:
		map-underscore-to-camel-case: true
		
@Service
public class ReservationService {
	@Autowired
	private ReservationMapper reservationMapper;
	public void insert(Reservation reservation){
		reservationMapper.insert(reservation);
    }
	public Reservation findByPhone(String phone) {
		return reservationMapper.findByPhone(phone);
	}
}
@AiService(
	wiringMode =AiServiceWiringMode.EXPLICIT,
	tools ="reservationTool"
}
@Component
public class ReservationTool {
	@Autowired
	private ReservationService reservationService,
	@Tool("添加志愿指导服务预约")
	public void addReservation(
        @P("考生姓名") String name,
        @P("考生性别") String gender
        @P("考生电话") String phone,
        @P("沟通时间") LocalDateTime communicationTime.
        @P("考生所在省份")String province,
        @P("考生预估分数") Integer estimatedScore)
    {
		Reservation reservation = new Reservation(null, name, gender, phone, communicationTime, province, estimatedScore));
		reservationService.insert(reservation);
    }
    //2.工具方法:查询预约信息
	@Tool("根据考生电话查询考生预约详情")
	public Reservation findReservation(@P("考生电话") String phone){
		return reserationSerice,fndBvPhope(phone);
    }
}

@AiService(
wiringMode =AiServiceWiringMode.EXPLICIT,
..tools= "reservationTool"
}