简介:

github项目地址: 学术分析AI应用
本文使用的AI大模型均为阿里云的灵积模型(Qwen-Plus)

一、自定义Advisor

  1. 在阶段二中,我们已经了解过了Advisor的继承关系图了,发现Advisor功能其实是和拦截器类似的。

所以我们尝试着自定义Advisor。

  1. 自定义Advisor需要大致步骤是需要实现两个类和重写一个方法
    1. 实现:CallAroundAdvisor/StreamAroundAdvisor
    2. 重新getOrder()

(一)、参考官网日志Advisor

  1. 官网地址:https://docs.spring.io/spring-ai/reference/api/advisors.html#_logging_advisor
  2. 我们在示例中可以看出,默认的日志级别是debug,
  3. 但是我们需要Info,所以我们就自定义一个日志Advisor
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {

	private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);

	@Override
	public String getName() {
		return this.getClass().getSimpleName();
	}

	@Override
	public int getOrder() {
		return 0;
	}


	@Override
	public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
		logRequest(chatClientRequest);

		ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);

		logResponse(chatClientResponse);

		return chatClientResponse;
	}

	@Override
	public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,
			StreamAdvisorChain streamAdvisorChain) {
		logRequest(chatClientRequest);

		Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);

		return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponses, this::logResponse);
	}

	private void logRequest(ChatClientRequest request) {
		logger.debug("request: {}", request);
	}

	private void logResponse(ChatClientResponse chatClientResponse) {
		logger.debug("response: {}", chatClientResponse);
	}

}

(二)、 自定义实现

  1. 定义类:myLogAdvisor
    package cn.varin.varaiagent.advisors;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.ai.chat.model.MessageAggregator;
import reactor.core.publisher.Flux;

@Slf4j
public class MyLogAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
    /**
     * 非流式同步方法
     * @param advisedRequest the advised request
     * @param chain the advisor chain
     * @return
     */
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {

        this.beforeLog(advisedRequest);
        // 获取到响应
        AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
        this.afterLog(advisedResponse);

        return advisedResponse;
    }

    /**
     * 流式异步方法
     * @param advisedRequest the advised request
     * @param chain the chain of advisors to execute
     * @return
     */
    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
        this.beforeLog(advisedRequest);
        Flux<AdvisedResponse> flux = chain.nextAroundStream(advisedRequest);
        return (new MessageAggregator()).aggregateAdvisedResponse(flux, this::afterLog);
    }



    /**
     * 提供唯一标识名
     * @return
     */

    @Override
    public String getName() {
        return "var 的 Advisor";
    }

    /**
     * 设置执行顺序
     * @return
     */

    @Override
    public int getOrder() {
        return 100;
    }

    /**
     * 定义一个请求前日志信息
     */
    public void beforeLog(AdvisedRequest advisedRequest) {

        log.info("beforeName:{}", "varin");
        log.info("advisedRequest:{}", advisedRequest);

    }
    /**
     * 定义一个响应后日志信息
     */
    public void afterLog(AdvisedResponse advisedResponse) {
        log.info("afterName:{}", "varin");

        log.info("advisedRequest:{}", advisedResponse);

    }
}
  1. 添加Advisor类
this.chatClient = chatClient
.defaultSystem(SYSTEM_PROPERTY)
.defaultAdvisors(
    new MessageChatMemoryAdvisor(new InMemoryChatMemory()),
    // 添加自定义advisor
    new MyLogAdvisor()
)
.build();
  1. 测试
@Test
public void testMyAdvisor() {
    String uuid= UUID.randomUUID().toString();
    ialdaApp.getMessage("你好,我是varin", uuid);

}
  1. 效果

