Spring AI实战之二:Chat API基础知识大串讲(重要)

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

Spring AI实战全系列链接

  1. Spring AI实战之一:快速体验(OpenAI)
  2. Spring AI实战之二:Chat API基础知识大串讲(重要)
  3. SpringAI+Ollama三部曲之一:极速体验
  4. SpringAI+Ollama三部曲之二:细说开发

本篇概览

  • 如果说前文是最简单的介绍Spring AI,满足Java程序员的好奇心,那么本篇就是正儿八经的基础课了:梳理Spring AI框架中的Chat核心API、类、接口,SpringAI的能力就是依靠它们释放出来的
  • 本篇的目标:学习SpringAI库的最基本的类、接口、API,并了解它们的具体用途

用一个问题开篇

  • 首先回答一个问题,为什么标题是Chat API基础知识,而不是Spring AI基础知识?
  • 因为Spring AI内部由很多部分组成,Chat只是其中之一,或者说大模型提供的能力有很多,聊天只是其中一部分,SpringAI提供的能力如下所示
    在这里插入图片描述
  • 所以,Spring AI的内容很多,Chat只是其中一部分,但是这部分非常重要且基础,适合用来入门
  • 接下来开始正式学习吧,东西不多,总结下来就是:六个概念+三个层次,掌握了它们,各种大模型都能轻松驾驭了

关于Chat API的六个核心概念和三个层次

  • 从业务逻辑上看,Chat API涉及到六个概念,分为三类,如下图所示
    在这里插入图片描述
  1. client:这个好理解,代表各模型的客户端,负责请求和响应的
  2. prompt:理解成请求的最外层封装,里面有message和option
  3. message:这个好理解了,发送到大模型的内容,另外还包含了一些属性在里面,以及消息类型
  4. option:相当于参数、控制项,例如本次对话的temperature(值越小,大模型回答越严谨,值越大,大模型回答越有创造性)
  5. response:响应对象,里面封装了大模型返回的信息,主要是generation
  6. geenration:这里面是具体的返回内容
  • 再来看三个层次,前面我们知道SpringAI支持大模型的多种能力,聊天只是其中一种,因此就有一个代表最顶层的抽象层,与大模型有关的各种能力,都在此有个定义,然后是代表各种能力的抽象层,如聊天、图片、嵌入式处理等,最后是每一种能力在各类具体大模型上的实现,如下图所示
    在这里插入图片描述
  • 到现在为止咱们还没有看一行代码一个API,但是从理论上对Chat API的定位、关系已经基本了解了,是时候结合代码来看了

官方图

  • 下图来自官方文档,结合前面的分析来看一下,后面有导读
    在这里插入图片描述
  • 先看最下面橙色这层,中间是client,这里有两种,ModelClient代表了常规的请求响应,StreamingModelClient代表了流式响应(数据并非一次性传输,而是建立链接后源源不断的输出)
  • client的左侧是request,里面包含了option,至于prompt,那是Chat的概念,所以不会出现在橙色这一层
  • client右侧是response,同样只有抽象的ResponseMeta和ResultMetaData,generation是Chat的概念,不会在橙色这一层出现
  • 再往上看,绿色的就是功能抽象层了,ChatClient继承了ModelClient,Prompt继承了ModelRequest,代表Chat领域的请求,同理CharResponse继承了ModelResponse
  • 有了理论基础,一张官方图就让我们看清了Chat API的大概,现在还缺点东西,就是具体的实现层,毕竟有很多种大模型能,最终编码时还是要用到实现层的类,有没有什么方式将实现层完美的展现出来?
  • 感谢SpringAI官方,实现层和功能抽象层的关系,被下面的官方图梳理得清清楚楚
    在这里插入图片描述
  • 此刻再来回顾Spring AI实战之一:快速体验(OpenAI)一文的代码,如下所示,尽管调用了OpenAI的接口,但是并未看到OpenAI相关的类,这是因为Spring已经做好了封装,咱们直接用依赖注入的ChatClient即可,这是抽象层接口,具体实现是SpringAI根据propeties的配置实例化的OpeAIChatClient对象
