LangChain4j与DashScope深度集成实战:一站式开发指南
本篇文章会通篇详细的讲清楚LangChain4j与DashScope集成的各个方面,从Springboot的集成到Ai对话、会话记忆、RAG、FunctionCalling、互联网搜索、结构化的输出、多模态等都给出相应的说明,希望通过这篇文章对于LLM不了解的同仁一样可以扩展出自己的AI应用。
DashScope(Qwen)
DashScope是阿里云开发的一个平台。
Qwen模型是由阿里云开发的一系列生成式AI模型。Qwen系列模型是专门为文本生成、摘要、问答和各种NPL任务而设计的。
简单SpringBoot集成
第一步:新增一个父Maven工程,目的是方便后面的依赖管理
父工程POM文件内容如下:
<?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>com.xiaoxie</groupId><artifactId>LangChain4j</artifactId><packaging>pom</packaging><version>1.0.0</version><modules><module>chat</module></modules><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><springboot.version>3.3.5</springboot.version><langchain4j_version>1.0.0-beta1</langchain4j_version></properties><dependencyManagement><dependencies><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain4j_version}</version></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-bom</artifactId><version>${langchain4j_version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${springboot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement></project>
这里几个版本说明:
- langchain4j:1.0.0-beta1
- SpringBoot:3.3.5
第二步:新增一个chat的子Maven模块
子模块中基础的pom文件如下:
<?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><parent><groupId>com.xiaoxie</groupId><artifactId>LangChain4j</artifactId><version>1.0.0</version></parent><artifactId>chat</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- springboot web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- langchain4j集成dashcope starter --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies></project>
第三步:对于chat模块,修改项目的yml文件,添加关于dashcope的apikey及模型相关信息
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-latest
注意:这里apikey我配置到了系统的环境变量当中
需要使用大家可以自行去阿里云dashcope去申请apikey,建议也配置到自己的环境变量中
第四步:我们chat模块新增一个Controller类进行聊天对话请求
@RestController
@RequestMapping("/chat")
public class ChatController {@Resourceprivate ChatLanguageModel chatLanguageModel;@GetMapping("/ai")public String chat(@RequestParam("message") String message) {return chatLanguageModel.chat(UserMessage.from(message)).aiMessage().text();}}
使用AiService
第一步:我们新增一个包service,在其中定义一个接口Assistant (这个名称是可以自定义的)
public interface Assistant {String chat(String message);
}
我们要实例化出来这个Assistant的Bean出来,这里实际上使用提反射机制来完成的。
第二步:我们新增config包,其中定义一个配置类AssistantConfig,用来实例化Assistant这个Bean,由于我们在配置类中我们需要使用到AiService这个工具类,所以此时在我们的chat这个模块中添加一个依赖:
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId>
</dependency>
配置类如下:
@Configuration
public class AssistantConfig {@Resourceprivate ChatLanguageModel chatLanguageModel;@Beanpublic Assistant getAssistant() {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).build();}
}
第三步:新增一个Controller类,使用AiService接口的代理类来实现聊天对话
@RestController
@RequestMapping("/assistant")
public class AssistantController {@Resourceprivate Assistant assistant;@GetMapping("/chat")public String chat(@RequestParam("message") String message) {return assistant.chat(message);}
}
调整AI对话中的场景角色
使用ChatLanguageModel
在Controller中我们在调用chat方法的时候可以传入多个Message,其中有一个SystemMessage就可以用来设置角色和场景相关的信息。而UserMessage是来自于用户传递过来的请求消息。
我们此时在配置文件中定义一个SystemMessage文本配置信息
ai:systemMessage: 你的名字中天鉴,拥有强大的智慧
最终ChatController修改为如下:
@RestController
@RequestMapping("/chat")
public class ChatController {@Resourceprivate ChatLanguageModel chatLanguageModel;@Value("${ai.systemMessage}")private String systemMessage;@GetMapping("/ai")public String chat(@RequestParam("message") String message) {return chatLanguageModel.chat(List.of(SystemMessage.systemMessage(systemMessage),UserMessage.from(message))).aiMessage().text();// return chatLanguageModel.chat(UserMessage.from(message)).aiMessage().text();}
}
使用AiService
在初始化AiService的Bean的时候,我们可以在创建Bean的类上加上一个注解@SystemMessage,给定其中的value值则可以完成指定。
@Bean
@SystemMessage("${ai.systemMessage}")
public Assistant getAssistant() {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).build();
}
如果我们不使用@SystemMessage注解的话也可以在构造的链式调用时调用systemMessageProvider()来指定
@Value("${ai.systemMessage}")
private String systemMessage;@Bean
public Assistant getAssistant() {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).systemMessageProvider(request -> systemMessage).build();
}
会话记忆
我们一般来说实现会话记忆的方式如下:
实际上我们调用原生API的时候,我们要实现的话要把所有会话历史内容发送给LLM
我们再新增一个Maven模块:memory
基础的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><parent><groupId>com.xiaoxie</groupId><artifactId>LangChain4j</artifactId><version>1.0.0</version></parent><artifactId>memory</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!-- springboot web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- langchain4j集成dashcope starter --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies></project>
主配置文件如下:
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-latest
我们要创建一个配置类来配置ChatMemory这个Bean
@Configuration
public class ChatMemoryConfig {@Bean("messageWindowChatMemory")public ChatMemory initChatMemory() {return MessageWindowChatMemory.builder().maxMessages(10) // 表示会话中最多保存10条消息.build();}
}
我们在与ai聊天的时候把用户的消息以及ai回复的所有消息都给到LLM
Controller类写为如下:
@RestController
@RequestMapping("/memory")
public class MemoryController {@Resourceprivate ChatLanguageModel chatLanguageModel;@Resource(name = "messageWindowChatMemory")private ChatMemory chatMemory;@GetMapping("/chat")public String chat(@RequestParam("message") String message) {chatMemory.add(UserMessage.from(message));// 这里要把chatMemory的所有历史消息都传给chatLanguageModelChatResponse response = chatLanguageModel.chat(chatMemory.messages());chatMemory.add(response.aiMessage());return response.aiMessage().text();}
}
这个时候我们再聊天时ai就感知上有了记忆,从上面可以看出来我们与ai聊天的话是无状态的,为了让他有记忆我们得每次转给它的消息中包含它历史的回复信息。
MemoryId
我们在与ai聊天的时候会创建很多会话,每个会话之间需要进行隔离(各自有自己独立的上下文),要达到这个隔离的效果我们就要使用到MemoryId。
我们新增一个AiService接口
public interface Assistant {String chat(@MemoryId String memoryId, @UserMessage String message);
}
这个接口中的方法比这前多了一个参数memoryId,我们就可以使用它来进行会话隔离
@MemoryId:表示参数是memoryId
@UserMessage:表示参数是用户提交的消息
创建一个配置类来创建这个接口对应的代理Bean
@Configuration
public class AssistantConfig {@Resourceprivate ChatLanguageModel chatLanguageModel;@Beanpublic Assistant assistant() {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel)// 必须要提供这个配置,否则会报错// 这个时候当传入不同的memoryId的时候,会创建不同的ChatMemory,从而达到按memoryId隔离的chatMemory.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)).build();}}
我们的Controller类中可以添加如下代码进行测试
@Resource
private Assistant assistant;@GetMapping("/assistant/chat")
public String assistantChat(@RequestParam("memoryId") String memoryId, @RequestParam("message") String message) {return assistant.chat(memoryId, message);
}
从上我们使用AiService按memoryId进行了会话记忆隔离,当我们不使用AiService时要实现隔离效果可以如下操作:
在controller类中再新增如下代码,来实现不使用AiService达到按memoryId隔离的效果
private final Map<String, ChatMemory> chatMemoryMap = new ConcurrentHashMap<>();@GetMapping("/memoryId/chat")
public String memoryIdChat(@RequestParam(value = "memoryId", required = false) String memoryId, @RequestParam("message") String message) {// 判断传入memory是否为空if (memoryId == null || memoryId.isEmpty()) {memoryId = "default";}// 获取或者创建 ChatMemory对象ChatMemory chatMemory = chatMemoryMap.computeIfAbsent(memoryId, key -> MessageWindowChatMemory.withMaxMessages(10));chatMemory.add(UserMessage.from(message));ChatResponse response = chatLanguageModel.chat(chatMemory.messages());chatMemory.add(response.aiMessage());return response.aiMessage().text();
}
会话记忆存储
MessageWeindowChatMemory中有一个属性:ChatMemoryStore
这个ChatMemoryStore有一个默认实现是InMemoryChatMemoryStore,它的存储是在内存中的,如果我们要存储到指定的存储设备(如:数据库)则我们需要自行去实现这个chatMemoryStore接口。
如果我们要把这个会话记忆存到mysql数据库中具体实现如下:
第一步:新增一个数据库及数据表,用来存储会话记录
CREATE TABLE `chat_memory_message` (`id` int NOT NULL AUTO_INCREMENT,`memory_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',`message` json DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
第二:我们使用了druid作为数据源,mybatis操作数据库,数据库使用mysql,所以对应的需要添加如下依赖:
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId>
</dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId>
</dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId>
</dependency>
第三步:新增数据源配置
# 数据源配置
spring:datasource:druid:driverClassName: com.mysql.cj.jdbc.Driver# 默认据源url: jdbc:mysql://localhost:3306/llm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: A7#mZ9!pL3$q# 初始连接数initialSize: 5# 最小连接池数量minIdle: 10# 最大连接池数量maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置连接超时时间connectTimeout: 30000# 配置网络超时时间socketTimeout: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 300000# 配置一个连接在池中最大生存的时间,单位是毫秒maxEvictableIdleTimeMillis: 900000# 配置检测连接是否有效validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsewebStatFilter:enabled: truestatViewServlet:enabled: true# 设置白名单,不填则允许所有访问allow:url-pattern: /druid/*# 控制台管理用户名和密码login-username: druidlogin-password: 123456filter:stat:enabled: true# 慢SQL记录log-slow-sql: trueslow-sql-millis: 1000merge-sql: truewall:config:multi-statement-allow: true
由于数据源的配置比较长,且一般不会动,我们单独把它放在application-druid.yml当中,在主配置文件application.yml激活这个配置即可,主配置文件激活这个配置,最终主配置文件变为如下:
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-latest# 启用Druid数据源配置,在application-druid.yml中
spring:profiles:active: druid################# mybatis ###############
mybatis:mapper-locations: classpath:mapper/**/*Mapper.xmltype-aliases-package: com.xiaoxie.**.domainconfiguration:map-underscore-to-camel-case: truetype-handlers-package: com.xiaoxie.**.mapper.typehandler
第三步:添加实体类
@Data
public class ChatMemoryMessage {private Integer id;private String memoryId;private String message;
}
第四步:新增mapper接口及对应的mapper映射文件
@Mapper
public interface ChatMemoryMessageMapper {String getMessageByMemoryId(@Param("memoryId") String memoryId);void update(ChatMemoryMessage chatMemoryMessage);void insert(ChatMemoryMessage chatMemoryMessage);void deleteByMemoryId(@Param("memoryId") String memoryId);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaoxie.mapper.ChatMemoryMessageMapper"><insert id="insert">insert into chat_memory_message(memory_id, message)values (#{memoryId}, #{message})</insert><update id="update">update chat_memory_message<set><if test="message != null">message = #{message},</if></set>where memory_id = #{memoryId}</update><delete id="deleteByMemoryId">delete from chat_memory_message where memory_id = #{memoryId}</delete><select id="getMessageByMemoryId" resultType="java.lang.String">select message from chat_memory_message where memory_id = #{memoryId}</select>
</mapper>
第五步:新增一个ChatMemoryStore的实现
@Component("chatMemoryMessage4MySQL")
public class ChatMemoryMessage4MySQL implements ChatMemoryStore {@Resourceprivate ChatMemoryMessageMapper chatMemoryMessageMapper;@Overridepublic List<ChatMessage> getMessages(Object memoryId) {String messages = chatMemoryMessageMapper.getMessageByMemoryId(memoryId.toString());return ChatMessageDeserializer.messagesFromJson(messages);}@Transactional@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages) {String messagesJson = ChatMessageSerializer.messagesToJson(messages);ChatMemoryMessage chatMemoryMessage = ChatMemoryMessage.builder().memoryId(memoryId.toString()).message(messagesJson).build();if (chatMemoryMessageMapper.getMessageByMemoryId(memoryId.toString()) != null) {chatMemoryMessageMapper.update(chatMemoryMessage);} else {chatMemoryMessageMapper.insert(chatMemoryMessage);}}@Transactional@Overridepublic void deleteMessages(Object memoryId) {chatMemoryMessageMapper.deleteByMemoryId(memoryId.toString());}
}
第六步:在AssistantConfig这个类中再配置一个使用这个存储方案的Assistant的Bean
@Resource(name="chatMemoryMessage4MySQL")
private ChatMemoryStore chatMemoryStore;@Bean("memoryAssistantMysql")
public Assistant memoryAssistantMysql() {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId) // 这个不能少,在传到mybati时需要这个memoryid.chatMemoryStore(chatMemoryStore).build()).build();
}
第七步:Controller中我们则可以使用这个Bean来进聊天
@Resource(name="memoryAssistantMysql")
private Assistant assistantMySQL;@GetMapping("/memoryStore/chat/assistant")
public String memoryStoreChatAssistant(@RequestParam("memoryId") String memoryId, @RequestParam("message") String message) {return assistantMySQL.chat(memoryId, message);
}
RAG能力
Rag叫做增强检索技术。
当我们使用LLM进行聊天的时候,LLM通常是无法知晓我们内部的信息的。我们如果希望要让LLM在聊天中可以读取到我们内部私有的知识库文档,就要用到RAG
我们先了准备一个东西:EmbeddingModel,它可以把我们的文档数据转为向量化的数据。
我们新增一个Maven子模块:rag
基于内存的向量存储
新的rag模块需要添加如下基础依赖
<dependencies><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-easy-rag</artifactId></dependency><!-- springboot web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- langchain4j集成dashcope starter --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>
注意:langchain4j-easy-rag 依赖我们继承父项目的,所以我们要在父亲项目中添加如下管理依赖
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-easy-rag</artifactId><version>${langchain4j_version}</version>
</dependency>
基础的配置文件内容如下:
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-latest
声明一个AiService接口
public interface Assistant {String chat(@MemoryId String memoryId, @UserMessage String message);
}
注意:这里还使用了memoryId进行隔离,当然这里不是必须的!
创建配置类来配置Assistant的Bean
@Configuration
public class AssistantConfig {@Resourceprivate ChatLanguageModel chatLanguageModel;// 基于内存的向量存储@Beanpublic EmbeddingStore<TextSegment> embeddingStore() {return new InMemoryEmbeddingStore<>();}@Beanpublic Assistant assistant(EmbeddingStore<TextSegment> embeddingStore) {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))// 这里直接使用一个基于内存的向量存储库.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore)).build();}}
后续我们要读取我们内部的文档信息,我们先新增一个工具类DocumentUtils
public class DocumentUtils {/*** 根据指定的目录加载目录下所有文档* @param dir 目录* @return 文档列表*/public static List<Document> loadDocuments(String dir) {return FileSystemDocumentLoader.loadDocuments(dir);}/*** 根据指定的目录加载目录下指定类型的文件* @param dir 指定要加载的目录* @param filter 文件类型* @return 文档列表*/public static List<Document> loadDocuments(String dir, String filter) {PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*" + filter);return FileSystemDocumentLoader.loadDocuments(dir, pathMatcher);}/*** 根据指定的目录加载目录下所有文档* @param dir 目录* @param containsSubDir 是否包含子目录* @return 文档列表*/public static List<Document> loadDocuments(String dir,Boolean containsSubDir) {if (containsSubDir) {return FileSystemDocumentLoader.loadDocumentsRecursively(dir);}return FileSystemDocumentLoader.loadDocuments(dir);}/*** 根据指定的目录加载目录下指定类型的文件* @param dir 指定要加载的目录* @param filter 文件类型* @param containsSubDir 是否包含子目录* @return 文档列表*/public static List<Document> loadDocuments(String dir,String filter,Boolean containsSubDir) {// 多级目录时写**PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:**" + filter);if (containsSubDir) {return FileSystemDocumentLoader.loadDocumentsRecursively(dir, pathMatcher);}return FileSystemDocumentLoader.loadDocuments(dir, pathMatcher);}
}
默认的langchang4j中使用了Apache的Tika来读取解析各种不同类型的文档
在controller中新增两个请求方法一个用来加载文档到指定的embeddingStore,一个用来与ai聊天对话
@RestController
@RequestMapping("/rag")
public class RagController {@Resourceprivate Assistant assistant;@Resourceprivate EmbeddingStore<TextSegment> embeddingStore;@GetMapping("/load")public String load() {List<Document> documents = DocumentUtils.loadDocuments("E:\\project\\IdeaProjects\\AI\\LangChain4j1.0\\Langchain4j\\rag\\src\\main\\resources\\documents");// 读取到的文档集合写到指定的embeddingStore,当前这里是memory中EmbeddingStoreIngestor.ingest(documents, embeddingStore);return "Load Success!!";}@GetMapping("/chat")public String chat(@RequestParam("memoryId") String memoryId, @RequestParam("message") String message) {return assistant.chat(memoryId, message);}
}
这个时候我们先请求/load,此时会把目录下所有文档进行解析分片存储到embeddingStore当中,然后我们请求/chat进行对话的时候就LLM可以基于我们的embeddingStroe中的内容进行回答,原因就在于我们创建AiService的Bean的时候指定了.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
基于pgvector的向量存储
pgvector的安装
第一步:先安装postgresql
第二步:下载pgvector插件:下载地址:vector: Open-source vector similarity search for Postgres / PostgreSQL Extension Network
第三步: 安装visual studio,这里主要是选“使用C++的桌面开发” 用于后面插件的编译与安装
在上面都完成后使用下面的方式进行插件安装
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
cd C:\Users\xxx\Downloads\vector-0.7.3
set "PGROOT=C:\Program Files\PostgreSQL\16"
nmake /F Makefile.win
nmake /F Makefile.win install
最后在数据库连接工具中,选中具体的数据库实例,执行下面的命令就可以扩展vector类型了
CREATE EXTENSION vector;
langchain4j中使用
首先我们在配置文件中添加pgvector配置信息
pgvector:database: vectorTesthost: localhostport: 5433user: postgrespassword: A7***$qtable: my_embeddingsdimension: 1024
这里根据你本地的postgresql进行配置,其中有一个配置dimension配置为了1024表示向量维度我们设置为了1024,这个是由于后面要使用的text-embedding-v3模型有关(这个embeddingModel支持的向量维度有1024)。
pom.xml中需要添加postgresql和pgvector的依赖
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId>
</dependency>
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-pgvector</artifactId>
</dependency>
由于我们依赖说明
postgresql:继承了SpringBoot的依赖,这里不需要指定版本
langchain4j-pgvector:继承我们自己的父项目的版本,所以我们在父项目中pom管理中此时需要添加如下依赖的管理
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-pgvector</artifactId><version>${langchain4j_version}</version>
</dependency>
我使用计划使用dashScope的text-embedding-v3模型,所以配置文件中添加相关配置,所以关于模型的相关配置最终如下:
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-latestembedding-model:apiKey: ${QWEN_API_KEY}modelName: text-embedding-v3
注意:我们要注意一下模型的向量维度!这里我们使用text-embedding-v3可以使用向量维度1024
在config包下我们添加一个配置信息映射类:PgVectorProp
/*** pgvector配置*/
@Configuration
@ConfigurationProperties(prefix = "pgvector")
@Data
public class PgVectorProp {private String host;private int port;private String database;private String user;private String password;private String table;private int dimension;
}
新增一个EmbeddingStore的Bean的配置类:EmbeddingStore4PgConfig
@Configuration
public class EmbeddingStore4PgConfig {@Resourceprivate PgVectorProp pgVectorProp;@Bean("embeddingStore4pg")public EmbeddingStore<TextSegment> embeddingStore() {return PgVectorEmbeddingStore.builder().table(pgVectorProp.getTable()).dropTableFirst(true) // 表示每次启动都先删除表 重新创建.database(pgVectorProp.getDatabase()).host(pgVectorProp.getHost()).port(pgVectorProp.getPort()).user(pgVectorProp.getUser()).password(pgVectorProp.getPassword()).dimension(pgVectorProp.getDimension()).build();}
}
在AssistanConfig类中我们新增一个Assistant的Bean的创建,整体代码如下:
@Configuration
public class AssistantConfig {@Resourceprivate ChatLanguageModel chatLanguageModel;@Resourceprivate EmbeddingModel embeddingModel;// pgvector向量存储@Resource(name="embeddingStore4pg")private EmbeddingStore<TextSegment> embeddingStore4pg;// 内存向量存储@Bean(name="embeddingStore4Mem")public EmbeddingStore<TextSegment> embeddingStore() {return new InMemoryEmbeddingStore<>();}@Bean("assistant4Mem")public Assistant assistant(@Qualifier("embeddingStore4Mem") EmbeddingStore<TextSegment> embeddingStore) {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))// 这里直接使用一个基于内存的向量存储库.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore)).build();}@Bean("assistant4pg")public Assistant assistant4pg() {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))// 这里直接使用一个基于pg的向量存储库.contentRetriever(EmbeddingStoreContentRetriever.builder().embeddingModel(embeddingModel).embeddingStore(embeddingStore4pg).maxResults(5).minScore(0.75).build()).build();}
}
我们此时关注assistant4pg()这个方法,在.contentRetriever()
中我们指定使用我们指定的embeddingModel(也就是我们配置的:text-embedding-v3模型),同时我们指定embeddingStore中使用我们创建的embeddingStore4pg,这样的话存储会存储到指定的库中。
Controller中我们要进行文档的解析加载与聊天添加如下代码实现加载文档到数据库和进行聊天
@Resource(name="assistant4pg")
private Assistant assistant4pg;
@Resource(name="embeddingStore4pg")
private EmbeddingStore<TextSegment> embeddingStore4pg;
@Resource
private EmbeddingModel embeddingModel;@GetMapping("/pg/load")
public String pgload() {List<Document> documents = DocumentUtils.loadDocuments("E:\\project\\IdeaProjects\\AI\\LangChain4j1.0\\Langchain4j\\rag\\src\\main\\resources\\documents");// 读取到的文档集合写到指定的embeddingStore,当前这里是memory中// EmbeddingStoreIngestor.ingest(documents, embeddingStore4pg);// 这里要调整一下不能使用默认的,修改为如下EmbeddingStoreIngestor.builder().embeddingStore(embeddingStore4pg) // 使用的embeddingStore4pg是存储到pgvector的.embeddingModel(embeddingModel) // 使用的embeddingModel是text-embedding-v3.build().ingest(documents);return "Load Success!!";
}@GetMapping("/pg/chat")
public String pgchat(@RequestParam("memoryId") String memoryId, @RequestParam("message") String message) {return assistant4pg.chat(memoryId, message);
}
切分文档方式
从上面我们可以看到DocumentSplitter有7个实现,如果我们加载文档到embeddingStore的时候不指定切分的方式则使用默认的切分方式:DocumentByParagraphSplitter(按段落去进行切分),如果我们需要调整这个切分的方式,则我们需要手动指定文档切分的方式。
如下所示我们在加载文档到embeddingStore的时候,指定按行来进行切分,并指定每段多少个字符,每段有多少重叠的字符。
注意:这里的重叠表示,上一个切出来的文本内容与下一个切出来的文本内容有多少个字符是重叠的,这个设置也有一些讲究,如果是对于连贯的内容则要重叠多一些字符这样的话在与LLM对话进行查询知识的时候它会更加精确找到相关的知识,对于非连贯的内容比较每一块都是不同的具体知识则可以考虑少一些重叠,因为多的重叠并会提高它的精度反而会导致查询的相关数据更多。
此时我们/load请求的方法写为如下:
@GetMapping("/pg/load")
public String pgload() {List<Document> documents = DocumentUtils.loadDocuments("E:\\project\\IdeaProjects\\AI\\LangChain4j1.0\\Langchain4j\\rag\\src\\main\\resources\\documents");// 读取到的文档集合写到指定的embeddingStore,当前这里是memory中// EmbeddingStoreIngestor.ingest(documents, embeddingStore4pg);// 这里要调整一下不能使用默认的,修改为如下EmbeddingStoreIngestor.builder().embeddingStore(embeddingStore4pg) // 使用的embeddingStore4pg是存储到pgvector的.embeddingModel(embeddingModel) // 使用的embeddingModel是text-embedding-v3.documentSplitter(new DocumentByLineSplitter(200,10)).build().ingest(documents);return "Load Success!!";
}
FunctionCalling
基础使用
接下来我们新增一个新的Maven子模块functionCall
其础的pom依赖如下:
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId>
</dependency><!-- springboot web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- langchain4j集成dashcope starter -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency><!-- lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
基础配置文件
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-latest
在接下来操作前我们先简单说明一下FunctionCalling
通常来说我们使用LLM都是用来对话的,如果我们希望使用我们的语言来调用一些自定义函数(完成业务功能而不仅仅只是对话),那要实现这个则就是使用FunctionCalling来完成的。
我们在func包下定义一个类Calculator
@Data
public class Calculator {private int a;private int b;public int add() {return a + b;}
}
Controller中对于聊天请求返回的结果中判断是否有toolExecutionRequests,有就执行工具,没有就直接使用ai返回的响应信息
@Slf4j
@RestController
@RequestMapping("/functionCall")
public class FunctionCallController {@Resourceprivate ChatLanguageModel chatLanguageModel;@GetMapping("/chat")public String chat(@RequestParam("message") String message) {// 定义ToolSpecificationToolSpecification tool1 = ToolSpecification.builder().name("Calculator").description("输入两个数,对这两个数进行求和").parameters(JsonObjectSchema.builder().addIntegerProperty("a", "第一个数").addIntegerProperty("b", "第二个数").required("a","b") // 指定必须的参数.build()).build();// 聊天时候,使用toolSpecificationsChatResponse response = chatLanguageModel.chat(ChatRequest.builder().messages(UserMessage.from(message)).parameters(ChatRequestParameters.builder().toolSpecifications(tool1).build()).build());// 在用户聊天时判断是否有toolExecutionRequests,如果没有,但是ai返回了响应信息则返回ai的响应信息List<ToolExecutionRequest> toolExecutionRequests = response.aiMessage().toolExecutionRequests();if (toolExecutionRequests == null || toolExecutionRequests.isEmpty()) {return response.aiMessage().text().isEmpty() ? "未收到ai有效响应" : response.aiMessage().text();}String result = "";for (ToolExecutionRequest toolExecutionRequest : toolExecutionRequests) {try{String className = "com.xiaoxie.func." + toolExecutionRequest.name();Class<?> clazz = Class.forName(className);Calculator calculator = (Calculator) JsonUtils.fromJson(toolExecutionRequest.arguments(), clazz);result = "对这两个数:【" + calculator.getA() + "," + calculator.getB() + "】求和的结果是:" + calculator.add();break; // 工具执行一次则退出} catch (Exception e) {log.error("执行工具失败", e);throw new RuntimeException("执行工具失败", e);}}return result;}
}
像上面这样我们手动去实现函数调用的过程还是比较麻烦的!!
除了上面这个手动去实现的方式,我们还可以使用注解
func包下新增一个AiServiceCalculator
public class AiServiceCalculator {@Tool("计算两数之和")public int add(int a, int b) {return a + b;}
}
这里我们使用了@Tool注解
在AiService的Bean创建中需要使用.tools来指定对应的工具类对象
@Configuration
public class AssistantConfig {@Resourceprivate ChatLanguageModel chatLanguageModel;@Beanpublic Assistant assistant() {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).tools(new AiServiceCalculator()).build();}
}
接下来在Controller当中则可以使用这个AiService的Bean来进行聊天
@Resource
private Assistant assistant;@GetMapping("/assistant/chat")
public String assistantChat(@RequestParam("message") String message)
{return assistant.chat(message);
}
注意:此时我们与ai进行对话的过程中如果需要进行调用工具函数会自动去调用。
如果我们的函数工具类中有多个@Tool方法,LLM会根据我们的语义自动选择相应的函数执行
联网搜索能力
我们要有联网搜索能力使用的是serarchapi
首先我们要去申请对应的apikey
地址:Google Search API for real-time SERP scraping
添加对应的pom依赖
<!-- web search -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-web-search-engine-searchapi</artifactId>
</dependency>
yml的配置中我们配置一下关于searchapi相关的配置参数
websearch:apiKey: ${SEARCH_API_KEY}engine: baidu
同时我们添加一个百炼的新的模型:qwq-plus,注意这个模型只支持流式输出所以整体的配置文件改为如下:
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-lateststreaming-chat-model:apiKey: ${QWEN_API_KEY}modelName: qwq-pluswebsearch:apiKey: ${SEARCH_API_KEY}engine: baidu
由于它只支持流式输出,所以不要添加依赖:
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId>
</dependency>
添加一个新的AiService的接口
public interface WebAssistant {Flux<String> chat(String message);
}
由于我们需要进行联网搜索,所以先要初始化一个SearchApiWebSearchEngine的Bean,新增配置类:WebSearchConfig
@Configuration
public class WebSearchConfig {@Resourceprivate WebSearchProp webSearchProp;@Beanpublic SearchApiWebSearchEngine SearchApiWebSearchEngine() {return SearchApiWebSearchEngine.builder().engine(webSearchProp.getEngine()).apiKey(webSearchProp.getApiKey()).build();}
}
其中WebSearchProp用来读取映射配置文件中的值
@Configuration
@ConfigurationProperties(prefix = "websearch")
@Data
public class WebSearchProp {private String apiKey;private String engine;
}
在AiService配置类中进行配置添加代码如下:
@Resource
private StreamingChatLanguageModel streamingChatLanguageModel;@Bean("webAssistant")
public WebAssistant webAssistant(SearchApiWebSearchEngine searchApiWebSearchEngine) {return AiServices.builder(WebAssistant.class).streamingChatLanguageModel(streamingChatLanguageModel).tools(new AiServiceCalculator(),new WebSearchTool(searchApiWebSearchEngine)).build();
}
这里我们可以看到在.tools中我们不仅仅是添加了我们前面创建 一个工具类对象,我们构造了一个WebSearchTool对象,这个对象在构造时传入了我们创建的searchApiWebSearchEngine
接下来在Controller当中我们就可以使用到联网的查询能力
@RestController
@RequestMapping("/webSearch")
public class WebSearchController {@Resourceprivate WebAssistant webAssistant;@GetMapping(value = "/chat", produces = "text/plain;charset=UTF-8")public Flux<String> chat(@RequestParam("message") String message) {return webAssistant.chat(message);}
}
这里我们要使用Flux<String>
输出的原因就是qwq-plus只支持流式输出
结构化输出
注意:如果是要指定输也JSON格式的数据qwen是不支持的!!
我们新增一个子模块struct_output
相关的pom依赖如下:
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId>
</dependency>
<!-- web search -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-web-search-engine-searchapi</artifactId>
</dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId>
</dependency><!-- springboot web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- langchain4j集成dashcope starter -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency><!-- lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
主配置文件如下:
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-max-lateststreaming-chat-model:apiKey: ${QWEN_API_KEY}modelName: qwq-pluswebsearch:apiKey: ${SEARCH_API_KEY}engine: baidu
我们使用的是qwen的模型是不支持JSON格式输出的,现在我们做的功能是要输出一个实体对象
新增一个Person类
@Data
public class Person {private String name;@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")private Date birthday;private String address;@Description("头衔及职位")private String title;
}
新增一个Assistant的接口方法,此方法返回的类型就是Person对象
public interface Assistant {String chat(String message);// 这个方法用来返回一个person对象Person getPerson(String message);
}
配置类中我们要做如下几件事:
1、关联联网搜索的配置信息
@Configuration
@ConfigurationProperties(prefix = "websearch")
@Data
public class WebSearchProp {private String apiKey;private String engine;
}
2、配置SearchApiWebSearchEngine
@Configuration
public class WebSearchConfig {@Resourceprivate WebSearchProp webSearchProp;@Beanpublic SearchApiWebSearchEngine webSearchEngine() {return SearchApiWebSearchEngine.builder().engine(webSearchProp.getEngine()).apiKey(webSearchProp.getApiKey()).build();}
}
3、配置一个基顾的AiService的Bean
@Configuration
public class AssistantConfig {@Resourceprivate ChatLanguageModel chatLanguageModel;@Beanpublic Assistant assistant(SearchApiWebSearchEngine searchApiWebSearchEngine) {return AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).build();}
}
完成上面的操作后我们在Controller类中进行请求,联网查询信息并让它返回对应的对象
@RestController
@RequestMapping("/structOutput")
public class StructOutputController {@Resourceprivate Assistant assistant;@Resourceprivate ChatLanguageModel chatLanguageModel;@Resourceprivate SearchApiWebSearchEngine searchApiWebSearchEngine;@GetMapping("/chat")public Person chat(@RequestParam("message") String message) {List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WebSearchTool.class);ChatResponse response = chatLanguageModel.chat(ChatRequest.builder().messages(UserMessage.from(message)).parameters(ChatRequestParameters.builder().toolSpecifications(toolSpecifications).build()).build());// 联网查询WebSearchTool webSearchTool = WebSearchTool.from(searchApiWebSearchEngine);List<ToolExecutionRequest> toolExecutionRequests = response.aiMessage().toolExecutionRequests();if (toolExecutionRequests == null || toolExecutionRequests.isEmpty()) {return null;}for (ToolExecutionRequest toolExecutionRequest : toolExecutionRequests) {// 联网查询结果String s = webSearchTool.searchWeb(toolExecutionRequest.arguments());if (!s.isEmpty()) {// 对于查询到的结果让ai进行解析返回一个Person对象return assistant.getPerson("从以下信息中获取雷军的信息:" + s);}}return null;}
}
此时我们请求:
http://localhost:8080/structOutput/chat?message=帮我查一下雷军个人简历的信息
最终返回结果是:
{"name": "雷军","birthday": "1969-12-16","address": "中国","title": "小米集团创始人、董事长兼首席执行官;金山软件前董事长;武汉大学校友"
}
实际上我们现在可以拿到对象了我们想输出是JSON也是可以的!!
如果我们要让ai输出的时候以Json格式输出可以类似像下面这样处理
@GetMapping("/chat2json")
public String chat2json(@RequestParam("message") String message) {// 定义响应的格式ResponseFormat responseFormat = ResponseFormat.builder().type(ResponseFormatType.JSON).jsonSchema(JsonSchema.builder().rootElement(JsonObjectSchema.builder().addStringProperty("name", "姓名").addStringProperty("birthday", "生日").addStringProperty("address", "地址").addStringProperty("title", "头衔及职位").build()).build()).build();ChatResponse response = chatLanguageModel.chat(ChatRequest.builder().messages(UserMessage.from(message)).parameters(ChatRequestParameters.builder().responseFormat(responseFormat).build()).build());return response.aiMessage().text();
}
多模态
新增一个子模块mutil
添加相关的基础pom依赖
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId>
</dependency>
<!-- web search -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-web-search-engine-searchapi</artifactId>
</dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId>
</dependency><!-- springboot web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- langchain4j集成dashcope starter -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency><!-- lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
配置文件
langchain4j:community:dashscope:chat-model:apiKey: ${QWEN_API_KEY}modelName: qwen-vl-max
注意:这里更换了模型为qwen-vl-max,因为这个模型是支持图片理解的,后续我们要传入图片让模型去识别
在resources下新增一个documents存入我们的图片文档
新增一个controller类
@RestController
@RequestMapping("/multi")
public class MultiController {@Resourceprivate ChatLanguageModel chatLanguageModel;@GetMapping("/chat")public String chat(@RequestParam("message") String message) throws IOException {// 读取到图片File file = new File("E:\\project\\IdeaProjects\\AI\\LangChain4j1.0\\Langchain4j\\mutil\\src\\main\\resources\\documents\\1.png");// 把file转为一个字节数组byte[] bytes = Files.readAllBytes(file.toPath());UserMessage userMessage = UserMessage.userMessage(TextContent.from(message), // 文本消息ImageContent.from(Base64.getEncoder().encodeToString(bytes), "image/png") // 图片信息);return chatLanguageModel.chat(userMessage).aiMessage().text();}}
这里注意,我们需要手动去封装一下UserMessage,把我们对话的用户文本和图片封装为一个UserMessage对象,然后再使用这个对象来向LLM发起请求对话
最终我们请求
http://localhost:8080/multi/chat?message=这个图片中所有数字的和是多少?
LLM返回结果
图片中的数字分别是 5、6、8、9 和 4。将这些数字相加:\[ 5 + 6 + 8 + 9 + 4 = 32 \]所以,这些数字的和是 32。
相关文章:
LangChain4j与DashScope深度集成实战:一站式开发指南
本篇文章会通篇详细的讲清楚LangChain4j与DashScope集成的各个方面,从Springboot的集成到Ai对话、会话记忆、RAG、FunctionCalling、互联网搜索、结构化的输出、多模态等都给出相应的说明,希望通过这篇文章对于LLM不了解的同仁一样可以扩展出自己的AI应用…...
逼用户升级Win11,微软开始给Win10限速
随着Windows10的支持时间越来越短,微软也加大了对Win10用户的驱赶力度。 最近,微软官宣了将要在今年6月份降低OneNote for Windows 10的同步速度。软件也将和Windows10在今年的10月14日一同停止支持和维护。 这将影响实时协作和多设备访问。 对OneNote…...
工作流引擎Flowable介绍及SpringBoot整合使用实例
Flowable简介 Flowable 是一个轻量级的业务流程管理(BPM)和工作流引擎,基于 Activiti 项目发展而来,专注于提供高性能、可扩展的工作流解决方案。它主要用于企业级应用中的流程自动化、任务管理和审批流等场景。 Flowable 的核心…...
推荐一个可以自定义github主页的网站
一、简介 Profile Readme Generator 是一个开源工具,可以帮助你快速创建个性化的 GitHub 个人简介(README)。它支持自定义内容和样式,让你的 GitHub 个人主页更加美观和专业。 二、使用步骤 (一)访问网站…...
【R语言可视化】相关系数热图
目录 热图无显著性 结果展示01: 热图显著性 结果展示02: ggplot2绘制三角热图 结果展示03: corrplot绘制三角热图 结果展示04: 热图无显著性 # 示例数据 data(mtcars) df <- mtcars# 计算相关矩阵 cor_matrix <- round(cor(df…...
【区块链 + 文化版权】文创链 | FISCO BCOS 应用案例
“文创链”是由四川省区块链行业协会、成都音像出版社有限公司共同发起, 由成都九天星空科技有限公司等联合打造的数字文创领域联盟链。平台采用FISCO BCOS 开源底层框架, 为数字文创产业构建一个高效、透明、可信的版权管理与交易平台。 平台专注于数字…...
# 使用自定义Shell脚本hello快速配置Linux用户账户
使用自定义Shell脚本快速配置Linux用户账户 在学校实验室管理Linux服务器,或者公司小团队管理服务器时,大家需要一个能隔离自己服务,但是自己又需要对服务器的完整权限的情形。创建和配置用户账户是一项常见但繁琐的任务。特别是当你需要频繁…...
PyTorch中的Tensor
PyTorch中的Tensor 是核心数据结构,类似于 NumPy 的多维数组,但具备 GPU 加速和自动求导等深度学习特性。 一、基本概念 核心数据结构 Tensor 是存储和操作数据的基础单元,支持标量(0D)、向量(1D&am…...
16-CSS3新增选择器
知识目标 掌握属性选择器的使用掌握关系选择器的使用掌握结构化伪类选择器的使用掌握伪元素选择器的使用 如何减少文档内class属性和id属性的定义,使文档变得更加简洁? 可以通过属性选择器、关系选择器、结构化伪类选择器、伪元素选择器。 1. 属性选择…...
关于笔记本电脑突然没有wifi图标解决方案
笔记本电脑突然没有wifi图标解决方案,设置里也看不见wifi,电脑突然就连不网络了 解决方案: 我的电脑——>管理——>服务和应用程序——>服务——>找到WLAN AutoConfig——>点击启动就好了...
Pytorch学习笔记(七)Learn the Basics - Optimizing Model Parameters
这篇博客瞄准的是 pytorch 官方教程中 Learn the Basics 章节的 Optimizing Model Parameters 部分。 官网链接:https://pytorch.org/tutorials/beginner/basics/optimization_tutorial.html 完整网盘链接: https://pan.baidu.com/s/1L9PVZ-KRDGVER-AJnXOvlQ?pwd…...
数据可视化TensorboardX和tensorBoard安装及使用
tensorBoard 和TensorboardX 安装及使用指南 tensorBoard 和 TensorBoardX 是用于可视化机器学习实验和模型训练过程的工具。TensorBoard 是 TensorFlow 官方提供的可视化工具,而 TensorBoardX 是其社区驱动的替代品,支持 PyTorch 等其他框架。以下是它…...
工业4G路由器赋能智慧停车场高效管理
工业4G路由器作为智慧停车场管理系统通信核心,将停车场内的各个子系统连接起来,包括车牌识别系统、道闸控制系统、车位检测系统、收费系统以及监控系统等。通过4G网络,将这些系统采集到的数据传输到云端服务器或管理中心,实现信息…...
深度学习1—Python基础
深度学习1—python基础 你的第一个程序 print(hello world and hello deep learning!)基本数据结构 空值 (None):在 Python 中,None 是一个特殊的对象,用于表示空值或缺失的值。它不同于数字 0,因为 0 是一个有意义的数字&#…...
数据结构十三、set map
一、set 1、size / empty size:返回set中实际元素的个数 empty:判断set是否为空 2、begin / end 这是两个迭代器,因此可以使用范围for来遍历整个红黑树。其中,遍历是按照中序遍历的顺序,因此是一个有序序列。 3、in…...
【大模型基础_毛玉仁】3.5 Prompt相关应用
目录 3.5 相关应用3.5.1 基于大语言模型的Agent3.5.2 数据合成3.5.3 Text-to-SQL3.5.4 GPTs 3.5 相关应用 Prompt工程应用广泛,能提升大语言模型处理基础及复杂任务的能力,在构建Agent、数据合成、Text-to-SQL转换和设计个性化GPTs等方面不可或缺。 . …...
自动驾驶VLA模型技术解析与模型设计
1.前言 2025年被称为“VLA上车元年”,以视觉语言动作模型(Vision-Language-Action Model, VLA)为核心的技术范式正在重塑智能驾驶行业。VLA不仅融合了视觉语言模型(VLM)的感知能力和端到端模型的决策能力,…...
【AI】Orin NX+ubuntu22.04上移植YoloV11,并使用DeepStream测试成功
【AI】郭老二博文之:AI学习目录汇总 1、烧写系统 新到的开发板,已经烧写好Ubuntu系统,版本为22.04。 如果没有升级到Ubuntu22.04,可以在电脑Ubuntu系统中使用SDKManager来烧写Ubuntu系统,网络情况好的话,也可以直接将CUDA、cuDNN、TensorRT、Deepstream等也安装上。 2…...
vscode 通过Remote-ssh远程连接服务器报错 could not establish connection to ubuntu
vscode 通过Remote-ssh插件远程连接服务器报错 could not establish connection to ubuntu,并且出现下面的错误打印: [21:00:57.307] Log Level: 2 [21:00:57.350] SSH Resolver called for "ssh-remoteubuntu", attempt 1 [21:00:57.359] r…...
ESP32S3 WIFI 实现TCP服务器和静态IP
一、 TCP服务器代码 代码由station_example_main的官方例程修改 /* WiFi station ExampleThis example code is in the Public Domain (or CC0 licensed, at your option.)Unless required by applicable law or agreed to in writing, thissoftware is distributed on an &q…...
第三课:Stable Diffusion图生图入门及应用
文章目录 Part01 图生图原理Part02 图生图基本流程Part03 随机种子作用解析Part04 图生图的拓展应用 Part01 图生图原理 当提示词不能足够表达用户需求的时候,加入图片能让AI更好的理解你的想法图片上的像素信息会在加噪和去噪的过程中,作为一种特征反映…...
蓝桥与力扣刷题(蓝桥 蓝桥骑士)
题目:小明是蓝桥王国的骑士,他喜欢不断突破自我。 这天蓝桥国王给他安排了 N 个对手,他们的战力值分别为 a1,a2,...,an,且按顺序阻挡在小明的前方。对于这些对手小明可以选择挑战,也可以选择避战。 身为高傲的骑士&a…...
Photoshop怎样保存为ico格式
1. 打开图像 开启 Photoshop 软件,选择 “文件” 菜单,点击 “打开” 选项,然后找到你想要保存为 ICO 格式的图像文件并打开。 2. 调整图像大小(可选) ICO 图标通常有特定尺寸要求,你可以根据需求调整图像…...
Ubuntu xinference部署本地模型bge-large-zh-v1.5、bge-reranker-v2-m3
bge-large-zh-v1.5 下载模型到指定路径: modelscope download --model BAAI/bge-large-zh-v1.5 --local_dir ./bge-large-zh-v1.5自定义 embedding 模型,custom-bge-large-zh-v1.5.json: {"model_name": "custom-bge-large…...
python笔记之判断月份有多少天
1、通过随机数作为目标月份 import random month random.randint(1,12)2、判断对应的年份是闰年还是平年 因为2月这个特殊月份,闰年有29天,而平年是28天,所以需要判断对应的年份属于闰年还是平年,代码如下 # 判断年份是闰年还…...
Kotlin泛型: 协变|逆变|不变
引言 无论java 通配符上限还是下限,都多少存在缺陷,要么存不安全,要么取不安全。而kotlin就解决这个问题。让out 纯输出, 让in纯输入。 java这块知识: java泛型的协变、逆变和不变-CSDN博客 协变 生产者out T 协变…...
高斯数据库的空分区的查看和清理
在 高斯数据库(GaussDB) 中,分区表是一种常见的表设计方式,用于优化大数据的查询性能。 一、空分区的影响: 存储空间占用 元数据开销:即使分区中没有数据,数据库仍然需要维护分区的元数据&…...
word使用自带的公式
文章目录 Word公式中word公式快捷键:word2016公式框输入多行word 公式加入空格:word公式如何输入矩阵:公式图片转为Latex语法word 能直接输入 latex 公式么word公式中有的是斜体有的不是 word文本中将文字转为上标的快捷键 Tips几个好用的网站࿱…...
Linux系统-ls命令
一、ls命令的定义 Linux ls命令(英文全拼:list directory contents)用于显示指定工作目录下之内容(列出目前工作目录所含的文件及子目录)。 二、ls命令的语法 ls [选项] [目录或文件名] ls [-alrtAFR] [name...] 三、参数[选项…...
数据结构:利用递推式计算next表
next 表是 KMP 算法的核心内容,下面介绍一种计算 next 表的方法:利用递推式计算 如图 6.3.1 所示,在某一趟匹配中,当对比到最后一个字符的时候,发现匹配失败(s[i] ≠ t[j])。根据 BF 算法&…...
Git操作
1 git init 项目初始化(init)成仓库 2、git add 管理文件 3、git commit -m <message> 告诉Git,把文件提交到仓库 4、git status 查看当前管理文件的状态,命令 5、git log 查看提交(commit)的…...
什么是快重传
原理: 在TCP连接中,接受方会对收到的数据包发送确认(ACK)。如果接受方收到一个乱序的数据包(即期望的下一个数据包尚未到达),它会重复发送对上一个已成功接受的数据包的确认。 当发送方连续收…...
计算机网络——物理层设备
目录 编辑 中继器 集线器(Hub) 集线器,中继器的一些特性 集线器和中继器不能“无限串联” 集线器连接的网络,物理上是星型拓扑,逻辑上是总线型拓扑 集线器连接的各网段会“共享带宽” 中继器 如果我们想要网络…...
CSS 预处理器
在面试中回答关于 CSS 预处理器的问题时,你可以从以下几个方面进行回答,展示你的知识深度和实践经验: 1. 什么是 CSS 预处理器? 你可以从定义和目的入手: “CSS 预处理器是一种扩展 CSS 功能的工具,它允许…...
解锁智能制造新体验:兰亭妙微 UE/UI 设计赋能行业变革
在智能制造时代的滚滚浪潮中,企业的数字化转型不仅是技术的革新,更是用户体验与交互界面的全面升级。然而,许多制造企业在这一转型过程中,面临着一系列 UI/UE 设计难题,严重阻碍了企业的数字化发展进程。兰亭妙微凭借专…...
计算机网络高频(三)UDP基础
计算机网络高频(三)UDP基础 1.UDP的头部格式是什么样的?⭐ UDP 头部具有以下字段: 源端口(Source Port):16 位字段,表示发送方的端口号。目标端口(Destination Port):16 位字段,表示接收方的端口号。长度(Length):16 位字段,表示 UDP 数据报(包括头部和数据部…...
Oracle数据库服务器地址变更与监听配置修改完整指南
一、前言 在企业IT运维中,Oracle数据库服务器地址变更是常见的运维操作。本文将详细介绍如何安全、高效地完成Oracle数据库服务器地址变更及相关的监听配置修改工作,确保数据库服务在迁移后能够正常运行。 二、准备工作 1. 环境检查 确认新旧服务器I…...
获取1688.item_password接口:解析淘口令真实URL
一、接口介绍 1688的item_password接口主要用于将1688平台的淘口令短链接转换为实际商品链接。它基于1688平台的后台数据和规则,对用户传入的淘口令进行解析和验证,通过相应的算法和数据匹配,找到对应的商品信息,并生成可直接访问…...
计算机网络的分类及其性能指标
一. 计算机网络的分类 1. 按分布范围分类 广域网(WAN) 也称远程网。广域网提供长距离通信,通常是几十千米到几千千米的区域,比如跨国通信。连接广域网的各结点交换机的链路一般是高速链路,具有较大的通信容量城域网&…...
Redis原理:watch命令
在前面的文章中有提到,在multi 前可以通过watch 来观察哪些key,被观察的这些key,会被redis服务器监控,涉及该key被修改时,则在exec 命令执行过程中会被识别出来,exec 就不会再执行命令。 源码分析 // 监控…...
微服务中的服务发现与注册中心
在微服务架构中,服务实例的数量可能随着流量负载自动扩展或缩减,因此服务之间如何高效地进行通信成为一个重要问题。本篇博客将介绍服务发现的概念,并结合 Consul 和 自定义注册中心 进行实践,帮助开发者在微服务架构下高效管理服…...
Flutter网络请求封装:高效、灵活、易用的Dio工具类
在Flutter开发中,网络请求是必不可少的功能。为了简化代码、提高开发效率,我们通常会封装一个网络请求工具类。本文基于Dio库,详细介绍如何封装一个高效、灵活、易用的网络请求工具类,支持以下功能: 单例模式…...
Axure项目实战:智慧城市APP(三)教育查询(显示与隐藏交互)
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! 课程主题:教育查询 主要内容:教育公告信息,小升初、初升高、高考成绩查询;教育公告信息为传统的信息页面,小升…...
案例实践 | 招商局集团以长安链构建“基于DID的航运贸易数据资产目录链”
概览 案例名称 基于DID的航运贸易数据资产目录链 业主单位 招商局集团 上线时间 2024年10月 用户群体 供数用数企业和个人 用户规模 集团内20企业 案例背景 招商局集团深入落实“促进数据高效流通使用、赋能实体经济”精神,深化集团数字化水平,…...
计算机网络入门:物理层与数据链路层详解
🌐 (专业解析 中学生也能懂!) 📖 前言 计算机网络就像数字世界的“高速公路系统”,而物理层和数据链路层是这条公路的基石。本文用 专业视角 和 生活化比喻 ,带你轻松理解这两层的核心原理&a…...
使用 Docker 部署 RabbitMQ 的详细指南
使用 Docker 部署 RabbitMQ 的详细指南 在现代应用程序开发中,消息队列系统是不可或缺的一部分。RabbitMQ 是一个流行的开源消息代理软件,它实现了高级消息队列协议(AMQP)。本文将详细介绍如何使用 Docker 部署 RabbitMQ…...
数据结构之基本队列-顺序结构实现-初始化-判断队列是否为空(front=rear)-出队-入队-队尾满了,调整队列-获取队头元素
数据结构之基本队列-顺序结构实现-初始化-判断队列是否为空(frontrear)-出队-入队-队尾满了,调整队列-获取队头元素——完整可运行代码 #include <stdio.h>#define MAXSIZE 100 typedef int ElemType;typedef struct {ElemType data[MAXSIZE];int front;int…...
如何用 Postman 发送 POST 请求?
POST 请求是 HTTP 协议中用于提交数据的一种方法,Postman 提供了丰富的功能来支持用户发送包含各种信息的 POST 请求,如文本数据、JSON 或 XML 数据结构、文件等。 Postman 发送 post 请求教程...
基于Spring Boot的网上商城系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
mysql中的聚簇索引,什么是聚簇索引和非聚簇索引
文章目录 1. 什么是聚簇索引2. 非聚簇索引3. 聚簇索引的优缺点4. 聚簇索引的使用场景5. 聚簇索引和主键索引的异同前言: 在继续讲解专栏内容之前,先学习几个概念,以便更好了解: 什么是聚簇索引什么是回表这篇文章详细分析 聚簇索引。回表的理解可以进入这篇文章:什么是回表…...