Spring AI Advisors API 提供了一种灵活且强大的方式来拦截、修改和增强 Spring 应用程序中的 AI 驱动交互。其核心思想类似于 Spring AOP(面向切面编程)中的“通知”(Advice),允许开发者在不修改核心业务逻辑的情况下,在 AI 调用前后或过程中插入自定义逻辑。

Advisors 的作用

Advisors 的主要作用包括:

  • 封装重复的生成式 AI 模式:将常见的 AI 交互模式(如聊天记忆、RAG 检索增强生成)封装成可重用的组件。
  • 数据转换与增强:在发送到大型语言模型(LLMs)的数据和从 LLMs 返回的数据之间进行转换和增强。
  • 跨模型和用例的可移植性:提供一种机制,使得 AI 逻辑可以在不同的模型和用例之间轻松移植。
  • 可观测性:Advisors 参与到 Spring AI 的可观测性栈中,可以查看与其执行相关的指标和跟踪。

核心接口

Spring AI Advisors API 主要围绕以下几个核心接口构建:

  • Advisor:所有 Advisor 的父接口,继承自 Spring 的 Ordered 接口,用于定义 Advisor 的执行顺序和名称。
  • CallAdvisor:用于非流式(non-streaming)场景的 Advisor 接口,其 adviseCall 方法用于拦截和处理同步的 AI 调用。
  • StreamAdvisor:用于流式(streaming)场景的 Advisor 接口,其 adviseStream 方法用于拦截和处理异步的 AI 流式 AI 调用。
  • AdvisorChain:定义了执行 Advisor 链的上下文。
  • CallAdvisorChainCallAdvisor 的链,用于编排同步 Advisor 的执行。
  • StreamAdvisorChainStreamAdvisor 的链,用于编排流式 Advisor 的执行。
  • BaseAdvisor:一个抽象基类,实现了 CallAdvisorStreamAdvisor 的通用方面,减少了实现 Advisor 所需的样板代码,并提供了 beforeafter 方法来简化逻辑的插入。

这些接口共同构成了 Spring AI Advisors 的核心骨架,允许开发者以统一的方式处理不同类型的 AI 交互。

架构设计与执行流程

Spring AI Advisors 的设计借鉴了 Spring 框架中经典的责任链模式和 AOP 思想,使得 AI 请求的处理流程高度可扩展和可定制。理解其内部架构和执行流程对于有效利用 Advisors 至关重要。

类图概览

首先,我们通过一个简化的类图来概览 Spring AI Advisors 的核心接口和它们之间的关系:

从类图中可以看出:

  • Advisor 是所有顾问的基石,它继承了 Ordered 接口,这意味着每个顾问都有一个排序值,用于确定其在链中的执行顺序。
  • CallAdvisorStreamAdvisor 分别处理同步和异步(流式)的 AI 调用。
  • CallAdvisorChainStreamAdvisorChain 是顾问链的抽象,它们负责按顺序调用链中的下一个顾问。
  • BaseAdvisor 提供了一个方便的抽象,它实现了 CallAdvisorStreamAdvisor 的默认行为,并引入了 beforeafter 方法,使得开发者可以更容易地在请求处理前后插入逻辑。

请求处理流程

Spring AI Advisors 的请求处理流程是一个典型的责任链模式。当一个 AI 请求(ChatClientRequest)被发起时,它会依次通过配置好的 Advisor 链,每个 Advisor 都有机会在请求发送到 LLM 之前对其进行修改或增强,并在 LLM 返回响应之后对其进行处理。