package com.bolingcavalry.helloopenai.controller;

import org.springframework.ai.chat.ChatClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;


import java.util.Map;

@RestController
public class SimpleAiController {
	// 负责处理OpenAI的bean,所需参数来自properties文件
	private final ChatClient chatClient;

	public SimpleAiController(ChatClient chatClient) {
		this.chatClient = chatClient;
	}

	@PostMapping("/ai/simple")
	public Map<String, String> completion(@RequestBody Map<String,String> map) {
		return Map.of("generation", chatClient.call(map.get("message")));
	}
}
  • 其实说到这里,您已经对Chat API有了比较清晰的理解了,来看看那六大概念的具体代码吧,接下来是一段自然的、水到渠成的体验,毕竟已经领会了其神,现在是观其形的时候
  • 接下来要看的代码如下图所示
    在这里插入图片描述

ChatClient

  • 大模型聊天功能的客户端接口,在进程中,其实现就是各大模型对应的客户端类
public interface ChatClient extends ModelClient<Prompt, ChatResponse> {

	default String call(String message) {// implementation omitted
	}

    @Override
	ChatResponse call(Prompt prompt);
}
  • 可见主要是call方法,这就是最常规的聊天功能,调用call发送请求,返回值就是大模型的响应

StreamingChatClient

  • 这也是客户端类,用于调用大模型的功能,与ChatClient不同的是,ChatClient是请求响应,返回对象ChatResponse就是大模型返回的全部内容,而StreamingChatClient返回的是Flux,这是流式返回,可以讲大模型的响应进行流式输出,如果您使用过各种大模型聊天工具,会发现响应的内容并非一次性展现,而是一段一段的内容,持续不断的展现出来,这就是流式响应的效果
@FunctionalInterface
public interface StreamingChatClient extends StreamingModelClient<Prompt, ChatResponse> {

	default Flux<String> stream(String message) {
		Prompt prompt = new Prompt(message);
		return stream(prompt).map(response -> (response.getResult() == null || response.getResult().getOutput() == null
				|| response.getResult().getOutput().getContent() == null) ? ""
						: response.getResult().getOutput().getContent());
	}

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

}
  • 注意注解FunctionalInterface,表明这是个函数式接口

Prompt

  • 前面看过了ChatClient和StreamingChatClient,会发现入参都是Prompt,可见这就是和大模型一次聊天的入参
  • 下面是Prompt的源码,去掉了构造函数、toString这些之后就会发现,最重要的是Message和ChatOption,所以Prompt只是个打包,真正要提交到大模型的其实是Message和ChatOption
public class Prompt implements ModelRequest<List<Message>> {

    private final List<Message> messages;

    private ChatOptions modelOptions;

	@Override
	public ChatOptions getOptions() {..}

	@Override
	public List<Message> getInstructions() {...}
	
	public String getContents() {
		StringBuilder sb = new StringBuilder();
		for (Message message : getInstructions()) {
			sb.append(message.getContent());
		}
		return sb.toString();
	}
    // constructors and utility methods omitted
}
  • 如果您对OpenAI有所了解,就知道prompt(提示词)并非只有用户输入的聊天内容那么简单,而是system、user 、assistant等多种类型 ,所以这里的Prompt并非只是一个外壳那么简单,它与不同类型的message、不同的辅助类等一起提供了完善的提示词功能,这个会有单独的文章来说明和实战,本篇只要记得它的最终形态就是打好的包用于提交给大模型
  • 如果只是最基本的聊天,下面这个构造方法来创建对象就行了
	public Prompt(String contents) {
		this(new UserMessage(contents));
	}

Message

  • Message很好理解:在聊天过程中,聊天内容对应的对象,请求和响应用的都是Message,不过由于消息类型的多样性,Message被设计成了接口,根据不同类型都有对应的实现,如下图所示
    在这里插入图片描述
  • Message自身非常简单,能保证使用方取到消息内容、类型即可