/Users/varya/Library/Java/JavaVirtualMachines/corretto-21.0.8/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:62954,suspend=y,server=n -javaagent:/Users/varya/Library/Caches/JetBrains/IntelliJIdea2025.2/captureAgent/debugger-agent.jar=file:///var/folders/3x/q93yq50j1_nc092fh57488bm0000gn/T/capture10569107792162395090.props -ea -agentpath:/private/var/folders/3x/q93yq50j1_nc092fh57488bm0000gn/T/idea_libasyncProfiler_dylib_temp_folder/libasyncProfiler.dylib=version,jfr,event=wall,interval=10ms,cstack=no,file=/Users/varya/IdeaSnapshots/IALDAAppTest_testMyAdvisor_2025_09_29_132227.jfr,log=/private/var/folders/3x/q93yq50j1_nc092fh57488bm0000gn/T/IALDAAppTest_testMyAdvisor_2025_09_29_132227.jfr.log.txt,logLevel=DEBUG -Didea.test.cyclic.buffer.size=1048576 -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Ddebugger.agent.enable.coroutines=true -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath /Users/varya/.m2/repository/org/junit/platform/junit-platform-launcher/1.11.4/junit-platform-launcher-1.11.4.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit5-rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar:/Users/varya/Desktop/github/var-ai-agent/target/test-classes:/Users/varya/Desktop/github/var-ai-agent/target/classes:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.4.10/spring-boot-starter-web-3.4.10.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-starter/3.4.10/spring-boot-starter-3.4.10.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot/3.4.10/spring-boot-3.4.10.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/3.4.10/spring-boot-autoconfigure-3.4.10.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-starter-logging/3.4.10/spring-boot-starter-logging-3.4.10.jar:/Users/varya/.m2/repository/ch/qos/logback/logback-classic/1.5.18/logback-classic-1.5.18.jar:/Users/varya/.m2/repository/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18.jar:/Users/varya/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.24.3/log4j-to-slf4j-2.24.3.jar:/Users/varya/.m2/repository/org/apache/logging/log4j/log4j-api/2.24.3/log4j-api-2.24.3.jar:/Users/varya/.m2/repository/org/slf4j/jul-to-slf4j/2.0.17/jul-to-slf4j-2.0.17.jar:/Users/varya/.m2/repository/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar:/Users/varya/.m2/repository/org/yaml/snakeyaml/2.3/snakeyaml-2.3.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-starter-json/3.4.10/spring-boot-starter-json-3.4.10.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.18.4/jackson-databind-2.18.4.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.18.4/jackson-datatype-jdk8-2.18.4.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.18.4/jackson-datatype-jsr310-2.18.4.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.18.4/jackson-module-parameter-names-2.18.4.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/3.4.10/spring-boot-starter-tomcat-3.4.10.jar:/Users/varya/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/10.1.46/tomcat-embed-core-10.1.46.jar:/Users/varya/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/10.1.46/tomcat-embed-el-10.1.46.jar:/Users/varya/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.46/tomcat-embed-websocket-10.1.46.jar:/Users/varya/.m2/repository/org/springframework/spring-web/6.2.11/spring-web-6.2.11.jar:/Users/varya/.m2/repository/org/springframework/spring-beans/6.2.11/spring-beans-6.2.11.jar:/Users/varya/.m2/repository/io/micrometer/micrometer-observation/1.14.11/micrometer-observation-1.14.11.jar:/Users/varya/.m2/repository/io/micrometer/micrometer-commons/1.14.11/micrometer-commons-1.14.11.jar:/Users/varya/.m2/repository/org/springframework/spring-webmvc/6.2.11/spring-webmvc-6.2.11.jar:/Users/varya/.m2/repository/org/springframework/spring-aop/6.2.11/spring-aop-6.2.11.jar:/Users/varya/.m2/repository/org/springframework/spring-context/6.2.11/spring-context-6.2.11.jar:/Users/varya/.m2/repository/org/springframework/spring-expression/6.2.11/spring-expression-6.2.11.jar:/Users/varya/.m2/repository/org/projectlombok/lombok/1.18.40/lombok-1.18.40.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-starter-test/3.4.10/spring-boot-starter-test-3.4.10.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-test/3.4.10/spring-boot-test-3.4.10.jar:/Users/varya/.m2/repository/org/springframework/boot/spring-boot-test-autoconfigure/3.4.10/spring-boot-test-autoconfigure-3.4.10.jar:/Users/varya/.m2/repository/com/jayway/jsonpath/json-path/2.9.0/json-path-2.9.0.jar:/Users/varya/.m2/repository/jakarta/xml/bind/jakarta.xml.bind-api/4.0.2/jakarta.xml.bind-api-4.0.2.jar:/Users/varya/.m2/repository/jakarta/activation/jakarta.activation-api/2.1.4/jakarta.activation-api-2.1.4.jar:/Users/varya/.m2/repository/net/minidev/json-smart/2.5.2/json-smart-2.5.2.jar:/Users/varya/.m2/repository/net/minidev/accessors-smart/2.5.2/accessors-smart-2.5.2.jar:/Users/varya/.m2/repository/org/ow2/asm/asm/9.7.1/asm-9.7.1.jar:/Users/varya/.m2/repository/org/assertj/assertj-core/3.26.3/assertj-core-3.26.3.jar:/Users/varya/.m2/repository/net/bytebuddy/byte-buddy/1.15.11/byte-buddy-1.15.11.jar:/Users/varya/.m2/repository/org/awaitility/awaitility/4.2.2/awaitility-4.2.2.jar:/Users/varya/.m2/repository/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar:/Users/varya/.m2/repository/org/junit/jupiter/junit-jupiter/5.11.4/junit-jupiter-5.11.4.jar:/Users/varya/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.11.4/junit-jupiter-api-5.11.4.jar:/Users/varya/.m2/repository/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar:/Users/varya/.m2/repository/org/junit/platform/junit-platform-commons/1.11.4/junit-platform-commons-1.11.4.jar:/Users/varya/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar:/Users/varya/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.11.4/junit-jupiter-params-5.11.4.jar:/Users/varya/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.11.4/junit-jupiter-engine-5.11.4.jar:/Users/varya/.m2/repository/org/junit/platform/junit-platform-engine/1.11.4/junit-platform-engine-1.11.4.jar:/Users/varya/.m2/repository/org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar:/Users/varya/.m2/repository/net/bytebuddy/byte-buddy-agent/1.15.11/byte-buddy-agent-1.15.11.jar:/Users/varya/.m2/repository/org/objenesis/objenesis/3.3/objenesis-3.3.jar:/Users/varya/.m2/repository/org/mockito/mockito-junit-jupiter/5.14.2/mockito-junit-jupiter-5.14.2.jar:/Users/varya/.m2/repository/org/skyscreamer/jsonassert/1.5.3/jsonassert-1.5.3.jar:/Users/varya/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar:/Users/varya/.m2/repository/org/springframework/spring-core/6.2.11/spring-core-6.2.11.jar:/Users/varya/.m2/repository/org/springframework/spring-jcl/6.2.11/spring-jcl-6.2.11.jar:/Users/varya/.m2/repository/org/springframework/spring-test/6.2.11/spring-test-6.2.11.jar:/Users/varya/.m2/repository/org/xmlunit/xmlunit-core/2.10.4/xmlunit-core-2.10.4.jar:/Users/varya/.m2/repository/cn/hutool/hutool-all/5.8.38/hutool-all-5.8.38.jar:/Users/varya/.m2/repository/com/github/xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter/4.4.0/knife4j-openapi3-jakarta-spring-boot-starter-4.4.0.jar:/Users/varya/.m2/repository/com/github/xiaoymin/knife4j-core/4.4.0/knife4j-core-4.4.0.jar:/Users/varya/.m2/repository/com/github/xiaoymin/knife4j-openapi3-ui/4.4.0/knife4j-openapi3-ui-4.4.0.jar:/Users/varya/.m2/repository/org/springdoc/springdoc-openapi-starter-webmvc-ui/2.3.0/springdoc-openapi-starter-webmvc-ui-2.3.0.jar:/Users/varya/.m2/repository/org/springdoc/springdoc-openapi-starter-webmvc-api/2.3.0/springdoc-openapi-starter-webmvc-api-2.3.0.jar:/Users/varya/.m2/repository/org/springdoc/springdoc-openapi-starter-common/2.3.0/springdoc-openapi-starter-common-2.3.0.jar:/Users/varya/.m2/repository/io/swagger/core/v3/swagger-core-jakarta/2.2.19/swagger-core-jakarta-2.2.19.jar:/Users/varya/.m2/repository/org/apache/commons/commons-lang3/3.17.0/commons-lang3-3.17.0.jar:/Users/varya/.m2/repository/io/swagger/core/v3/swagger-annotations-jakarta/2.2.19/swagger-annotations-jakarta-2.2.19.jar:/Users/varya/.m2/repository/io/swagger/core/v3/swagger-models-jakarta/2.2.19/swagger-models-jakarta-2.2.19.jar:/Users/varya/.m2/repository/jakarta/validation/jakarta.validation-api/3.0.2/jakarta.validation-api-3.0.2.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.4/jackson-dataformat-yaml-2.18.4.jar:/Users/varya/.m2/repository/org/webjars/swagger-ui/5.10.3/swagger-ui-5.10.3.jar:/Users/varya/.m2/repository/com/alibaba/dashscope-sdk-java/2.21.10/dashscope-sdk-java-2.21.10.jar:/Users/varya/.m2/repository/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21.jar:/Users/varya/.m2/repository/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar:/Users/varya/.m2/repository/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar:/Users/varya/.m2/repository/com/google/errorprone/error_prone_annotations/2.27.0/error_prone_annotations-2.27.0.jar:/Users/varya/.m2/repository/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar:/Users/varya/.m2/repository/org/slf4j/slf4j-simple/2.0.17/slf4j-simple-2.0.17.jar:/Users/varya/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.25/kotlin-stdlib-jdk8-1.9.25.jar:/Users/varya/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib/1.9.25/kotlin-stdlib-1.9.25.jar:/Users/varya/.m2/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar:/Users/varya/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.25/kotlin-stdlib-jdk7-1.9.25.jar:/Users/varya/.m2/repository/com/squareup/okio/okio/3.6.0/okio-3.6.0.jar:/Users/varya/.m2/repository/com/squareup/okio/okio-jvm/3.6.0/okio-jvm-3.6.0.jar:/Users/varya/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.25/kotlin-stdlib-common-1.9.25.jar:/Users/varya/.m2/repository/com/squareup/okhttp3/logging-interceptor/4.12.0/logging-interceptor-4.12.0.jar:/Users/varya/.m2/repository/com/squareup/okhttp3/okhttp-sse/4.12.0/okhttp-sse-4.12.0.jar:/Users/varya/.m2/repository/com/squareup/okhttp3/okhttp/4.12.0/okhttp-4.12.0.jar:/Users/varya/.m2/repository/com/github/victools/jsonschema-generator/4.31.1/jsonschema-generator-4.31.1.jar:/Users/varya/.m2/repository/com/fasterxml/classmate/1.7.0/classmate-1.7.0.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.18.4.1/jackson-core-2.18.4.1.jar:/Users/varya/.m2/repository/com/alibaba/fastjson/1.2.47/fastjson-1.2.47.jar:/Users/varya/.m2/repository/com/alibaba/cloud/ai/spring-ai-alibaba-starter/1.0.0-M6.1/spring-ai-alibaba-starter-1.0.0-M6.1.jar:/Users/varya/.m2/repository/com/alibaba/cloud/ai/spring-ai-alibaba-autoconfigure/1.0.0-M6.1/spring-ai-alibaba-autoconfigure-1.0.0-M6.1.jar:/Users/varya/.m2/repository/org/springframework/ai/spring-ai-spring-boot-autoconfigure/1.0.0-M6/spring-ai-spring-boot-autoconfigure-1.0.0-M6.jar:/Users/varya/.m2/repository/com/alibaba/cloud/ai/spring-ai-alibaba-core/1.0.0-M6.1/spring-ai-alibaba-core-1.0.0-M6.1.jar:/Users/varya/.m2/repository/org/springframework/ai/spring-ai-core/1.0.0-M6/spring-ai-core-1.0.0-M6.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/module/jackson-module-jsonSchema/2.18.4/jackson-module-jsonSchema-2.18.4.jar:/Users/varya/.m2/repository/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final.jar:/Users/varya/.m2/repository/io/swagger/core/v3/swagger-annotations/2.2.25/swagger-annotations-2.2.25.jar:/Users/varya/.m2/repository/com/github/victools/jsonschema-module-swagger-2/4.37.0/jsonschema-module-swagger-2-4.37.0.jar:/Users/varya/.m2/repository/org/antlr/ST4/4.3.4/ST4-4.3.4.jar:/Users/varya/.m2/repository/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar:/Users/varya/.m2/repository/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar:/Users/varya/.m2/repository/io/projectreactor/reactor-core/3.7.11/reactor-core-3.7.11.jar:/Users/varya/.m2/repository/org/springframework/spring-messaging/6.2.11/spring-messaging-6.2.11.jar:/Users/varya/.m2/repository/io/micrometer/micrometer-core/1.14.11/micrometer-core-1.14.11.jar:/Users/varya/.m2/repository/org/hdrhistogram/HdrHistogram/2.2.2/HdrHistogram-2.2.2.jar:/Users/varya/.m2/repository/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar:/Users/varya/.m2/repository/io/micrometer/context-propagation/1.1.3/context-propagation-1.1.3.jar:/Users/varya/.m2/repository/com/knuddels/jtokkit/1.1.0/jtokkit-1.1.0.jar:/Users/varya/.m2/repository/com/github/victools/jsonschema-module-jackson/4.37.0/jsonschema-module-jackson-4.37.0.jar:/Users/varya/.m2/repository/org/springframework/ai/spring-ai-retry/1.0.0-M6/spring-ai-retry-1.0.0-M6.jar:/Users/varya/.m2/repository/org/springframework/retry/spring-retry/2.0.12/spring-retry-2.0.12.jar:/Users/varya/.m2/repository/org/springframework/spring-webflux/6.2.11/spring-webflux-6.2.11.jar:/Users/varya/.m2/repository/commons-codec/commons-codec/1.17.2/commons-codec-1.17.2.jar:/Users/varya/.m2/repository/com/alibaba/nacos/nacos-client-mse-extension/1.0.4/nacos-client-mse-extension-1.0.4.jar:/Users/varya/.m2/repository/com/aliyun/aliyun-java-sdk-core/4.5.17/aliyun-java-sdk-core-4.5.17.jar:/Users/varya/.m2/repository/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar:/Users/varya/.m2/repository/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar:/Users/varya/.m2/repository/commons-logging/commons-logging/1.2/commons-logging-1.2.jar:/Users/varya/.m2/repository/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar:/Users/varya/.m2/repository/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar:/Users/varya/.m2/repository/org/jacoco/org.jacoco.agent/0.8.6/org.jacoco.agent-0.8.6-runtime.jar:/Users/varya/.m2/repository/org/ini4j/ini4j/0.5.4/ini4j-0.5.4.jar:/Users/varya/.m2/repository/io/opentracing/opentracing-api/0.33.0/opentracing-api-0.33.0.jar:/Users/varya/.m2/repository/io/opentracing/opentracing-util/0.33.0/opentracing-util-0.33.0.jar:/Users/varya/.m2/repository/io/opentracing/opentracing-noop/0.33.0/opentracing-noop-0.33.0.jar:/Users/varya/.m2/repository/com/aliyun/aliyun-java-sdk-kms/2.16.3/aliyun-java-sdk-kms-2.16.3.jar:/Users/varya/.m2/repository/com/aliyun/kms/kms-transfer-client/0.1.0/kms-transfer-client-0.1.0.jar:/Users/varya/.m2/repository/com/aliyun/alibabacloud-dkms-gcs-sdk/0.2.8/alibabacloud-dkms-gcs-sdk-0.2.8.jar:/Users/varya/.m2/repository/com/aliyun/tea-util/0.2.7/tea-util-0.2.7.jar:/Users/varya/.m2/repository/com/aliyun/tea/1.2.3/tea-1.2.3.jar:/Users/varya/.m2/repository/com/aliyun/alibabacloud-dkms-gcs-openapi-util/0.2.8/alibabacloud-dkms-gcs-openapi-util-0.2.8.jar:/Users/varya/.m2/repository/com/aliyun/alibabacloud-dkms-gcs-openapi-credential/0.2.8/alibabacloud-dkms-gcs-openapi-credential-0.2.8.jar:/Users/varya/.m2/repository/com/aliyun/alibabacloud-dkms-gcs-openapi/0.2.8/alibabacloud-dkms-gcs-openapi-0.2.8.jar:/Users/varya/.m2/repository/com/google/protobuf/protobuf-java/4.0.0-rc-2/protobuf-java-4.0.0-rc-2.jar:/Users/varya/.m2/repository/com/google/protobuf/protobuf-java-util/4.0.0-rc-2/protobuf-java-util-4.0.0-rc-2.jar:/Users/varya/.m2/repository/com/google/guava/guava/29.0-android/guava-29.0-android.jar:/Users/varya/.m2/repository/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar:/Users/varya/.m2/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:/Users/varya/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:/Users/varya/.m2/repository/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar:/Users/varya/.m2/repository/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar:/Users/varya/.m2/repository/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70.jar:/Users/varya/.m2/repository/com/alibaba/cloud/spring-alibaba-nacos-config/2023.0.1.3/spring-alibaba-nacos-config-2023.0.1.3.jar:/Users/varya/.m2/repository/com/alibaba/nacos/nacos-client/2.4.2/nacos-client-2.4.2.jar:/Users/varya/.m2/repository/com/alibaba/nacos/nacos-auth-plugin/2.4.2/nacos-auth-plugin-2.4.2.jar:/Users/varya/.m2/repository/com/alibaba/nacos/nacos-encryption-plugin/2.4.2/nacos-encryption-plugin-2.4.2.jar:/Users/varya/.m2/repository/com/alibaba/nacos/nacos-logback-adapter-12/2.4.2/nacos-logback-adapter-12-2.4.2.jar:/Users/varya/.m2/repository/com/alibaba/nacos/logback-adapter/1.1.3/logback-adapter-1.1.3.jar:/Users/varya/.m2/repository/com/alibaba/nacos/nacos-log4j2-adapter/2.4.2/nacos-log4j2-adapter-2.4.2.jar:/Users/varya/.m2/repository/org/apache/httpcomponents/httpasyncclient/4.1.5/httpasyncclient-4.1.5.jar:/Users/varya/.m2/repository/org/apache/httpcomponents/httpcore-nio/4.4.16/httpcore-nio-4.4.16.jar:/Users/varya/.m2/repository/io/prometheus/simpleclient/0.16.0/simpleclient-0.16.0.jar:/Users/varya/.m2/repository/io/prometheus/simpleclient_tracer_otel/0.16.0/simpleclient_tracer_otel-0.16.0.jar:/Users/varya/.m2/repository/io/prometheus/simpleclient_tracer_common/0.16.0/simpleclient_tracer_common-0.16.0.jar:/Users/varya/.m2/repository/io/prometheus/simpleclient_tracer_otel_agent/0.16.0/simpleclient_tracer_otel_agent-0.16.0.jar:/Users/varya/.m2/repository/org/apache/opennlp/opennlp-tools/2.3.3/opennlp-tools-2.3.3.jar:/Users/varya/.m2/repository/dev/langchain4j/langchain4j-community-dashscope/1.5.0-beta11/langchain4j-community-dashscope-1.5.0-beta11.jar:/Users/varya/.m2/repository/dev/langchain4j/langchain4j-core/1.5.0/langchain4j-core-1.5.0.jar:/Users/varya/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.18.4/jackson-annotations-2.18.4.jar:/Users/varya/.m2/repository/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 cn.varin.varaiagent.app.IALDAAppTest,testMyAdvisor
已连接到地址为 ''127.0.0.1:62954',传输: '套接字'' 的目标虚拟机
SLF4J(W): Class path contains multiple SLF4J providers.
SLF4J(W): Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@5f049ea1]
SLF4J(W): Found provider [org.slf4j.simple.SimpleServiceProvider@72cc7e6f]
SLF4J(W): See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J(I): Actual provider is of type [ch.qos.logback.classic.spi.LogbackServiceProvider@5f049ea1]
13:22:28.375 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [cn.varin.varaiagent.app.IALDAAppTest]: IALDAAppTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
13:22:28.419 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration cn.varin.varaiagent.VarAiAgentApplication for test class cn.varin.varaiagent.app.IALDAAppTest
2025-09-29T13:22:28.564+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.a.nacos.client.logging.NacosLogging    : Nacos Logging Adapter Builder: com.alibaba.nacos.logger.adapter.logback12.LogbackNacosLoggingAdapterBuilder
2025-09-29T13:22:28.568+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.a.nacos.client.logging.NacosLogging    : Nacos Logging Adapter Builder: com.alibaba.nacos.logger.adapter.logback14.LogbackNacosLoggingAdapterBuilder
2025-09-29T13:22:28.568+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.a.nacos.client.logging.NacosLogging    : Nacos Logging Adapter: com.alibaba.nacos.logger.adapter.logback14.LogbackNacosLoggingAdapter match ch.qos.logback.classic.Logger success.
2025-09-29T13:22:28.570+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.a.nacos.client.logging.NacosLogging    : Nacos Logging Adapter Builder: com.alibaba.nacos.logger.adapter.log4j2.Log4j2NacosLoggingAdapterBuilder

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::               (v3.4.10)

