三、ChatClient&Chat Model简化与AI模型的交互

1.Chat Model

对话模型是一种利用人工智能技术,能够生成类似人类对话响应的工具。通过向预训练语言模型(如 GPT 等)发送提示词或部分对话内容,模型依据自身训练数据及对自然语言模式的理解,生成对话的延续或完整回复,并返回给应用程序。应用程序可以将其呈现给用户或用于进一步处理。

Spring AI Chat Model API 设计目标为简单且可移植的接口,用于与各种人工智能模型进行交互,使开发人员能够在不同模型之间进行切换,且只需进行最少的代码更改。这种设计符合 Spring 的模块化和可互换性理念。同时,借助 Prompt(用于输入封装)和 ChatResponse(用于输出处理)等辅助类,Chat Model API 统一了与人工智能模型的通信。

2.ChatClient

ChatClient``对ChatModel与 StreamingChatModel进行了封装,采用了 Fluent API 的风格,可以进行链式调用。相比如ChatModel 原子类API,ChatClient屏蔽了与AI大模型的交互的复杂性,但是ChatModel API更灵活,尤其当系统中要接入多个大模型并且有个性化配置时。

Fluent API 是一种编程风格的 API 设计,其主要特点是通过方法链的方式来实现操作的连贯性和可读性

ChatModel类似于JDK中的核心JDBC库。ChatClient可以比作JdbcClient,它构建在ChatModel 之上,并通过 Advisor 提供更高级的功能,提供上下文记忆,上下文提示词扩充等功能。

3.接口详解

上图所示,ChatModel接口继承了StreamingChatModel接口,所以ChatModel具备流式处理响应的能力。同时ChatClient接口是对话模型更上高一层次的抽象。在上一章介绍过自动注入ChatClient无需关注ChatModelSpring AI会自动加载所配置的AI模型。当我们需要集成多个AI模型时,这时就需要使用ChatModel接口,如下代码所示在同一个项目中创建2个不同对话模型。

java 复制代码
@GetMapping("/ai/zhipu")
    public ChatResponse zhipu(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {

        var zhiPuAiApi = new ZhiPuAiApi(System.getenv("ZHIPU_AI_API_KEY"));

        var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, ZhiPuAiChatOptions.builder()
                .model(ZhiPuAiApi.ChatModel.GLM_3_Turbo.getValue())
                .temperature(0.4)
                .maxTokens(200)
                .build());

        ChatResponse response = chatModel.call(
                new Prompt("Generate the names of 5 famous pirates."));
        return response;
    }

    @GetMapping("/ai/openapi")
    public ChatResponse openapi(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        var openAiApi = new OpenAiApi(System.getenv("OPENAI_API_KEY"));
        var openAiChatOptions = OpenAiChatOptions.builder()
                .model("gpt-3.5-turbo")
                .temperature(0.4)
                .maxTokens(200)
                .build();
        var chatModel = new OpenAiChatModel(openAiApi, openAiChatOptions);

        ChatResponse response = chatModel.call(
                new Prompt("Generate the names of 5 famous pirates."));
        return response;

    }


 // 流式响应
  Flux<ChatResponse> streamResponse = chatModel.stream(
    new Prompt("Generate the names of 5 famous pirates."));

3.1ChatClient接口

3.1.1 创建ChatClient方式

自动配置

Spring AI 提供了 Spring Boot 自动配置,创建一个 ChatClient.Builder Bean,以便注入到使用的类中,快速入门中使用的是此方式。

java 复制代码
@RestController
class AiController {

    private final ChatClient chatClient;

    public AiController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/ai")
    String generation(String userInput) {
        return this.chatClient.prompt()
            .user(userInput)
            .call()
            .content();
    }
}

代码创建

可以通过设置属性 spring.ai.chat.client.enabled=false 来禁用 ChatClient.Builder 的自动配置。如果多个对话模型一起使用,这很有用。然后,为每个你需要的 ChatModel 以编程方式创建一个 ChatClient.Builder 实例。