public interface Message {

	String getContent();

	List<Media> getMedia();

	Map<String, Object> getProperties();

	MessageType getMessageType();

}
  • 另外要注意的是消息类型,一共四种
public enum MessageType {

	USER("user"),

	ASSISTANT("assistant"),

	SYSTEM("system"),

	FUNCTION("function");

ChatOptions

  • ChatOptions代表可以传递给大模型的控制参数,具体有哪些参数和大模型自身开放的特性有关,举个例子,下面是OpenAI开放的参数
  1. presencePenalty : 影响模型在生成文本时重复词语或概念的倾向
  2. frequencyPenalty:影响模型在生成文本时对已出现过词语的偏好程度
  • 按照上面的解释,既然各种大模型都有自己的参数,那么设计ChatOptions能干啥?应该能放一些通用的控制参数吧,打开代码一看果然如此,共有三个通用参数,我都加了中文注释,另外请关注类的注释,也说明了这些参数是通用的、可移植、夸模型
/**
 * The ChatOptions represent the common options, portable across different chat models.
 */
public interface ChatOptions extends ModelOptions {
	// 大模型生成的内容应该更严谨还是更有创造性
	Float getTemperature();
	// 返回概率超过P的所有内容
	Float getTopP();
	// 返回概率最高的前K个内容
	Integer getTopK();
}
  • ChatOptions只是接口,对应的实现是ChatOptionsImpl,源码没啥好看的,就是temperature、topP、topK的get和set而已,为了实例化ChatOptionsImpl,还有配套工具ChatOptionsBuilder,用法如下
ChatOptions portablePromptOptions = ChatOptionsBuilder.builder()
			.withTemperature(0.9f)
			.withTopK(100)
			.withTopP(0.6f)
			.build();
  • 代码看到这里,长期CRUD的我不禁产生一个想法:ChatOptions接口应该很不实用,而且用起来也很别扭,因为各大模型特有的参数和这个接口都没有关系,去看了下OpenAiChatClient.java(Ollama的客户端实现类,里面有段代码是用来封装请求的),果然,这代码真是不够优雅(个人感觉)
    在这里插入图片描述

ChatResponse

  • 看完请求该看响应了,既然Generation才是真正的响应内容,那么ChatResponse也就是个壳,里面包了Generation,打开源码一看,只有Generation和ChatResponseMetadata,这个ChatResponseMetadata可以理解为元信息,主要返回了大模型的API的使用情况说明,以及限速的详细信息
public class ChatResponse implements ModelResponse<Generation> {

    private final ChatResponseMetadata chatResponseMetadata;
	private final List<Generation> generations;

	@Override
	public ChatResponseMetadata getMetadata() {...}

    @Override
	public List<Generation> getResults() {...}

    // other methods omitted
}

Generation

  • Generation中有响应的具体信息,由ChatGenerationMetadata和AssistantMessage组成
public class Generation implements ModelResult<AssistantMessage> {

	private AssistantMessage assistantMessage;
	private ChatGenerationMetadata chatGenerationMetadata;

	@Override
	public AssistantMessage getOutput() {...}

	@Override
	public ChatGenerationMetadata getMetadata() {...}

    // other methods omitted
}
  • ChatGenerationMetadata代表返回内容的元信息,包含了结束原因、生成内容的过滤规则
  • AssistantMessage更容易理解了:类型是ASSISTANT的消息,这个assistant就是助理角色,assistant消息就是大模型返回的聊天响应,源码如下
public class AssistantMessage extends AbstractMessage {

	public AssistantMessage(String content) {
		super(MessageType.ASSISTANT, content);
	}

	public AssistantMessage(String content, Map<String, Object> properties) {
		super(MessageType.ASSISTANT, content, properties);
	}

