Spring AI alibaba Prompt模板&Advisor自定义

本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别。

文章目录


前言

本篇主要介绍Spring AI alibaba Prompt的概念,以及如何模板化配置,自定义Advisor拦截器。

一、Prompt

Prompt是提示词,简单来说,每次使用AI,在对话框中输入的提问内容,对于大模型来说就是提示词。

提示词也可以进行细分,主要可分为以下三种类型:

  • 用户提示词:即用户每次向大模型进行提问的内容。
  • 系统提示词:在进行AI应用开发时,开发者给大模型预先设置的规则和角色定位。例如:"扮演深耕编程领域的专家。开场向用户表明身份,告知用户可倾诉编程学习方面的难题。引导用户详述事情经过、对方反应及自身想法,以便给出专属解决方案。"通常不同领域的AI应用,预设的角色都是不同的,这一点要开发者根据实际业务需求进行定制。
  • 助手提示词:是每次大模型在处理完用户的提问后,响应的内容。在多轮对话中,助手提示词也会作为上下文的一部分,影响后续的互动。

这三类提示词在代码中的体现,都实现了Message接口:


二、Prompt模板化配置

在AI应用开发中,需要定义系统提示词。可以将提示词作为常量定义,或者保存在数据库中。Spring AI的官方提供了模板化配置的功能。模板化配置

Spring AI 中快速模板化的一个关键组件是PromptTemplate类,旨在方便创建结构化提示,然后发送到人工智能模型进行处理。

Spring AI 提供了PromptTemplate的不同实现类,对应不同角色的消息:

  • SystemPromptTemplate:系统消息
  • AssistantPromptTemplate:助手消息

同时Spring AI 支持从模板文件中读取Prompt信息,并且动态替换占位符的功能,首先在项目的resources目录下定义一个文件:system-message.st,文件中的内容带有占位符:

"扮演深耕{field}领域的专家。开场向用户表明身份,告知用户可倾诉{type}难题。引导用户详述事情经过、对方反应及自身想法,以便给出专属解决方案。"

因为上述的文案,是给AI大模型预设的,所以应该构造一个SystemPromptTemplate,并且将读取到的文件内容赋值给它:

java 复制代码
@Value("classpath:/promts/system-message.st") 
Resource systemResource;

//.....

SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);

然后使用SystemPromptTemplatecreateMessageAPI,进行占位符填充,并获取最终的提示词。

java 复制代码
String text = systemPromptTemplate.createMessage(Map.of("field", "JAVA编程", "type", "学习方向")).getText();

三、Advisor

Spring AI中的Advisor,相当于Spring 的AOP切面,允许用户在调用大模型前后,执行一些操作,getOrder()方法决定多个Advisor组成的责任链的执行顺序(较低的值首先被执行),而getName()则是提供一个自定义的名称。

它有两个实现类,分别是CallAroundAdvisorStreamAroundAdvisor。代表了不同的模式(流式或非流式)。用户在自定义实现时最好两个都要实现。

和Spring 的 AOP切面一样,多个Advisor会组成责任链,责任链和大模型之间的交互:

  1. Spring AI从用户的Prompt提示词,创建一个ChatClientRequest ,并且创建一个context 上下文对象,用于在整个责任链中进行消息传递。
  2. 链中的每个Advisor都会处理请求,并可能对请求的内容进行修改。也可以直接拦截请求,不向下一个Advisor传递,并且构造响应,返回结果。
  3. 由框架提供的最终Advisor将请求发送至大模型。
  4. 模型的响应随后通过责任链进行传递并转化为ChatClientResponse(包含了context 上下文对象),每个后置的Advisor也可以对响应进行处理。
  5. 最终的ChatClientResponse通过提取ChatCompletion返回客户端。

Spring AI提供了一些内置的Advisor,对功能进行扩展:


日志拦截器,在请求和响应时打印日志,但是级别是debug

敏感词拦截器,拦截用户自定义的敏感词,如果发现用户提问包含敏感词,就直接拦截,构造响应返回给用户,响应内容也是可以用户自定义的