以下是请求处理的详细流程图:

  1. 请求初始化:Spring AI 框架从用户的 Prompt 创建一个 AdvisedRequest 对象,并附带一个空的 AdvisorContext 对象。AdvisorContext 用于在整个 Advisor 链中共享状态和数据。
  2. Advisor 链处理(请求阶段)AdvisedRequest 沿着 Advisor 链依次传递。每个 Advisor 都会执行其 adviseCall(同步)或 adviseStream(流式)方法。在这个阶段,Advisor 可以:
    • 检查未密封的 Prompt 数据。
    • 自定义和增强 Prompt 数据(例如,添加系统消息、上下文信息、函数定义等)。
    • 调用链中的下一个实体(chain.nextCall()chain.nextStream())。
    • 选择性地阻塞请求,即不调用 nextCall/nextStream,而是直接生成响应。在这种情况下,该 Advisor 负责填充响应。
  1. 发送到 LLM:链中的最后一个 Advisor(通常由框架自动添加)负责将处理后的请求发送到实际的 Chat Model(大型语言模型)。
  2. LLM 响应Chat Model 处理请求并返回一个响应。
  3. Advisor 链处理(响应阶段):LLM 的响应被传递回 Advisor 链,并转换为 AdvisedResponseAdvisedResponse 同样包含共享的 AdvisorContext 实例。每个 Advisor 都有机会处理或修改响应,例如,进行后处理、日志记录、数据提取等。
  4. 返回客户端:最终的 AdvisedResponse 返回给客户端,并从中提取 ChatCompletion

时序图直观地展示了请求如何从 Client 经过 ChatClient,然后逐级深入到 Advisor 链,最终到达 ChatModel。响应则以相反的顺序回溯,每个 Advisor 再次有机会处理响应。这种“洋葱式”的结构确保了请求和响应在到达核心 AI 模型之前和之后都能被灵活地处理。

Advisor 的执行顺序

Advisor 在链中的执行顺序由其 getOrder() 方法返回的值决定。Spring 框架中的 Ordered 接口定义了以下语义:

  • 值越小,优先级越高Ordered.HIGHEST_PRECEDENCE (Integer.MIN_VALUE) 表示最高优先级,Ordered.LOWEST_PRECEDENCE (Integer.MAX_VALUE) 表示最低优先级。
  • 相同值不保证顺序:如果多个 Advisor 具有相同的 order 值,它们的执行顺序是不确定的。

Advisor 链的操作类似于一个栈:

  • 请求处理阶段order 值最低(优先级最高)的 Advisor 最先处理请求。它位于栈的顶部,因此在请求向下传递时,它首先被调用。
  • 响应处理阶段order 值最低(优先级最高)的 Advisor 最后处理响应。当响应从 LLM 返回并沿着链回溯时,它会最后被调用。

这种设计使得开发者可以精确控制 Advisor 的执行时机。例如,如果你希望一个 Advisor 在所有其他 Advisor 之前处理请求并在所有其他 Advisor 之后处理响应(例如,用于全局日志记录或异常处理),你应该给它设置一个非常低的 order 值(接近 Ordered.HIGHEST_PRECEDENCE)。反之,如果你希望它最后处理请求并最先处理响应(例如,用于最终的数据格式化),则设置一个较高的 order 值(接近 Ordered.LOWEST_PRECEDENCE)。

对于需要同时在请求和响应阶段都处于链条最前端的用例,可以考虑使用两个独立的 Advisor,并为它们配置不同的 order 值,并通过 AdvisorContext 共享状态。

代码分析:深入理解 BaseAdvisor

在 Spring AI Advisors API 中,BaseAdvisor 接口扮演着至关重要的角色,它极大地简化了自定义 Advisor 的实现。通过提供 CallAdvisorStreamAdvisor 的默认实现,BaseAdvisor 将复杂的拦截逻辑抽象为两个核心方法:beforeafter

让我们再次回顾 BaseAdvisor 的定义:

public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {Scheduler DEFAULT_SCHEDULER = Schedulers.boundedElastic();@Overridedefault ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");Assert.notNull(callAdvisorChain, "callAdvisorChain cannot be null");ChatClientRequest processedChatClientRequest = before(chatClientRequest, callAdvisorChain);ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(processedChatClientRequest);return after(chatClientResponse, callAdvisorChain);}@Overridedefault Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,StreamAdvisorChain streamAdvisorChain) {Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");Assert.notNull(streamAdvisorChain, "streamAdvisorChain cannot be null");Assert.notNull(getScheduler(), "scheduler cannot be null");Flux<ChatClientResponse> chatClientResponseFlux = Mono.just(chatClientRequest).publishOn(getScheduler()).map(request -> this.before(request, streamAdvisorChain)).flatMapMany(streamAdvisorChain::nextStream);return chatClientResponseFlux.map(response -> {if (AdvisorUtils.onFinishReason().test(response)) {response = after(response, streamAdvisorChain);}return response;}).onErrorResume(error -> Flux.error(new IllegalStateException("Stream processing failed", error)));}@Overridedefault String getName() {return this.getClass().getSimpleName();}/*** Logic to be executed before the rest of the advisor chain is called.* 在调用顾问链的其余部分之前执行的逻辑*/ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain);/*** Logic to be executed after the rest of the advisor chain is called.*/ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain);/*** Scheduler used for processing the advisor logic when streaming.* 流式传输时用于处理顾问逻辑的调度器。*/default Scheduler getScheduler() {return DEFAULT_SCHEDULER;}}

关键点分析:

  1. 统一的拦截点BaseAdvisor 通过 adviseCalladviseStream 方法,为同步和流式调用提供了统一的拦截逻辑。这两个方法内部都调用了 before 方法来处理请求,然后调用 chain.nextCall()chain.nextStream() 将请求传递给链中的下一个 Advisor 或最终的 AI 模型,最后调用 after 方法来处理响应。
  2. before 方法
    • ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain)
    • 作用:在请求发送到链中的下一个 Advisor 或 AI 模型之前执行的逻辑。你可以在这里修改 chatClientRequest,例如添加或修改消息、参数等。返回的 ChatClientRequest 将被传递给链的后续部分。
  1. after 方法
    • ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain)
    • 作用:在链中的下一个 Advisor 或 AI 模型返回响应之后执行的逻辑。你可以在这里对 chatClientResponse 进行后处理,例如解析响应、提取信息、日志记录等。返回的 ChatClientResponse 将被传递回链的上游。
  1. 流式处理的复杂性:对于流式处理 (adviseStream),BaseAdvisor 使用 Reactor 的 FluxMono 来处理异步流。before 方法在 Mono.just(chatClientRequest).map(...) 中被调用,确保在流开始之前对请求进行预处理。after 方法则在 Flux.map(...) 中被调用,并且通过 AdvisorUtils.onFinishReason().test(response) 判断是否是流的最终响应,以确保 after 逻辑在整个流完成时才执行,而不是对流中的每个分块都执行。
  2. 调度器 (Scheduler)BaseAdvisor 提供了 getScheduler() 方法,默认使用 Schedulers.boundedElastic()。这允许在处理流式 Advisor 逻辑时指定一个调度器,以避免阻塞主线程,这对于响应式编程至关重要。

通过实现 BaseAdvisor,开发者只需要关注 beforeafter 这两个业务逻辑相关的核心方法,而无需关心 Advisor 链的内部调用机制和流式处理的复杂性,大大降低了开发难度。


自定义 Advisor 示例

理解了 Spring AI Advisors 的核心概念和架构后,我们来看几个实际的自定义 Advisor 示例,它们将帮助你更好地掌握如何在自己的应用中利用这一强大机制。

1. 日志记录 Advisor (SimpleLoggerAdvisor)

一个常见的需求是在 AI 请求处理的各个阶段进行日志记录,以便于调试和监控。我们可以实现一个简单的日志记录 Advisor,它在请求发送前和响应返回后打印相关信息。这个 Advisor 只观察请求和响应,不进行修改。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.ai.chat.client.advisor.AdvisorUtils;public class SimpleLoggerAdvisor implements BaseAdvisor {private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);@Overridepublic String getName() {return this.getClass().getSimpleName();}@Overridepublic int getOrder() {return 0; // 默认顺序,可以根据需要调整}@Overridepublic ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {logger.debug("BEFORE: {}", chatClientRequest);return chatClientRequest; // 不修改请求}@Overridepublic ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {logger.debug("AFTER: {}", chatClientResponse);return chatClientResponse; // 不修改响应}
}

