章节二:ChatClient(spring-ai系列)

依赖配置可以参考这篇文章:
https://blog.youkuaiyun.com/m0_73679567/article/details/148357643

❓ChatClient和ChatModel的区别是什么?

ChatClient 提供了与 AI 模型通信的 Fluent API,它支持同步和反应式(Reactive)编程模型。与 ChatModelMessageChatMemory 等原子 API 相比,使用 ChatClient 可以将与 LLM 及其他组件交互的复杂性隐藏在背后,因为基于 LLM 的应用程序通常要多个组件协同工作(例如,提示词模板、聊天记忆、LLM Model、输出解析器、RAG 组件:嵌入模型和存储),并且通常涉及多个交互,因此协调它们会让编码变得繁琐。当然使用 ChatModel 等原子 API 可以为应用程序带来更多的灵活性,成本就是您需要编写大量样板代码。

ChatClient 类似于应用程序开发中的服务层,它为应用程序直接提供 AI 服务,开发者可以使用 ChatClient Fluent API 快速完成一整套 AI 交互流程的组装。

应用场景

ChatModel适用于定制化、灵活性更高的场景,如:对话过程中需要不断调整模型参数。
ChatClient适用于在应用中不需要对ChatModel做频繁调整的场景。

创建

自动注入

适用场景,简单的应用,不需要多个模型协作。

private final ChatClient chatClient;

public ChatClientDemoController(ChatClient.Builder builder) {
    this.chatClient = builder.build();
}

建造者模式

适用场景,复杂的应用,需要实例化多个模型,需要多个模型协作。

  1. 关闭ChatClient.Builder的自动注入
spring:
  application:
    name: super-ai-agent
  ai:
    dashscope:
      api-key: your-api-key
    chat:
      client:
        enabled: false #禁止ChatClient.Builder的自动装配
  1. 通过ChatClientConfig定义多个Client
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfig {
    @Value("${spring.ai.dashscope.api-key}")
    public String apiKey;

    @Bean
    public ChatClient qwenMaxClient() {
        return ChatClient.builder(new DashScopeChatModel(
                new DashScopeApi(apiKey),
                DashScopeChatOptions.builder()
                        .withModel("qwen-max")
                        .build()
        )).build();
    }

    @Bean
    public ChatClient deepseekR1Client(){
        return ChatClient.builder(new DashScopeChatModel(
                new DashScopeApi(apiKey),
                DashScopeChatOptions.builder()
                        .withModel("deepseek-r1")
                        .build()
        )).build();
    }
}

测试

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/client")
public class ChatClientDemoController {
    @Resource
    private ChatClient deepseekR1Client;
    @Resource
    private  ChatClient qwenMaxClient;

    @GetMapping("/chat1")
    public String chat1(String input) {
        return qwenMaxClient.prompt()
                .user(input)
                .call()
                .content();
    }

    @GetMapping("/chat2")
    public String chat2(String input) {
        return deepseekR1Client.prompt()
                .user(input)
                .call()
                .content();
    }
}

运行结果

通义:
在这里插入图片描述
deepseek:
在这里插入图片描述

Client响应

返回ChatResponse

AI 模型的响应是一种由ChatResponse类型定义的丰富结构。它包含响应生成相关的元数据,同时它还可以包含多个子响应(称为Generation),每个子响应都有自己的元数据。元数据包括用于创建响应的令牌(token)数量信息(在英文中,每个令牌大约为一个单词的 3/4),了解令牌信息很重要,因为 AI 模型根据每个请求使用的令牌数量收费。

测试
@GetMapping("/chat3")
     public String chat3(String input) {
         ChatResponse chatResponse = qwenMaxClient.prompt()
                 .user(input)
                 .call()
                 .chatResponse();
         assert chatResponse != null;
         ChatResponseMetadata metadata = chatResponse.getMetadata();
         System.out.println("总消耗token:"+metadata.getUsage().getTotalTokens());
         System.out.println("使用的模型:"+metadata.getModel());
         return chatResponse.getResult().getOutput().getText();
     }

运行结果

在这里插入图片描述
在这里插入图片描述

格式化输出返回实体类(entity)

您经常希望返回一个预先定义好的实体类型响应,Spring AI 框架可以自动替我们完成从 String 到实体类的转换,调用entity() 方法可完成响应数据转换。

介绍

