首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring AI实战之二:Chat API基础知识大串讲(重要)

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

作者头像
程序员欣宸
发布于 2024-05-27 00:14:48
发布于 2024-05-27 00:14:48
7.3K00
代码可运行
举报
文章被收录于专栏:实战docker实战docker
运行总次数:0
代码可运行
欢迎访问我的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:这里面是具体的返回内容
  7. 再来看三个层次,前面我们知道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对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
  • 大模型聊天功能的客户端接口,在进程中,其实现就是各大模型对应的客户端类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,这是流式返回,可以讲大模型的响应进行流式输出,如果您使用过各种大模型聊天工具,会发现响应的内容并非一次性展现,而是一段一段的内容,持续不断的展现出来,这就是流式响应的效果
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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、不同的辅助类等一起提供了完善的提示词功能,这个会有单独的文章来说明和实战,本篇只要记得它的最终形态就是打好的包用于提交给大模型
  • 如果只是最基本的聊天,下面这个构造方法来创建对象就行了
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public Prompt(String contents) {
		this(new UserMessage(contents));
	}
Message
  • Message很好理解:在聊天过程中,聊天内容对应的对象,请求和响应用的都是Message,不过由于消息类型的多样性,Message被设计成了接口,根据不同类型都有对应的实现,如下图所示
  • Message自身非常简单,能保证使用方取到消息内容、类型即可
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Message {

	String getContent();

	List<Media> getMedia();

	Map<String, Object> getProperties();

	MessageType getMessageType();

}
  • 另外要注意的是消息类型,一共四种
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public enum MessageType {

	USER("user"),

	ASSISTANT("assistant"),

	SYSTEM("system"),

	FUNCTION("function");
ChatOptions
  • ChatOptions代表可以传递给大模型的控制参数,具体有哪些参数和大模型自身开放的特性有关,举个例子,下面是OpenAI开放的参数
  • presencePenalty : 影响模型在生成文本时重复词语或概念的倾向
  • frequencyPenalty:影响模型在生成文本时对已出现过词语的偏好程度
  • 按照上面的解释,既然各种大模型都有自己的参数,那么设计ChatOptions能干啥?应该能放一些通用的控制参数吧,打开代码一看果然如此,共有三个通用参数,我都加了中文注释,另外请关注类的注释,也说明了这些参数是通用的、可移植、夸模型
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 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,用法如下
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的使用情况说明,以及限速的详细信息
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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组成
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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消息就是大模型返回的聊天响应,源码如下
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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系列
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Kotlin 语言中的“关键字” Keywords in Kotlin修饰符关键字
但在 kotlin, 有一些关键字在某些情况下可以用作标识符。 在 kotlin 中基本上有四种类型的关键字:
一个会写诗的程序员
2018/12/06
8410
Kotlin 语言中的“关键字”  Keywords in Kotlin修饰符关键字
JAVA基础语法——标识符、修饰符、关键字(个人整理总结)
多个单词组成时第一个单词首字母小写,其他单词首字母大写(例:lastAccessTime、getTime)。
泰斗贤若如
2019/08/07
4.8K0
JAVA基础语法——标识符、修饰符、关键字(个人整理总结)
Kotlin学习之路(2)数据类型
每一种语言都有自己的基本数据类型,Kotlin也有自己的数据类型,类似与Java包括整型 浮点型 布尔类型等。
全栈程序员站长
2021/04/07
8450
【Pyhton学习】常用标识符与关键字
在Python中,标识符是程序员定义的名称,用于标识变量、函数、类等。标识符需遵循以下规则:
鸽芷咕
2025/05/20
1050
【Pyhton学习】常用标识符与关键字
java 标识符,分隔符,关键字[通俗易懂]
Java语言中,对于变量,常量,函数,语句块也有名字,我们统统称之为Java标识符. 标识符是用来给类、对象、方法、变量、接口和自定义数据类型命名的。
全栈程序员站长
2022/09/08
8250
《Java从入门到失业》第三章:基础语法及基本程序结构(3.2-3.5):标识符、关键字、注释、变量及常量
       上面我们知道我们自定义一个类,需要一个类名。在Java中,还有很多需要命名的组成部分,例如方法名,变量名等。标识符的命名需要遵循Java的规范,总结如下:
用户7801119
2020/09/27
4150
2.1 java基础语法之关键字和标识符
大家好,我们在前面的几篇博客中已经给大家介绍了java的环境安装,第一个java程序,和java环境变量的配置,那么我们这一章节主要给大家介绍一个java中的基础语法,也就是java这门语言当中的一些基本用法,java中的基础语法主要包括java中的关键字和标识符,java中的变量和数据类型,java中的运算符,java中的流程控制语句。大概分为这么4大模块,我们会分别介绍,本篇文章我们主要来研究一下java中的关键字和标识符。
一缕82年的清风
2022/01/10
2560
java标识符与关键字_4、Java标识符和关键字
标识符:Java对各种变量,方法和类等要素命名时使用的字符序列称为标识符。(凡是自己可以起名的地方都叫标识符,都遵循标识符的规则)
全栈程序员站长
2022/09/08
3440
Java基础入门篇(二)——Java注释、关键字和标识符
前面几篇文章用Java带大家一起了解了几个游戏小项目,感兴趣的小伙伴可以点击文章观摩下,手把手教你用Java打造一款简单故事书(上篇)、手把手教你用Java打造一款简单故事书(下篇)、手把手教你用Java打造一款简单考试系统(上篇)、手把手教你用Java打造一款简单考试系统(下篇)接下来的几篇文章是关于Java基础的,希望对大家的学习有帮助,欢迎大家在讨论区留言。
Java进阶者
2021/01/22
5710
【python基础教程】关键字与标识符
关键字是python语言中一些已经被赋予特定意义的单词。开发程序是,不可以把这些关键字作文变量、函数、类、模块和其他对象的名称来使用。python语言中的关键字如下表所示
hacker707
2022/11/27
3980
【python基础教程】关键字与标识符
Java标识符与关键字
1.Java注释 ☞编码加上注释!!!!  ● 单行注释:// 注释内容 (最常用)  ● 多行注释:/* 注释内容 / (不推荐)  ● 文档注释:/* 文档注释 */ (常见于方法和类之上描述方法和类的作用),可自动生成文档 2.Java标识符 Java中,对于变量、常量、函数、语句块都有名字,统称为Java标识符。 ◆对标识符的三点要求:  ●标识符由字母、数字、_(下划线)、$组成,不能以数字开头,不能用Java中的关键字  ●标识符采用有意义的简单命名  ●“$”不要在代码中出现 ◆驼峰命名法: 大驼峰:定义类、接口时使用   单词以大写字母开头,若有多个单词,每个单词首字母大写      public class FirstClass 小驼峰:定义变量、函数时使用   若标识符只有一个单词,全小写;若标识符由多个单词组成,从第二个单词开始首字母大写     int mathScore = 10 常量:所有单词全部大写,多个单词间以 - 分隔 3.关键字 注意:  ●Java中有两个未使用的保留字:goto、const  ●Java中有三个特殊含义的单词:null、ture、false  ●JDK1.4后追加了 assert关键字;JDK1.5以后追加了enum关键字  4.数据类型划分 注意:对数据类型的选择  ●在程序开发之中,整数就用int,描述小数用double。  ●long一般用于描述日期、时间、内存或文件大小(字节)  ●如果要进行编码转换或者进行二进制流的操作,使用byte(-127~128)  ●char一般在描述中文中会用到(基本忽略)   4.1 基本数据类型(八大基本类型) 4.1.1 数值型 整型:默认值0 byte(-128~127)<short < int(-231~231) < long 在Java中,任何一个整型常量都是int类型 当数据类型达到最大值时,换一个保存范围更大的类型来解决数据溢出问题 Java中声明long常量,需要在数字后加 l 或 L 在进行数学计算时,小的数据类型自动转为大的数据类型,大的数据类型变为小的数据类型必须强制类型转换,可能会溢出。(最高位取反) byte(-128~127)与int类型: 当整型常量在byte保存范围中,可以直接赋值给byte变量;常量大小超出byte范围,int变量赋值给byte变量,所有赋值必须强转。
用户7886150
2020/12/02
3630
Java基础之关键字,标识符,注释,数据类型
Java的关键字对java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等。
南风
2019/04/22
4340
【面试题精讲】标识符和关键字的区别是什么
在上面的示例中,myVariable是一个标识符,用来表示一个整数类型的变量。if是一个关键字,用于控制程序的流程。
程序员朱永胜
2023/09/28
1.3K0
不可不看的Java基础知识整理,注释、关键字、运算符
万丈高楼平地起,要想学好汉语首先学拼音,想学好英语首先学26个字母,对于编程语言来说,一样的道理,要想学好必须先掌握其基础语法和知识,今天我们就来唠一唠Java语言中那些出现频率极高,又很基础的知识点吧!
JavaBuild
2024/05/27
970
不可不看的Java基础知识整理,注释、关键字、运算符
【初识Go】| Day2 数据类型、关键字、标识符
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
yussuy
2020/12/16
5980
【初识Go】| Day2 数据类型、关键字、标识符
Java 基础标识符
标识符: 程序员为自己定义的类,方法或者变量等起的名称。     标识符由大写字母,数字,下划线(_)和美元符号组成,但不能以数字开头。 Java 语言中严格区分大小写。     包名: 使用小写字母。         类名和接口名: 通常定义为由具有含义的单词组成,所有单词的首字母大写。   方法名: 通常也是由具有含义的单词组成,第一个单词首字母小写,其他单词的首字母都大写。   变量名: 成员变量和方法相同,局部变量全部使用小写。   常量名: 全部使用大写, 最后已用下划线分割单词     关键字:
用户1197315
2018/01/19
8550
Java 基础语法(1)- 注释、标识符、关键字
背景 要开始磕 Java 了,虽然以前学过用过,但是差不多忘光光了... 现在直接搬狂神的视频素材,不再自己总结,要学的东西太多了... 注释 单行注释 // 多行注释 /* */ 文档
小菠萝测试笔记
2021/07/08
4510
Java 基础教学:基础语法 - 注释、标识符与关键字
Java是一种广泛使用的编程语言,它的语法规则和结构为编程提供了清晰的框架。为了编写出易于理解和维护的代码,必须掌握Java的基本语法元素,包括注释、标识符和关键字。本文档将详细介绍这些概念,并提供示例以帮助初学者了解和应用。
世间万物皆对象
2024/10/22
1820
第2章 Kotlin 语法基础第2章 Kotlin 语法基础
人与人之间通过语言来交流沟通,互相协作。人与计算机之间怎样“交流沟通”呢?答案是编程语言。一门语言有词、短语、句子、文章等,对应到编程语言中就是关键字、标识符、表达式、源代码文件等。通常一门编程语言的基本构成如下图所示
一个会写诗的程序员
2018/08/17
2.8K0
第2章 Kotlin 语法基础第2章 Kotlin 语法基础
Java基础-Java基础-02总结关键字,标识符,注释,常量进制,变量数据类型
程序员:为什么选择Java? 1:关键字(掌握) (1)被Java语言赋予特定含义的单词 (2)特点: 全部小写。 (3)注意事项: A:goto和const作为保留字存在。 B:类似于N
Java帮帮
2018/03/15
8120
Java基础-Java基础-02总结关键字,标识符,注释,常量进制,变量数据类型
推荐阅读
相关推荐
Kotlin 语言中的“关键字” Keywords in Kotlin修饰符关键字
更多 >
LV.0
全球人工智能信息服务
目录
  • 欢迎访问我的GitHub
  • Spring AI实战全系列链接
  • 本篇概览
  • 用一个问题开篇
  • 关于Chat API的六个核心概念和三个层次
  • 官方图
  • ChatClient
  • StreamingChatClient
  • Prompt
  • Message
  • ChatOptions
  • ChatResponse
  • Generation
  • 你不孤单,欣宸原创一路相伴
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档