# SpringAIAlibaba
**Repository Path**: johnstonguo/spring-aialibaba
## Basic Information
- **Project Name**: SpringAIAlibaba
- **Description**: 学习SpringAIAlibaba
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 5
- **Forks**: 1
- **Created**: 2025-04-08
- **Last Updated**: 2025-06-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: MCPServer, SpringAI, SpringAIAlibaba
## README
# Spring AI Alibaba
## 什么是 Spring AI Alibaba?
Spring AI Alibaba 开源项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。
[](https://img.alicdn.com/imgextra/i1/O1CN01uhDvMY22HZ4q1OZMM_!!6000000007095-2-tps-5440-2928.png)
Spring AI Alibaba 作为开发 AI 应用程序的基础框架,定义了以下抽象概念与 API,并提供了 API 与通义系列模型的适配。
- 开发复杂 AI 应用的高阶抽象 Fluent API — ChatClient
- 提供多种大模型服务对接能力,包括主流开源与阿里云通义大模型服务(百炼)等
- 支持的模型类型包括聊天、文生图、音频转录、文生语音等
- 支持同步和流式 API,在保持应用层 API 不变的情况下支持灵活切换底层模型服务,支持特定模型的定制化能力(参数传递)
- 支持 Structured Output,即将 AI 模型输出映射到 POJOs
- 支持矢量数据库存储与检索
- 支持函数调用 Function Calling
- 支持构建 AI Agent 所需要的工具调用和对话内存记忆能力
- 支持 RAG 开发模式,包括离线文档处理如 DocumentReader、Splitter、Embedding、VectorStore 等,支持 Retrieve 检索
以上框架功能可让您实现常见 AI 应用的快速开发,例如 “通过文档进行问答” 或 “通过文档进行聊天” 等。
## 核心概念
本节介绍 Spring AI 框架使用的核心概念。我们建议仔细阅读,以了解框架实现背后的思想。
### 模型(Model)
AI 模型是旨在处理和生成信息的算法,通常模仿人类的认知功能。通过从大型数据集中学习模式和见解,这些模型可以做出预测、文本、图像或其他输出,从而增强各个行业的各种应用。
AI 模型有很多种,每种都适用于特定的用例。虽然 ChatGPT 及其生成 AI 功能通过文本输入和输出吸引了用户,但许多模型和公司都提供不同的输入和输出。在 ChatGPT 之前,许多人都对文本到图像的生成模型着迷,例如 Midjourney 和 Stable Diffusion。

Sprig AI 目前支持以语言、图像和音频形式处理输入和输出的模型。上表中的最后一行接受文本作为输入并输出数字,通常称为嵌入文本(Embedding Text),用来表示 AI 模型中使用的内部数据结构。Sprig AI 提供了对 Embedding 的支持以支持开发更高级的应用场景。
GPT 等模型的独特之处在于其预训练特性,正如 GPT 中的“P”所示——Chat Generative Pre-trained Transformer。这种预训练功能将 AI 转变为通用的开发工具,开发者使用这种工具不再需要广泛的机器学习或模型训练背景。
### 提示(Prompt)
Prompt作为语言基础输入的基础,指导AI模型生成特定的输出。对于熟悉ChatGPT的人来说,Prompt似乎只是输入到对话框中的文本,然后发送到API。然而,它的内涵远不止于此。在许多AI模型中,Prompt的文本不仅仅是一个简单的字符串。
ChatGPT的API包含多个文本输入,每个文本输入都有其角色。例如,系统角色用于告知模型如何行为并设定交互的背景。还有用户角色,通常是来自用户的输入。
撰写有效的Prompt既是一门艺术,也是一门科学。ChatGPT旨在模拟人类对话,这与使用SQL“提问”有很大的区别。与AI模型的交流就像与另外一个人对话一样。
这种互动风格的重要性使得“Prompt工程”这一学科应运而生。现在有越来越多的技术被提出,以提高Prompt的有效性。投入时间去精心设计Prompt可以显著改善生成的输出。
分享Prompt已成为一种共同的实践,且正在进行积极的学术研究。例如,最近的一篇研究论文发现,最有效的Prompt之一可以以“深呼吸一下,分步进行此任务”开头。这表明语言的重要性之高。我们尚未完全了解如何充分利用这一技术的前几代版本,例如ChatGPT 3.5,更不用说正在开发的新版本了。
#### 提示词模板(Prompt Template)
创建有效的Prompt涉及建立请求的上下文,并用用户输入的特定值替换请求的部分内容。这个过程使用传统的基于文本的模板引擎来进行Prompt的创建和管理。Spring AI采用开源库StringTemplate来实现这一目的。
例如,考虑以下简单的Prompt模板:
```
Tell me a {adjective} joke about {content}.
```
在Spring AI中,Prompt模板可以类比于Spring MVC架构中的“视图”。一个模型对象,通常是java.util.Map,提供给Template,以填充模板中的占位符。渲染后的字符串成为传递给AI模型的Prompt的内容。
传递给模型的Prompt在具体数据格式上有相当大的变化。从最初的简单字符串开始,Prompt逐渐演变为包含多条消息的格式,其中每条消息中的每个字符串代表模型的不同角色。
### 嵌入(Embedding)
嵌入(Embedding)是文本、图像或视频的数值表示,能够捕捉输入之间的关系,Embedding通过将文本、图像和视频转换为称为向量(Vector)的浮点数数组来工作。这些向量旨在捕捉文本、图像和视频的含义,Embedding数组的长度称为向量的维度。
通过计算两个文本片段的向量表示之间的数值距离,应用程序可以确定用于生成嵌入向量的对象之间的相似性。

作为一名探索人工智能的Java开发者,理解这些向量表示背后的复杂数学理论或具体实现并不是必需的。对它们在人工智能系统中的作用和功能有基本的了解就足够了,尤其是在将人工智能功能集成到您的应用程序中时。
Embedding在实际应用中,特别是在检索增强生成(RAG)模式中,具有重要意义。它们使数据能够在语义空间中表示为点,这类似于欧几里得几何的二维空间,但在更高的维度中。这意味着,就像欧几里得几何中平面上的点可以根据其坐标的远近关系而接近或远离一样,在语义空间中,点的接近程度反映了意义的相似性。关于相似主题的句子在这个多维空间中的位置较近,就像图表上彼此靠近的点。这种接近性有助于文本分类、语义搜索,甚至产品推荐等任务,因为它允许人工智能根据这些点在扩展的语义空间中的“位置”来辨别和分组相关概念。
您可以将这个语义空间视为一个向量。
### Token
token是 AI 模型工作原理的基石。输入时,模型将单词转换为token。输出时,它们将token转换回单词。
在英语中,一个token大约对应一个单词的 75%。作为参考,莎士比亚的全集总共约 90 万个单词,翻译过来大约有 120 万个token。

也许更重要的是 “token = 金钱”。在托管 AI 模型的背景下,您的费用由使用的token数量决定。输入和输出都会影响总token数量。
此外,模型还受到 token 限制,这会限制单个 API 调用中处理的文本量。此阈值通常称为“上下文窗口”。模型不会处理超出此限制的任何文本。
例如,ChatGPT3 的token限制为 4K,而 GPT4 则提供不同的选项,例如 8K、16K 和 32K。Anthropic 的 Claude AI 模型的token限制为 100K,而 Meta 的最新研究则产生了 1M token限制模型。
要使用 GPT4 总结莎士比亚全集,您需要制定软件工程策略来切分数据并在模型的上下文窗口限制内呈现数据。Spring AI 项目可以帮助您完成此任务。
### 结构化输出(Structured Output)
即使您要求回复为 JSON ,AI 模型的输出通常也会以 `java.lang.String` 的形式出现。它可能是正确的 JSON,但它可能并不是你想要的 JSON 数据结构,它只是一个字符串。此外,在提示词 Prompt 中要求 “返回JSON” 并非 100% 准确。
这种复杂性导致了一个专门领域的出现,涉及创建 Prompt 以产生预期的输出,然后将生成的简单字符串转换为可用于应用程序集成的数据结构。

[结构化输出转换](https://java2ai.com/docs/1.0.0-M6.1/tutorials/structured-output/)采用精心设计的提示,通常需要与模型进行多次交互才能实现所需的格式。
### 将您的数据和 API 引入 AI 模型
如何让人工智能模型与不在训练集中的数据一同工作?
请注意,GPT 3.5/4.0 数据集仅支持截止到 2021 年 9 月之前的数据。因此,该模型表示它不知道该日期之后的知识,因此它无法很好的应对需要用最新知识才能回答的问题。一个有趣的小知识是,这个数据集大约有 650GB。
有三种技术可以定制 AI 模型以整合您的数据:
- `Fine Tuning` 微调:这种传统的机器学习技术涉及定制模型并更改其内部权重。然而,即使对于机器学习专家来说,这是一个具有挑战性的过程,而且由于 GPT 等模型的大小,它极其耗费资源。此外,有些模型可能不提供此选项。
- `Prompt Stuffing` 提示词填充:一种更实用的替代方案是将您的数据嵌入到提供给模型的提示中。考虑到模型的令牌限制,我们需要具备过滤相关数据的能力,并将过滤出的数据填充到在模型交互的上下文窗口中,这种方法俗称“提示词填充”。Spring AI 库可帮助您基于“提示词填充” 技术,也称为[检索增强生成 (RAG)](https://java2ai.com/docs/1.0.0-M6.1/#检索增强生成rag)实现解决方案。

- [Function Calling](https://java2ai.com/docs/1.0.0-M6.1/tutorials/function-calling/):此技术允许注册自定义的用户函数,将大型语言模型连接到外部系统的 API。Spring AI 大大简化了支持[函数调用](https://java2ai.com/docs/1.0.0-M6.1/tutorials/function-calling/)所需编写的代码。
### 检索增强生成(RAG)
一种称为检索增强生成 (RAG) 的技术已经出现,旨在解决为 AI 模型提供额外的知识输入,以辅助模型更好的回答问题。
该方法涉及批处理式的编程模型,其中涉及到:从文档中读取非结构化数据、对其进行转换、然后将其写入矢量数据库。从高层次上讲,这是一个 ETL(提取、转换和加载)管道。矢量数据库则用于 RAG 技术的检索部分。
在将非结构化数据加载到矢量数据库的过程中,最重要的转换之一是将原始文档拆分成较小的部分。将原始文档拆分成较小部分的过程有两个重要步骤:
1. 将文档拆分成几部分,同时保留内容的语义边界。例如,对于包含段落和表格的文档,应避免在段落或表格中间拆分文档;对于代码,应避免在方法实现的中间拆分代码。
2. 将文档的各部分进一步拆分成大小仅为 AI 模型令牌 token 限制的一小部分的部分。
RAG 的下一个阶段是处理用户输入。当用户的问题需要由 AI 模型回答时,问题和所有“类似”的文档片段都会被放入发送给 AI 模型的提示中。这就是使用矢量数据库的原因,它非常擅长查找具有一定相似度的“类似”内容。

- [ETL 管道](https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html) 提供了有关协调从数据源提取数据并将其存储在结构化向量存储中的流程的更多信息,确保在将数据传递给 AI 模型时数据具有最佳的检索格式。
- [ChatClient - RAG](https://java2ai.com/docs/1.0.0-M6.1/tutorials/chat-client/#检索增强生成rag) 解释了如何使用`QuestionAnswerAdvisor` Advisor 在您的应用程序中启用 RAG 功能。
### 函数调用(Function Calling)
大型语言模型 (LLM) 在训练后即被冻结,导致知识陈旧,并且无法访问或修改外部数据。
[Function Calling](https://docs.spring.io/spring-ai/reference/api/functions.html)机制解决了这些缺点,它允许您注册自己的函数,以将大型语言模型连接到外部系统的 API。这些系统可以为 LLM 提供实时数据并代表它们执行数据处理操作。
Spring AI 大大简化了您需要编写的代码以支持函数调用。它为您处理函数调用对话。您可以将函数作为提供,`@Bean`然后在提示选项中提供该函数的 bean 名称以激活该函数。此外,您可以在单个提示中定义和引用多个函数。

- (1)执行聊天请求并发送函数定义信息。后者提供`name`(`description`例如,解释模型何时应调用该函数)和`input parameters`(例如,函数的输入参数模式)。
- (2)当模型决定调用该函数时,它将使用输入参数调用该函数,并将输出返回给模型。
- (3)Spring AI 为您处理此对话。它将函数调用分派给适当的函数,并将结果返回给模型。
- (4)模型可以执行多个函数调用来检索所需的所有信息。
- (5)一旦获取了所有需要的信息,模型就会生成响应。
请关注[函数调用](https://docs.spring.io/spring-ai/reference/api/functions.html)文档以获取有关如何在不同 AI 模型中使用此功能的更多信息。
### 评估人工智能的回答(Evaluation)
有效评估人工智能系统回答的正确性,对于确保最终应用程序的准确性和实用性非常重要,一些新兴技术使得预训练模型本身能够用于此目的。
Evaluation 评估过程涉及分析响应是否符合用户的意图、与查询的上下文强相关,一些指标如相关性、连贯性和事实正确性等都被用于衡量 AI 生成的响应的质量。
一种方法是把用户的请求、模型的响应一同作为输入给到模型服务,对比模型给的响应或回答是否与提供的响应数据一致。
此外,利用矢量数据库(Vector Database)中存储的信息作为补充数据可以增强评估过程,有助于确定响应的相关性。
## Spring AI 项目简介
[Spring AI](https://docs.spring.io/spring-ai/reference/index.html) 项目由 Spring 官方开源并维护的 AI 应用开发框架,该项目目标是简化包含人工智能(AI)功能的应用程序的开发,避免不必要的复杂性。该项目从著名的 Python 项目(例如 LangChain 和 LlamaIndex)中汲取灵感,但 Spring AI 并非这些项目的直接移植,该项目的成立基于这样的信念:下一波生成式 AI 应用将不仅面向 Python 开发人员,还将遍及多种编程语言。从本质上讲,Spring AI 解决了 AI 集成的基本挑战:Connecting your enterprise Data and APIs with the AI Models。
### Spring AI 与 Spring AI Alibaba 深度解析
---
#### **一、框架概述与核心原理**
1. **Spring AI**
• **定位**:Spring 官方推出的通用 AI 应用开发框架,旨在简化 Java 开发者集成大模型的过程。
• **原理**:
◦ 通过抽象层(如 `ChatClient`)提供统一接口,支持 OpenAI、Azure、HuggingFace 等主流国际模型。
◦ 模块化设计,结合 Spring Boot 的自动配置能力,实现“一次编码,多模型切换”。
◦ 支持多模态能力(对话、文生图、RAG 检索增强生成)和函数调用(Function Calling)。
2. **Spring AI Alibaba**
• **定位**:阿里云基于 Spring AI 的本地化实现,专注阿里通义系列大模型及国内生态集成。
• **原理**:
◦ 深度集成阿里云灵积平台(Model-as-a-Service),默认适配通义千问(Qwen)等模型。
◦ 扩展了本地化功能,如内容安全过滤、中文 Prompt 模板管理,并优化了国内网络调用效率。
◦ 提供企业级特性:流式 API、会话记忆、矢量数据库适配等。
---
#### **二、核心区别与适用场景**
| **维度** | **Spring AI** | **Spring AI Alibaba** |
| -------------- | ------------------------------------- | ------------------------------------------ |
| **模型支持** | 国际主流模型(OpenAI、AWS、Google等) | 阿里云通义系列为主,兼容部分开源模型 |
| **生态系统** | Spring 原生生态,适配多云服务 | 阿里云生态(如百炼平台)深度集成 |
| **功能优化** | 通用型 API 抽象,适合全球化项目 | 本地化增强(如数据合规、中文 Prompt 支持) |
| **开发便利性** | 需自行处理国内网络限制和合规要求 | 内置阿里云 API Key 管理和安全调用机制 |
**常见使用场景**:
• **Spring AI**:
• 需要对接多国际模型的跨境项目(如跨境电商客服系统)。
• 依赖 LangChain 式工作流的复杂 AI 应用(如多步骤数据分析)。
• **Spring AI Alibaba**:
• 国内企业级 AI 应用(如政务智能问答、金融风控)。
• 快速集成通义模型的场景(如电商文案生成、智能导购)。
---
#### **三、后端项目选型建议**
1. **选择 Spring AI 的情况**:
• 项目需兼容多模型(如同时使用 GPT 和 Claude)。
• 团队熟悉 Spring 生态且无需国内合规支持。
2. **选择 Spring AI Alibaba 的情况**:
• 项目部署在国内且依赖阿里云服务(如通义千问、OSS 存储)。
• 需快速实现生产级 AI 功能(如流式聊天接口、RAG 文档处理)。
• 对中文支持、内容安全有强需求(如社交媒体内容审核)。
---
#### **四、大模型热潮下的后端实践**
当前大模型技术更倾向于 **多模态融合** 和 **低代码集成**。对于 Java 后端开发者:
• **推荐 Spring AI Alibaba**:
• 优势:国内网络优化、开箱即用的通义模型支持、企业级运维工具(如监控和限流)。
• 典型用例:通过 `@CrossOrigin` 注解快速构建支持 Prompt 的流式 API(如智能客服接口)。
• **补充方案**:若项目涉及混合云架构,可结合 Spring AI 对接国际模型 + Spring AI Alibaba 处理国内需求。
---
## 快速入门
### 添加依赖
首先,需要在项目中添加 `spring-ai-alibaba-starter` 依赖,它将通过 Spring Boot 自动装配机制初始化与阿里云通义大模型通信的 `ChatClient`、`ChatModel` 相关实例。
```xml
com.alibaba.cloud.ai
spring-ai-alibaba-starter
1.0.0-M5.1
```
> 注意:由于 spring-ai 相关依赖包还没有发布到中央仓库,如出现 spring-ai-core 等相关依赖解析问题,请在您项目的 pom.xml 依赖中加入如下仓库配置。
>
> ```xml
>
>
> spring-milestones
> Spring Milestones
> https://repo.spring.io/milestone
>
> false
>
>
>
> ```
### 注入 ChatClient
接下来,在普通 Controller Bean 中注入 `ChatClient` 实例,这样你的 Bean 就具备与 AI 大模型智能对话的能力了。
```java
@RestController
@RequestMapping("/helloworld")
public class HelloworldController {
private static final String DEFAULT_PROMPT = "你是一个博学的智能聊天助手,请根据用户提问回答!";
private final ChatClient dashScopeChatClient;
public HelloworldController(ChatClient.Builder chatClientBuilder) {
this.dashScopeChatClient = chatClientBuilder
.defaultSystem(DEFAULT_PROMPT)
// 实现 Chat Memory 的 Advisor
// 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
// 实现 Logger 的 Advisor
.defaultAdvisors(
new SimpleLoggerAdvisor()
)
// 设置 ChatClient 中 ChatModel 的 Options 参数
.defaultOptions(
DashScopeChatOptions.builder()
.withTopP(0.7)
.build()
)
.build();
}
@GetMapping("/simple/chat")
public String simpleChat(String query) {
return dashScopeChatClient.prompt(query).call().content();
}
}
```
以上示例中,ChatClient 使用默认参数调用大模型,Spring AI Alibaba 还支持通过 `DashScopeChatOptions` 调整与模型对话时的参数,`DashScopeChatOptions` 支持两种不同维度的配置方式:
1. 全局默认值,即 `ChatClient` 实例初始化参数
可以在 `application.yaml` 文件中指定 `spring.ai.dashscope.chat.options.*` 或调用构造函数 `ChatClient.Builder.defaultOptions(options)`、`DashScopeChatModel(api, options)` 完成配置初始化。
2. 每次 Prompt 调用前动态指定
```java
String result = dashScopeChatClient
.prompt(query)
.options(DashScopeChatOptions.builder().withTopP(0.8).build())
.call()
.content();
```
关于 `DashScopeChatOptions` 配置项的详细说明,请查看参考手册。
### 更多资料
### 基础示例与API使用
- [ChatClient 详细说明](https://java2ai.com/docs/1.0.0-M6.1/tutorials/chat-client/)
- [Prompt Template 提示词模板](https://java2ai.com/docs/1.0.0-M6.1/tutorials/prompt/)
- [Function Calling](https://java2ai.com/docs/1.0.0-M6.1/tutorials/function-calling/)
### 高级示例
- [使用 RAG 开发 Q&A 答疑助手](https://java2ai.com/docs/1.0.0-M6.1/practices/rag)
- [具备连续对话能力的聊天机器人](https://java2ai.com/docs/1.0.0-M6.1/practices/memory)
## 基础API-ChatCLient
### 配置好客户端信息并注入
```java
/**
* @author guochuantao
* @version 1.0
* @description
* @since 2025/3/31 下午2:31
*/
@Configuration
public class ChatConfig {
private static final String DEFAULT_PROMPT = "你是一个博学的甜美可爱的智能聊天女助手,请根据用户提问回答!";
@Bean
public ChatClient chatClient(ChatClient.Builder clientBuilder) {
System.out.println("ChatClient Bean is created");
return clientBuilder.defaultSystem(DEFAULT_PROMPT)
// 实现 Chat Memory 的 Advisor
// 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
// 设置 ChatClient 中 ChatModel 的 Options 参数
.defaultOptions(
DashScopeChatOptions.builder()
// 设置 ChatModel 的 TopP 参数 该参数的含义是,当模型预测出多个答案时,优先返回概率最高的答案。
.withTopP(0.7)
.build()
)
.build();
}
}
```
### 工具类
```java
public class CommonUtil {
/**
* 从JSON对象中获取指定字段并校验非空
* @param jsonObject JSON参数对象,不能为null
* @param fieldName 需要提取的字段名
* @param errorMsg 校验失败时的错误提示
* @return 字段对应的非空字符串值
* @throws IllegalArgumentException 参数不合法时抛出
*/
public static String getAndCheck(JSONObject jsonObject, String fieldName, String errorMsg) {
// 防御性校验:参数对象不能为空
if (jsonObject == null) {
throw new IllegalArgumentException("参数对象不能为空");
}
// 双重校验:字段存在性检查 + 非空内容检查
if (ObjectUtil.isNull(jsonObject.get(fieldName))) {
throw new IllegalArgumentException(errorMsg);
}
String value = jsonObject.getString(fieldName);
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException(errorMsg);
}
return value;
}
}
```
### controller
```java
/**
* 简单对话 get是可以接受复杂数据json的
* @param param 参数
* @return 回答内容
*/
@GetMapping("/simple2content")
public String simple2content(@RequestBody JSONObject param) {
return chatClient.prompt(CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!"))
.call().content();
}
/**
* {
* "result": {
* "metadata": {
* "finishReason": "STOP",
* "contentFilters": [],
* "empty": true
* },
* "output": {
* "messageType": "ASSISTANT",
* "metadata": {
* "finishReason": "STOP",
* "role": "ASSISTANT",
* "id": "85d6c155-935f-97e8-ae57-94471beb7dfe",
* "messageType": "ASSISTANT"
* },
* "toolCalls": [],
* "media": [],
* "content": "我现在的角色是一个**博学的智能聊天助手**,专门根据用户的提问提供准确、全面和有用的回答。无论你是想了解知识、寻求建议、创作内容,还是单纯想聊聊天,我都会尽力满足你的需求。如果有任何问题,请随时向我提问! 😊",
* "text": "我现在的角色是一个**博学的智能聊天助手**,专门根据用户的提问提供准确、全面和有用的回答。无论你是想了解知识、寻求建议、创作内容,还是单纯想聊聊天,我都会尽力满足你的需求。如果有任何问题,请随时向我提问! 😊"
* }
* },
* "metadata": {
* "id": "85d6c155-935f-97e8-ae57-94471beb7dfe",
* "model": "",
* "rateLimit": {
* "requestsRemaining": 0,
* "requestsLimit": 0,
* "tokensRemaining": 0,
* "tokensLimit": 0,
* "requestsReset": "PT0S",
* "tokensReset": "PT0S"
* },
* "usage": {
* "generationTokens": 61,
* "totalTokens": 159,
* "promptTokens": 98
* },
* "promptMetadata": [],
* "empty": true
* },
* "results": [
* {
* "metadata": {
* "finishReason": "STOP",
* "contentFilters": [],
* "empty": true
* },
* "output": {
* "messageType": "ASSISTANT",
* "metadata": {
* "finishReason": "STOP",
* "role": "ASSISTANT",
* "id": "85d6c155-935f-97e8-ae57-94471beb7dfe",
* "messageType": "ASSISTANT"
* },
* "toolCalls": [],
* "media": [],
* "content": "我现在的角色是一个**博学的智能聊天助手**,专门根据用户的提问提供准确、全面和有用的回答。无论你是想了解知识、寻求建议、创作内容,还是单纯想聊聊天,我都会尽力满足你的需求。如果有任何问题,请随时向我提问! 😊",
* "text": "我现在的角色是一个**博学的智能聊天助手**,专门根据用户的提问提供准确、全面和有用的回答。无论你是想了解知识、寻求建议、创作内容,还是单纯想聊聊天,我都会尽力满足你的需求。如果有任何问题,请随时向我提问! 😊"
* }
* }
* ]
* }
*/
@GetMapping("/simple3chatResponse")
public ChatResponse simpleChat3ChatResponse(@RequestBody JSONObject param) {
return chatClient.prompt(CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!"))
.call().chatResponse();
}
/**
* 输入
* {
* "inputInfo":"帮我生成一个user对象,包含姓名、年龄、性别"
* }
*
* 返回
* {
* "name": "张三",
* "age": 25
* }
*
* [
* {
* "name": "张三",
* "age": 25
* }
* ]
*/
@GetMapping("/simple4entity")
public List simpleChat4entity(@RequestBody JSONObject param) {
return chatClient.prompt(CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!"))
.call()
// .entity(User.class);
.entity(new ParameterizedTypeReference>() {
});
}
/**
* flux 流式返回结果 支持实时获取生成过程的分块响应
*/
@GetMapping("/simple5flux")
public Flux simple5flux(@RequestBody JSONObject param) {
return chatClient.prompt().user(CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!"))
.stream().content();
}
```
## 基础API-ChatModel
### controller
```java
// 原子API
@Resource
private ChatModel chatModel;
// 直接提供AI服务
@Resource
private ChatClient chatClient;
/**
* 简单对话 get是可以接受复杂数据json的
* @param param 参数
* @return 回答内容
*/
@GetMapping("/simple")
public String simpleChat(@RequestBody JSONObject param) {
// 接收并校验参数
String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");
// 构建chatClient
ChatClient.Builder builder = ChatClient.builder(chatModel);
ChatClient chatClient = builder.defaultSystem("你是一个精通Java、Python的程序大佬。").build();
System.out.println("输入内容:" + inputInfo);
String content = chatClient.prompt().user(inputInfo).call().content();
System.out.println("回答内容:" + content);
return content;
}
@PostMapping(value = "/stream", produces = "text/event-stream;charset=UTF-8")
public Flux> streamChat(@RequestBody JSONObject param) {
// 接收并校验参数
String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");
return chatModel.stream(new Prompt(inputInfo))
.map(response -> ServerSentEvent.builder()
.data(response.getResult().getOutput().getContent()).build())
.doOnComplete(() -> log.info("响应完成!"))
.doOnError(e -> log.error("响应异常:", e));
}
```
## 默认设置模板绑定_对话id记忆
```java
package com.aisino.springai.demos.chat.controller;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
/**
* @author guochuantao
* @version 1.0
* @description
* @since 2025/4/2 上午11:33
*/
@RestController
@RequestMapping("/chat")
@CrossOrigin
public class DefaultSettingsChatController {
private final ChatClient chatClient;
public DefaultSettingsChatController(ChatClient.Builder chatClient) {
this.chatClient = chatClient
.defaultSystem("你是一个{role},请根据用户提问回答!") // 模板动态修改系统模型角色
.defaultAdvisors(
// 实现 Chat Memory 的 Advisor 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
.build();
}
/**
* {
* "message":"你好",
* "role":"博学多实的温柔可爱的小女生,名字叫璇璇"
* }
* ========如果参数是上面的,输出的结果就是下面的否则就是默认女助手。========
* 你好呀~我是璇璇!今天天气真好,阳光暖暖的,让人心情特别愉快呢。你有什么想和我聊的吗?我很期待和你成为好朋友哦!
*/
@GetMapping("/default")
public String chat(@RequestBody JSONObject param) {
String roleInfo = param.getString("role") != null ? param.getString("role") : "博学的甜美可爱的智能聊天女助手";
String message = param.getString("message");
return chatClient.prompt()
.system(role -> role.param("role", roleInfo))
.user(message)
.call()
//.stream() // 流式返回数据结果
.content();
}
/**
* ChatClient 使用自定义的 Advisor 实现功能增强. [[必须指定同一个 Conversation ID]]
* eg:
* http://127.0.0.1:8080/chat/advisor/123?message=你好,我叫苓茜,之后的会话中都带上我的名字
* 好的,苓茜,很高兴认识你!在之后的会话中,我都会带上你的名字,有什么问题或需要聊的话题吗?苓茜。
* http://127.0.0.1:8080/chat/advisor/123?message=我叫什么名字?
* 你叫苓茜呀。有什么事情想要分享或者讨论吗,苓茜?
*/
@GetMapping("/advisor/{id}")
public Flux advisorChat(
HttpServletResponse response,
@PathVariable("id") String id,// Conversation ID
@RequestParam("message") String message) {
response.setCharacterEncoding("UTF-8");
return this.chatClient.prompt(message)
.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, id)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.stream().content();
}
}
```
## 工具FunctionCalling
### 简单版实现
#### 定义函数工具,交给spring管理
```java
package com.aisino.springai.demos.chat.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
import java.util.function.Function;
@Configuration
public class FunctionTools {
private static final Logger logger = LoggerFactory.getLogger(FunctionTools.class);
// 定义请求参数结构
public record AddRequest(int num1, int num2) {}
public record MultiplyRequest(int num1, int num2) {}
@Bean
@Description("执行加法运算") // 函数描述(AI模型据此理解功能)
public Function addFunction() {
return request -> {
logger.info("调用加法函数:{} + {}", request.num1(), request.num2());
return request.num1() + request.num2();
};
}
@Bean
@Description("执行乘法运算")
public Function multiplyFunction() {
return request -> {
logger.info("调用乘法函数:{} × {}", request.num1(), request.num2());
return request.num1() * request.num2();
};
}
}
```
#### controller调用
```java
/**
* function calling
* {
* "inputInfo":"计算一下78 * 2等于多少"
* }
*
* 计算结果是 78 * 2 = 156。
*
* multiplyFunction会被调用
*/
@GetMapping("/simple6function")
public Flux simple6function(@RequestBody JSONObject param) {
return chatClient.prompt()
.user(CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!"))
.functions("multiplyFunction", "addFunction")
.stream().content();
}
```
### 官方案例-天气 两种方式
https://github.com/springaialibaba/spring-ai-alibaba-examples/tree/main/spring-ai-alibaba-tool-calling-example?spm=4347728f.4c33ebab.0.0.5b795ca9DVTjx3
#### function
##### 获取天气api的key
```java
package com.alibaba.cloud.ai.toolcall.component.weather;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.ai.alibaba.toolcalling.weather")
public class WeatherProperties {
private String apiKey;
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
}
```
##### 解析工具
```java
package com.alibaba.cloud.ai.toolcall.component.weather;
import cn.hutool.extra.pinyin.PinyinUtil;
public class WeatherUtils {
public static String preprocessLocation(String location) {
if (containsChinese(location)) {
return PinyinUtil.getPinyin(location, "");
}
return location;
}
public static boolean containsChinese(String str) {
return str.matches(".*[\u4e00-\u9fa5].*");
}
}
```
##### 需要打模型调用的方法bean,注册
```java
package com.alibaba.cloud.ai.toolcall.component.weather.function;
import com.alibaba.cloud.ai.toolcall.component.weather.WeatherProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
@Configuration
@ConditionalOnClass(WeatherService.class)
@ConditionalOnProperty(prefix = "spring.ai.alibaba.toolcalling.weather", name = "enabled", havingValue = "true")
public class WeatherAutoConfiguration {
@Bean(name = "getWeatherFunction")
@ConditionalOnMissingBean
@Description("Use api.weather to get weather information.")
public WeatherService getWeatherServiceFunction(WeatherProperties properties) {
return new WeatherService(properties);
}
}
```
##### 核心获取天气状态的函数被方法实现
```java
package com.alibaba.cloud.ai.toolcall.component.weather.function;
import com.alibaba.cloud.ai.toolcall.component.weather.WeatherProperties;
import com.alibaba.cloud.ai.toolcall.component.weather.WeatherUtils;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* @author yingzi
* @date 2025/3/27:11:07
*/
public class WeatherService implements Function {
private static final Logger logger = LoggerFactory.getLogger(WeatherService.class);
private static final String WEATHER_API_URL = "https://api.weatherapi.com/v1/forecast.json";
private final WebClient webClient;
private final ObjectMapper objectMapper = new ObjectMapper();
public WeatherService(WeatherProperties properties) {
this.webClient = WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.defaultHeader("key", properties.getApiKey())
.build();
}
public static Response fromJson(Map json) {
Map location = (Map) json.get("location");
Map current = (Map) json.get("current");
Map forecast = (Map) json.get("forecast");
List