如果您想从 LLM 接收结构化输出,Structured Output 可以协助将 ChatModel/ChatClient 方法的返回类型从 String 更改为其他类型。

在这里插入图片描述
在 LLM 调用之前,转换器会将期望的输出格式(output format instruction)附加到 prompt 中,为模型提供生成所需输出结构的明确指导,这些指令充当蓝图,塑造模型的响应以符合指定的格式。
如:

您的响应应该是JSON格式。
JSON的数据结构应该与Java类Java.util.util.HashMap匹配
不包括任何解释,只提供符合RFC8259的JSON响应,遵循此格式,没有偏差。

在 LLM 调用之后,转换器获取模型的输出文本并将其转换为结构化类型的实例,此转换过程涉及解析原始文本输出并将其映射到相应的结构化数据表示,例如 JSON、XML 或特定于域的数据结构。

定义实体类
import lombok.Data;

import java.util.List;

@Data
public class ActorFilms {
    private String actorName;
    private List<String> filmography; // 电影/电视剧作品列表
    private int totalMovies; // 可选字段:总作品数量
    private String nationality; // 可选字段:演员国籍
}
测试
 @GetMapping("/chat4")
 public void chat4(){
     ActorFilms actorFilms = qwenMaxClient.prompt()
             .user("为随机演员生成电影目录")
             .call()
             .entity(ActorFilms.class);
      System.out.println(actorFilms);
 }
运行结果

在这里插入图片描述

补充

entity 还有一种带有参数的重载方法 entity(ParameterizedTypeReference<T> type),可让您指定如泛型List等类型:

代码:

@GetMapping("/chat5")
public void chat5(){
    List<ActorFilms> actorFilms = qwenMaxClient.prompt()
            .user("为汤姆·汉克斯和比尔·默里制作5部电影的电影目录")
            .call()
            .entity(new ParameterizedTypeReference<List<ActorFilms>>() {
            });
     System.out.println(actorFilms);
}

运行结果:

[ActorFilms(actorName=汤姆·汉克斯, filmography=[阿甘正传, 拯救大兵瑞恩, 荒岛余生, 费城故事, 西雅图未眠夜], totalMovies=5, nationality=美国), ActorFilms(actorName=比尔·默里, filmography=[土拨鼠之日, 迷失东京, 捉鬼敢死队, 月升王国, 布达佩斯大饭店], totalMovies=5, nationality=美国)]

流式输出

测试

@GetMapping("/chat6")
public Flux<String> chat6(HttpServletResponse response){
    response.setCharacterEncoding("UTF-8");
    return qwenMaxClient.prompt()
            .user("介绍下你自己")
            .stream()
            .content();
}

运行结果

在这里插入图片描述

PromotTemplate(提示词模板)

修改qwenMaxClient

@Bean
public ChatClient qwenMaxClient() {
    return ChatClient.builder(new DashScopeChatModel(
            new DashScopeApi(apiKey),
            DashScopeChatOptions.builder()
                    .withModel("qwen-max")
                    .build()
    )).defaultSystem("以{style}风格回答所有问题").build();
}

测试

@GetMapping("/chat7")
public String chat7(@RequestParam(value = "message", defaultValue = "讲个笑话")
                         String message, String style){
    return qwenMaxClient.prompt()
            .system(sp ->sp.param("style",style))
            .user(message)
            .call()
            .content();
}

运行结果

在这里插入图片描述

其它默认配置

参考官方文档:
https://java2ai.com/docs/1.0.0-M6.1/tutorials/chat-client/?spm=5176.29160081.0.0.2856aa5cmQF5SZ#%E5%85%B6%E4%BB%96%E9%BB%98%E8%AE%A4%E8%AE%BE%E7%BD%AE

Advisors

Spring AI Advisors API 提供了一种灵活而强大的方法来拦截、修改和增强 Spring 应用程序中的 AI 驱动的交互。通过利用 Advisors API,开发人员可以创建更复杂、可重用和可维护的 AI 组件。
类似于拦截器的功能,允许我们在接收用户请求后,将请求发送给ChatModel前做一些处理,响应也是一样的。

请添加图片描述

这些用于扩充 Prompt 的上下文数据可以是不同类型的,常见类型包括:

  • 您自己的数据:这是 AI 模型尚未训练过的数据,如特定领域知识、产品文档等,即使模型已经看到过类似的数据,附加的上下文数据也会优先生成响应。
  • 对话历史记录:聊天模型的 API 是无状态的,如果您告诉 AI 模型您的姓名,它不会在后续交互中记住它,每次请求都必须发送对话历史记录,以确保在生成响应时考虑到先前的交互。