	@Override
	public String toString() {
		return "AssistantMessage{" + "content='" + getContent() + '\'' + ", properties=" + properties + ", messageType="
				+ messageType + '}';
	}

}
  • 至此,基础理论知识已经过了一遍,相信大家和我一样,进入了一看就会,一用就废的微妙阶段,不急,下一篇就是精彩的实战篇,这些知识点终究会在实战中用到,随着一行行代码一次次请求被理解,最终融汇贯通

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/644583.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

three.js能实现啥效果?看过来,这里都是它的菜(09)

Hi&#xff0c;这是第九期了&#xff0c;继续分享three.js在可视化大屏中的应用&#xff0c;本期分享位移动画的实现。 位移动画 Three.js位移动画是指在Three.js中实现物体位置的平移动画。通过改变物体的位置属性&#xff0c;可以实现物体沿着指定路径从一个位置移动到另一…

ros2编写pcl节点加载pcd文件

初次学习ros2和pcl&#xff0c;尝试在ros2中创建节点&#xff0c;加载pcd文件&#xff0c;并在rviz中进行可视化&#xff0c;记录一下整个过程。 编辑环境 ubuntu20.04 ros2_foxy 创建节点 mkdir -p proj_ws_pcl/src #创建工程文件夹 cd proj_ws_pcl/src #创建源码文件夹 …

车载电子电器架构 —— 智能座舱技术

车载电子电器架构 —— 智能座舱技术 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的…

构建php环境

目录 php简介 官网php安装包 选择下载稳定版本 &#xff08;建议使用此版本&#xff0c;文章以此版本为例&#xff09; 安装php解析环境 准备工作 安装依赖 zlib-devel 和 libxml2-devel包。 安装扩展工具库 安装 libmcrypt 安装 mhash 安装mcrypt 安装php 选项含…

Next.js里app和pages文件夹的区别

最近开始学 Next.js&#xff0c;因为纯自学&#xff0c;有时候网上找到的学习资料都是几年前的&#xff0c;难免会有点 outdated&#xff0c;因此当自己创建的项目结构和视频里呈现的结构不一致时&#xff0c;难免会有点困惑。 例如&#xff0c;今天遇到的第一个问题就是&…

光速入门python的OpenCV

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理python的OpenCV模块的关键知识点 争取用最短的时间入门OpenCV 并且做到笔记功能直接复制使用 OpenCV简介 不浪费时间的介绍: 就是类似于ps操作图片。 至于为什么不直接用ps&#xff0c;因为只有程序能…

社交媒体数据恢复:绿洲

本教程将向您展示如何在绿洲平台上备份和恢复数据&#xff0c;但不涉及推荐任何具体的数据恢复软件。 一、绿洲平台数据备份 为了确保数据的安全&#xff0c;在日常使用过程中&#xff0c;我们需要定期备份绿洲平台上的数据。以下是备份绿洲平台数据的步骤&#xff1a; 登录绿…

【SpringCloud】服务注册与发现

目录 Eureka/注册中心简介模式 使用Eureka实现注册中心1.创建一个名称为demo-eureka-server的Spring Boot项目2.添加项目依赖3. 在启动类添加启动注解4.添加配置信息Eureka的自我保护机制为Eureka Server添加用户认证1.添加依赖2. 添加配置信息3.添加放行代码4.启动服务&#x…

springboot+vue+mybatis校园兼职平台+PPT+论文+讲解+售后

社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个学生的使用。互联网具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自己要…

VMware虚拟机中ubuntu使用记录(10)—— 如何在Ubuntu18.04中使用自己的单目摄像头运行ORB_SLAM3(亲测有效,踩坑记录)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ORB_SLAM3源码编译二、ORB_SLAM3实时单目相机测试1. 查看摄像头的话题2. 运行测试 三. 运行测试可能的报错1. 报错一(1) 问题描述(2) 原因分析(3) 解决 2. …

得帆信息PMO总监李健达受邀为第十三届中国PMO大会演讲嘉宾

全国PMO专业人士年度盛会 上海得帆信息技术有限公司aPaaS业务线副总裁、PMO总监李健达先生受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“AI时代的PMO工作法”。大会将于6月29-30日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#x…

天干物燥小心火烛-智慧消防可视化大屏,隐患防治于未然。

智慧消防可视化大屏通常包括以下内容&#xff1a; 1.实时监控&#xff1a; 显示消防设备、传感器、监控摄像头等设备的实时状态和数据&#xff0c;包括火灾报警、烟雾报警、温度报警等。 2.建筑结构&#xff1a; 显示建筑物的结构图和平面图&#xff0c;包括楼层分布、消防通…

HDFS 组织架构

优质博文&#xff1a;IT-BLOG-CN 一、HDFS 概述 HDFS 产生背景&#xff1a; 随着数据量越来越多&#xff0c;一个系统存储不下所有的数据&#xff0c;那么就需要分配到多个操作系统的磁盘中进行存储&#xff0c;但是不方便管理和维护&#xff0c;迫切需要一种系统来管理多台机…

技术前沿 |【BLIP:统一理解和生成的自举多模态模型研究】

BLIP&#xff1a;统一理解和生成的自举多模态模型研究 摘要引言一、BLIP模型概述二、 BLIP模型在多模态任务中的应用三、总结 摘要 本文介绍了BLIP&#xff08;Bootstrapping Language-Image Pre-training&#xff09;模型&#xff0c;一个前沿的多模态模型&#xff0c;通过自…

2024 一键批量下载雪球和东方财富文章导出excel和pdf

之前分享过雪球批量下载工具2023 批量下载雪球文章导出pdf&#xff0c;以市场高标解读这个号为例&#xff0c;下载效果&#xff1a; 下载文章后用我开发的htmltopdf.exe批量转换html为pdf&#xff0c;不过要注意不要放在中文目录下&#xff0c;否则提示错误 utf-8 codec cant d…

数据结构之栈和队列(超详解

目录 一.栈 1.栈的基本概念 2.栈的基本操作 3.栈的储存结构 ①栈的顺序储存 (1)基本概念 (2)代码实现 ②栈的链式储存 (1)基本概念 (2)代码实现 二.队列 1.队列的基本概念 2.队列的基本操作 3.队列的储存结构 ①队列的链式储存 (1)基本概念 ​编辑 (2)代码实现 ②…

浅析3D NAND多层架构的可靠性问题

SSD的存储介质是什么&#xff0c;它就是NAND闪存。那你知道NAND闪存是怎么工作的吗&#xff1f;其实&#xff0c;它就是由很多个晶体管组成的。这些晶体管里面存储着电荷&#xff0c;代表着我们的二进制数据&#xff0c;要么是“0”&#xff0c;要么是“1”。 目前业内3D-NAND工…

【吊打面试官系列】Java高并发篇 - ReadWriteLock 是什么 ?

大家好&#xff0c;我是锋哥。今天分享关于 【ReadWriteLock 是什么 &#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; ReadWriteLock 是什么 &#xff1f; 首先明确一下&#xff0c;不是说 ReentrantLock 不好&#xff0c;只是 ReentrantLock 某些时候有局限。 …

【动态规划】斐波那契数列模型(C++)

目录 1137.第N个泰波那契数 解法&#xff08;动态规划&#xff09; 算法流程 1. 状态表⽰&#xff1a; 2. 状态转移⽅程&#xff1a; 3. 初始化&#xff1a; 4. 填表顺序&#xff1a; 5. 返回值&#xff1a; C算法代码 优化&#xff1a; 滚动数组 测试&#xff1a; …

bootstrap实现九宫格效果(猫捉老鼠游戏)

最近&#xff0c;孩子的幼儿园让家长体验“半日助教活动”&#xff0c;每个家长需要讲授15-20分钟的课程。作为一名程序员&#xff0c;实在没有能教的课程&#xff0c;只能做了一个小游戏&#xff0c;带着小朋友们熟悉数字。 效果大致是这样的。九宫格的左上角是一只小猫图片&…