代码分析:

  • SimpleLoggerAdvisor 实现了 BaseAdvisor 接口,因此它同时支持同步和流式调用。
  • getName() 方法返回 Advisor 的名称,通常是类名。
  • getOrder() 方法返回 Advisor 的执行顺序。这里设置为 0,表示一个中等的优先级。你可以根据需要在 Ordered.HIGHEST_PRECEDENCEOrdered.LOWEST_PRECEDENCE 之间调整。
  • before() 方法在请求处理前被调用,我们在这里记录了 ChatClientRequest 的内容。由于这个 Advisor 只是观察者,所以直接返回了原始的 chatClientRequest
  • after() 方法在响应返回后被调用,我们在这里记录了 ChatClientResponse 的内容。同样,直接返回了原始的 chatClientResponse

如何使用:

你可以通过 ChatClient.builder().defaultAdvisors() 方法将 SimpleLoggerAdvisor 配置到 ChatClient 中:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;// 假设 chatModel 已经注入
ChatModel chatModel = ...;ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor()
)
.build();String response = chatClient.prompt()
.user("你好,Spring AI!")
.call()
.content();System.out.println(response);

当上述代码执行时,你将在日志中看到 BEFOREAFTER 的输出,展示了请求和响应的详细信息。

2. 重读 Advisor (ReReadingAdvisor)

“重读”(Re-Reading)是一种提高大型语言模型推理能力的技术,其核心思想是将用户的输入查询重复一次,例如:

{Input_Query}
Read the question again: {Input_Query}

我们可以实现一个 Advisor 来自动将用户的输入查询转换为这种格式:

import java.util.HashMap;
import java.util.Map;import reactor.core.publisher.Flux;import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;public class ReReadingAdvisor implements BaseAdvisor {@Overridepublic String getName() {return this.getClass().getSimpleName();}@Overridepublic int getOrder() {return 0; // 默认顺序,可以根据需要调整}@Overridepublic ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {Map<String, Object> advisedUserParams = new HashMap<>(chatClientRequest.userParams());advisedUserParams.put("re2_input_query", chatClientRequest.userText());return ChatClientRequest.from(chatClientRequest).userText("""{re2_input_query}Read the question again: {re2_input_query}""").userParams(advisedUserParams).build();}@Override   public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {return chatClientResponse; // 不修改响应}
}

代码分析:

  • ReReadingAdvisor 也实现了 BaseAdvisor
  • 关键逻辑在于 before() 方法。它首先获取原始的 userText,并将其作为参数 re2_input_query 放入 advisedUserParams 中。
  • 然后,它使用 ChatClientRequest.from(chatClientRequest).userText(...) 来构建一个新的 ChatClientRequest,其中 userText 被修改为包含重读指令的模板字符串。这里利用了 Spring AI 的模板功能,{re2_input_query} 会被实际的用户输入替换。
  • after() 方法同样不修改响应。

如何使用:

ReReadingAdvisor 配置到 ChatClient 中:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;// 假设 chatModel 已经注入
ChatModel chatModel = ...;ChatClient chatClient = ChatClient.builder(chatModel).defaultAdvisors(new ReReadingAdvisor()).build();String response = chatClient.prompt().user("请解释一下量子力学").call().content();System.out.println(response);

当用户输入“请解释一下量子力学”时,实际发送给 LLM 的 Prompt 将会是:

请解释一下量子力学
Read the question again: 请解释一下量子力学

这有助于 LLM 更好地理解和处理复杂的查询,从而提高响应质量。

3. 结合使用:聊天记忆 Advisor (MessageChatMemoryAdvisor) 和 RAG Advisor (QuestionAnswerAdvisor)

Spring AI 提供了开箱即用的 Advisors,例如 MessageChatMemoryAdvisor 用于管理聊天记忆,QuestionAnswerAdvisor 用于实现 RAG(检索增强生成)。这些 Advisor 同样遵循上述设计模式,可以方便地集成到你的应用中。

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.vectorstore.VectorStore;// 假设 chatModel 和 vectorStore 已经注入
ChatModel chatModel = ...;
VectorStore vectorStore = ...;
ChatMemory chatMemory = new InMemoryChatMemory();ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(), // 聊天记忆 AdvisorQuestionAnswerAdvisor.builder(vectorStore).build() // RAG Advisor
)
.build();String conversationId = "user-123"; // 假设的用户会话ID// 第一次对话
String response1 = chatClient.prompt()
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.user("你好,我叫小明。")
.call()
.content();
System.out.println("Response 1: " + response1);// 第二次对话,会利用聊天记忆
String response2 = chatClient.prompt()
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.user("我刚才说了什么?")
.call()
.content();
System.out.println("Response 2: " + response2);