可以通过ChatClientdefaultAdvisorsAPI,加入自定义的拦截器。

java 复制代码
        chatClient = ChatClient.builder(
                dashScopeChatModel
        ).defaultSystem(text)
                //基于数据库的会话记忆保存
                .defaultAdvisors(new MessageChatMemoryAdvisor(dbBasedChatMemory),
                        //自定义日志拦截器
                        new MyLogAdvisor(),
                        //敏感词检测
                        new SensitiveWordCheckAdvisor())
                .build();

四、自定义Advisor

用户自定义Advisor,最好需要同时实现CallAroundAdvisor, StreamAroundAdvisor两个接口,分别重写流式和非流式的相关方法,例如自定义的日志拦截器:

java 复制代码
/**
 * 自定义日志拦截器
 */
@Slf4j
public class MyLogAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {


    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        //前置增强
        log.info("自定义日志拦截器,AI request:{}",advisedRequest.userText());
        //调用下一个拦截器
        AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
        //后置增强
        log.info("自定义日志拦截器,AI response:{}",advisedResponse.response().getResult().getOutput().getText());
        return advisedResponse;
    }

    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
        //前置增强
        log.info("自定义日志拦截器,AI request:{}",advisedRequest.userText());
        Flux<AdvisedResponse> advisedResponseFlux = chain.nextAroundStream(advisedRequest);
        return new MessageAggregator().aggregateAdvisedResponse(advisedResponseFlux, new Consumer<AdvisedResponse>() {
            @Override
            public void accept(AdvisedResponse advisedResponse) {
                //后置增强
                log.info("自定义日志拦截器,AI response:{}",advisedResponse.response().getResult().getOutput().getText());
            }
        });
    }

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

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

自定义Re2拦截器,其实现方式是取出用户的提问词,然后重复发给AI。但是每次用户的对话是需要消耗token的,这样的做法相当于消耗了双倍的token。

java 复制代码
/**
 * 自定义 Re2 Advisor
 * 可提高大型语言模型的推理能力
 */
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {


    private AdvisedRequest before(AdvisedRequest advisedRequest) {

        Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
        advisedUserParams.put("re2_input_query", advisedRequest.userText());

        return AdvisedRequest.from(advisedRequest)
                .userText("""
                        {re2_input_query}
                        Read the question again: {re2_input_query}
                        """)
                .userParams(advisedUserParams)
                .build();
    }

    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        return chain.nextAroundCall(this.before(advisedRequest));
    }

    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
        return chain.nextAroundStream(this.before(advisedRequest));
    }

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

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }
}
相关推荐
CV实验室3 小时前
NeurIPS 2025 | 北大等提出C²Prompt:解耦类内与类间知识,攻克FCL遗忘难题!
人工智能·计算机视觉·prompt·论文·cv
敲代码的嘎仔3 小时前
牛客算法基础noob59 简写单词
java·开发语言·数据结构·程序人生·算法·leetcode·学习方法
少许极端3 小时前
算法奇妙屋(四)-归并分治
java·算法·排序算法·分治·归并
盟接之桥4 小时前
盟接之桥说制造:源头制胜,降本增效:从“盟接之桥”看供应链成本控制的底层逻辑
大数据·网络·人工智能·安全·制造
数据皮皮侠4 小时前
中国绿色制造企业数据(绿色工厂|绿色供应链|绿色园区|绿色产品,2017-2023)
大数据·运维·服务器·人工智能·制造·微信开放平台
RFID舜识物联网4 小时前
NFC技术如何破解电子制造领域的效率瓶颈与追溯难题
大数据·人工智能·嵌入式硬件·物联网·安全·制造
特立独行的猫a5 小时前
C 语言各种指针详解
java·c语言·开发语言
l12345sy5 小时前
Day31_【 NLP _1.文本预处理 _(1)文本处理的基本方法】
人工智能·自然语言处理·nlp·文本基本处理·jieba词性标注对照表
Rewloc6 小时前
Trae CN配置Maven环境
java·maven