java 复制代码
/**
 * Spring AI supports Spring Boot 3.2.x and 3.3.x
 * JDK 17
 * zhipuai
 */
@RestController
public class AiController {
    
    private final ChatClient chatClient;
    //注入ZhiPuAi ChatModel
    public AiController(ZhiPuAiChatModel zhiPuAiChatModel) {
        //编码创建ChatClient对象
        this.chatClient= ChatClient.create(zhiPuAiChatModel);

    }
    @GetMapping("/ai")
    String generation(String msg) {
        //用户输入的信息提交给大模型,使用的是ChatClient与大模型交互。
        return this.chatClient.prompt()
                .user(msg)
                .call()
                .content();
    }
}
3.1.2 核心API

ChatClient 对象提供了三个prompt()重载方法,启动Fluent API。三个方法如下:

1.prompt(): 不带参数的方法可以构建用户、系统以及提示词的其他部分。

java 复制代码
chatClient.prompt()
                .user(("你给我说一个笑话")
                .call()
                .content();

2.prompt(Prompt prompt): 该方法接受一个Prompt参数,创建一个带Prompt的Fluent API。

scss 复制代码
chatClient.prompt(new Prompt("你给我说一个笑话"))
                .call()
                .content();

3.prompt(String content): 该方法和之前的重载方法类似。它接收用户输入的文本内容。

java 复制代码
chatClient.prompt("你给我说一个笑话")
                .call()
                .content();
3.1.3 响应结果

ChatClient API 提供了3种使用FluentAPI 格式化AI模型响应的方法。

  • 返回ChatResponse
java 复制代码
    //同步返回ChatResponse
    @GetMapping("/chatResponse")
    ChatResponse chatResponse() {
        //用户输入的信息提交给大模型,使用的是ChatClient与大模型交互。
        return this.chatClient.prompt()
                .user("给我讲一个笑话")
                .call()
                .chatResponse();
    }
    
    //流式返回ChatResponse
    @GetMapping("/fluxChatResponse")
    Flux<ChatResponse> fluxChatResponse() {
        Flux<ChatResponse> output = chatClient.prompt()
                .user("给我讲一个笑话")
                .stream()
                .chatResponse();
        return output;
    }
  • 返回实体对象(Entity)
java 复制代码
import java.util.List;
record ActorFilms(String actor, List<String> movies) {}
  //同步返回实体
  @GetMapping("/entity")
    ActorFilms entity() {
        ActorFilms actorFilms = chatClient.prompt()
                .user("随机生成周星驰的5部电影作品")
                .call()
                .entity(ActorFilms.class);
        return actorFilms;
    }
  • 返回String
java 复制代码
   @GetMapping("/fluxString")
    Flux<String> fluxString() {
        Flux<String> output = chatClient.prompt()
                .user("给我讲一个笑话")
                .stream()
                .content();
        return output;
    }
java 复制代码
  //同步返回字符串
 @GetMapping("/ai")
        String generation(String msg) {
            return this.chatClient.prompt()
                    .user(msg)
                    .call()
                    .content();//直接返回字符串
        }

大型语言模型(LLM)生成结构化输出的能力对于下游系统使用大模型能力非常重要。开发人员希望快速将人工智能模型的结果转换为可以传递给其他应用程序函数和方法的数据类型,例如 JSONXMLJava 类。后面的内容结构化输出章节会详细的介绍使用。

3.1.3 默认设置

ChatClient 通过设置默认值,在调用时只需要指定用户文本,无需在运行时代码中为每个请求设置系统文本。

  • 默认设置

    下面的示例中,配置系统文本"你是一个会讲笑话的智能助手,可以讲不同风格的笑话"。为了避免在运行时代码中重复设置系统文本,在@Configuration配置类中创建一个ChatClient实例。

java 复制代码
@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是一个会讲笑话的智能助手,用周星驰幽默的风格讲笑话。")
                .build();
    }
}
java 复制代码
@RestController
class AIController {
    //直接构造器注入,在Config已经创建了对象
    private final ChatClient chatClient;

