SpringAI+DeepSeek大模型应用开发——4 对话机器人

目录​​​​​​​

​​​​​​​​​​​​​​项目初始化

pom文件

配置模型

ChatClient

同步调用

流式调用

日志功能

对接前端

解决跨域

会话记忆功能

ChatMemory

添加会话记忆功能

会话历史

管理会话id

保存会话id

查询会话历史

完善会话记忆

定义可序列化的Message

方案一:定期持久化

方案二:自定义ChatMemory

方案三:Cassandra

​​​​​​​项目初始化

创建一个新的SpringBoot工程,勾选Web、MySQL驱动、Ollama:

pom文件

主要引入的依赖如下

<properties><java.version>17</java.version><spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.10.1</version>
</dependency>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
<dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>${spring-ai.version}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

SpringAI完全适配了SpringBoot的自动装配功能,而且给不同的大模型提供了不同的starter,比如:

<!--Anthropic-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
</dependency><!--Azure OpenAI-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
</dependency><!--DeepSeek-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency><!--Hugging Face-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-huggingface-spring-boot-starter</artifactId>
</dependency><!--Ollama-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency><!--OpenAI-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

配置模型

在配置文件中配置模型的参数信息,以Ollama为例:

spring:application:name: heima-aiai:ollama:base-url: http://localhost:11434chat:model: deepseek-r1:7bopenai:base-url: https://dashscope.aliyuncs.com/compatible-modeapi-key: 填百炼大模型平台自己的api keychat:options:model: qwen-plusembedding:options:model: text-embedding-v3dimensions: 1024

ChatClient

  • ChatClient中封装了与AI大模型对话的各种API,同时支持同步式或响应式交互;

  • 在使用之前,需要声明一个ChatClient;在ai.config包下新建一个CommonConfiguration类:

  • 系统预设:在SpringAI中,设置System信息非常方便,不需要在每次发送时封装到Message,而是创建ChatClient时指定即可;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class CommonConfiguration {// 注意参数中的model就是使用的模型,这里用了Ollama,也可以选择OpenAIChatModel@Beanpublic ChatClient chatClient(OllamaChatModel model) {  return ChatClient.builder(model) // 创建ChatClient工厂,利用它可以自由选择模型、添加各种自定义配置.defaultOptions(ChatOptions.builder().model("qwen-omni-turbo").build()).defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小团团,请以小团团的身份和语气回答问题。") //系统预设 .build(); // 构建ChatClient实例}
}
同步调用

定义一个Controller,在其中接收用户发送的提示词,然后把提示词发送给大模型,交给大模型处理,拿到结果后返回;

package com.shisan.ai.controller;import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RequiredArgsConstructor
@RestController
@RequestMapping("/ai")
public class ChatController {private final ChatClient chatClient;// 请求方式和路径不要改动,将来要与前端联调@RequestMapping("/chat")public String chat(@RequestParam String prompt) {return chatClient.prompt(prompt)   // 传入user提示词.call()    // 同步调用请求,会等待AI全部输出完才返回结果.content(); //返回响应内容}
}

启动项目,在浏览器中访问:http://localhost:8080/ai/chat?prompt=你好

流式调用
  • 同步调用需要等待很长时间页面才能看到结果,用户体验不好。为了解决这个问题,可以改进调用方式为流式调用;

  • 使用了WebFlux技术实现流式调用;修改ChatController中的chat方法:

// 注意看返回值,是Flux<String>,也就是流式结果,另外需要设定响应类型和编码,不然前端会乱码
@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
public Flux<String> chat(@RequestParam(String prompt) {return chatClient.prompt(prompt).stream() // 流式调用.content();
}
日志功能

默认情况下,AI交互时是不记录日志的,我们无法得知SpringAI 组织的提示词到底长什么样,这样不方便我们调试。

SpringAI基于AOP机制实现与大模型对话过程的增强、拦截、修改等功能,所有的增强通知都需要实现Advisor接口;Spring提供了一些Advisor的默认实现,来实现一些基本的增强功能:

  • SimpleLoggerAdvisor:日志记录的Advisor;

  • MessageChatMemoryAdvisor:会话记忆的Advisor;

  • QuestionAnswerAdvisor:实现RAG的Advisor;

当然,也可以自定义Advisor,具体可以参考:Advisors API 

添加日志功能

@Configuration
public class CommonConfiguration {// 注意参数中的model就是使用的模型,这里用了Ollama,也可以选择OpenAIChatModel@Beanpublic ChatClient chatClient(OllamaChatModel model) {  return ChatClient.builder(model) // 创建ChatClient工厂,利用它可以自由选择模型、添加各种自定义配置.defaultOptions(ChatOptions.builder().model("qwen-omni-turbo").build()).defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小团团,请以小团团的身份和语气回答问题。") //系统预设.defaultAdvisors(new SimpleLoggerAdvisor(),new MessageChatMemoryAdvisor(chatMemory)).build(); // 构建ChatClient实例}
}

日志级别

#配置 application.yaml即可,重启项目,再次聊天就能在IDEA的运行控制台中看到AI对话的日志信息了
logging:level:org.springframework.ai: debugcom.itheima.ai: debug

对接前端

npm运行

进入spring-ai-protal文件夹(该文件夹要放在非中文目录下),然后执行cmd命令:

# 安装依赖
npm install
# 运行程序
npm run dev

启动后,访问http://localhost:5173即可看到页面:

nginx运行

若不关心源码,进入spring-ai-nginx文件夹(该文件夹要放在非中文目录下),然后执行cmd命令

# 启动Nginx
start nginx.exe
# 停止
nginx.exe -s stop
  • 启动后,访问http://localhost:5173即可看到页面。

解决跨域

前后端在不同端口,存在跨域问题,因此需要在服务端解决cors问题;在ai.config包中添加一个MvcConfiguration类:

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*").exposedHeaders("Content-Disposition");}
}

测试

会话记忆功能

前面讲过,让AI有会话记忆的方式就是把每一次历史对话内容拼接到Prompt中,一起发送过去,这种方式比较挺麻烦;

如果使用了SpringAI,并不需要自己拼接,SpringAI自带了会话记忆功能,可以把历史会话保存下来,下一次请求AI时会自动拼接,非常方便。

ChatMemory

会话记忆功能同样是基于AOP实现,Spring提供了一个MessageChatMemoryAdvisor的通知,可以像之前添加日志通知一样添加到ChatClient即可;不过,要注意的是,MessageChatMemoryAdvisor需要指定一个ChatMemory实例,也就是会话历史保存的方式;

ChatMemory接口声明如下:

public interface ChatMemory {// TODO: consider a non-blocking interface for streaming usagesdefault void add(String conversationId, Message message) {this.add(conversationId, List.of(message));}// 添加会话信息到指定conversationId的会话历史中void add(String conversationId, List<Message> messages);// 根据conversationId查询历史会话List<Message> get(String conversationId, int lastN);// 清除指定conversationId的会话历史void clear(String conversationId);
}

可以看到,所有的会话记忆都是与conversationId有关联的,也就是会话Id,将来不同会话Id的记忆自然是分开管理的;

目前,在SpringAI中有两个ChatMemory的实现:

  • InMemoryChatMemory:会话历史保存在内存中;

  • CassandraChatMemory:会话保存在Cassandra数据库中(需要引入额外依赖,并且绑定了向量数据库,不够灵活);

目前选择用InMemoryChatMemory来实现。

添加会话记忆功能

CommonConfiguration配置类中添加