代码分析:

  • MessageChatMemoryAdvisor 会根据 ChatMemory.CONVERSATION_ID 参数管理会话历史,将其添加到发送给 LLM 的 Prompt 中,从而实现多轮对话的上下文感知。
  • QuestionAnswerAdvisor 会利用 VectorStore 进行信息检索,并将检索到的相关文档片段添加到 Prompt 中,以增强 LLM 的回答能力,实现 RAG 模式。
  • 通过 advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId)),我们可以在运行时为 Advisor 提供参数,这使得 Advisor 更加灵活和可配置。

这些示例展示了 Spring AI Advisors 的强大功能和灵活性,无论是简单的日志记录,还是复杂的 Prompt 增强,甚至集成高级的 AI 模式,Advisors 都提供了一个优雅的解决方案。

最佳实践

在使用 Spring AI Advisors 时,遵循一些最佳实践可以帮助你构建更健壮、高效和可维护的 AI 应用。

  1. 明确 Advisor 的职责:每个 Advisor 应该只负责一个单一的、明确的功能。例如,一个 Advisor 负责日志记录,另一个负责 Prompt 增强,而不是将所有逻辑都塞到一个 Advisor 中。这有助于提高代码的可读性、可测试性和可重用性。
  2. 合理设置 order:仔细考虑每个 Advisor 的执行顺序。例如,如果一个 Advisor 需要依赖另一个 Advisor 修改后的 Prompt,那么它应该在依赖的 Advisor 之后执行。对于全局性的操作(如日志、异常处理),通常设置较高的优先级(较低的 order 值),以便它们在请求处理的最早和最晚阶段介入。
  3. 利用 BaseAdvisor 简化实现:对于大多数自定义 Advisor,优先考虑实现 BaseAdvisor 接口。它提供了 beforeafter 两个清晰的扩展点,大大减少了样板代码,让你能够专注于核心业务逻辑。
  4. 善用 AdvisorContext 共享状态:如果多个 Advisor 之间需要共享数据或状态,请使用 AdvisorContext。这比在 Advisor 之间传递复杂对象或使用全局变量更优雅和安全。
  5. 区分同步和流式处理:如果你的 Advisor 需要同时支持同步和流式 AI 调用,确保正确实现 CallAdvisorStreamAdvisor 的逻辑。BaseAdvisor 已经为你处理了大部分复杂性,但仍需注意流式处理中 after 方法的触发时机(通常是流结束时)。
  6. 错误处理:在 Advisor 中实现适当的错误处理机制。如果一个 Advisor 抛出异常,它可能会中断整个链的执行。考虑如何优雅地处理这些异常,例如通过日志记录、回退机制或将错误信息传递给 AdvisorContext

结论

Spring AI Advisors 提供了一个强大而灵活的机制,用于在 Spring AI 应用程序中拦截、修改和增强 AI 驱动的交互。通过深入理解其核心概念、架构设计、执行流程以及 BaseAdvisor 的实现细节,开发者可以有效地利用这一特性来构建更具可扩展性、可维护性和智能化的 AI 应用。

无论是简单的日志记录、复杂的 Prompt 增强,还是集成如聊天记忆和 RAG 等高级 AI 模式,Advisors 都提供了一个优雅且符合 Spring 哲学的设计模式。掌握 Advisors 的使用,将使你能够更好地控制 AI 模型的行为,为用户提供更智能、更个性化的体验。

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

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

