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 {
    @Bean
    ChatClient 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

@Bean
    ChatClient 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: debug
    cn.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

 @Bean
    ChatClient 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;
	}

@Override
	public 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}
            """;
    @Override
    public 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;
    }

    @Override
    public AdvisedResponse after(AdvisedResponse advisedResponse) {
        return advisedResponse;
    }

    /*存在多个拦截器的时候定义拦截器的优先级 数字越小优先级越高先执行*/
    @Override
    public int getOrder() {
        return 0;
    }
}

重新构造chatClient

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

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