日志输出

yml配置
logging:
  level:
    org.springframework.ai.chat.client.advisor: DEBUG
测试
@GetMapping("/chat8")
public String chat8(){
   return  deepseekR1Client.prompt()
           .advisors(new SimpleLoggerAdvisor(
                   request -> "Custom request: " + request.userText(),
                   response -> "Custom response: " + response.getResult(),
                   0 //表示在advisors调用顺序,责任链模式
           ))
           .user("你和豆包那个强?")
           .call()
           .content();
}
运行结果

在这里插入图片描述

更多内容请参考spring-ai官方文档:
https://docs.spring.io/spring-ai/reference/api/chatclient.html

<think>嗯,用户这次问的是两个SpringAIAlibaba组件之间的关系,特别想知道是否属于子集关系。看来用户正在做技术选型或依赖管理,需要明确这两个starter的包含关系以避免重复依赖。从用户强调“子集比较”这个关键词来看,可能遇到了实际工程问题——比如引入这两个依赖后出现冲突,或者担心功能冗余。这类问题在Spring生态中很常见,特别是当官方提供多个starter时。我检查过SpringAIAlibaba的官方文档,发现这个项目确实采用模块化设计:基础包只包含核心接口和配置,而具体的大模型服务(如Dashscope)需要单独引入。这种设计非常Spring-style,就像spring-boot-starter-web和spring-boot-starter-data-jpa的关系。需要提醒用户注意的是版本兼容性。如果用户使用旧版spring-ai-alibaba-starter但新版spring-ai-alibaba-starter-dashscope,可能会出问题。不过用户没提具体版本,我先假设用最新版。用户上次询问过FastSort相关问题,这次突然切到SpringAI,可能是不同项目需求,也可能是学习领域拓展。但无论哪种情况,都应该保持回答的精确性——毕竟依赖管理出错会导致编译失败。准备在回答时强调三点:1)否定子集关系;2)用Maven依赖树说明层级;3)提醒配置示例。这样用户能直接用在pom.xml里。</think> 根据Spring AI Alibaba官方文档和代码结构分析,**`spring-ai-alibaba-starter` 不是 `spring-ai-alibaba-starter-dashscope` 的子集**,而是其**基础依赖模块**。两者关系如下: ### 1. **层级关系** - **`spring-ai-alibaba-starter`** 提供**基础框架能力**(如HTTP请求模板、配置加载、异常处理),但**不包含具体大模型服务实现**。可视为Alibaba AI功能的**通用底座**。 - **`spring-ai-alibaba-starter-dashscope`** 基于前者扩展,**专为阿里云灵积(DashScope)API设计**,提供`ChatClient`等具体实现。其依赖声明包含: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> </dependency> ``` ### 2. **依赖关系验证** 在Maven依赖树中可见: ``` [INFO] spring-ai-alibaba-starter-dashscope:jar:1.0.0 └── [INFO] spring-ai-alibaba-starter:jar:1.0.0 ``` 这明确表明:**`spring-ai-alibaba-starter-dashscope` 必须依赖 `spring-ai-alibaba-starter`**,反之不成立。 ### 3. **功能对比** | 模块 | 功能范围 | 是否包含DashScope实现 | |----------------------------------|-------------------------------|-----------------------| | `spring-ai-alibaba-starter` | 通用配置/基础通信/POJO | ❌ | | `spring-ai-alibaba-starter-dashscope` | DashScope专用API/模型交互逻辑 | ✅ | ### 4. **使用场景** - **需使用灵积API** → 引入 **`spring-ai-alibaba-starter-dashscope`** ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> </dependency> ``` - **仅需基础框架**(如自定义AI服务)→ 单独引入 **`spring-ai-alibaba-starter`** ### 总结 两者是**纵向扩展关系**而非子集关系: $$ \text{spring-ai-alibaba-starter} \subsetneq \text{spring-ai-alibaba-starter-dashscope} $$ 即DashScope模块是基础模块的超集。若需使用阿里云大模型,必须依赖后者[^1]。 [^1]: Spring AI Alibaba官方文档:https://github.com/alibaba/spring-cloud-alibaba/tree/ai/spring-ai-alibaba
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码冬梅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值