相关文章

Linux SSH服务全面配置指南:从基础到安全加固

Linux SSH服务全面配置指南&#xff1a;从基础到安全加固 概述 作为网络安全工程师&#xff0c;SSH&#xff08;Secure Shell&#xff09;服务的安全配置是我们日常工作中不可忽视的重要环节。本文将从基础配置到高级安全加固&#xff0c;全面解析SSH服务的各项参数&#xff…

.NET测试工具Parasoft dotTEST内置安全标准,编码合规更高效

在追求开发速度的时代&#xff0c;确保代码安全并满足严苛的行业合规标准如OWASP、CWE、PCI DSS、ISO 26262等已成为开发者的核心挑战&#xff0c;但开发人员常因复杂的编码标准和漏洞排查而效率低下。.NET测试工具Parasoft dotTEST内置安全标准&#xff0c;实现即插即用&#…

对象的finalization机制Test

Java语言提供了对象终止(finalization)机制来允许开发人员自定义对象被销毁之前的处理逻辑。当垃圾回收器发现没有引用指向一个对象时&#xff0c;通常接下来要做的就是垃圾回收&#xff0c;即清除该对象&#xff0c;而finalization机制使得在清除此对象之前&#xff0c;总会先…

AI初学者如何对大模型进行微调?——零基础保姆级实战指南

仅需8GB显存&#xff0c;三步完成个人专属大模型训练 四步实战&#xff1a;从环境配置到模型发布 步骤1&#xff1a;云端环境搭建&#xff08;10分钟&#xff09; 推荐使用阿里魔塔ModelScope免费GPU资源&#xff1a; # 注册后执行环境初始化 pip3 install --upgrade pip pi…

“单一职责”模式之装饰器模式

目录 “单一职责”模式装饰器模式 Decorator引例动机 Motivation模式定义结构 Structure要点总结 “单一职责”模式 在软件组件的设计中&#xff0c;如果责任划分的不清晰&#xff0c;使用继承得到的结果往往是随着需求的变化&#xff0c;子类急剧膨胀&#xff0c;同时充斥着重…

idea, CreateProcess error=206, 文件名或扩展名太长

idea, CreateProcess error206, 文件名或扩展名太长 解决 “CreateProcess error206, 文件名或扩展名太长” 错误 CreateProcess error206 是 Windows 系统特有的错误&#xff0c;表示命令行参数超出了 Windows 的 32767 字符限制。这个问题在 Java 开发中尤其常见&#xff0c…

一键高效率图片MD5修改工具PHP版

文章目录 图片MD5修改工具项目简介功能特点技术原理系统需求安装方法使用方法Web界面模式命令行模式文件结构常见问题注意事项开发者信息效果演示更多干货🎁1.如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “👍点赞” “✍️评论” “💙收藏” 一键三连哦!2.❤️…

跨主机用 Docker Compose 部署 PostgreSQL + PostGIS 主从

q下面是跨主机用 Docker Compose 部署 PostgreSQL PostGIS 主从复制的完整详细步骤&#xff08;主库 从库&#xff09;&#xff0c;主从都用官方 PostGIS 镜像 postgis/postgis:15-3.3&#xff0c;并注意网络与持久化。复制即可。 &#x1f6a9; 跨主机 PostgreSQL PostGIS …

会议动态|千眼狼高速摄像机、DIC测量系统等科学仪器亮相第十五届全国爆炸力学学术会议

第十五届全国爆炸力学学术会议于6月28日在绍兴盛大召开&#xff0c;会议汇聚来自全国爆炸力学与冲击领域专家学者2000余人&#xff0c;聚焦“爆炸与冲击动力学工程应用”、“材料动态力学行为与损伤断裂“、“工程爆破与毁伤评估”、“含能材料与水中爆炸”、“结构动态响应与安…

vscode一个文件夹有残余的git仓库文件,已经失效了,怎样进行清空仓库残余文件并重新初始化git--ubuntu