@Bean
public ChatMemory chatMemory() {return new InMemoryChatMemory();
}@Beanpublic ChatClient chatClient(AlibabaOpenAiChatModel model, ChatMemory chatMemory) {return ChatClient.builder(model).defaultOptions(ChatOptions.builder().model("qwen-omni-turbo").build()).defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小团团,请以小团团的身份和语气回答问题。").defaultAdvisors(new SimpleLoggerAdvisor(),    //记录日志new MessageChatMemoryAdvisor(chatMemory)  //添加会话记忆功能).build();}

现在聊天会话已经有记忆功能了,不过现在的会话记忆还是不完善的,接下来的章节还会继续补充

会话历史

会话记忆:是指让大模型记住每一轮对话的内容,不至于前一句刚问完,下一句就忘了;

会话历史:是指要记录总共有多少不同的对话;

以DeepSeek为例,页面上的会话历史:

ChatMemory中,会记录一个会话中的所有消息,记录方式是以conversationId为key,以List<Message>为value,根据这些历史消息,大模型就能继续回答问题,这就是所谓的会话记忆;

而会话历史,其实就是每一个会话的conversationId,用它去查询List<Message>,注意,在接下来业务中,以chatId来代conversationId

管理会话id

由于会话记忆是以conversationId来管理的,也就是会话id(以后简称为chatId)将来要查询会话历史,其实就是查询历史中有哪些chatId;因此,为了实现查询会话历史记录,必须记录所有的chatId。

定义一个ai.repository包,然后新建一个ChatHistoryRepository管理会话历史接口:

public interface ChatHistoryRepository {/*** 保存会话记录* @param type 业务类型,如:chat、service、pdf* @param chatId 会话ID*/void save(String type, String chatId);/*** 获取会话ID列表* @param type 业务类型,如:chat、service、pdf* @return 会话ID列表*/List<String> getChatIds(String type);
}

在这个包下继续创建一个实现类InMemoryChatHistoryRepository

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Component
@RequiredArgsConstructor
public class InMemoryChatHistoryRepository implements ChatHistoryRepository {private Map<String, List<String>> chatHistory = new HashMap<>();  //存储不同类型的聊天 ID//业务类型,如:chat、service、pdf	//按类型存储聊天 ID,避免重复@Overridepublic void save(String type, String chatId) {List<String> chatIds = chatHistory.computeIfAbsent(type, k -> new ArrayList<>());if (chatIds.contains(chatId)) {return;}chatIds.add(chatId);}//根据类型返回对应的聊天 ID 列表@Overridepublic List<String> getChatIds(String type) {return chatHistory.getOrDefault(type, List.of());}
}

接下来,修改ChatController中的chat方法,做到以下3点:

  • 添加一个请求参数:chatId,每次前端请求AI时都需要传递chatId;

  • 每次处理请求时,将chatId存储到ChatRepository;

  • 每次发请求到AI大模型时,都传递自定义的chatId;

保存会话id

接下来,修改ChatController中的chat方法,做到以下3点:

  • 添加一个请求参数:chatId,每次前端请求AI时都需要传递chatId

  • 每次处理请求时,将chatId存储到ChatRepository;

  • 每次发请求到AI大模型时,都传递自定义的chatId;

private final ChatClient chatClient;
private final ChatHistoryRepository chatHistoryRepository;
//流式调用
@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chat(@RequestParam("prompt") String prompt,@RequestParam("chatId") String chatId,@RequestParam(value = "files", required = false) List<MultipartFile> files) {// 1.保存会话id(如果存在会直接返回)chatHistoryRepository.save("chat", chatId);// 2.请求模型if (files == null || files.isEmpty()) {// 没有附件,纯文本聊天return textChat(prompt, chatId);} else {// 有附件,多模态聊天return multiModalChat(prompt, chatId, files);}
}//纯文本聊天
private Flux<String> textChat(String prompt, String chatId) {return chatClient.prompt()  //请求模型.user(prompt)      //预设//通过AdvisorContext,也就是以key-value形式存入上下文.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)) .stream().content();
}

查询会话历史

定义一个新的Controller,专门实现回话历史的查询。包含两个接口:

  • 根据业务类型查询会话历史列表(将来有3个不同业务,需要分别记录历史。可以自己扩展成按userId记录,根据UserId查询)

  • 根据chatId查询指定会话的历史消息;

private final ChatHistoryRepository chatHistoryRepository;
private final ChatMemory chatMemory;//根据业务类型查询会话历史列表
@GetMapping("/{type}")
public List<String> getChatIds(@PathVariable("type") String type) {return chatHistoryRepository.getChatIds(type);
}
//根据chatId查询指定会话的历史消息
@GetMapping("/{type}/{chatId}")
public List<MessageVO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {List<Message> messages = chatMemory.get(chatId, Integer.MAX_VALUE);if(messages == null) {return List.of();}//由于Message并不符合页面的需要,所以需要自己定义一个VOreturn messages.stream().map(MessageVO::new).toList();
}
@NoArgsConstructor
@Data
public class MessageVO {private String role;private String content;public MessageVO(Message message) {switch (message.getMessageType()) {case USER: role = "user";  break;case ASSISTANT:role = "assistant";break;default:role = "";break;}this.content = message.getText();}
}

重启服务,现在AI聊天机器人就具备会话记忆和会话历史功能了!

完善会话记忆

目前,会话记忆是基于内存,重启服务就没了;如果要持久化保存,这里提供了3种办法:

  1. 依然是基于InMemoryChatMemory,但是在项目停机时,或者使用定时任务实现自动持久化;

  2. 自定义基于Redis的ChatMemory

  3. 基于SpringAI官方提供的CassandraChatMemory,同时会自动启用CassandraVectorStore。

定义可序列化的Message

前面的两种方案,都面临一个问题,SpringAI中的Message类未实现Serializable接口,也没提供public的构造方法,因此无法基于任何形式做序列化。所以必须定义一个可序列化的Message类,方便后续持久化。定义一ai.entity.po包,新建一个Msg类:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Msg {MessageType messageType;String text;Map<String, Object> metadata;List<AssistantMessage.ToolCall> toolCalls;//将SpringAI的Message转为我们的Msgpublic Msg(Message message) {this.messageType = message.getMessageType();this.text = message.getText();this.metadata = message.getMetadata();if(message instanceof AssistantMessage am) {this.toolCalls = am.getToolCalls();}}//实现将我们的Msg转为SpringAI的Messagepublic Message toMessage() {return switch (messageType) {case SYSTEM -> new SystemMessage(text);case USER -> new UserMessage(text, List.of(), metadata);case ASSISTANT -> new AssistantMessage(text, metadata, toolCalls, List.of());default -> throw new IllegalArgumentException("Unsupported message type: " + messageType);};}
}

方案一:定期持久化

接下来,将SpringAI提供的InMemoryChatMemory中的数据持久化到本地磁盘,并且在项目启动时加载;

本方案中,采用Spring的生命周期方法,在项目启动时加载持久化文件,在项目停机时持久化数据

也可以考虑使用定时任务完成持久化,项目启动加载的方案;

修改ai.repository.InMemoryChatHistoryRepository类,添加持久化功能:

//项目启动时
@PostConstruct
private void init() {// 1.初始化会话历史记录this.chatHistory = new HashMap<>();// 2.加载本地会话历史和会话记忆FileSystemResource historyResource = new FileSystemResource("chat-history.json");FileSystemResource memoryResource = new FileSystemResource("chat-memory.json");if (!historyResource.exists()) {return;}try {// 会话历史Map<String, List<String>> chatIds = this.objectMapper.readValue(historyResource.getInputStream(), new TypeReference<>() {});if (chatIds != null) {this.chatHistory = chatIds;}// 会话记忆Map<String, List<Msg>> memory = this.objectMapper.readValue(memoryResource.getInputStream(), new TypeReference<>() {});if (memory != null) {memory.forEach(this::convertMsgToMessage);  //转成message}} catch (IOException ex) {throw new RuntimeException(ex);}
}private void convertMsgToMessage(String chatId, List<Msg> messages) {this.chatMemory.add(chatId, messages.stream().map(Msg::toMessage).toList());
}@PreDestroy
private void persistent() {String history = toJsonString(this.chatHistory);String memory = getMemoryJsonString();FileSystemResource historyResource = new FileSystemResource("chat-history.json");FileSystemResource memoryResource = new FileSystemResource("chat-memory.json");try (PrintWriter historyWriter = new PrintWriter(historyResource.getOutputStream(), true, StandardCharsets.UTF_8);PrintWriter memoryWriter = new PrintWriter(memoryResource.getOutputStream(), true, StandardCharsets.UTF_8)) {historyWriter.write(history);memoryWriter.write(memory);} catch (IOException ex) {log.error("IOException occurred while saving vector store file.", ex);throw new RuntimeException(ex);} catch (SecurityException ex) {log.error("SecurityException occurred while saving vector store file.", ex);throw new RuntimeException(ex);} catch (NullPointerException ex) {log.error("NullPointerException occurred while saving vector store file.", ex);throw new RuntimeException(ex);}
}private String getMemoryJsonString() {Class<InMemoryChatMemory> clazz = InMemoryChatMemory.class;try {Field field = clazz.getDeclaredField("conversationHistory");field.setAccessible(true);Map<String, List<Message>> memory = (Map<String, List<Message>>) field.get(chatMemory);Map<String, List<Msg>> memoryToSave = new HashMap<>();memory.forEach((chatId, messages) -> memoryToSave.put(chatId, messages.stream().map(Msg::new).toList()));return toJsonString(memoryToSave);} catch (NoSuchFieldException | IllegalAccessException e) {throw new RuntimeException(e);}
}private String toJsonString(Object object) {ObjectWriter objectWriter = this.objectMapper.writerWithDefaultPrettyPrinter();try {return objectWriter.writeValueAsString(object);} catch (JsonProcessingException e) {throw new RuntimeException("Error serializing documentMap to JSON.", e);}
}

方案二:自定义ChatMemory

基于Redis来实现自定义ChatMemory;

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

ai.repository包中新建一个RedisChatMemory类:由于使用的是Redis的Set结构,无序的,因此要确保chatId是单调递增的。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.ai.entity.po.Msg;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;@RequiredArgsConstructor
@Component
public class RedisChatMemory implements ChatMemory {private final StringRedisTemplate redisTemplate;private final ObjectMapper objectMapper;private final static String PREFIX = "chat:";@Overridepublic void add(String conversationId, List<Message> messages) {if (messages == null || messages.isEmpty()) {return;}List<String> list = messages.stream().map(Msg::new).map(msg -> {try {return objectMapper.writeValueAsString(msg);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}).toList();redisTemplate.opsForList().leftPushAll(PREFIX + conversationId, list);}@Overridepublic List<Message> get(String conversationId, int lastN) {List<String> list = redisTemplate.opsForList().range(PREFIX + conversationId, 0, lastN);if (list == null || list.isEmpty()) {return List.of();}return list.stream().map(s -> {try {return objectMapper.readValue(s, Msg.class);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}).map(Msg::toMessage).toList();}@Overridepublic void clear(String conversationId) {redisTemplate.delete(PREFIX + conversationId);}
}

方案三:Cassandra

  • SpringAI官方提供了CassandraChatMemory,但是是跟CassandraVectorStore绑定的,不太灵活;

  • 首先,需要安装一个Cassandra访问,使用Docker安装:

docker run -d --name cas -p 9042:9042  cassandra

在项目中添加cassandra依赖:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-cassandra-store-spring-boot-starter</artifactId>
</dependency>

配置Cassandra地址:

spring:cassandra:contact-points: 192.168.150.101:9042local-datacenter: datacenter1
  • 基于Cassandra的ChatMemory已经实现了,其它不变。

  • 注意:多种ChatMemory实现方案不能共存,只能选择其一。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/76620.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 关键字

本章列出了Java 语言的所有关键字和“类关键字的单词”。 “受限关键字”是指&#xff0c;它们旨在模块声明中是关键字&#xff0c;在其他情况下则是标识符。 “受限标识符”是指&#xff0c;除非用在某些特定位置&#xff0c;否则他们只是标识符。例如&#xff0c;var一般都…

AI重塑网络安全:机遇与威胁并存的“双刃剑”时代

一、引言 人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;正在深刻改变网络安全行业的格局。从ChatGPT生成钓鱼邮件到AI驱动的漏洞挖掘&#xff0c;从零信任架构的普及到安全大模型的实战应用&#xff0c;AI既是攻击者的“新武器”&#xff0c;也是防御者的“新…

网络原理——UDP

1、 与TCP的关键区别 特性UDPTCP连接方式无连接面向连接可靠性不可靠可靠数据顺序不保证顺序保证顺序传输速度更快相对较慢头部开销8字节20-60字节流量控制无有拥塞控制无有适用场景实时应用、广播/多播可靠性要求高的应用 2、UDP 报文结构 报文结构大致可以分为首部和载荷&a…

STM32——新建工程并使用寄存器以及库函数进行点灯

本文是根据江协科技提供的教学视频所写&#xff0c;旨在便于日后复习&#xff0c;同时供学习嵌入式的朋友们参考&#xff0c;文中涉及到的所有资料也均来源于江协科技&#xff08;资料下载&#xff09;。 新建工程并使用寄存器以及库函数进行点灯操作 新建工程步骤1.建立工程2.…

Unocss 类名基操, tailwindcss 类名

这里只列出 unocss 的可实现类名&#xff0c;tailwindcss 可以拿去试试用 1. 父元素移入&#xff0c;子元素改样式 <!-- 必须是 group 类名 --> <div class"group"><div class"group-hover:color-red">Text</div> </div>2…

深度学习入门(一)

一、简介 深度学习是机器学习领域新兴且关键的研究方向。机器学习重点在于让计算机从数据中挖掘规律以预测未知&#xff0c;而深度学习借助构建多层神经网络&#xff0c;自动学习数据的复杂特征&#xff0c;从而实现更精准的模式识别&#xff0c;在图像、语音等众多领域广泛应…

element-plus中,Steps 步骤条组件的使用

目录 一.基本使用 1.代码 2.效果展示 3.代码解读 二.案例&#xff1a;修改用户的密码 1.期望效果 2.代码 3.展示效果 结语 一.基本使用 1.代码 从官网复制如下代码&#xff1a; <template><div><el-stepsstyle"max-width: 600px":space&quo…

jax 备忘录

https://zhuanlan.zhihu.com/p/532504225 https://docs.jax.dev/en/latest/index.html

NLTK 基础入门:用 Python 解锁自然语言处理

自然语言处理&#xff08;NLP&#xff09;是人工智能领域的重要分支&#xff0c;它让计算机能够理解、处理和生成人类语言。而 NLTK&#xff08;Natural Language Toolkit&#xff09; 作为 Python 生态中最经典的 NLP 库之一&#xff0c;提供了丰富的工具和资源&#xff0c;是…

ElementUI中checkbox v-model绑定值为布尔、字符串或数字类型

这篇博客介绍了在Vue.js中使用El-Checkbox组件时&#xff0c;如何设置和处理v-model的布尔值和类型转换。通过示例代码展示了如何设置true-label和false-label属性来改变选中状态的值&#xff0c;适用于需要特定类型&#xff08;如字符串或整数&#xff09;的场景。v-model不能…

JBoss 项目修复笔记:绕开 iframe 安全问题,JSF 与 Angular 最小代价共存方案

JBoss 项目修复笔记&#xff1a;绕开 iframe 安全问题&#xff0c;JSF 与 Angular 最小代价共存方案 本篇笔记衔接的内容为&#xff1a;JBoss WildFly 本地开发环境完全指南&#xff0c;里面简单的描述了一下怎么配置 docker&#xff0c;在本地启动一个可以运行的 JBoss 和 W…

Linux文件时间戳详解:Access、Modify、Change时间的区别与作用

在 Linux 系统中&#xff0c;文件的这三个时间戳&#xff08;Access、Modify、Change&#xff09;分别表示不同的文件状态变更时间&#xff0c;具体含义如下&#xff1a; 1. Access Time (Access) 含义&#xff1a;文件最后一次被访问的时间&#xff08;读取内容或执行&#xf…

SpringBoot项目打包为window安装包

SpringBoot项目打包为window安装包 通过jpackage及maven插件的方式将springboot项目打包为exe或msi pom.xml 添加插件 <plugin><groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>3.1.0</vers…

pip永久换镜像地址

要将 pip 永久设置为阿里云镜像源&#xff0c;可以通过修改 pip 的全局配置文件来实现。以下是具体步骤&#xff1a; 步骤 1&#xff1a;创建或修改 pip 配置文件 根据你的操作系统&#xff0c;配置文件的路径略有不同&#xff1a; Linux/macOS 配置文件路径&#xff1a;~/.…

PI0 Openpi 部署(仅测试虚拟环境)

https://github.com/Physical-Intelligence/openpi/tree/main 我使用4070tisuper, 14900k,完全使用官方默认设置&#xff0c;没有出现其他问题。 目前只对examples/aloha_sim进行测试&#xff0c;使用docker进行部署, 默认使用pi0_aloha_sim模型(但是文档上没找到对应的&…

XAttention

XAttention: Block Sparse Attention with Antidiagonal Scoring 革新Transformer推理的高效注意力机制资源​​ ​​论文链接​​&#xff1a;XAttention: Block Sparse Attention with Antidiagonal Scoring ​​代码开源​​&#xff1a;GitHub仓库 XAttention是韩松团队提…

前端中的浮动、定位与布局

在前端开发中&#xff0c;布局是构建网页结构的基础。而浮动&#xff08;float&#xff09;、定位&#xff08;position&#xff09;以及各种布局方法则是实现网页布局的关键工具。 一、浮动&#xff08;Float&#xff09; 浮动是CSS中用于控制元素在页面中排列方式的一种属性…

Linux 动、静态库的实现

前言&#xff1a;当我们写了一段代码实现了一个方法&#xff0c;如果我们不想把方法的实现过程暴露给别人看&#xff0c;可以把代码打包成一个库&#xff0c;其中形成后缀为.a的是静态库&#xff0c;后缀为.so的为动态库&#xff1b;当别人想使用你的方法时&#xff0c;把打包好…

ubuntu--字体设置

样式和字体大小 在终端右键-->选择"Preferences"-->勾选"Custom font": 选择自己喜欢的样式&#xff0c;然后通过size滑动条调整字体大小&#xff0c;选择即可&#xff1a;

Qt核心知识总结

Qt核心知识总结 Qt 是一个功能强大、跨平台的 C 应用程序开发框架&#xff0c;广泛应用于图形用户界面&#xff08;GUI&#xff09;应用程序的开发&#xff0c;同时也支持非 GUI 应用程序的开发。本文将从入门到精通的角度&#xff0c;详细解析 Qt 的核心知识点&#xff0c;帮…