当前位置: 首页 > 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.xdnf.cn/news/1239751.html

相关文章:

  • io_destroy系统调用及示例
  • 基于 LangChain + 通义千问 + bge-large 中文 Embedding 搭建一个RAG问答示例
  • FastAPI入门:安全性
  • 第12届蓝桥杯Scratch图形化【省赛】初级组 2021年4月24日
  • MySQL学习之MVCC多版本并发控制
  • python常用数据类型
  • 13.Redis 的级联复制
  • 03.一键编译安装Redis脚本
  • sqli-labs:Less-23关卡详细解析
  • 【运维基础】Linux 硬盘分区管理
  • 数据集相关类代码回顾理解 | StratifiedShuffleSplit\transforms.ToTensor\Counter
  • Corrosion2靶机练习笔记
  • 选择排序原理与C语言实现详解
  • 第15届蓝桥杯Scratch图形化国赛初/中级组2024年9月7日真题
  • 【LeetCode刷题指南】--对称二叉树,另一颗树的子树
  • 【量化交易】日内交易有效特征因子
  • Socket编程——TCP协议
  • 智慧社区(六)——社区居民人脸识别功能实现详解:从腾讯 API 集成到模拟验证
  • CMake 命令行参数完全指南(2)
  • C++入门自学Day5-- C/C++内存管理(续)
  • 控制建模matlab练习08:根轨迹
  • 【图像处理基石】如何使用deepseek进行图像质量的分析?
  • pycharm上如何添加conda环境
  • 当Windows远程桌面出现“身份验证错误。要求的函数不受支持”的问题
  • [硬件电路-150]:数字电路 - 数字电路与模拟电路的异同
  • Ollama模型库模型下载慢完美解决(全平台)
  • 算法讲解--最大连续1的个数
  • RSA 解密逻辑
  • 【从零开始学习Redis】初识Redis
  • 第13章 文件输入/输出