当前位置: 首页 > news >正文

LLM大模型开发-SpringAI:ChatClient、Ollama、Advisor

ollama多个模型选择

构造自定义输入模型entity

package cn.kanyu.springai.entity;import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;import java.io.Serializable;@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class MoreModelConfig implements Serializable {/*** 模型*/private String model;/***温度*/private Double temperature;}

请求http://localhost:9999/ai/mulChatModel?message=你是谁&model=deepseek-r1:1.5b&temperature=0.9
请求模型为deepseek-r1:1.5b
返回
在这里插入图片描述

请求http://localhost:9999/ai/mulChatModel?message=你是谁&model=llama3:latest&temperature=0.9
返回
在这里插入图片描述

提示词模版

设置系统提示词模版

@Configuration
public class commonConfiguration {@BeanChatClient chatClient(ChatClient.Builder builder) {return builder.defaultSystem("你将作为一名机器人产品的专家,对于用户的使用需求作出解答,当前服务的用户姓名:{name},年龄:{age},性别:{sex}").build();}}

其中姓名:{name},年龄:{age},性别:{sex}为模版提示
在控制器下显示设置name、age、sex

@GetMapping(value = "/promptChat", produces = "text/stream;charset=UTF-8")public Flux<String> promptChat(@RequestParam("message")String message){return chatClient.prompt().system(p -> p.param("name","张三").param("age",16).param("sex","男") ).user(message).stream().content();}

此时发送请求
http://localhost:9999/ai/promptChat?message=你是谁

返回响应
在这里插入图片描述
可以看到用户的think思考过程考虑了在系统中设置的模版提示词,用户叫做张三同时16岁

当然也可以在user内设置伪提示词,但更好在system设置提示词优先级更高

提示词最佳设置经验

1,清晰化表达,描述足够清楚

  • 补充必要背景信息:身份、场景、用途、已有内容,避免AI无端联想
  • 避免“或许、可能”等模糊修饰语

2,任务描述越清楚,AI执行越具体

  • 模糊:写一篇去北京的旅游攻略
  • 清晰:完成一篇去北京的旅游攻略,要求覆盖北京最著名最好玩的景点,同时注意避开人流量最高峰的景点,错峰出行,在北京的计划旅游10天

3,格式清晰(结构化)
可以通过markdown模式,确定一二三级标题、列表之类的,易于模型理解和推理
公示:【角色设定】+【具体任务(技能)】+【限制条件(约束)】+【参考示例】

# 角色
你是一位专业的北京旅游导游
##技能  
### 技能一:理解客户需求  
- 询问了解客户的旅行偏好,包括但不限于目的地、预算、出行日期和交通工具等
- 根据用户的需求,个性化提供旅游攻略 ### 技能二:规划旅游路线  
- 结合客户的旅行偏好,设计一条详细的旅游路线,包括形成安排、交通方式、住宿和餐饮建议
- 提供每个景点的详细介绍,包括历史背景、特色活动、最佳观看时间### 技能三:提供结合当地特色的实用建议  
- 给出旅行中的实用建议,如必备物品清单、安全提示等
- 回答用户关于旅行的任何问题
- 若有不确定的问题,可以调用搜索工具来获取相关信息## 限制
- 只讨论与旅游相关话题

使用以上提示词进行请求
http://localhost:9999/ai/promptChat?message=你是谁
返回
在这里插入图片描述
可以看到AI给出了相关的推理返回

advisor实现日志记录

默认日志拦截SimpleAdvisor

在这里插入图片描述
spring ai官方的advisor有两个实现 一个callAdvisor用于直接返回的AI调用,streamAdvisor用于流失输出的AI调用

在构造器配置类中设置defaultAdvisor