2025-09-29T13:22:28.647+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] cn.varin.varaiagent.app.IALDAAppTest     : Starting IALDAAppTest using Java 21.0.8 with PID 64593 (started by varya in /Users/varya/Desktop/github/var-ai-agent)
2025-09-29T13:22:28.647+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] cn.varin.varaiagent.app.IALDAAppTest     : The following 1 profile is active: "local"
2025-09-29T13:22:29.022+08:00  WARN 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] trationDelegate$BeanPostProcessorChecker : Bean 'com.alibaba.cloud.nacos.NacosConfigAutoConfiguration' of type [com.alibaba.cloud.nacos.NacosConfigAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). The currently created BeanPostProcessor [nacosAnnotationProcessor] is declared through a non-static factory method on that class; consider declaring it as static instead.
2025-09-29T13:22:29.025+08:00  WARN 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] trationDelegate$BeanPostProcessorChecker : Bean 'nacosConfigProperties' of type [com.alibaba.cloud.nacos.NacosConfigProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [nacosAnnotationProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2025-09-29T13:22:29.055+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.a.n.p.a.s.c.ClientAuthPluginManager    : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-09-29T13:22:29.055+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.a.n.p.a.s.c.ClientAuthPluginManager    : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-09-29T13:22:29.056+08:00  WARN 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] trationDelegate$BeanPostProcessorChecker : Bean 'nacosConfigManager' of type [com.alibaba.cloud.nacos.NacosConfigManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [nacosAnnotationProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2025-09-29T13:22:29.314+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] o.s.v.b.OptionalValidatorFactoryBean     : Failed to set up a Bean Validation provider: jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
2025-09-29T13:22:29.466+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] cn.varin.varaiagent.app.IALDAAppTest     : Started IALDAAppTest in 0.998 seconds (process running for 1.522)
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build what is described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3
WARNING: A Java agent has been loaded dynamically (/Users/varya/.m2/repository/net/bytebuddy/byte-buddy-agent/1.15.11/byte-buddy-agent-1.15.11.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2025-09-29T13:22:29.777+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.v.varaiagent.advisors.MyLogAdvisor     : beforeName:varin
2025-09-29T13:22:29.778+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.v.varaiagent.advisors.MyLogAdvisor     : advisedRequest:AdvisedRequest[chatModel=com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel@9976b27, userText=你好,我是varin, systemText=作为拥有多年学术研究与论文指导经验的专家,精通各学科研究方法与写作规范,需通过开放性问题启发式引导(探研究方向 / 写作瓶颈 / 结构需求等),提供选题评估、文献指导、方法分析、结构建议、表达规范等符合学术规范的专业建议,适配用户基础调整指导深度,以建议而非指令陪伴用户完成论文从选题到定稿全流程,协助解决各类写作问题。, chatOptions=DashScopeChatOptions: {"model":"qwen-plus","temperature":0.8,"enable_search":false,"incremental_output":true,"multi_model":false}, media=[], functionNames=[], functionCallbacks=[], messages=[], userParams={}, systemParams={}, advisors=[org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1@2a191f83, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$2@c8531b9, org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor@3adbd038, cn.varin.varaiagent.advisors.MyLogAdvisor@5e976553, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1@3c108c05, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$2@193d7ac7], advisorParams={chat_memory_response_size=10, chat_memory_conversation_id=e9704f10-82bd-4616-b909-07a38b00a1e2}, adviseContext={chat_memory_conversation_id=e9704f10-82bd-4616-b909-07a38b00a1e2, chat_memory_response_size=10}, toolContext={}]
2025-09-29T13:22:33.237+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.v.varaiagent.advisors.MyLogAdvisor     : afterName:varin
2025-09-29T13:22:33.237+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.v.varaiagent.advisors.MyLogAdvisor     : advisedRequest:AdvisedResponse[response=ChatResponse [metadata={ id: 37d84ee4-b68f-4716-9979-8ac06fce7635, usage: TokenUsage[outputTokens=48, inputTokens=115, totalTokens=163], rateLimit: org.springframework.ai.chat.metadata.EmptyRateLimit@9ba167e }, generations=[Generation[assistantMessage=AssistantMessage [messageType=ASSISTANT, toolCalls=[], textContent=你好,Varin!欢迎来交流~  
你是正在准备写论文、做研究,还是有其他学术写作方面的需求呢?😊  
可以和我聊聊你目前的研究方向或遇到的困难,我会尽力提供帮助!, metadata={finishReason=STOP, role=ASSISTANT, id=37d84ee4-b68f-4716-9979-8ac06fce7635, messageType=ASSISTANT, reasoningContent=}], chatGenerationMetadata=DefaultChatGenerationMetadata[finishReason='STOP', filters=0, metadata=0]]]], adviseContext={chat_memory_conversation_id=e9704f10-82bd-4616-b909-07a38b00a1e2, chat_memory_response_size=10}]
2025-09-29T13:22:33.241+08:00  INFO 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] cn.varin.varaiagent.app.IALDAApp         : text:你好,Varin!欢迎来交流~  
你是正在准备写论文、做研究,还是有其他学术写作方面的需求呢?😊  
可以和我聊聊你目前的研究方向或遇到的困难,我会尽力提供帮助!
2025-09-29T13:22:33.249+08:00  WARN 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [      Thread-14] c.a.n.common.http.HttpClientBeanHolder   : [HttpClientBeanHolder] Start destroying common HttpClient
2025-09-29T13:22:33.249+08:00  WARN 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [       Thread-1] c.a.n.common.executor.ThreadPoolManager  : [ThreadPoolManager] Start destroying ThreadPool
2025-09-29T13:22:33.249+08:00  WARN 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [      Thread-14] c.a.n.common.http.HttpClientBeanHolder   : [HttpClientBeanHolder] Destruction of the end
2025-09-29T13:22:33.249+08:00  WARN 64593 --- [spring-ai-alibaba-qwq-chat-client-example] [       Thread-1] c.a.n.common.executor.ThreadPoolManager  : [ThreadPoolManager] Destruction of the end
已与地址为 ''127.0.0.1:62954',传输: '套接字'' 的目标虚拟机断开连接

进程已结束,退出代码为 0

二、实现结构化输出

  1. SpringAI官方文档地址:https://docs.spring.io/spring-ai/reference/api/structured-output-converter.html

(一)简介

Spring AI 结构化输出功能通过转换器,调用前向提示附加格式指令,调用后将 LLM 输出转为 Java 对象、Map 等结构,助力下游系统可靠集成,简化开发。

(二)、工作原理

  1. 前置处理:通过提示模板附加格式指令(如指定 JSON 结构),明确告知 LLM 输出格式;
  2. 模型调用:LLM 按指令生成符合格式的文本输出;
  3. 后置转换:专用转换器解析输出文本,验证格式合规性后,自动映射为 Java 对象、Map 等结构化数据,供下游直接使用。
  4. 总结实现过程:使用 PromptTemplate 将格式指令附加到用户输入的末尾,并在entity方法中添加需要转换的数据类型格式。

(三)、内置结构话输出类

Sprin⁠g AI 提供了多‌种转换器实现,分别用于将输出转换为不同的结构:

AbstractConversionServiceOutputConverter:提供预配置的 GenericConversionService,用于将 LLM 输出转换为所需格式

AbstractMessageOutputConverter:支持 Spring AI Message 的转换

BeanOutputConverter:用于将输出转换为 Java Bean 对象(基于 ObjectMapper 实现)

MapOutputConverter:用于将输出转换为 Map 结构

ListOutputConverter:用于将输出转换为 List 结构

(四)、示例

需求:⁠使用结构化输出,来‌为用户生成学术分析报告,并转换为学术分析报告对象,包含报告标题‌和论文编写建议列表字段。

  1. 导入依赖
<!--        结构化输出依赖-->
<dependency>
  <groupId>com.github.victools</groupId>
  <artifactId>jsonschema-generator</artifactId>
  <version>4.38.0</version>
</dependency>
  1. 编写学术分析报告对象
record IALDAReport(String title, List<String> suggestions){}
  1. 在chatClient中添加属性
/**
 * 自定义结构化输出
 */
public IALDAReport getIALDAReport(String content,String chatId) {
    IALDAReport ialdaReport = this.chatClient.prompt()
    .user(content)
    .system(SYSTEM_PROPERTY+"每次对话后都要生成学术分析结果,标题为{用户名}的学术分析报告,内容为建议列表")
    .advisors(advisor -> advisor.param("chat_memory_conversation_id", chatId)
              .param("chat_memory_response_size", 10)).call()
    .entity(IALDAReport.class);

    log.info("IALDAReport:{}", ialdaReport);
    return ialdaReport;
}
  1. 测试用例
@Test
public void getIALDAReport() {
    String uuid= UUID.randomUUID().toString();
    ialdaApp.getIALDAReport("你好,我是varin", uuid);

}
  1. 效果:


Here is the JSON Schema instance your output must adhere to:
```{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "suggestions" : {
      "type" : "array",
      "items" : {
        "type" : "string"
      }
    },
    "title" : {
      "type" : "string"
    }
  },
  "additionalProperties" : false
}```
, chat_memory_response_size=10}, toolContext={}]
2025-09-29T16:27:05.000+08:00  INFO 65120 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.v.varaiagent.advisors.MyLogAdvisor     : afterName:varin
2025-09-29T16:27:05.001+08:00  INFO 65120 --- [spring-ai-alibaba-qwq-chat-client-example] [           main] c.v.varaiagent.advisors.MyLogAdvisor     : advisedRequest:AdvisedResponse[response=ChatResponse [metadata={ id: 98a4e35b-2825-4787-b9b2-9d059b6d049e, usage: TokenUsage[outputTokens=181, inputTokens=301, totalTokens=482], rateLimit: org.springframework.ai.chat.metadata.EmptyRateLimit@7a57c5d9 }, generations=[Generation[assistantMessage=AssistantMessage [messageType=ASSISTANT, toolCalls=[], textContent={
  "suggestions": [
    "明确研究方向:请说明您感兴趣的学科领域或具体研究主题,以便进一步指导。",
    "评估选题可行性:分析该选题的学术价值、创新性及资料可获得性。",
    "梳理文献基础:建议开展系统性文献综述,识别研究空白与理论框架。",
    "确定研究方法:根据研究问题选择定性、定量或混合研究设计。",
    "规划论文结构:建议遵循引言、文献综述、方法论、结果分析、讨论与结论的标准架构。",
    "注重学术规范:确保引用格式符合目标期刊或机构要求(如APA、MLA、Chicago等)。",
    "定期修改完善:建议分阶段撰写并反复修订,提升逻辑性与表达准确性。"
  ],
  "title": "varin的学术分析报告"
}, metadata={finishReason=STOP, role=ASSISTANT, id=98a4e35b-2825-4787-b9b2-9d059b6d049e, messageType=ASSISTANT, reasoningContent=}], chatGenerationMetadata=DefaultChatGenerationMetadata[finishReason='STOP', filters=0, metadata=0]]]], adviseContext={chat_memory_conversation_id=e916e02b-437b-4630-951f-746927421b1b, formatParam=Your response should be in JSON format.
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
Do not include markdown code blocks in your response.
Remove the ```json markdown from the output.
Here is the JSON Schema instance your output must adhere to:
```{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "suggestions" : {
      "type" : "array",
      "items" : {
        "type" : "string"
      }
    },
    "title" : {
      "type" : "string"
    }
  },
  "additionalProperties" : false
}```
,

三、对话记忆持久化

(一)、需求

之前我们使用了基于内存的对话记忆来保存对话上下文,但是服务器一旦重启了,对话记忆就会丢失。有时,我们可能希望将对话记忆持久化,保存到文件、数据库、Redis 或者其他对象存储中,怎么实现呢?

(二)、SpringAI提供的处理内置类

InMemoryChatMemory:内存存储

CassandraChatMemory:在 Cassandra 中带有过期时间的持久化存储

Neo4jChatMemory:在 Neo4j 中没有过期时间限制的持久化存储

JdbcChatMemory:在 JDBC 中没有过期时间限制的持久化存储

注意:在以上的案例中并没有我们需要的实现方式,所以我们可以自定义实现它

(三)、自定义实现对话记忆--存在本地文件中

实现步骤:实现 ChatMemory类,并重写其中方法,修改成自己需要的。

  1. 实现
package cn.varin.varaiagent.chatMemory;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class FileChatMemory implements ChatMemory {

    // 文件路径
    private    final String  BASE_DIR;
    private  static  final Kryo kryo  = new Kryo();;
    static {
        kryo.setRegistrationRequired(false);

        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());

    }
    public FileChatMemory(String dir) {
        this.BASE_DIR = dir;
        File file = new File(dir);
        if (!file.exists()) {
            file.mkdirs();
        }

    }

    @Override
    public void add(String conversationId, Message message) {
        List<Message> messages = getMessages(conversationId);
        messages.add(message);
        save(conversationId, messages);
    }

    @Override
    public void add(String conversationId, List<Message> messages) {
        List<Message> messageList = getMessages(conversationId);
        messageList.addAll(messages);
        save(conversationId, messageList);


    }

    @Override
    public List<Message> get(String conversationId, int lastN) {

        List<Message> messages = getMessages(conversationId);

        return  messages.stream()
                .skip(Math.max(0,messages.size()-lastN))
                .toList();


    }

    @Override
    public void clear(String conversationId) {
        File file = getFile(conversationId);
        if (file.exists()) {
            file.delete();
        }
    }


    /**
     * 获取到文件
     * @param id
     * @return
     */
    private  File getFile(String id) {
        return new File(BASE_DIR , id+".kryo");
    }

    /**
     * 保存信息
     * @param id
     * @param messages
     */
    private void save(String id, List<Message> messages) {
        File file = getFile(id);
        try (Output output = new Output(new FileOutputStream(file))) { // 使用 try-with-resources 自动关闭
            kryo.writeObject(output, messages);
            // 无需手动关闭,try-with-resources 会自动处理
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * id查询信息
     */
    private List<Message> getMessages(String id) {
        File file = getFile(id);
        if (!file.exists()) {
            return new ArrayList<>();
        }
        try (Input input = new Input(new FileInputStream(file))) { // 同样使用 try-with-resources
            // 使用泛型类型读取
            return kryo.readObject(input, ArrayList.class);
        } catch (IOException e) {
            // 若文件损坏,可删除文件并返回空列表
            file.delete();
            return new ArrayList<>();
        }
    }
}
  1. 调用
public String  fileSaveMessage(String content,String chatId) {
    String  fileDir = System.getProperty("user.dir")+"/chat-memory";

    FileChatMemory fileChatMemory = new FileChatMemory(fileDir);

    ChatResponse chatResponse = this.chatClient.prompt()
    .user(content)
    .advisors(advisor -> advisor.param("chat_memory_conversation_id", chatId)
              .advisors(new MessageChatMemoryAdvisor(fileChatMemory))
              .param("chat_memory_response_size", 10))

    .call().chatResponse();
    String text = chatResponse.getResult().getOutput().getText();
    log.info("text:{}", text);
    return text;
}
  1. 测试
@Test
public void fileSaveMessageTest() {
    String uuid= UUID.randomUUID().toString();
    ialdaApp.fileSaveMessage("你好,我是varin", uuid);

}
  1. 结果

四、PropmtTamplate模版

PromptTemplate 是 Spring AI 框架中用于构建和管理提示词的核心组件。允许开发者创建带有占位符的文本模板,然后在运行时动态替换这些占位符。

它相当于 AI 交互中的 “视图层”,类似于 Spring MVC 中的视图模板(或者 JSP)。通过使用 PromptTemplate,你可以更加结构化、可维护地管理 AI 应用中的提示词,使其更易于优化和扩展,同时降低硬编码带来的维护成本。

官网文档:https://docs.spring.io/spring-ai/reference/api/prompt.html#_prompttemplate

  1. 实现
package cn.varin.varaiagent.promptTamplate;

import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyPromptTemplate {

    private String prompt;
    private String name;
    private String major;
    private Map<String,Object > options;

    public MyPromptTemplate() {
    }

    public MyPromptTemplate( String name, String major) {
        this.name = name;
        this.major = major;
        this.prompt = "你好,我是{name},是{majoy}专业的学生。";
        options = new HashMap<>();
        options.put("name",this.name);
        options.put("majoy",this.major);
    }

    public String getPrompt() {
        PromptTemplate promptTemplate = new PromptTemplate(this.prompt);
        return  promptTemplate.render(this.options);
    }

}
  1. 测试
@Test
public void promptTemplateTest() {
    MyPromptTemplate myPromptTemplate = new MyPromptTemplate("varin","软件工程");
    String uuid= UUID.randomUUID().toString();
    ialdaApp.fileSaveMessage(myPromptTemplate.getPrompt(), uuid);

}
  1. 效果

五、多模态开发

(一)、简介

AI 多模态是指能够同时处理、理解和生成多种不同类型数据的能力,比如文本、图像、音频、视频、PDF、结构化数据(比如表格)等。

(二)、阿里SDK示例--- 视觉理解(qwen-VL)

模型:qwen-vl-max、qwen-vl-plus

官网文档地址:https://help.aliyun.com/zh/model-studio/vision?spm=a2c4g.11186623.help-menu-2400256.d_0_2_0.10bc7a06hOoBP3&scm=20140722.H_2845871._.OR_help-T_cn~zh-V_1#5aab095b1daqg

package cn.varin.varaiagent.test;

import java.util.Arrays;
import java.util.Collections;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult;
import com.alibaba.dashscope.common.MultiModalMessage;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.exception.UploadFileException;
import com.alibaba.dashscope.utils.JsonUtils;
import com.alibaba.dashscope.utils.Constants;

/**
 * 多模态开发:  视觉理解
 * 可以使用的模型:qwen-vl-max、qwen-vl-plus
 */
public class Main {

    // 若使用新加坡地域的模型,请取消下列注释
    //    static {Constants.baseHttpApiUrl="https://dashscope-intl.aliyuncs.com/api/v1";}

    public static void simpleMultiModalConversationCall()
    throws ApiException, NoApiKeyException, UploadFileException {

        MultiModalConversation conv = new MultiModalConversation();
        MultiModalMessage systemMessage = MultiModalMessage.builder().role(Role.SYSTEM.getValue())
        .content(Arrays.asList(
            Collections.singletonMap("text", "You are a helpful assistant."))).build();
        MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue())
        .content(Arrays.asList(
            Collections.singletonMap("image", "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"),
            Collections.singletonMap("text", "图中描绘的是什么景象?"))).build();
        MultiModalConversationParam param = MultiModalConversationParam.builder()
        // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx")
        // 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
        .apiKey("key")
        .model("qwen-vl-max-latest")  // 此处以qwen-vl-max-latest为例,可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/models
        .messages(Arrays.asList(systemMessage, userMessage))
        .build();
        MultiModalConversationResult result = conv.call(param);
        System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent().get(0).get("text"));
    }
    public static void main(String[] args) {
        try {
            simpleMultiModalConversationCall();
        } catch (ApiException | NoApiKeyException | UploadFileException e) {
            System.out.println(e.getMessage());
        }
        System.exit(0);
    }
}

加油呀~~~

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