vscode一个文件夹有残余的git仓库文件&#xff0c;已经失效了&#xff0c;怎样进行清空仓库残余文件并重新初始化git–ubuntu 首先要把工作区里重要的文件备份好&#xff0c;防止操作时数据丢失。删除.git文件夹 rm -rf .git初始化 (base) zd4090zd4090-System-Product-Name:…

6月30日作业

思维导图 一、读取文件&#xff0c;效果类似 cat 的功能 代码 #include <25041head.h>int main(int argc, const char *argv[]) {//打开文件printf("请输入你要读取的文件路径&#xff1a;");char str[128]"";scanf("%s",str);FILE *fpf…

ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法

我在Ubuntu 24.04.2 LTS下载源码安装Python 3.13.5时&#xff1a; #./configure --enable-loadable-sqlite-extensions --enable-optimizations #make 显示错误信息&#xff1a; Could not build the ssl module! Python requires a OpenSSL 1.1.1 or newer 查询我的openssl版…

Ai工具分享(2):Vscode+Cline无限免费的使用教程

大家好,我是程序员寒山。 今天给大家分享一个最新的免费使用的Ai插件Cline的方法,之前也给大家分享过一些免费的方案,但是这些都是随时在变化,之前推荐的很多都不能使用了。 Ai工具分享(2):Vscode+Cline无限免费的使用教 今天再给大家推荐一个,可以免费使用,且没有token…

Docker 目录迁移脚本【Windows Junction 类型链接】

Docker 目录迁移脚本完整教程&#xff1a;从诞生到自动化使用 一、脚本诞生背景与开发历程 1. 为什么需要迁移 Docker 目录&#xff1f; 系统盘空间压力&#xff1a;Docker 镜像和容器数据通常存储在C:\Users\用户名目录下&#xff0c;随着使用时间增长会占用大量系统盘空间…

spring-ai 工作流

目录 工作流概念工作流程图spring-boot 编码定义节点 (Node)定义节点图StateGraphcontroller测试浏览器测试用户输入 工作流概念 工作流是以相对固化的模式来人为地拆解任务&#xff0c;将一个大任务拆解为包含多个分支的固化流程。工作流的优势是确定性强&#xff0c;模型作为…

重头开始学ROS(6)---Eigen库安装与使用

Eigen库 矩阵运算是一种非常重要的运算方式&#xff0c;在Matlab中&#xff0c;矩阵运算可以轻松的实现&#xff0c;但在C这种偏底层的语言中&#xff0c;若不借助第三方库&#xff0c;矩阵运算需要我们进行较为复杂的代码设计。Eigen库是一个用于线性运算的C模板库&#xff0…

【STM32】外部中断

STM32 外部中断&#xff08;EXTI&#xff09;概述 这篇文章结合示例代码&#xff0c;系统性地讲述 STM32 外部中断&#xff08;EXTI&#xff09;实验的原理、以及配置流程。目的在于辅助读者掌握STM32F1 外部中断机制。 STM32F1xx官方资料&#xff1a;《STM32中文参考手册V10》…

LeetCode Hot100 算法题 (矩阵篇)

1、73. 矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]// 将第一行…

Flutter基础(项目创建)

一、使用命令行创建项目 1. 确认 Flutter 环境正常 要保证 Flutter SDK 已经正确安装&#xff0c;并且环境变量配置无误。可以通过执行以下命令来验证&#xff1a; flutter doctor 要保证所有检查项都显示绿色对勾&#xff0c;要是有问题&#xff0c;可按照提示进行修复。 …

【Actix Web】Rust Web开发实战:Actix Web框架全面指南(2025企业级应用版)

​ 在2025年高并发、低延迟成为Web服务核心指标的背景下&#xff0c;​​Actix Web凭借异步Actor模型与零成本抽象​​&#xff0c;成为Rust生态中生产环境部署率最高的Web框架。本指南深入解析Actix Web 4.0核心技术&#xff0c;覆盖​​百万级并发架构设计​​、​​内存安全…