    AIController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/ai/defaults")
    public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message) {
        return Map.of("completion", this.chatClient.prompt().user(message).call().content());
    }
}
shell 复制代码
❯ curl http://localhost:8080/ai/defaults?message=给我讲个笑话
{
  "completion": "有一天,唐僧师徒四人行至火焰山,见一老翁躺在地上,气息奄奄。唐僧说:"悟空,快去救救这位老翁。"悟空便将老翁扶起,问道:"老翁,你怎么了?"老翁答道:"唉,我被这火焰山烤得受不了了,想喝口水。"悟空便去取水,途中遇到一只乌鸦,乌鸦对悟空说:"悟空,你为何那么辛苦?我有一个办法,能让老翁马上凉爽。"悟空好奇地问:"哦?什么办法?"乌鸦说:"你把老翁放在我背上,我带你飞到北极,那里凉快极了!"悟空心想:"这办法不错,但我不能让唐僧担心。"于是,悟空带着乌鸦回到原地,对唐僧说:"师傅,我有个办法能让老翁凉爽,但需要你配合。"唐僧问:"什么办法?"悟空答:"你把老翁放在我背上,我带你飞到北极。"唐僧疑惑地问:"那你怎么办?"悟空笑着说:"放心吧,我会在乌鸦的背上飞。"\n\n这个笑话有点周星驰式的幽默,希望您喜欢!"
}
  • 带参数的系统设置
java 复制代码
@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是一个会讲笑话的智能助手,用{style}幽默的风格讲笑话。")
                .build();
    }
}
java 复制代码
@GetMapping("/ai/defaultsParameters")
public Map<String, String> defaultsParameters(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message,
                                              @RequestParam(value = "style") String style) {
    return Map.of("completion",
            this.chatClient.prompt()
                    .system(sp -> sp.param("style", style))
                    .user(message)
                    .call()
                    .content());
}

这个例子展示了,动态填充系统默认参数的方式,笑话的风格用户动态传入。

shell 复制代码
❯ curl http://localhost:8080/ai/defaultsParameters?message=给我讲个笑话&style=赵本山
{
  "completion": "
那好,我给你讲一个有关于我和老张头的笑话。\n\n这天,我和老张头去赶集,正巧碰见一个卖耗子药的。卖耗子药的哥们儿在那儿吆喝:"我这耗子药,一吃就死,绝无仅有,无效退款!"老张头听了,好奇心起,就买了一包。\n\n回家的路上,老张头突然发现耗子药不见了,吓得他出了一身冷汗,连忙问我:"哎呀,本山啊,我这耗子药咋不见了?这可咋整啊?"\n\n我一看他紧张的样子,就逗他:"老张头,你是不是把耗子药揣裤兜里了?说不定是你自己忘带了。"\n\n老张头一拍大腿:"哎呦,还真是!我咋就忘了这茬呢!"说完,他赶紧掏出耗子药,小心翼翼地揣回裤兜里。\n\n到了家,老张头把耗子药放在老鼠出没的地方。没过多久,一只老鼠跑过来,闻了闻耗子药,然后夹着尾巴跑了。老张头一看,高兴地跟我说:"本山,你看,这耗子药真灵,老鼠一闻就跑了!"\n\n我笑了笑,说:"老张头,那是因为老鼠知道你这人太实在,它害怕吃了你的耗子药,回头还得给你送回来,说'这耗子药味道不好,退款吧!
"
}

3.2 ChatModel接口

ChatModel接口定义如下:

java 复制代码
  public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {

	default String call(String message) {
		...
	}
	default String call(Message... messages) {
        ...
	}
	@Override
	ChatResponse call(Prompt prompt);
  }