@BeanChatClient chatClient(ChatClient.Builder builder) {return builder.defaultSystem("# 角色\n" +"你是一位专业的北京旅游导游\n" +"##技能  \n" +"### 技能一:理解客户需求  \n" +"- 询问了解客户的旅行偏好,包括但不限于目的地、预算、出行日期和交通工具等\n" +"- 根据用户的需求,个性化提供旅游攻略 \n" +"\n" +"### 技能二:规划旅游路线  \n" +"- 结合客户的旅行偏好,设计一条详细的旅游路线,包括形成安排、交通方式、住宿和餐饮建议\n" +"- 提供每个景点的详细介绍,包括历史背景、特色活动、最佳观看时间\n" +"\n" +"### 技能三:提供结合当地特色的实用建议  \n" +"- 给出旅行中的实用建议,如必备物品清单、安全提示等\n" +"- 回答用户关于旅行的任何问题\n" +"- 若有不确定的问题,可以调用搜索工具来获取相关信息\n" +"\n" +"## 限制\n" +"- 只讨论与旅游相关话题" ).defaultAdvisors(new SimpleLoggerAdvisor()).build();}

其次打开日志,设置下yml debug级别

logging:level:org.springframework.ai.chat.client.advisor: debugcn.kanyu: debug

在这里插入图片描述
可以看到控制态输出了对应的日志信息

request和response

那spirngAI怎样实现的呢
看下advisor的源码

@Override//	AdvisedRequest 所有的AI请求输入都会记录  StreamAroundAdvisorChain 调用链 AOP思想public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {//记录请求日志advisedRequest = before(advisedRequest);//执行相关操作Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);//this::observeAfter 记录返回日志return new MessageAggregator().aggregateAdvisedResponse(advisedResponses, this::observeAfter);}

敏感词拦截

设置SafeGuardAdvisor

 @BeanChatClient chatClient(ChatClient.Builder builder) {return builder.defaultSystem("# 角色\n" +"你是一位专业的北京旅游导游\n" +"##技能  \n" +"### 技能一:理解客户需求  \n" +"- 询问了解客户的旅行偏好,包括但不限于目的地、预算、出行日期和交通工具等\n" +"- 根据用户的需求,个性化提供旅游攻略 \n" +"\n" +"### 技能二:规划旅游路线  \n" +"- 结合客户的旅行偏好,设计一条详细的旅游路线,包括形成安排、交通方式、住宿和餐饮建议\n" +"- 提供每个景点的详细介绍,包括历史背景、特色活动、最佳观看时间\n" +"\n" +"### 技能三:提供结合当地特色的实用建议  \n" +"- 给出旅行中的实用建议,如必备物品清单、安全提示等\n" +"- 回答用户关于旅行的任何问题\n" +"- 若有不确定的问题,可以调用搜索工具来获取相关信息\n" +"\n" +"## 限制\n" +"- 只讨论与旅游相关话题" ).defaultAdvisors(new SimpleLoggerAdvisor(),new SafeGuardAdvisor(List.of("张三"))).build();}

请求
http://localhost:9999/ai/promptChat?message=张三在哪里
在这里插入图片描述
此时返回了I’m unable to respond to that due to sensitive content. Could we rephrase or discuss something else?

看下其源码实现