call(String message) 创建一个UserMessage发送给AI模型,执行调用返回String

call(Message... messages) 将多个Message发送给AI模型,执行调用返回String

call(Prompt prompt)发送一个Prompt提示词给AI模型,执行调用返回返回ChatResponse

Message 接口有多种实现,对应 AI 模型可以处理的消息类别,后面章节内容详细介绍。

3.3 StreamingChatModel接口

StreamingChatModel接口定义如下:

java 复制代码
   public interface StreamingChatModel extends StreamingModel<Prompt, ChatResponse> {

	default Flux<String> stream(String message) {
       ....
	}

	default Flux<String> stream(Message... messages) {
	   ....
	}

	@Override
	Flux<ChatResponse> stream(Prompt prompt);

   }

stream(String message) 创建一个Message发送给AI模型,执行调用返回Flux String

stream(Message... messages) 将多个Message发送给AI模型,执行调用返回Flux String

stream(Prompt prompt)发送一个Prompt提示词给AI模型,执行调用返回返回Flux ChatResponse

stream() 方法采用与 ChatModel 类似的 StringPrompt 参数,但它使用反应式 Flux API 传输响应(流式)。

Spring中实现非阻塞的响应式编程模型使用的是Reactor框架,Reactor 提供了两种主要的异步序列APIFluxMonoFlux用于处理0到N个元素的异步序列,而Mono则用于处理0到1个元素的异步序列。

3.4 ChatOptions接口

ChatOptions 接口继承自 ModelOptions,定义了一些可传递给 AI 模型的选项,如模型名称、频率、最大令牌数等。每个特定模型的实现可以有自己的选项,启动时可设置默认配置,运行时可通过 Prompt 请求覆盖。

接口定义如下:

scss 复制代码
public interface ChatOptions extends ModelOptions {

	String getModel();
	Float getFrequencyPenalty();
	Integer getMaxTokens();
	Float getPresencePenalty();
	List<String> getStopSequences();
	Float getTemperature();
	Integer getTopK();
	Float getTopP();
	ChatOptions copy();

}

对于每个特定模型的 ChatModel/StreamingChatModel 实现都可以有自己的选项,这些选项可以传递给 AI 模型。例如,OpenAI Chat 模型有自己的选项,如 logitBiasseeduser

scss 复制代码
     var openAiChatOptions = OpenAiChatOptions.builder()
                .seed(1024).
                 user("user")
                .logitBias(new HashMap<>())
                .build();

4.总结

本章主要围绕 Spring AI 中的对话模型相关组件展开介绍,涵盖 Chat ModelChatClient 以及多个关联接口,详细阐述了它们的功能、设计目标、使用方式及相互关系。Spring AI 通过这些精心设计的对话模型组件及接口,为开发人员提供了一套功能丰富、灵活易用且高度模块化的工具集,助力在不同场景下高效开发与人工智能模型交互的应用程序,满足多样化的业务需求。

相关推荐
编程小筑22 分钟前
R语言的数据库编程
开发语言·后端·golang
大熊程序猿40 分钟前
golang 环境变量配置
开发语言·后端·golang
拾忆,想起1 小时前
深入浅出负载均衡:理解其原理并选择最适合你的实现方式
分布式·后端·微服务·负载均衡
uzong2 小时前
大模型给我的开发提效入门篇
人工智能·后端
uzong2 小时前
在mac上搭建一个安卓开发环境
后端
uzong2 小时前
新公司在使用的 Hibernate Validator 框架
java·后端
dgiij3 小时前
node.js的进程保活
后端·node.js·bash
蒜蓉大猩猩3 小时前
Node.js - Express框架
后端·架构·node.js·express
蒜蓉大猩猩4 小时前
Node.js --- 详解MongoDB与Mongoose
数据库·后端·mongodb·node.js
昔我往昔4 小时前
Spring Boot中如何处理跨域请求(CORS)
java·spring boot·后端