public class SafeGuardAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {private static final String DEFAULT_FAILURE_RESPONSE = "I'm unable to respond to that due to sensitive content. Could we rephrase or discuss something else?";private static final int DEFAULT_ORDER = 0;private final String failureResponse;private final List<String> sensitiveWords;private final int order;public SafeGuardAdvisor(List<String> sensitiveWords) {this(sensitiveWords, DEFAULT_FAILURE_RESPONSE, DEFAULT_ORDER);}public SafeGuardAdvisor(List<String> sensitiveWords, String failureResponse, int order) {Assert.notNull(sensitiveWords, "Sensitive words must not be null!");Assert.notNull(failureResponse, "Failure response must not be null!");this.sensitiveWords = sensitiveWords;this.failureResponse = failureResponse;this.order = order;}@Overridepublic AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {if (!CollectionUtils.isEmpty(this.sensitiveWords)&& this.sensitiveWords.stream().anyMatch(w -> advisedRequest.userText().contains(w))) {return createFailureResponse(advisedRequest);}return chain.nextAroundCall(advisedRequest);}private AdvisedResponse createFailureResponse(AdvisedRequest advisedRequest) {return new AdvisedResponse(ChatResponse.builder().generations(List.of(new Generation(new AssistantMessage(this.failureResponse)))).build(), advisedRequest.adviseContext());}

其中aroundCall会判断advisedRequest.userText().contains(w)
用户的输入是否包括敏感词
包括则返回createFailureResponse

自定义拦截器

在这里插入图片描述
自定义advisor

package cn.kanyu.springai.config;import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.rag.Query;
import reactor.core.publisher.Flux;import java.util.Map;public class ReReadingAdvisor implements BaseAdvisor {private static final  String DEFAULT_USER_TEXT_ADVISE= """{re2_input_query}Read the question again:{re2_input_query}""";@Overridepublic AdvisedRequest before(AdvisedRequest request) {String contents = request.userText();Query originalQuery = Query.builder().text(new PromptTemplate(DEFAULT_USER_TEXT_ADVISE, request.userParams()).render(Map.of("re2_input_query",contents))).history(request.messages()).build();AdvisedRequest request1 = AdvisedRequest.from(request).userText(originalQuery.text()).build();return request1;}@Overridepublic AdvisedResponse after(AdvisedResponse advisedResponse) {return advisedResponse;}/*存在多个拦截器的时候定义拦截器的优先级 数字越小优先级越高先执行*/@Overridepublic int getOrder() {return 0;}
}

重新构造chatClient

@BeanChatClient chatClient(ChatClient.Builder builder) {return builder.defaultSystem(   "你是一位旅游专家" )
//                .defaultAdvisors(new SimpleLoggerAdvisor(),new SafeGuardAdvisor(List.of("张三"))).defaultAdvisors(new SimpleLoggerAdvisor(),new ReReadingAdvisor()).build();}

在这里插入图片描述
可以看到日志输出

http://www.dtcms.com/a/313494.html

相关文章:

  • greenfoot主要api
  • 广东省省考备考(第六十五天8.3)——判断推理:图形推理(数量规律题目总结)
  • 使用C++实现日志(3)
  • sqli-labs:Less-23关卡详细解析
  • C的数据类型与变量
  • 2025 Java开发真实试题-阿里面试题分析
  • C语言与数据结构:从基础到实战
  • 机器学习——过采样(OverSampling),解决类别不平衡问题,案例:逻辑回归 信用卡欺诈检测
  • 前端工程化:Vue3(一)
  • 2025年EAAI SCI1区TOP,森林救援调度与路径规划:一种新型蚁群优化算法应用,深度解析+性能实测
  • 智能化门禁常见问题处理思路
  • Linux mount挂载选项详解(重点关注nosuid)
  • 使用Perl和库WWW::Curl的爬虫程序!
  • [spring-cloud: 服务注册]-源码解析
  • Spring Boot AOP 优雅实现异常重试机制
  • 多线程异步日志系统与实现及 TCP/IP C/S 模型
  • IO流-字节流-FileOutputStream
  • day50预训练模型 CBAM注意力
  • Effective C++ 条款20:宁以pass-by-reference-to-const替换pass-by-value
  • LeetCode 2122.还原原数组
  • centos7安装桌面客户软件并远程连接
  • 学习笔记《区块链技术与应用》第五天 分叉
  • Matlab 高斯牛顿法拟合曲线
  • 力扣-200.岛屿数量
  • 01数据结构-二叉搜索树
  • PAT 甲级题目讲解:1012《The Best Rank》
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-55,(知识点:STM32,外设及其特点)
  • 力扣-124.二叉树中的最大路径和
  • LLM调研
  • 计算用户日活:从数据设计到可视化的全流程(高频场景题)