change(ai): remove ai module

1116-yuluo/remove-ai
shown 3 months ago committed by GitHub
parent af89634803
commit 4508b85a6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -25,10 +25,6 @@
<!-- Apache RocketMQ -->
<rocketmq.version>5.3.1</rocketmq.version>
<!-- Spring AI -->
<spring.ai.version>1.0.0-M1</spring.ai.version>
<dashscope-sdk-java.version>2.15.1</dashscope-sdk-java.version>
<!-- scheduling -->
<shedlock.version>4.23.0</shedlock.version>
<schedulerx.worker.version>1.11.4</schedulerx.worker.version>
@ -50,19 +46,6 @@
<version>${nacos.client.version}</version>
</dependency>
<!-- Spring AI & dashscope SDK-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>${dashscope-sdk-java.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>${spring.ai.version}</version>
</dependency>
<!-- Sentinel -->
<dependency>
<groupId>com.alibaba.csp</groupId>
@ -235,11 +218,6 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-alibaba-nacos-config</artifactId>
@ -288,14 +266,6 @@
<version>${revision}</version>
</dependency>
<!-- Testing Dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-test</artifactId>
<version>${spring.ai.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2023-2024 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.alibaba.cloud</groupId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-ai-chat-msg-context-example</artifactId>
<name>Spring Cloud Starter Alibaba AI Chat Message Context Holder Example</name>
<description>Example for Chat Message Context Holder By Spring Cloud Alibaba AI</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>

@ -1,35 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@SpringBootApplication
public class ChatMsgApplication {
public static void main(String[] args) {
SpringApplication.run(ChatMsgApplication.class, args);
}
}

@ -1,47 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.context;
import java.util.List;
import org.springframework.ai.chat.messages.Message;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
public interface MessageContextHolder {
/**
* Th default session id key.
* Can use session_id request_id &etc.
*/
String SCA_SESSION_ID = "SCA_SESSION_ID";
void addMsg(String sessionId, Message msg);
void removeMsg(String sessionId);
List<Message> getMsg(String sessionId);
default String getSCASessionId() {
return SCA_SESSION_ID;
}
}

@ -1,76 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.context.defaults;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import com.alibaba.cloud.ai.example.tongyi.context.MessageContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.stereotype.Component;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@Component
public class MemoryMessageContextHolder implements MessageContextHolder {
private static final Logger log = LoggerFactory.getLogger(MemoryMessageContextHolder.class);
private final Map<String, List<Message>> msgContextHolderMap = new HashMap<>();
@Override
public void addMsg(String sessionId, Message msg) {
msgContextHolderMap.computeIfAbsent(sessionId, k -> new ArrayList<>());
log.info("addMsg: sessionId={}, msg={}", sessionId, msg);
}
@Override
public void removeMsg(String sessionId) {
msgContextHolderMap.remove(sessionId);
}
@Override
public List<Message> getMsg(String sessionId) {
return new ArrayList<>(msgContextHolderMap.getOrDefault(sessionId, new ArrayList<>()));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("MessageContextHolderImpl{");
StringJoiner joiner = new StringJoiner(", ", "{", "}");
for (Map.Entry<String, List<Message>> entry : msgContextHolderMap.entrySet()) {
joiner.add(entry.getKey() + "=" + entry.getValue().toString());
}
sb.append("msgContextHolderMap=").append(joiner);
sb.append('}');
return sb.toString();
}
}

@ -1,52 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.context.defaults;
import java.util.List;
import com.alibaba.cloud.ai.example.tongyi.context.MessageContextHolder;
import org.springframework.ai.chat.messages.Message;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
//@Component
public class RedisMessageContextHolder implements MessageContextHolder {
@Override
public void addMsg(String sessionId, Message msg) {
System.out.println("RedisMessageContextHolder addMsg");
}
@Override
public void removeMsg(String sessionId) {
System.out.println("RedisMessageContextHolder removeMsg");
}
@Override
public List<Message> getMsg(String sessionId) {
System.out.println("RedisMessageContextHolder getMsg");
return null;
}
}

@ -1,45 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.controller;
import com.alibaba.cloud.ai.example.tongyi.service.ChatMsgService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@RestController
@RequestMapping("/chat")
public class ChatMsgController {
@Autowired
private ChatMsgService msgService;
@GetMapping("/msg")
public String completion(@RequestParam String message) {
return msgService.completion(message);
}
}

@ -1,69 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service;
import com.alibaba.cloud.ai.example.tongyi.context.MessageContextHolder;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@Service
public class ChatMsgService {
private final ChatModel chatModel;
private final MessageContextHolder messageContextHolder;
@Autowired
public ChatMsgService(ChatModel chatModel, MessageContextHolder messageContextHolder) {
this.chatModel = chatModel;
this.messageContextHolder = messageContextHolder;
}
public String completion(String message) {
// create chat prompt
Prompt prompt = new Prompt(new UserMessage(message));
// collect user message
messageContextHolder.addMsg(
messageContextHolder.getSCASessionId(),
prompt.getInstructions().get(0)
);
ChatResponse resp = chatModel.call(prompt);
// collect model response
messageContextHolder.addMsg(
messageContextHolder.getSCASessionId(),
resp.getResult().getOutput()
);
return resp.getResult().getOutput().getContent();
}
}

@ -1,26 +0,0 @@
#
# Copyright 2023-2024 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
server:
port: 8084
spring:
application:
name: tongyi-example
# please setting api-key. suggestion by environment variable.
# Note: api-key is invalid, please apply for a new one.
# export SPRING_CLOUD_AI_TONGYI_API_KEY=sk-a3d73b1709bf4a178c28ed7c8b3b5a345

@ -1,118 +0,0 @@
# Spring Cloud Alibaba AI Example
## Project description
The Spring Cloud Alibaba AI module is based on [Spring AI 0.8.1](https://docs.spring.io/spring-ai/reference/0.8-SNAPSHOT/index.html) the project API to complete the access of the general series of large models. This project demonstrates how to use `spring-cloud-starter-alibaba-ai` the Spring Cloud microservice application to integrate with the generic family model.
[model service dashscope](https://help.aliyun.com/zh/dashscope/) It is a big model application service launched by Alibaba. Based on the concept of "Model-as-a-Service" (MaaS), Lingji Model Service provides a variety of model services including model reasoning and model fine-tuning training through standardized APIs around AI models in various fields.
- Current completion of spring-ai chat api and image api.
## Application access
### Access `spring-cloud-starter-alibaba-ai`
1. Add the following dependencies to the project POM. XML:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
```
2. Add the following configuration to the application. Yml configuration file:
> Note: It is recommended to set the api-key as an environment variable to avoid api-key leakage.
>
> ```shell
> export SPRING_CLOUD_AI_TONGYI_API_KEY=sk-a3d73b1709bf4a178c28ed7c8b3b5a45
> ```
```yml
spring:
cloud:
ai:
tongyi:
# apikey is invalid.
api-key: sk-a3d73b1709bf4a178c28ed7c8b3b5a45
```
3. Add the following code:
```yml
controller:
@Autowired
@Qualifier("tongYiSimpleServiceImpl")
private TongYiService tongYiSimpleService;
@GetMapping("/example")
public String completion(
@RequestParam(value = "message", defaultValue = "Tell me a joke")
String message
) {
return tongYiSimpleService.completion(message);
}
service:
private final ChatClient chatClient;
@Autowired
public TongYiSimpleServiceImpl(ChatClient chatClient, StreamingChatClient streamingChatClient) {
this.chatClient = chatClient;
this.streamingChatClient = streamingChatClient;
}
@Override
public String completion(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return chatClient.call(prompt).getResult().getOutput().getContent();
}
```
At this point, the simplest model access is complete! It is slightly different from the code in this example, but the code in the example does not need to be modified. The corresponding function can be accomplished without modification.
4. Start the application
This Example project supports the following two startup methods:
1. IDE direct startup: find the main class `TongYiApplication` and execute the main method to start the application.
2. Start after packaging and compiling: First `mvn clean package`, compile and package the project, and then enter the `target` folder to `java -jar spring-cloud-ai-example.jar` start the application.
## Validate
Browser address bar input: `http://localhost:8080/ai/example`
The following response is returned:
```json
{
"Tell me a joke": "Sure, here's a classic one for you:\n\nWhy was the math book sad?\n\nBecause it had too many problems.\n\nI hope that made you smile! If you're looking for more, just let me know."
}
```
## Simple front pages
cd `resources/static`open index.html file by local browser, input your question. then you get ai-models output(make api-keys effective):
![ai-example](./images/sca-ai-example-front.gif)
## Configuration item description
More configuration refer:
https://help.aliyun.com/zh/dashscope/developer-reference/api-details
## More examples:
This example consists of 6 samples, implemented by different serviceimpl, you can refer to the readme file in each serviceimpl, use @RestController as the entry point in the controller, you can use the browser or curl tool to request the interface. You can use a browser or curl tool to request the interface.
> The example is already functional and does not require any code changes. Just replace the apikey in the corresponding example, the apikey provided in the project is invalid.

@ -1,117 +0,0 @@
# Spring Cloud Alibaba AI Example
## 项目说明
Spring Cloud Alibaba AI 模块基于 [Spring AI 0.8.1](https://docs.spring.io/spring-ai/reference/0.8-SNAPSHOT/index.html) 项目 API 完成通义系列大模型的接入。本项目演示如何使用 `spring-cloud-starter-alibaba-ai` 完成 Spring Cloud 微服务应用与通义系列模型的整合。
[模型服务灵积](https://help.aliyun.com/zh/dashscope/) 是阿里巴巴推出的一个大模型应用服务。灵积模型服务建立在“模型即服务”Model-as-a-ServiceMaaS的理念基础之上围绕AI各领域模型通过标准化的API提供包括模型推理、模型微调训练在内的多种模型服务。
- 目前只完成 spring-ai chat completion api 和 image api 的接入。
## 应用接入
### 接入 `spring-cloud-starter-alibaba-ai`
1. 在项目 pom.xml 中加入以下依赖:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
```
2. 在 application.yml 配置文件中加入以下配置:
> Note: 推荐使用环境变量的方式设置 api-key避免 api-key 泄露。
>
> ```shell
> export SPRING_CLOUD_AI_TONGYI_API_KEY=sk-a3d73b1709bf4a178c28ed7c8b3b5a45
> ```
```yaml
spring:
cloud:
ai:
tongyi:
# apiKey is invalid.
api-key: sk-a3d73b1709bf4a178c28ed7c8b3b5a45
```
3. 添加如下代码:
```java
controller:
@Autowired
@Qualifier("tongYiSimpleServiceImpl")
private TongYiService tongYiSimpleService;
@GetMapping("/example")
public String completion(
@RequestParam(value = "message", defaultValue = "Tell me a joke")
String message
) {
return tongYiSimpleService.completion(message);
}
service:
private final ChatClient chatClient;
@Autowired
public TongYiSimpleServiceImpl(ChatClient chatClient, StreamingChatClient streamingChatClient) {
this.chatClient = chatClient;
this.streamingChatClient = streamingChatClient;
}
@Override
public String completion(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return chatClient.call(prompt).getResult().getOutput().getContent();
}
```
至此,便完成了最简单的模型接入!和本 example 中的代码略有不同,但 example 中的代码无需修改。可完成对应功能。
4. 启动应用
本 Example 项目支持如下两种启动方式:
1. IDE 直接启动:找到主类 `TongYiApplication`,执行 main 方法启动应用。
2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,进入 `target` 文件夹执行 `java -jar spring-cloud-ai-example.jar` 启动应用。
## 验证
浏览器地址栏输入:`http://localhost:8080/ai/example`
返回如下响应:
```json
{
"Tell me a joke": "Sure, here's a classic one for you:\n\nWhy was the math book sad?\n\nBecause it had too many problems.\n\nI hope that made you smile! If you're looking for more, just let me know."
}
```
## 简易前端验证
进入 `resources/static` 目录下,使用浏览器打开 index.html 文件,输入问题,即可获得输出响应(确保 api-key 有效):
![ai-example](./images/sca-ai-example-front.gif)
## 配置项说明
更多配置项参考:
https://help.aliyun.com/zh/dashscope/developer-reference/api-details
## 更多 example 示例:
本 example 由 6 个示例组成,由不同的 serviceimpl 实现,您可以参考每个 serviceimpl 中的 readme 文件,在 controller 中使用 @RestController 作为入口点,您可以使用浏览器或者 curl 工具对接口进行请求。完成对应功能接入的练习。
> example 已完成功能,无需修改代码。只在对应的 example 中替换 apikey 即可,项目中提供的 apikey 无效。

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2023-2024 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.alibaba.cloud</groupId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-ai-example</artifactId>
<name>Spring Cloud Starter Alibaba AI Example</name>
<description>Example demonstrating how to use Spring Cloud Alibaba AI</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>

@ -1,36 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@SpringBootApplication
public class TongYiApplication {
public static void main(String[] args) {
SpringApplication.run(TongYiApplication.class);
}
}

@ -1,172 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.controller;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.models.ActorsFilms;
import com.alibaba.cloud.ai.example.tongyi.models.Completion;
import com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionParam;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.image.ImageResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* TongYi models Spring Cloud Alibaba Controller.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@RestController
@RequestMapping("/ai")
@CrossOrigin
public class TongYiController {
@Autowired
@Qualifier("tongYiSimpleServiceImpl")
private TongYiService tongYiSimpleService;
@GetMapping("/example")
public String completion(
@RequestParam(value = "message", defaultValue = "Tell me a joke")
String message
) {
return tongYiSimpleService.completion(message);
}
@GetMapping("/stream")
public Map<String, String> streamCompletion(
@RequestParam(value = "message", defaultValue = "请告诉我西红柿炖牛腩怎么做?")
String message
) {
return tongYiSimpleService.streamCompletion(message);
}
@Autowired
@Qualifier("tongYiOutputParseServiceImpl")
private TongYiService tongYiOutputService;
@GetMapping("/output")
public ActorsFilms generate(
@RequestParam(value = "actor", defaultValue = "Jeff Bridges") String actor
) {
return tongYiOutputService.genOutputParse(actor);
}
@Autowired
@Qualifier("tongYiPromptTemplateServiceImpl")
private TongYiService tongYiPromptTemplateService;
@GetMapping("/prompt-tmpl")
public AssistantMessage completion(@RequestParam(value = "adjective", defaultValue = "funny") String adjective,
@RequestParam(value = "topic", defaultValue = "cows") String topic) {
return tongYiPromptTemplateService.genPromptTemplates(adjective, topic);
}
@Autowired
@Qualifier("tongYiRolesServiceImpl")
private TongYiService tongYiRolesService;
@GetMapping("/roles")
public AssistantMessage generate(
@RequestParam(value = "message", defaultValue = "Tell me about three famous pirates from the Golden Age of Piracy and why they did. Write at least a sentence for each pirate.") String message,
@RequestParam(value = "name", defaultValue = "bot") String name,
@RequestParam(value = "voice", defaultValue = "pirate") String voice) {
return tongYiRolesService.genRole(message, name, voice);
}
@Autowired
@Qualifier("tongYiStuffServiceImpl")
private TongYiService tongYiStuffService;
@GetMapping("/stuff")
public Completion completion(@RequestParam(value = "message",
defaultValue = "Which athletes won the mixed doubles gold medal in curling at the 2022 Winter Olympics?") String message,
@RequestParam(value = "stuffit", defaultValue = "false") boolean stuffit) {
return tongYiStuffService.stuffCompletion(message, stuffit);
}
@Autowired
@Qualifier("tongYiImagesServiceImpl")
private TongYiService tongYiImgService;
@GetMapping("/img")
public ImageResponse genImg(@RequestParam(value = "prompt",
defaultValue = "Painting a picture of blue water and blue sky.") String imgPrompt) {
return tongYiImgService.genImg(imgPrompt);
}
@Autowired
@Qualifier("tongYiAudioSimpleServiceImpl")
private TongYiService tongYiAudioService;
@GetMapping("/audio/speech")
public String genAudio(@RequestParam(value = "prompt",
defaultValue = "你好Spring Cloud Alibaba AI 框架!") String prompt) {
return tongYiAudioService.genAudio(prompt);
}
@Autowired
@Qualifier("tongYiAudioTranscriptionServiceImpl")
private TongYiService tongYiAudioTranscriptionService;
/**
* audio transcription. Support urls audio resource.
* {@link Resource}
* {@link TranscriptionParam}
* @param url audio url.
* @return transcription result, is String type.
*/
@GetMapping("/audio/transcription")
public String audioTranscription(@RequestParam(value = "audioUrls",
defaultValue = "https://dashscope.oss-cn-beijing.aliyuncs.com/samples/audio/paraformer/realtime_asr_example.wav") String url) {
return tongYiAudioTranscriptionService.audioTranscription(url);
}
@Autowired
@Qualifier("tongYiTextEmbeddingServiceImpl")
private TongYiService tongYiTextEmbeddingService;
@GetMapping("/textEmbedding")
public List<Double> textEmbedding(@RequestParam(value = "text",
defaultValue = "Spring Cloud Alibaba AI 框架!") String text) {
return tongYiTextEmbeddingService.textEmbedding(text);
}
}

@ -1,57 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.models;
import java.util.List;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
public class ActorsFilms {
private String actor;
private List<String> movies;
public ActorsFilms() {
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
public List<String> getMovies() {
return movies;
}
public void setMovies(List<String> movies) {
this.movies = movies;
}
@Override
public String toString() {
return "ActorsFilms{" + "actor='" + actor + '\'' + ", movies=" + movies + '}';
}
}

@ -1,37 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.models;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
public class Completion {
private final String completion;
public Completion(String completion) {
this.completion = completion;
}
public String getCompletion() {
return completion;
}
}

@ -1,108 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.models.ActorsFilms;
import com.alibaba.cloud.ai.example.tongyi.models.Completion;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.image.ImageResponse;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
public abstract class AbstractTongYiServiceImpl implements TongYiService {
private static final String INFO_PREFIX = "please implement ";
private static final String INFO_SUFFIX = "() method.";
@Override
public String completion(String message) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread().getStackTrace()[2].getMethodName());
}
@Override
public Map<String, String> streamCompletion(String message) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public ActorsFilms genOutputParse(String actor) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public AssistantMessage genPromptTemplates(String adjective, String topic) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public AssistantMessage genRole(String message, String name, String voice) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public Completion stuffCompletion(String message, boolean stuffit) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public ImageResponse genImg(String imgPrompt) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public String genAudio(String text) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public List<Double> textEmbedding(String text) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
@Override
public String audioTranscription(String url) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
}

@ -1,117 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.models.ActorsFilms;
import com.alibaba.cloud.ai.example.tongyi.models.Completion;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.ai.image.ImageResponse;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
public interface TongYiService {
/**
* Hello World example.
*
* @param message conversation content question.
* @return AI answer.
*/
String completion(String message);
/**
* Stream call.
*
* @param message conversation content question.
* @return AI answer.
*/
Map<String, String> streamCompletion(String message);
/**
* Output parse object.
*
* @param actor actor name.
* @return Object.
*/
ActorsFilms genOutputParse(String actor);
/**
* Prompt template.
*
* @param adjective params1.
* @param topic params2.
* @return AI answer.
*/
AssistantMessage genPromptTemplates(String adjective, String topic);
/**
* AI role example.
*
* @param message question content,
* @param name params1.
* @param voice params2.
* @return AI answer.
*/
AssistantMessage genRole(String message, String name, String voice);
/**
* Stuff and answer.
*
* @param message question.
* @param stuffit is stuff.
* @return Completion object.
*/
Completion stuffCompletion(String message, boolean stuffit);
/**
* Gen images.
* @param imgPrompt prompt info.
* @return {@link ImageResponse}
*/
ImageResponse genImg(String imgPrompt);
/**
* Gen audio.
* @param text prompt info.
* @return ByteBuffer object.
*/
String genAudio(String text);
/**
* Audio Transcription.
* @param audioUrls url of the audio file to be transcribed.
* @return the result file Path.
*/
String audioTranscription(String audioUrls);
/**
* TongYI LLM Text embedding.
* @param text input text.
* @return {@link EmbeddingResponse}
*/
List<Double> textEmbedding(String text);
}

@ -1,24 +0,0 @@
# Spring Cloud Alibaba AI Audio
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/audio`
`controller` 将会调用 `TongYiService` 中的 `genAudio` 方法,完成服务请求得到响应。
有一个可选的 `ptompt` 参数其默认值为“你好Spring Cloud Alibaba AI 框架!”。 请求响应来自 Alibaba TongYi 语音合成服务。
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl http://localhost:8080/ai/audio
# Response:
D:\open_sources\sca-ai\spring-ai\04-11-18-21-59.wav
```
返回参数为保存到当前根路径下的音频文明路径。

@ -1,82 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.audio.speech;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.SpeechModel;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisAudioFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
@Service
public class TongYiAudioSimpleServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiService.class);
private final SpeechModel speechClient;
@Autowired
public TongYiAudioSimpleServiceImpl(SpeechModel client) {
this.speechClient = client;
}
@Override
public String genAudio(String text) {
logger.info("gen audio prompt is: {}", text);
var resWAV = speechClient.call(text);
return save(resWAV, SpeechSynthesisAudioFormat.WAV.getValue());
}
private String save(ByteBuffer audio, String type) {
String currentPath = System.getProperty("user.dir");
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd-HH-mm-ss");
String fileName = currentPath + File.separator + now.format(formatter) + "." + type;
File file = new File(fileName);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(audio.array());
}
catch (Exception e) {
throw new RuntimeException(e);
}
return fileName;
}
}

@ -1,25 +0,0 @@
# Spring Cloud Alibaba AI Audio Transcription
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/audio/transcription`
`controller` 将会调用 `TongYiService` 中的 `audioTranscription` 方法,完成服务请求得到响应。
可设置`file_urls`参数,提供一个或多个需要进行语音识别的音视频文件。
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl -X GET "http://localhost:8080/ai/audio/transcription?audioUrls=url1&audioUrls=url2"
# Response:
D:\Code\spring-cloud-alibaba\05-13-20-47-08.txt
D:\Code\spring-cloud-alibaba\05-13-20-47-09.txt
```
返回参数为保存到当前根路径下的音频转录文本文件的路径。

@ -1,126 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.audio.transcription;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import com.alibaba.cloud.ai.tongyi.audio.transcription.TongYiAudioTranscriptionModel;
import com.alibaba.cloud.ai.tongyi.audio.transcription.api.AudioTranscriptionPrompt;
import com.alibaba.cloud.ai.tongyi.audio.transcription.api.AudioTranscriptionResult;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
@Service
public class TongYiAudioTranscriptionServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiService.class);
private final TongYiAudioTranscriptionModel audioTranscriptionModel;
@Autowired
public TongYiAudioTranscriptionServiceImpl(final TongYiAudioTranscriptionModel transcriptionModel) {
this.audioTranscriptionModel = transcriptionModel;
}
@Override
public String audioTranscription(String audioUrls) {
Resource resource;
try {
resource = new UrlResource(audioUrls);
}
catch (IOException e) {
logger.error("Failed to create resource.");
throw new RuntimeException(e);
}
AudioTranscriptionPrompt audioTranscriptionPrompt = new AudioTranscriptionPrompt(resource);
return save(audioTranscriptionModel.call(audioTranscriptionPrompt).getResults());
}
private String save(List<AudioTranscriptionResult> resultList) {
String currentPath = System.getProperty("user.dir");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd-HH-mm-ss");
StringBuilder retPaths = new StringBuilder();
for (AudioTranscriptionResult audioTranscriptionResult : resultList) {
String tUrl = audioTranscriptionResult.getOutput();
LocalDateTime now = LocalDateTime.now();
String fileName = currentPath + File.separator + now.format(formatter) + ".txt";
retPaths.append(fileName).append("\n");
try {
URL url = new URL(tUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
StringBuilder sb = new StringBuilder();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream()); FileOutputStream fileOutputStream = new FileOutputStream(fileName)) {
byte[] dataBuffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
sb.append(new String(dataBuffer, 0, bytesRead));
}
JsonObject rootObj = JsonParser.parseString(sb.toString()).getAsJsonObject();
JsonArray transcriptsArray = rootObj.getAsJsonArray("transcripts");
for (var transcriptElement : transcriptsArray) {
JsonObject transcriptObj = transcriptElement.getAsJsonObject();
String text = transcriptObj.get("text").getAsString();
fileOutputStream.write(text.getBytes());
}
logger.info("File downloaded successfully{}\n", fileName);
}
}
else {
logger.error("The download failed, and the response code{}",
responseCode);
}
connection.disconnect();
}
catch (IOException e) {
logger.error("An error occurred during the file download process.");
}
}
return retPaths.toString();
}
}

@ -1,39 +0,0 @@
# Spring Cloud Alibaba AI Hello World
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/example`
`controller` 将会调用 `TongYiService` 中的 `completion` 方法,完成服务请求得到响应。
有一个可选的 `message` 参数,其默认值为“告诉我一个笑话”。 请求响应来自 Alibaba TongYi models 服务。
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl http://localhost:8080/ai/example
# Response:
Sure, here's one for you:
Why don't scientists trust atoms?
Because they make up everything!
```
现在使用 message 参数:
```shell
$ curl --get --data-urlencode 'message=Tell me a joke about a cow.' http://localhost:8080/ai/example
# Response:
Here's a classic cow joke for you:
Why did the farmer separate the chicken and the sheep from the cows?
Because he wanted to have eggs-clusive relationships with his hens!
```

@ -1,85 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.helloworld;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.StreamingChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* The Chat simple example service implementation.
* There is optional message parameter whose default value is "Tell me a joke".
* pl The response to the request is from the TongYi models Service.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@Service
public class TongYiSimpleServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiSimpleServiceImpl.class);
private final ChatModel chatModel;
private final StreamingChatModel streamingChatModel;
@Autowired
public TongYiSimpleServiceImpl(ChatModel chatModel, StreamingChatModel streamingChatModel) {
this.chatModel = chatModel;
this.streamingChatModel = streamingChatModel;
}
@Override
public String completion(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return chatModel.call(prompt).getResult().getOutput().getContent();
}
@Override
public Map<String, String> streamCompletion(String message) {
StringBuilder fullContent = new StringBuilder();
streamingChatModel.stream(new Prompt(message))
.flatMap(chatResponse -> Flux.fromIterable(chatResponse.getResults()))
.map(content -> content.getOutput().getContent())
.doOnNext(fullContent::append)
.last()
.map(lastContent -> Map.of(message, fullContent.toString()))
.block();
logger.info(fullContent.toString());
return Map.of(message, fullContent.toString());
}
}

@ -1,75 +0,0 @@
# Spring Cloud Alibaba AI Image
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/img`
`controller` 将会调用 `TongYiService` 中的 `genImg` 方法,完成服务请求得到响应。
有一个可选的 `prompt` 参数为生成图片的提示信息其默认值为“Painting a picture of blue water and blue sky.”。
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl http://localhost:8080/ai/img
```
响应结果为:(base64 数据太多,使用 `xxx` 代替)
```json
{
"result": {
"output": {
"url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/1d/42/20240408/8d820c8d/e9913e23-24e9-4de7-8977-e4ccab33a231-1.png?Expires=1712670359&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=bMEaNS0RGTpD2yXO0lTMMY5AWxM%3D",
"b64Json": "xxxx"
},
"metadata": null
},
"metadata": {
"created": 1712583967354,
"taskId": "43d7e458-9f3e-4a51-9a39-17559d8f135d",
"metrics": {
"total": 4,
"succeeded": 4,
"failed": 0
},
"usage": {
"imageCount": 4
}
},
"results": [
{
"output": {
"url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/1d/42/20240408/8d820c8d/e9913e23-24e9-4de7-8977-e4ccab33a231-1.png?Expires=1712670359&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=bMEaNS0RGTpD2yXO0lTMMY5AWxM%3D",
"b64Json": "xxxx"
},
"metadata": null
},
{
"output": {
"url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/1d/2b/20240408/8d820c8d/0bd0b40f-4e34-46da-8706-8f2ec86274d7-1.png?Expires=1712670359&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=dl3sMGQn8p7y%2FzKOmPR%2B64prQV4%3D",
"b64Json": "xxxx"
},
"metadata": null
},
{
"output": {
"url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/1d/62/20240408/c34adf05/ffb89a14-14c5-4740-ab55-37b59a69aaef-1.png?Expires=1712670359&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=vYd667hVPQUTt8aiJDBFxN%2B08jI%3D",
"b64Json": "xxxx"
},
"metadata": null
},
{
"output": {
"url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/1d/b5/20240408/8d820c8d/594b8672-c1ce-49b6-bab0-06e3616b4e0e-1.png?Expires=1712670359&OSSAccessKeyId=LTAI5tQZd8AEcZX6KZV4G8qL&Signature=ERyoV7pmmI8sZJwSFLpzknhfFEk%3D",
"b64Json": "xxxx"
},
"metadata": null
}
]
}
```

@ -1,53 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.images;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* gen images example.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@Service
public class TongYiImagesServiceImpl extends AbstractTongYiServiceImpl {
private final ImageModel imageModel;
@Autowired
public TongYiImagesServiceImpl(ImageModel imageModel) {
this.imageModel = imageModel;
}
@Override
public ImageResponse genImg(String imgPrompt) {
var prompt = new ImagePrompt(imgPrompt);
return imageModel.call(prompt);
}
}

@ -1,137 +0,0 @@
# Spring Cloud Alibaba AI Output
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/output`
`controller` 将会调用 `TongYiService` 中的 `genOutputParse` 方法,完成服务请求得到响应。
有一个可选的 `actor` 参数为演员的名字其默认值为“Jeff Bridges”。
演员姓名位于硬编码的文本中:
```java
String userMessage = """
Generate the filmography for the actor {actor}.
{format}
""";
```
`format` 变量来自 `OutputParser` 中。
## BeanOutputParser
`BeanOutputParser` 当请求获得响应时,会将响应内容解析为一个 JavaBean 并返回给用户:
```java
var outputParser = new BeanOutputParser<>(ActorsFilms.class);
String format = outputParser.getFormat();
```
模型响应内容会被解析为类 `ActorsFils` 的类属性:
```java
ActorsFilms actorsFilms = outputParser.parse(content);
```
> 返回的数据结构必须符合 `https://json-schema.org/draft/2020-12/schema` 规范,不然会解析失败,这里采用替换的方式先行处理:
> ```java
> // {@link BeanOutputParser#getFormat}
> // simple solve.
> String content = generation.getOutput().getContent()
> .replace("```json", "")
> .replace("```", "");
> ```
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl http://localhost:8080/ai/output
```
响应结果为:
```json
{
"actor": "Jeff Bridges",
"movies": [
"Starman (1984)",
"The Natural (1984)",
"The Longest Week (1979)",
"Against the Wind (1977)",
"Fat City (1978)",
"American Heart (1981)",
"Iron Eagle (1986)",
"The China Syndrome (1979)",
"Blazing Saddles (1974)",
"Winter Kills (1979)",
"Theetreowners of Broadway (1973)",
"Trinity (1990)",
"Tokyo Story (1953) [English dub voice]",
"The Longest Ride (2015)",
"Another Day in the Valley (1991)",
"Stardust Memories (1976)",
"Jagged Edge (1985)",
"The Adventures of Ford Fairlane (1996)",
"The Vanishing (1988)",
"Dances with Wolves (1990)",
"The Mirror Has Two Faces (1996)",
"The Door in the Floor (2004)",
"The Way Back (2010)",
"The Secret Life of Walter Mitty (2013)",
"The Big Lebowski (1998)",
"The Iron Giant (1999) [voice]",
"The Man Who Wasn't There (2001)",
"True Grit (2010)",
"Crazy Heart (2009)",
"Iron Man 2 (2010) [voice]",
"TRON: Legacy (2010)",
"The Giver (2014)",
"Hell or High Water (2016)"
]
}
```
现在使用 actor 参数:
```shell
$ curl --get --data-urlencode 'actor=Bill Murray' http://localhost:8080/ai/output
```
Response
```json
{
"actor": "Bill Murray",
"movies": [
"The<[email protected]>",
"Caddyshack",
"Ghostbusters",
"Stripes",
"Scrooged",
"Groundhog Day",
"What About Bob?",
" Coneheads ",
"Stuart Saves His Family",
" Rushmore",
"The Royal Tenenbaums",
"Lost in Translation",
"Garfield: The Movie",
"Osmosis Jones",
"The Life Aquatic with Steve Zissou",
"Hot Fuzz",
"Get Smart",
"The Grand Budapest Hotel",
"Trainwreck",
"Chef",
"Inside Out",
"Zootopia",
"On the Rocks",
"Spies in Disguise"
]
}
```

@ -1,76 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.output;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.models.ActorsFilms;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.stereotype.Service;
/**
* The BeanOutputParser generates an TongYI JSON compliant schema for a
* JavaBean and provides instructions to use that schema when replying to a request.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@Service
public class TongYiOutputParseServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiOutputParseServiceImpl.class);
private final ChatModel chatModel;
public TongYiOutputParseServiceImpl(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public ActorsFilms genOutputParse(String actor) {
var outputParser = new BeanOutputConverter<>(ActorsFilms.class);
String format = outputParser.getFormat();
logger.info("format: " + format);
String userMessage = """
Generate the filmography for the actor {actor}.
{format}
""";
PromptTemplate promptTemplate = new PromptTemplate(userMessage, Map.of("actor", actor, "format", format));
Prompt prompt = promptTemplate.create();
Generation generation = chatModel.call(prompt).getResult();
// {@link BeanOutputParser#getFormat}
// simple solve.
String content = generation.getOutput().getContent()
.replace("```json", "")
.replace("```", "");
return outputParser.parse(content);
}
}

@ -1,65 +0,0 @@
# Spring Cloud Alibaba AI Prompt Template
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/prompt-tmpl`
`controller` 将会调用 `TongYiService` 中的 `genPromptTemplates` 方法,完成服务请求得到响应。
本示例代码展示如何使用 `StringTemplate` 引擎和 `Spring AI PromptTemplate` 类。目录中 `resources\prompts` 文件为 `joke-prompt`。此文件由 Spring 加载:
```java
@Value("classpath:/prompts/joke-prompt.st")
private Resource jokeResource;
```
文件内容为:
```json
Tell me a {adjective} joke about {topic}.
```
接受两个可选参数:
1. `adjective`,其默认值为 `funny`
2. `topic`,其默认值为 `cows`
请求响应来自 Alibaba TongYi models.
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl http://localhost:8080/ai/prompt-tmpl
```
响应结果为:
```json
{
"messageType": "ASSISTANT",
"properties": {},
"content": "Why did the cow go to art school? Because she wanted to learn how to draw moo-vements!",
"media": []
}
```
现在使用 adjective 和 topic 参数:
```shell
$ curl --get --data-urlencode message='Tell me about 3 famous physicists' --data-urlencode name='yuluo' --data-urlencode voice='Rick Sanchez' http://localhost:8080/ai/roles
```
Response
```json
{
"messageType": "ASSISTANT",
"properties": {},
"content": "Sure, here's another one:\n\nWhy did the farmer separate the chicken and the cow?\n\nBecause he wanted to have eggs-clusive relationships with his hens!",
"media": []
}
```

@ -1,60 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.prompttemplate;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
/**
* The TongYiPromptTemplateServiceImpl shows how to use the StringTemplate Engine and the Spring AI PromptTemplate class.
* In the resources\prompts directory is the file joke-prompt.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@Service
public class TongYiPromptTemplateServiceImpl extends AbstractTongYiServiceImpl {
private final ChatModel chatModel;
@Value("classpath:/prompts/joke-prompt.st")
private Resource jokeResource;
public TongYiPromptTemplateServiceImpl(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public AssistantMessage genPromptTemplates(String adjective, String topic) {
PromptTemplate promptTemplate = new PromptTemplate(jokeResource);
Prompt prompt = promptTemplate.create(Map.of("adjective", adjective, "topic", topic));
return chatModel.call(prompt).getResult().getOutput();
}
}

@ -1,80 +0,0 @@
# Spring Cloud Alibaba AI Roles
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/roles`
`controller` 将会调用 `TongYiService` 中的 `genRole` 方法,完成服务请求得到响应。
接受带有三个可选参数的PromptTemplateControllerHTTP GET 请求http://localhost:8080/ai/roles
`message` 用户请求消息,默认值为 `Tell me about three famous pirates from the Golden Age of Piracy and why they did. Write at least a sentence for each pirate.`;
`name` AI助手的名字默认值为 `Bob`
`voice` AI 助手回复的语音风格。默认值为 `pirate`
请求响应来自 Alibaba TongYi models 服务。
## Roles
对于每个角色,都会创建一条消息,该消息将作为提示的一部分发送到 AI 模型。
> 用户消息是“消息”的内容。
>
> 系统消息为 AI 模型设置响应的上下文。
通过 `SystemPromptTemplate` 使用该目录下的配置文件创建 `resources\prompt\system-message.st`
该文件内容为:
```json
You are a helpful AI assistant.
You are an AI assistant that helps people find information.
Your name is {name}
You should reply to the user's request with your name and also in the style of a {voice}.
```
用户消息和系统消息组合在一起创建 `Prompt`
```java
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
```
> 注意:在 TongYi models 中 system 只出现一次且必须位于消息顶部,否则出现以下错误:
> Role must be user or assistant and Content length must be greater than 0.
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl http://localhost:8080/ai/roles
```
Response:
```json
{
"messageType": "ASSISTANT",
"properties": {},
"content": "Ahoy matey! I be Bot, the swashbucklin' AI at yer service! Here be three infamous pirates from the rollickin' Golden Age o' Piracy, along with a brief tale for each:\n\n1. Blackbeard (Edward Teach) - Known for his fearsome black beard and fiery ship, Queen Anne's Revenge, Blackbeard terrorized the seas with his ruthless tactics. He sought treasure and power, often leaving a trail of dread behind.\n\n2. Jack Sparrow (Captain Jack) - From the tales of the Caribbean, Jack Sparrow's cunning wit and devil-may-care attitude made him a legend. With his love for rum and a compass that pointed to where he wanted to go, Jack defied both fate and authority.\n\n3. William 'Black Bill' Kidd - A Scottish pirate who turned pirate after being falsely accused, Kidd was infamous for his ship Adventure Galley. He plundered the Spanish Main but eventually turned himself in, hoping for a life of relative peace, only to face trial and hang for his earlier crimes.\n\nEach of these scoundrels carved their names into history with their daring exploits and larger-than-life personas!",
"media": []
}
```
现在使用 message, name, voice 参数:
```shell
$ curl --get --data-urlencode message=="Tell me about 3 famous physicists" name=="yuluo" voice=="Rick Sanchez" http://localhost:8080/ai/roles
```
Response:
```json
{
"messageType":"ASSISTANT",
"properties":{},
"content":"Yo dawg, I'm Yuluo, the quantum physicist's best friend! Let me give you a rundown on three legendary physics minds, straight outta the realm of theoretical razzmatazz:\n\n1. Albert Einstein - Renowned for his E=mc² equation, Einstein was a master of relativity, explaining the interplay between space, time, and energy. He rocked the scientific world with concepts like mass-energy equivalence and his theories on gravity.\n\n2. Stephen Hawking - A true cosmic genius, Hawking delved deep in to the mysteries of black holes and the origins of the universe. Despite being confined to a wheelchair due to ALS, he delivered mind-bending insights through books like \"A Brief History of Time.\"\n\n3. Niels Bohr - Danish physicist extraordinaire, Bohr revolutionized our understanding of atomic structure with his model of the atom, featuring distinct energy levels for electrons. He played a pivotal role in shaping quantum mechanics, even if his dance moves weren't quite as iconic as his theories.\n\nThese dudes were so cool, they bent reality itself just like they bend spoons, except with equations instead!",
"media":[]}
```

@ -1,71 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.roles;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@Service
public class TongYiRolesServiceImpl extends AbstractTongYiServiceImpl {
private final ChatModel chatModel;
public TongYiRolesServiceImpl(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Value("classpath:/prompts/assistant-message.st")
private Resource systemResource;
@Override
public AssistantMessage genRole(String message, String name, String voice) {
/**
TongYi model rules: Role must be user or assistant and Content length must be greater than 0.
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
org.springframework.ai.chat.messages.Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice));
In TongYi models, System role must appear at the top of the message and can only appear once.
https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.4dbcc11akAaRbs#b9ad0a10cfhpe
*/
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
org.springframework.ai.chat.messages.Message systemPromptTemplateMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice));
UserMessage userMessage = new UserMessage(message);
Prompt prompt = new Prompt(List.of(systemPromptTemplateMessage, userMessage));
return chatModel.call(prompt).getResult().getOutput();
}
}

@ -1,67 +0,0 @@
# Spring Cloud Alibaba AI Stuff models
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/stuff`
`controller` 将会调用 `TongYiService` 中的 `stuffCompletion` 方法,完成服务请求得到响应。
模型训练数据仅仅到 2021 年,对之后的问题无法回答。此示例将演示如何使用一些数据完成对模型上下文的填充,使得模型能够回答更多的问题。
将以 2022 年的冰壶比赛冠军是谁为例进行演示。
```shell
$ curl http://localhost:8080/ai/stuff
{"completion":"As previously mentioned, my knowledge is current until 2021 and I do not have information about the 2022 Winter Olympics results. Therefore, I can't provide the winner of the mixed doubles gold medal in curling from those games."}
```
## 预处理内容
### qa-prompt.st
```json
Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
Helpful Answer:
```
```md
Jack and arokg, slim won the gold medal in mixed doubles curling at the 2022 Winter Olympics.
```
以上内容将由 Spring 加载到 Resource 对象中:
```java
@Value("classpath:/docs/wikipedia-curling.md")
private Resource docsToStuffResource;
@Value("classpath:/prompts/qa-prompt.st")
private Resource qaPromptResource;
```
一并填充到 Prompt 中,由 Alibaba TongYi models 返回响应。
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具对接口发起请求:
```shell
$ curl http://localhost:8080/ai/stuff
# Response:
{"completion":"I'm sorry, but I don't have information on the specific winners of the 2022 Winter Olympics events. My knowledge is current until 2021, and I cannot provide real-time or future sports results."}
```
现在使用 stuffit 参数:
```shell
$ curl --get --data-urlencode 'stuffit=true' http://localhost:8080/ai/stuff
# Response:
{"completion":"The athletes who won the mixed doubles gold medal in curling at the 2022 Winter Olympics, according to the provided context, were Jack and Arokg Slim."}
```

@ -1,75 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.stuff;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.models.Completion;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
/**
* Train the model using pre-found data to enhance the AI model to achieve the desired results.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@Service
public class TongYiStuffServiceImpl extends AbstractTongYiServiceImpl {
private final ChatModel chatModel;
public TongYiStuffServiceImpl(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Value("classpath:/docs/wikipedia-curling.md")
private Resource docsToStuffResource;
@Value("classpath:/prompts/qa-prompt.st")
private Resource qaPromptResource;
// TongYi model: Range of input length should be [1, 6000]
@Override
public Completion stuffCompletion(String message, boolean stuffit) {
PromptTemplate promptTemplate = new PromptTemplate(qaPromptResource);
Map<String, Object> map = new HashMap<>();
map.put("question", message);
if (stuffit) {
map.put("context", docsToStuffResource);
}
else {
map.put("context", "");
}
Prompt prompt = promptTemplate.create(map);
Generation generation = chatModel.call(prompt).getResult();
return new Completion(generation.getOutput().getContent());
}
}

@ -1,22 +0,0 @@
# Spring Cloud Alibaba AI Text Embedding
`TongYiController` 接受一个 HTTP GET 请求 `http://localhost:8080/ai/textEmbedding`
`controller` 将会调用 `TongYiService` 中的 `genAudio` 方法,完成服务请求得到响应。
有一个可选的 `text` 参数其默认值为“Spring Cloud Alibaba AI 框架!”。 请求响应来自 Alibaba TongYi Text Embedding 服务。
## 构建和运行
1. 修改配置文件 `application.yml` 中的 apikey 为有效的 apikey
2. 通过 IDE 或者 `./mvnw spring-boot:run` 运行应用程序。
## 访问接口
使用 curl 工具或者使用浏览器对接口发起请求:
```shell
$ curl http://localhost:8080/ai/textEmbedding
# Response:
为一组向量集合
```

@ -1,47 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl.textembedding;
import java.util.List;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.stereotype.Service;
/**
* @author why_ohh
* @author <a href="mailto:550588941@qq.com">why_ohh</a>
*/
@Service
public class TongYiTextEmbeddingServiceImpl extends AbstractTongYiServiceImpl {
private final EmbeddingModel embeddingModel;
public TongYiTextEmbeddingServiceImpl(EmbeddingModel embeddingModel) {
this.embeddingModel = embeddingModel;
}
@Override
public List<Double> textEmbedding(String text) {
return embeddingModel.embed(text);
}
}

@ -1,26 +0,0 @@
#
# Copyright 2023-2024 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
server:
port: 8080
spring:
application:
name: tongyi-example
# please setting api-key. suggestion by environment variable.
# Note: api-key is invalid, please apply for a new one.
# export SPRING_CLOUD_ALIBABA_TONGYI_API_KEY=sk-a3d73b1709bf4a178c28ed7c8b3b5a345

@ -1 +0,0 @@
Jack and arokg, slim won the gold medal in mixed doubles curling at the 2022 Winter Olympics.

@ -1,4 +0,0 @@
You are a helpful AI assistant.
You are an AI assistant that helps people find information.
Your name is {name}
You should reply to the user's request with your name and also in the style of a {voice}.

@ -1,7 +0,0 @@
Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
Helpful Answer:

@ -1,199 +0,0 @@
<!--
Copyright 2023-2024 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="js/marked.min.js"></script>
<title>SCA AI Example Front</title>
<style>
body {
background-color: #f8f9fa;
font-family: Arial, sans-serif;
}
.container {
margin: 50px auto;
width: 800px;
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
h1 {
color: #2ecc71;
text-align: center;
margin-bottom: 30px;
}
label {
display: block;
margin-bottom: 10px;
color: #333;
}
input[type="text"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}
input[type="submit"] {
background-color: #2ecc71;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 3px;
cursor: pointer;
width: 100%;
}
.chat-box {
width: 100%;
height: 500px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
overflow-y: scroll;
}
.message {
margin-bottom: 10px;
padding: 10px;
background-color: #f1f1f1;
border-radius: 3px;
}
.user-message {
background-color: #2ecc71;
color: #fff;
}
.bot-message {
background-color: #3498db;
color: #fff;
}
.loader {
text-align: center;
}
.loader::after {
content: "";
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #ccc;
border-top-color: #2ecc71;
animation: spin 1s infinite linear;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<h1>Spring Cloud Alibaba AI Example</h1>
<form id="form">
<label for="message">User Message</label>
<input type="text" id="message" name="message" placeholder="Entry your question...(make api key is effective)!">
<br>
<br>
<input type="submit" value="Send">
</form>
<br>
<div id="loader" class="loader" style="display: none;"></div>
<div id="chat-box" class="chat-box"></div>
</div>
<script>
var loader = document.getElementById("loader");
document.getElementById("form").addEventListener("submit", function(event) {
event.preventDefault();
var messageInput = document.getElementById("message");
var message = messageInput.value;
messageInput.value = "";
var chatBox = document.getElementById("chat-box");
var userMessage = document.createElement("div");
userMessage.className = "message user-message";
userMessage.textContent = "User: " + message;
chatBox.appendChild(userMessage);
chatBox.scrollTop = chatBox.scrollHeight;
loader.style.display = "block";
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:8080/ai/example?message=" + encodeURIComponent(message), true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
loader.style.display = "none";
if (xhr.status === 200) {
var response = xhr.responseText;
var botMessage = document.createElement("div");
botMessage.className = "message bot-message";
var botMessageText = document.createElement("span");
botMessageText.className = "message-text";
botMessage.appendChild(botMessageText);
botMessageText.innerHTML = marked.marked(response);
chatBox.appendChild(botMessage);
chatBox.scrollTop = chatBox.scrollHeight;
} else if (xhr.status === 400) {
var error = JSON.parse(xhr.responseText);
var errorMessage = document.createElement("div");
errorMessage.className = "message bot-message";
errorMessage.textContent = "Bot: " + error.message;
chatBox.appendChild(errorMessage);
chatBox.scrollTop = chatBox.scrollHeight;
} else {
var errorMessage = document.createElement("div");
errorMessage.className = "message bot-message";
errorMessage.textContent = "Bot: Failed to connect to the backend service. Please make sure the backend service is running.";
chatBox.appendChild(errorMessage);
chatBox.scrollTop = chatBox.scrollHeight;
}
}
};
xhr.onloadstart = function() {
loader.style.display = "block";
};
xhr.onloadend = function() {
loader.style.display = "none";
};
xhr.send();
});
</script>
</body>
</html>

@ -1,131 +0,0 @@
# Spring Cloud Alibaba AI RAG Example
This sample describes how to implement a RAG (Retrieval Augmented Generation) application using SCA AI and Spring AI RedisVector Store.
> RAG is a generative model based on retrieval, which combines retrieval and generation to produce more accurate and diverse texts.
> SCA AI: Spring Cloud Alibaba AI, adapting TongYi LLM big model through Spring AI API.
> Spring AI: The Spring AI project aims to simplify the development of applications that include artificial intelligence features and avoid unnecessary complexity.
> Spring AI RedisVector Store: Redis extends the core functionality of Redis OSS to allow Redis to be used as a vector database. Spring AI provides the RedisVector Store adapter.
> Project Code Address: [spring-cloud-ai-rag-example](https://github.com/alibaba/spring-cloud-alibaba/tree/2023.x/spring-cloud-alibaba-examples/ai-example/spring-cloud-ai-rag-example)
## 1. Environmental preparation
Use Docker Compose to deploy a Redis service to store vector data.
```yaml
version: '3.8'
services:
redis:
image: redis/redis-stack-server
container_name: redis
hostname: redis
ports:
- 6379:6379
```
Start with `docker compose up -d`, and then you can `docker ps | grep redis` check to see if the container is running properly.
## 2. Project dependency
> This project introduces `spring-cloud-alibaba-ai-starter` and `spring-ai-redis-spring-boot-starter` realizes RAG application.
You need to introduce the following dependencies in the POM. XML:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
<version>${spring.ai.version}</version>
</dependency>
```
## 3. Configuration
Configure the following information in application.yml: > Note: It is recommended that you configure apiKey via an environment variable for apiKey security.
> Note: It is recommended to configure the apiKey via environment variables for apiKey security.
> Reference: https://github.com/alibaba/spring-cloud-alibaba/tree/2023.x/spring-cloud-alibaba-examples/ai-example/spring-cloud-ai-example#% E6%8E%A5%E5%85%A5-spring-cloud-starter-alibaba-ai:
```yaml
spring:
ai:
vectorstore:
redis:
# Configure Redis connection URI, default value is redis://127.0.0.1:6379
# uri: redis://127.0.0.1:6379
index: peer
Prefix: peer
```
## 4. Write the code
The `loader` classes are as follows:
```java
@Override
public void run(ApplicationArguments args) throws Exception {
Map<String, Object> indexInfo = vectorStore.getJedis().ftInfo(properties.getIndex());
int numDocs = Integer.parseInt((String) indexInfo.getOrDefault("num_docs", "0"));
if (numDocs > 20000) {
logger.info("Embeddings already loaded. Skipping");
return;
}
Resource file = data;
if (Objects.requireNonNull(data.getFilename()).endsWith(".gz")) {
GZIPInputStream inputStream = new GZIPInputStream(data.getInputStream());
file = new InputStreamResource(inputStream, "beers.json.gz");
}
logger.info("Creating Embeddings...");
JsonReader loader = new JsonReader(file, KEYS);
vectorStore.add(loader.get());
logger.info("Embeddings created.");
}
```
The `Service` classes are as follows:
```java
public Generation retrieve(String message) {
SearchRequest request = SearchRequest.query(message).withTopK(topK);
List<Document> docs = store.similaritySearch(request);
Message systemMessage = getSystemMessage(docs);
UserMessage userMessage = new UserMessage(message);
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
ChatResponse response = client.call(prompt);
return response.getResult();
}
private Message getSystemMessage(List<Document> similarDocuments) {
String documents = similarDocuments.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n"));
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemBeerPrompt);
return systemPromptTemplate.createMessage(Map.of("documents", documents));
}
```
## 5. Run and verify
You can start the SprigBoot main class and then use a browser to access:
```shell
# request params is promptthe default valueWhat ber pairs well with smoked meats?"
http://localhost:8081/rag/chat
```
To experience the RAG application.

@ -1,131 +0,0 @@
# Spring Cloud Alibaba AI RAG Example
本示例介绍如何使用 SCA AI 和 Spring AI RedisVectorStore 实现 RAGRetrieval Augmented Generation应用。
> RAG 是一个基于检索的生成模型,它将检索和生成结合在一起,以生成更加准确和多样化的文本。
> SCA AI: Spring Cloud Alibaba AI, 通过 Spring AI API 适配 TongYi LLM 大模型。
> Spring AI: Spring AI项目旨在简化包含人工智能功能的应用程序的开发避免不必要的复杂性。
> Spring AI RedisVectorStore: Redis 扩展了 Redis OSS 的核心功能,允许将 Redis 用作矢量数据库Spring AI 提供了 RedisVectorStore 适配器。
> 项目代码地址:[spring-cloud-ai-rag-example](https://github.com/alibaba/spring-cloud-alibaba/tree/2023.x/spring-cloud-alibaba-examples/ai-example/spring-cloud-ai-rag-example)
## 1. 环境准备
使用 Docker Compose 部署一个 Redis 服务,用于存储向量数据。
```yaml
version: '3.8'
services:
redis:
image: redis/redis-stack-server
container_name: redis
hostname: redis
ports:
- 6379:6379
```
使用 `docker compose up -d` 启动,然后您可以通过 `docker ps | grep redis` 查看容器是否正常运行。
## 2. 项目依赖
> 本项目通过引入 `spring-cloud-alibaba-ai-starter``spring-ai-redis-spring-boot-starter` 实现 RAG 应用。
您需要在 pom.xml 中引入如下依赖:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
<version>${spring.ai.version}</version>
</dependency>
```
## 3. 配置
在 application.yml 中配置如下信息:
> 注意:为了保证 apiKey 安全,建议通过环境变量的方式配置 apiKey。
> 参考https://github.com/alibaba/spring-cloud-alibaba/tree/2023.x/spring-cloud-alibaba-examples/ai-example/spring-cloud-ai-example#%E6%8E%A5%E5%85%A5-spring-cloud-starter-alibaba-ai:
```yaml
spring:
ai:
vectorstore:
redis:
# Configure the Redis connection URI, default value is redis://127.0.0.1:6379
# uri: redis://127.0.0.1:6379
index: peer
prefix: peer
```
## 4. 编写代码
`loader` 类如下所示:
```java
@Override
public void run(ApplicationArguments args) throws Exception {
Map<String, Object> indexInfo = vectorStore.getJedis().ftInfo(properties.getIndex());
int numDocs = Integer.parseInt((String) indexInfo.getOrDefault("num_docs", "0"));
if (numDocs > 20000) {
logger.info("Embeddings already loaded. Skipping");
return;
}
Resource file = data;
if (Objects.requireNonNull(data.getFilename()).endsWith(".gz")) {
GZIPInputStream inputStream = new GZIPInputStream(data.getInputStream());
file = new InputStreamResource(inputStream, "beers.json.gz");
}
logger.info("Creating Embeddings...");
JsonReader loader = new JsonReader(file, KEYS);
vectorStore.add(loader.get());
logger.info("Embeddings created.");
}
```
`Service` 类如下所示:
```java
public Generation retrieve(String message) {
SearchRequest request = SearchRequest.query(message).withTopK(topK);
List<Document> docs = store.similaritySearch(request);
Message systemMessage = getSystemMessage(docs);
UserMessage userMessage = new UserMessage(message);
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
ChatResponse response = client.call(prompt);
return response.getResult();
}
private Message getSystemMessage(List<Document> similarDocuments) {
String documents = similarDocuments.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n"));
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemBeerPrompt);
return systemPromptTemplate.createMessage(Map.of("documents", documents));
}
```
## 5. 运行并验证
您可以通过启动 SpringBoot 主类,之后使用浏览器访问:
```shell
# 参数为 prompt默认值为What ber pairs well with smoked meats?"
http://localhost:8081/rag/chat
```
来体验 RAG 应用。

@ -1,9 +0,0 @@
version: '3.8'
services:
redis:
image: redis/redis-stack-server
container_name: redis
hostname: redis
ports:
- 6379:6379

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2023-2024 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.alibaba.cloud</groupId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-ai-rag-example</artifactId>
<name>Spring Cloud Starter Alibaba AI RAG Example</name>
<description>Example build RAG Application By Spring Cloud Alibaba AI</description>
<packaging>jar</packaging>
<properties>
<spring.ai.version>1.0.0-M1</spring.ai.version>
<redis.jedis.version>5.1.0</redis.jedis.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
<version>${spring.ai.version}</version>
<exclusions>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis.jedis.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>

@ -1,41 +0,0 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.ai.rag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@SpringBootApplication
public class RAGApplication {
private static final Logger logger = LoggerFactory.getLogger(RAGApplication.class);
public static void main(String[] args) {
SpringApplication.run(RAGApplication.class, args);
logger.info("RAGApplication started successfully.");
}
}

@ -1,50 +0,0 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.ai.rag.controller;
import com.alibaba.cloud.example.ai.rag.service.RAGService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@CrossOrigin
@RestController
@RequestMapping("/rag")
public class RAGController {
@Autowired
private RAGService ragService;
@GetMapping("/chat")
public String chatMessage(@RequestParam(value = "prompt",
defaultValue = "What ber pairs well with smoked meats?") String prompt) {
return ragService.retrieve(prompt)
.getOutput()
.getContent();
}
}

@ -1,83 +0,0 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.ai.rag.loader;
import java.util.Map;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
import org.springframework.ai.reader.JsonReader;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@Component
public class RAGDataLoader implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(RAGDataLoader.class);
private static final String[] KEYS = { "name", "abv", "ibu", "description" };
@Value("classpath:/data/beers.json.gz")
private Resource data;
private final RedisVectorStore vectorStore;
private final RedisVectorStoreProperties properties;
public RAGDataLoader(RedisVectorStore vectorStore, RedisVectorStoreProperties properties) {
this.vectorStore = vectorStore;
this.properties = properties;
}
@Override
public void run(ApplicationArguments args) throws Exception {
Map<String, Object> indexInfo = vectorStore.getJedis().ftInfo(properties.getIndex());
int numDocs = Integer.parseInt((String) indexInfo.getOrDefault("num_docs", "0"));
if (numDocs > 20000) {
logger.info("Embeddings already loaded. Skipping");
return;
}
Resource file = data;
if (Objects.requireNonNull(data.getFilename()).endsWith(".gz")) {
GZIPInputStream inputStream = new GZIPInputStream(data.getInputStream());
file = new InputStreamResource(inputStream, "beers.json.gz");
}
logger.info("Creating Embeddings...");
JsonReader loader = new JsonReader(file, KEYS);
vectorStore.add(loader.get());
logger.info("Embeddings created.");
}
}

@ -1,85 +0,0 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.ai.rag.service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
@Service
public class RAGService {
@Value("classpath:/prompts/system-qa.st")
private Resource systemBeerPrompt;
@Value("${topk:10}")
private int topK;
private final ChatModel chatModel;
private final VectorStore store;
public RAGService(ChatModel chatModel, VectorStore store) {
this.chatModel = chatModel;
this.store = store;
}
public Generation retrieve(String message) {
SearchRequest request = SearchRequest.query(message).withTopK(topK);
List<Document> docs = store.similaritySearch(request);
Message systemMessage = getSystemMessage(docs);
UserMessage userMessage = new UserMessage(message);
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
ChatResponse response = chatModel.call(prompt);
return response.getResult();
}
private Message getSystemMessage(List<Document> similarDocuments) {
String documents = similarDocuments.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n"));
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemBeerPrompt);
return systemPromptTemplate.createMessage(Map.of("documents", documents));
}
}

@ -1,37 +0,0 @@
#
# Copyright 2023-2024 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
server:
port: 8081
spring:
application:
name: sca-ai-rag-example
cloud:
# We recommend configuring the api-key via an environment variable.
# Please see https://github.com/alibaba/spring-cloud-alibaba/tree/2023.x/spring-cloud-alibaba-examples/ai-example/spring-cloud-ai-example#%E6%8E%A5%E5%85%A5-spring-cloud-starter-alibaba-ai:
# ai:
# tongyi:
# connection:
# api-key: sk-xxxxxx
ai:
vectorstore:
redis:
# Configure the Redis connection URI, default value is redis://127.0.0.1:6379
# uri: redis://127.0.0.1:6379
index: peer
prefix: peer

@ -1,7 +0,0 @@
You're assisting with questions about products in a beer catalog.
Use the information from the DOCUMENTS section to provide accurate answers.
The answer involves referring to the ABV or IBU of the beer, include the beer name in the response.
If unsure, simply state that you don't know.
DOCUMENTS:
{documents}

@ -1,3 +0,0 @@
[Spring AI](https://docs.spring.io/spring-ai/reference/0.8-SNAPSHOT/index.html) <br><br>
[通义大模型](https://help.aliyun.com/zh/dashscope/developer-reference/model-introduction) <br><br>
[Spring AI chat completion api](https://docs.spring.io/spring-ai/reference/0.8-SNAPSHOT/api/chatclient.html)

@ -1,121 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2023-2024 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-starters</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
<name>Spring Cloud Starter Alibaba AI</name>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>

@ -1,244 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi;
import java.util.Objects;
import com.alibaba.cloud.ai.tongyi.audio.speech.TongYiAudioSpeechModel;
import com.alibaba.cloud.ai.tongyi.audio.speech.TongYiAudioSpeechProperties;
import com.alibaba.cloud.ai.tongyi.audio.transcription.TongYiAudioTranscriptionModel;
import com.alibaba.cloud.ai.tongyi.audio.transcription.TongYiAudioTranscriptionProperties;
import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel;
import com.alibaba.cloud.ai.tongyi.chat.TongYiChatProperties;
import com.alibaba.cloud.ai.tongyi.common.constants.TongYiConstants;
import com.alibaba.cloud.ai.tongyi.common.exception.TongYiException;
import com.alibaba.cloud.ai.tongyi.embedding.TongYiTextEmbeddingModel;
import com.alibaba.cloud.ai.tongyi.embedding.TongYiTextEmbeddingProperties;
import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
import com.alibaba.dashscope.audio.asr.transcription.Transcription;
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import com.alibaba.dashscope.embeddings.TextEmbedding;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.ApiKey;
import com.alibaba.dashscope.utils.Constants;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
@AutoConfiguration
@ConditionalOnClass({
TongYiChatModel.class,
TongYiImagesModel.class,
TongYiAudioSpeechModel.class,
TongYiTextEmbeddingModel.class,
TongYiAudioTranscriptionModel.class
})
@EnableConfigurationProperties({
TongYiChatProperties.class,
TongYiImagesProperties.class,
TongYiAudioSpeechProperties.class,
TongYiConnectionProperties.class,
TongYiTextEmbeddingProperties.class,
TongYiAudioTranscriptionProperties.class
})
public class TongYiAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Generation generation() {
return new Generation();
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public ImageSynthesis imageSynthesis() {
return new ImageSynthesis();
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public SpeechSynthesizer speechSynthesizer() {
return new SpeechSynthesizer();
}
@Bean
@ConditionalOnMissingBean
public Transcription transcription() {
return new Transcription();
}
@Bean
@ConditionalOnMissingBean
public TextEmbedding textEmbedding() {
return new TextEmbedding();
}
@Bean
@ConditionalOnMissingBean
public FunctionCallbackContext springAiFunctionManager(ApplicationContext context) {
FunctionCallbackContext manager = new FunctionCallbackContext();
manager.setApplicationContext(context);
return manager;
}
@Bean
@ConditionalOnProperty(
prefix = TongYiChatProperties.CONFIG_PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public TongYiChatModel tongYiChatClient(Generation generation,
TongYiChatProperties chatOptions,
TongYiConnectionProperties connectionProperties
) {
settingApiKey(connectionProperties);
return new TongYiChatModel(generation, chatOptions.getOptions());
}
@Bean
@ConditionalOnProperty(
prefix = TongYiImagesProperties.CONFIG_PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public TongYiImagesModel tongYiImagesClient(
ImageSynthesis imageSynthesis,
TongYiImagesProperties imagesOptions,
TongYiConnectionProperties connectionProperties
) {
settingApiKey(connectionProperties);
return new TongYiImagesModel(imageSynthesis, imagesOptions.getOptions());
}
@Bean
@ConditionalOnProperty(
prefix = TongYiAudioSpeechProperties.CONFIG_PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public TongYiAudioSpeechModel tongYiAudioSpeechClient(
SpeechSynthesizer speechSynthesizer,
TongYiAudioSpeechProperties speechProperties,
TongYiConnectionProperties connectionProperties
) {
settingApiKey(connectionProperties);
return new TongYiAudioSpeechModel(speechSynthesizer, speechProperties.getOptions());
}
@Bean
@ConditionalOnProperty(
prefix = TongYiAudioTranscriptionProperties.CONFIG_PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public TongYiAudioTranscriptionModel tongYiAudioTranscriptionClient(
Transcription transcription,
TongYiAudioTranscriptionProperties transcriptionProperties,
TongYiConnectionProperties connectionProperties) {
settingApiKey(connectionProperties);
return new TongYiAudioTranscriptionModel(
transcriptionProperties.getOptions(),
transcription
);
}
@Bean
@ConditionalOnProperty(
prefix = TongYiTextEmbeddingProperties.CONFIG_PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public TongYiTextEmbeddingModel tongYiTextEmbeddingClient(
TextEmbedding textEmbedding,
TongYiConnectionProperties connectionProperties
) {
settingApiKey(connectionProperties);
return new TongYiTextEmbeddingModel(textEmbedding);
}
/**
* Setting the API key.
* @param connectionProperties {@link TongYiConnectionProperties}
*/
private void settingApiKey(TongYiConnectionProperties connectionProperties) {
String apiKey;
try {
// It is recommended to set the key by defining the api-key in an environment variable.
var envKey = System.getenv(TongYiConstants.SCA_AI_TONGYI_API_KEY);
if (Objects.nonNull(envKey)) {
Constants.apiKey = envKey;
return;
}
if (Objects.nonNull(connectionProperties.getApiKey())) {
apiKey = connectionProperties.getApiKey();
}
else {
apiKey = ApiKey.getApiKey(null);
}
Constants.apiKey = apiKey;
}
catch (NoApiKeyException e) {
throw new TongYiException(e.getMessage());
}
}
}

@ -1,52 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi;
import org.springframework.boot.context.properties.ConfigurationProperties;
import static com.alibaba.cloud.ai.tongyi.common.constants.TongYiConstants.SCA_AI_CONFIGURATION;
/**
* Spring Cloud Alibaba AI TongYi LLM connection properties.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
@ConfigurationProperties(TongYiConnectionProperties.CONFIG_PREFIX)
public class TongYiConnectionProperties {
/**
* Spring Cloud Alibaba AI connection configuration prefix.
*/
public static final String CONFIG_PREFIX = SCA_AI_CONFIGURATION + "connection";
/**
* TongYi LLM API key.
*/
private String apiKey;
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
}

@ -1,40 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio;
/**
* More models see: https://help.aliyun.com/zh/dashscope/model-list?spm=a2c4g.11186623.0.i5
* Support all models in list.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public final class AudioSpeechModels {
private AudioSpeechModels() {
}
/**
* Male Voice of the Tongue().
* zh & en.
* Default sample rate: 48 Hz.
*/
public static final String SAMBERT_ZHICHU_V1 = "sambert-zhichu-v1";
}

@ -1,43 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
public final class AudioTranscriptionModels {
private AudioTranscriptionModels() {
}
/**
* Paraformer Chinese and English speech recognition model supports audio or video speech recognition with a sampling rate of 16kHz or above.
*/
public static final String Paraformer_V1 = "paraformer-v1";
/**
* Paraformer Chinese speech recognition model, support 8kHz telephone speech recognition.
*/
public static final String Paraformer_8K_V1 = "paraformer-8k-v1";
/**
* The Paraformer multilingual speech recognition model supports audio or video speech recognition with a sample rate of 16kHz or above.
*/
public static final String Paraformer_MTL_V1 = "paraformer-mtl-v1";
}

@ -1,233 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech;
import java.nio.ByteBuffer;
import com.alibaba.cloud.ai.tongyi.audio.AudioSpeechModels;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.Speech;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.SpeechModel;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.SpeechPrompt;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.SpeechResponse;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.SpeechStreamModel;
import com.alibaba.cloud.ai.tongyi.metadata.audio.TongYiAudioSpeechResponseMetadata;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import io.reactivex.Flowable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import org.springframework.util.Assert;
/**
* TongYiAudioSpeechClient is a client for TongYi audio speech service for Spring Cloud Alibaba AI.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiAudioSpeechModel implements SpeechModel, SpeechStreamModel {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Default speed rate.
*/
private static final float SPEED_RATE = 1.0f;
/**
* TongYi models api.
*/
private final SpeechSynthesizer speechSynthesizer;
/**
* TongYi models options.
*/
private final TongYiAudioSpeechOptions defaultOptions;
/**
* TongYiAudioSpeechClient constructor.
* @param speechSynthesizer the speech synthesizer
*/
public TongYiAudioSpeechModel(SpeechSynthesizer speechSynthesizer) {
this(speechSynthesizer, null);
}
/**
* TongYiAudioSpeechClient constructor.
* @param speechSynthesizer the speech synthesizer
* @param tongYiAudioOptions the tongYi audio options
*/
public TongYiAudioSpeechModel(SpeechSynthesizer speechSynthesizer, TongYiAudioSpeechOptions tongYiAudioOptions) {
Assert.notNull(speechSynthesizer, "speechSynthesizer must not be null");
Assert.notNull(tongYiAudioOptions, "tongYiAudioOptions must not be null");
this.speechSynthesizer = speechSynthesizer;
this.defaultOptions = tongYiAudioOptions;
}
/**
* Call the TongYi audio speech service.
* @param text the text message to be converted to audio.
* @return the audio byte buffer.
*/
@Override
public ByteBuffer call(String text) {
var speechRequest = new SpeechPrompt(text);
return call(speechRequest).getResult().getOutput();
}
/**
* Call the TongYi audio speech service.
* @param prompt the speech prompt.
* @return the speech response.
*/
@Override
public SpeechResponse call(SpeechPrompt prompt) {
var SCASpeechParam = merge(prompt.getOptions());
var speechSynthesisParams = toSpeechSynthesisParams(SCASpeechParam);
speechSynthesisParams.setText(prompt.getInstructions().getText());
logger.info(speechSynthesisParams.toString());
var res = speechSynthesizer.call(speechSynthesisParams);
return convert(res, null);
}
/**
* Call the TongYi audio speech service.
* @param prompt the speech prompt.
* @param callback the result callback.
* {@link SpeechSynthesizer#call(SpeechSynthesisParam, ResultCallback)}
*/
public void call(SpeechPrompt prompt, ResultCallback<SpeechSynthesisResult> callback) {
var SCASpeechParam = merge(prompt.getOptions());
var speechSynthesisParams = toSpeechSynthesisParams(SCASpeechParam);
speechSynthesisParams.setText(prompt.getInstructions().getText());
speechSynthesizer.call(speechSynthesisParams, callback);
}
/**
* Stream the TongYi audio speech service.
* @param prompt the speech prompt.
* @return the speech response.
* {@link SpeechSynthesizer#streamCall(SpeechSynthesisParam)}
*/
@Override
public Flux<SpeechResponse> stream(SpeechPrompt prompt) {
var SCASpeechParam = merge(prompt.getOptions());
Flowable<SpeechSynthesisResult> resultFlowable = speechSynthesizer
.streamCall(toSpeechSynthesisParams(SCASpeechParam));
return Flux.from(resultFlowable)
.flatMap(
res -> Flux.just(res.getAudioFrame())
.map(audio -> {
var speech = new Speech(audio);
var respMetadata = TongYiAudioSpeechResponseMetadata.from(res);
return new SpeechResponse(speech, respMetadata);
})
).publishOn(Schedulers.parallel());
}
public TongYiAudioSpeechOptions merge(TongYiAudioSpeechOptions target) {
var mergeBuilder = TongYiAudioSpeechOptions.builder();
mergeBuilder.withModel(defaultOptions.getModel() != null ? defaultOptions.getModel() : target.getModel());
mergeBuilder.withPitch(defaultOptions.getPitch() != null ? defaultOptions.getPitch() : target.getPitch());
mergeBuilder.withRate(defaultOptions.getRate() != null ? defaultOptions.getRate() : target.getRate());
mergeBuilder.withFormat(defaultOptions.getFormat() != null ? defaultOptions.getFormat() : target.getFormat());
mergeBuilder.withSampleRate(defaultOptions.getSampleRate() != null ? defaultOptions.getSampleRate() : target.getSampleRate());
mergeBuilder.withTextType(defaultOptions.getTextType() != null ? defaultOptions.getTextType() : target.getTextType());
mergeBuilder.withVolume(defaultOptions.getVolume() != null ? defaultOptions.getVolume() : target.getVolume());
mergeBuilder.withEnablePhonemeTimestamp(defaultOptions.isEnablePhonemeTimestamp() != null ? defaultOptions.isEnablePhonemeTimestamp() : target.isEnablePhonemeTimestamp());
mergeBuilder.withEnableWordTimestamp(defaultOptions.isEnableWordTimestamp() != null ? defaultOptions.isEnableWordTimestamp() : target.isEnableWordTimestamp());
return mergeBuilder.build();
}
public SpeechSynthesisParam toSpeechSynthesisParams(TongYiAudioSpeechOptions source) {
var mergeBuilder = SpeechSynthesisParam.builder();
mergeBuilder.model(source.getModel() != null ? source.getModel() : AudioSpeechModels.SAMBERT_ZHICHU_V1);
mergeBuilder.text(source.getText() != null ? source.getText() : "");
if (source.getFormat() != null) {
mergeBuilder.format(source.getFormat());
}
if (source.getRate() != null) {
mergeBuilder.rate(source.getRate());
}
if (source.getPitch() != null) {
mergeBuilder.pitch(source.getPitch());
}
if (source.getTextType() != null) {
mergeBuilder.textType(source.getTextType());
}
if (source.getSampleRate() != null) {
mergeBuilder.sampleRate(source.getSampleRate());
}
if (source.isEnablePhonemeTimestamp() != null) {
mergeBuilder.enablePhonemeTimestamp(source.isEnablePhonemeTimestamp());
}
if (source.isEnableWordTimestamp() != null) {
mergeBuilder.enableWordTimestamp(source.isEnableWordTimestamp());
}
if (source.getVolume() != null) {
mergeBuilder.volume(source.getVolume());
}
return mergeBuilder.build();
}
/**
* Convert the TongYi audio speech service result to the speech response.
* @param result the audio byte buffer.
* @param synthesisResult the synthesis result.
* @return the speech response.
*/
private SpeechResponse convert(ByteBuffer result, SpeechSynthesisResult synthesisResult) {
if (synthesisResult == null) {
return new SpeechResponse(new Speech(result));
}
var responseMetadata = TongYiAudioSpeechResponseMetadata.from(synthesisResult);
var speech = new Speech(synthesisResult.getAudioFrame());
return new SpeechResponse(speech, responseMetadata);
}
}

@ -1,262 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech;
import com.alibaba.cloud.ai.tongyi.audio.AudioSpeechModels;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisTextType;
import org.springframework.ai.model.ModelOptions;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiAudioSpeechOptions implements ModelOptions {
/**
* Audio Speech models.
*/
private String model = AudioSpeechModels.SAMBERT_ZHICHU_V1;
/**
* Text content.
*/
private String text;
/**
* Input text type.
*/
private SpeechSynthesisTextType textType = SpeechSynthesisTextType.PLAIN_TEXT;
/**
* synthesis audio format.
*/
private SpeechSynthesisAudioFormat format = SpeechSynthesisAudioFormat.WAV;
/**
* synthesis audio sample rate.
*/
private Integer sampleRate = 16000;
/**
* synthesis audio volume.
*/
private Integer volume = 50;
/**
* synthesis audio speed.
*/
private Float rate = 1.0f;
/**
* synthesis audio pitch.
*/
private Float pitch = 1.0f;
/**
* enable word level timestamp.
*/
private Boolean enableWordTimestamp = false;
/**
* enable phoneme level timestamp.
*/
private Boolean enablePhonemeTimestamp = false;
public static Builder builder() {
return new Builder();
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public SpeechSynthesisTextType getTextType() {
return textType;
}
public void setTextType(SpeechSynthesisTextType textType) {
this.textType = textType;
}
public SpeechSynthesisAudioFormat getFormat() {
return format;
}
public void setFormat(SpeechSynthesisAudioFormat format) {
this.format = format;
}
public Integer getSampleRate() {
return sampleRate;
}
public void setSampleRate(Integer sampleRate) {
this.sampleRate = sampleRate;
}
public Integer getVolume() {
return volume;
}
public void setVolume(Integer volume) {
this.volume = volume;
}
public Float getRate() {
return rate;
}
public void setRate(Float rate) {
this.rate = rate;
}
public Float getPitch() {
return pitch;
}
public void setPitch(Float pitch) {
this.pitch = pitch;
}
public Boolean isEnableWordTimestamp() {
return enableWordTimestamp;
}
public void setEnableWordTimestamp(Boolean enableWordTimestamp) {
this.enableWordTimestamp = enableWordTimestamp;
}
public Boolean isEnablePhonemeTimestamp() {
return enablePhonemeTimestamp;
}
public void setEnablePhonemeTimestamp(Boolean enablePhonemeTimestamp) {
this.enablePhonemeTimestamp = enablePhonemeTimestamp;
}
/**
* Build a options instances.
*/
public static class Builder {
private final TongYiAudioSpeechOptions options = new TongYiAudioSpeechOptions();
public Builder withModel(String model) {
options.model = model;
return this;
}
public Builder withText(String text) {
options.text = text;
return this;
}
public Builder withTextType(SpeechSynthesisTextType textType) {
options.textType = textType;
return this;
}
public Builder withFormat(SpeechSynthesisAudioFormat format) {
options.format = format;
return this;
}
public Builder withSampleRate(Integer sampleRate) {
options.sampleRate = sampleRate;
return this;
}
public Builder withVolume(Integer volume) {
options.volume = volume;
return this;
}
public Builder withRate(Float rate) {
options.rate = rate;
return this;
}
public Builder withPitch(Float pitch) {
options.pitch = pitch;
return this;
}
public Builder withEnableWordTimestamp(Boolean enableWordTimestamp) {
options.enableWordTimestamp = enableWordTimestamp;
return this;
}
public Builder withEnablePhonemeTimestamp(Boolean enablePhonemeTimestamp) {
options.enablePhonemeTimestamp = enablePhonemeTimestamp;
return this;
}
public TongYiAudioSpeechOptions build() {
return options;
}
}
}

@ -1,78 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech;
import com.alibaba.cloud.ai.tongyi.audio.AudioSpeechModels;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisAudioFormat;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import static com.alibaba.cloud.ai.tongyi.common.constants.TongYiConstants.SCA_AI_CONFIGURATION;
/**
* TongYi audio speech configuration properties.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
@ConfigurationProperties(TongYiAudioSpeechProperties.CONFIG_PREFIX)
public class TongYiAudioSpeechProperties {
/**
* Spring Cloud Alibaba AI configuration prefix.
*/
public static final String CONFIG_PREFIX = SCA_AI_CONFIGURATION + "audio.speech";
/**
* Default TongYi Chat model.
*/
public static final String DEFAULT_AUDIO_MODEL_NAME = AudioSpeechModels.SAMBERT_ZHICHU_V1;
/**
* Enable TongYiQWEN ai audio client.
*/
private boolean enabled = true;
@NestedConfigurationProperty
private TongYiAudioSpeechOptions options = TongYiAudioSpeechOptions.builder()
.withModel(DEFAULT_AUDIO_MODEL_NAME)
.withFormat(SpeechSynthesisAudioFormat.WAV)
.build();
public TongYiAudioSpeechOptions getOptions() {
return this.options;
}
public void setOptions(TongYiAudioSpeechOptions options) {
this.options = options;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -1,87 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech.api;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
import org.springframework.ai.model.ModelResult;
import org.springframework.lang.Nullable;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class Speech implements ModelResult<ByteBuffer> {
private final ByteBuffer audio;
private SpeechMetadata speechMetadata;
public Speech(ByteBuffer audio) {
this.audio = audio;
}
@Override
public ByteBuffer getOutput() {
return this.audio;
}
@Override
public SpeechMetadata getMetadata() {
return speechMetadata != null ? speechMetadata : SpeechMetadata.NULL;
}
public Speech withSpeechMetadata(@Nullable SpeechMetadata speechMetadata) {
this.speechMetadata = speechMetadata;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Speech that)) {
return false;
}
return Arrays.equals(audio.array(), that.audio.array())
&& Objects.equals(speechMetadata, that.speechMetadata);
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(audio.array()), speechMetadata);
}
@Override
public String toString() {
return "Speech{" + "text=" + audio + ", speechMetadata=" + speechMetadata + '}';
}
}

@ -1,80 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech.api;
import java.util.Objects;
/**
* The {@link SpeechMessage} class represents a single text message to
* be converted to speech by the TongYi LLM TTS.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class SpeechMessage {
private String text;
/**
* Constructs a new {@link SpeechMessage} object with the given text.
* @param text the text to be converted to speech
*/
public SpeechMessage(String text) {
this.text = text;
}
/**
* Returns the text of this speech message.
* @return the text of this speech message
*/
public String getText() {
return text;
}
/**
* Sets the text of this speech message.
* @param text the new text for this speech message
*/
public void setText(String text) {
this.text = text;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SpeechMessage that)) {
return false;
}
return Objects.equals(text, that.text);
}
@Override
public int hashCode() {
return Objects.hash(text);
}
}

@ -1,43 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech.api;
import org.springframework.ai.model.ResultMetadata;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public interface SpeechMetadata extends ResultMetadata {
/**
* Null Object.
*/
SpeechMetadata NULL = SpeechMetadata.create();
/**
* Factory method used to construct a new {@link SpeechMetadata}.
* @return a new {@link SpeechMetadata}
*/
static SpeechMetadata create() {
return new SpeechMetadata() {
};
}
}

@ -1,51 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech.api;
import java.nio.ByteBuffer;
import org.springframework.ai.model.Model;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
@FunctionalInterface
public interface SpeechModel extends Model<SpeechPrompt, SpeechResponse> {
/**
* Generates spoken audio from the provided text message.
* @param message the text message to be converted to audio.
* @return the resulting audio bytes.
*/
default ByteBuffer call(String message) {
SpeechPrompt prompt = new SpeechPrompt(message);
return call(prompt).getResult().getOutput();
}
/**
* Sends a speech request to the TongYi TTS API and returns the resulting speech response.
* @param request the speech prompt containing the input text and other parameters.
* @return the speech response containing the generated audio.
*/
SpeechResponse call(SpeechPrompt request);
}

@ -1,90 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech.api;
import java.util.Objects;
import com.alibaba.cloud.ai.tongyi.audio.speech.TongYiAudioSpeechOptions;
import org.springframework.ai.model.ModelRequest;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class SpeechPrompt implements ModelRequest<SpeechMessage> {
private TongYiAudioSpeechOptions speechOptions;
private final SpeechMessage message;
public SpeechPrompt(String instructions) {
this(new SpeechMessage(instructions), TongYiAudioSpeechOptions.builder().build());
}
public SpeechPrompt(String instructions, TongYiAudioSpeechOptions speechOptions) {
this(new SpeechMessage(instructions), speechOptions);
}
public SpeechPrompt(SpeechMessage speechMessage) {
this(speechMessage, TongYiAudioSpeechOptions.builder().build());
}
public SpeechPrompt(SpeechMessage speechMessage, TongYiAudioSpeechOptions speechOptions) {
this.message = speechMessage;
this.speechOptions = speechOptions;
}
@Override
public SpeechMessage getInstructions() {
return this.message;
}
@Override
public TongYiAudioSpeechOptions getOptions() {
return speechOptions;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SpeechPrompt that)) {
return false;
}
return Objects.equals(speechOptions, that.speechOptions) && Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(speechOptions, message);
}
}

@ -1,101 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech.api;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.alibaba.cloud.ai.tongyi.metadata.audio.TongYiAudioSpeechResponseMetadata;
import org.springframework.ai.model.ModelResponse;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class SpeechResponse implements ModelResponse<Speech> {
private final Speech speech;
private final TongYiAudioSpeechResponseMetadata speechResponseMetadata;
/**
* Creates a new instance of SpeechResponse with the given speech result.
* @param speech the speech result to be set in the SpeechResponse
* @see Speech
*/
public SpeechResponse(Speech speech) {
this(speech, TongYiAudioSpeechResponseMetadata.NULL);
}
/**
* Creates a new instance of SpeechResponse with the given speech result and speech
* response metadata.
* @param speech the speech result to be set in the SpeechResponse
* @param speechResponseMetadata the speech response metadata to be set in the
* SpeechResponse
* @see Speech
* @see TongYiAudioSpeechResponseMetadata
*/
public SpeechResponse(Speech speech, TongYiAudioSpeechResponseMetadata speechResponseMetadata) {
this.speech = speech;
this.speechResponseMetadata = speechResponseMetadata;
}
@Override
public Speech getResult() {
return speech;
}
@Override
public List<Speech> getResults() {
return Collections.singletonList(speech);
}
@Override
public TongYiAudioSpeechResponseMetadata getMetadata() {
return speechResponseMetadata;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SpeechResponse that)) {
return false;
}
return Objects.equals(speech, that.speech)
&& Objects.equals(speechResponseMetadata, that.speechResponseMetadata);
}
@Override
public int hashCode() {
return Objects.hash(speech, speechResponseMetadata);
}
}

@ -1,55 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech.api;
import java.nio.ByteBuffer;
import reactor.core.publisher.Flux;
import org.springframework.ai.model.StreamingModel;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
@FunctionalInterface
public interface SpeechStreamModel extends StreamingModel<SpeechPrompt, SpeechResponse> {
/**
* Generates a stream of audio bytes from the provided text message.
*
* @param message the text message to be converted to audio
* @return a Flux of audio bytes representing the generated speech
*/
default Flux<ByteBuffer> stream(String message) {
SpeechPrompt prompt = new SpeechPrompt(message);
return stream(prompt).map(SpeechResponse::getResult).map(Speech::getOutput);
}
/**
* Sends a speech request to the TongYi TTS API and returns a stream of the resulting
* speech responses.
* @param prompt the speech prompt containing the input text and other parameters.
* @return a Flux of speech responses, each containing a portion of the generated audio.
*/
@Override
Flux<SpeechResponse> stream(SpeechPrompt prompt);
}

@ -1,191 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.transcription;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.alibaba.cloud.ai.tongyi.audio.AudioTranscriptionModels;
import com.alibaba.cloud.ai.tongyi.audio.transcription.api.AudioTranscriptionPrompt;
import com.alibaba.cloud.ai.tongyi.audio.transcription.api.AudioTranscriptionResponse;
import com.alibaba.cloud.ai.tongyi.audio.transcription.api.AudioTranscriptionResult;
import com.alibaba.cloud.ai.tongyi.common.exception.TongYiException;
import com.alibaba.cloud.ai.tongyi.metadata.audio.TongYiAudioTranscriptionResponseMetadata;
import com.alibaba.dashscope.audio.asr.transcription.Transcription;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionParam;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionQueryParam;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionResult;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionTaskResult;
import org.springframework.ai.model.Model;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* TongYiAudioTranscriptionModel is a client for TongYi audio transcription service for
* Spring Cloud Alibaba AI.
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
public class TongYiAudioTranscriptionModel
implements Model<AudioTranscriptionPrompt, AudioTranscriptionResponse> {
/**
* TongYi models options.
*/
private final TongYiAudioTranscriptionOptions defaultOptions;
/**
* TongYi models api.
*/
private final Transcription transcription;
public TongYiAudioTranscriptionModel(Transcription transcription) {
this(null, transcription);
}
public TongYiAudioTranscriptionModel(TongYiAudioTranscriptionOptions defaultOptions,
Transcription transcription) {
Assert.notNull(transcription, "transcription must not be null");
Assert.notNull(defaultOptions, "defaultOptions must not be null");
this.defaultOptions = defaultOptions;
this.transcription = transcription;
}
@Override
public AudioTranscriptionResponse call(AudioTranscriptionPrompt prompt) {
TranscriptionParam transcriptionParam;
if (prompt.getOptions() != null) {
var param = merge(prompt.getOptions());
transcriptionParam = toTranscriptionParam(param);
transcriptionParam.setFileUrls(prompt.getOptions().getFileUrls());
}
else {
Resource instructions = prompt.getInstructions();
try {
transcriptionParam = TranscriptionParam.builder()
.model(AudioTranscriptionModels.Paraformer_V1)
.fileUrls(List.of(String.valueOf(instructions.getURL())))
.build();
}
catch (IOException e) {
throw new TongYiException("Failed to create resource", e);
}
}
List<TranscriptionTaskResult> taskResultList;
try {
// Submit a transcription request
TranscriptionResult result = transcription.asyncCall(transcriptionParam);
// Wait for the transcription to complete
result = transcription.wait(TranscriptionQueryParam
.FromTranscriptionParam(transcriptionParam, result.getTaskId()));
// Get the transcription results
System.out.println(result.getOutput().getAsJsonObject());
taskResultList = result.getResults();
System.out.println(Arrays.toString(taskResultList.toArray()));
return new AudioTranscriptionResponse(
taskResultList.stream().map(taskResult ->
new AudioTranscriptionResult(taskResult.getTranscriptionUrl())
).collect(Collectors.toList()),
TongYiAudioTranscriptionResponseMetadata.from(result)
);
}
catch (Exception e) {
throw new TongYiException("Failed to call audio transcription", e);
}
}
public TongYiAudioTranscriptionOptions merge(TongYiAudioTranscriptionOptions target) {
var mergeBuilder = TongYiAudioTranscriptionOptions.builder();
mergeBuilder
.withModel(defaultOptions.getModel() != null ? defaultOptions.getModel()
: target.getModel());
mergeBuilder.withChannelId(
defaultOptions.getChannelId() != null ? defaultOptions.getChannelId()
: target.getChannelId());
mergeBuilder.withDiarizationEnabled(defaultOptions.getDiarizationEnabled() != null
? defaultOptions.getDiarizationEnabled()
: target.getDiarizationEnabled());
mergeBuilder.withDisfluencyRemovalEnabled(
defaultOptions.getDisfluencyRemovalEnabled() != null
? defaultOptions.getDisfluencyRemovalEnabled()
: target.getDisfluencyRemovalEnabled());
mergeBuilder.withTimestampAlignmentEnabled(
defaultOptions.getTimestampAlignmentEnabled() != null
? defaultOptions.getTimestampAlignmentEnabled()
: target.getTimestampAlignmentEnabled());
mergeBuilder.withSpecialWordFilter(defaultOptions.getSpecialWordFilter() != null
? defaultOptions.getSpecialWordFilter()
: target.getSpecialWordFilter());
mergeBuilder.withAudioEventDetectionEnabled(
defaultOptions.getAudioEventDetectionEnabled() != null
? defaultOptions.getAudioEventDetectionEnabled()
: target.getAudioEventDetectionEnabled());
return mergeBuilder.build();
}
public TranscriptionParam toTranscriptionParam(
TongYiAudioTranscriptionOptions source) {
var mergeBuilder = TranscriptionParam.builder();
mergeBuilder.model(source.getModel() != null ? source.getModel()
: AudioTranscriptionModels.Paraformer_V1);
mergeBuilder.fileUrls(
source.getFileUrls() != null ? source.getFileUrls() : new ArrayList<>());
if (source.getPhraseId() != null) {
mergeBuilder.phraseId(source.getPhraseId());
}
if (source.getChannelId() != null) {
mergeBuilder.channelId(source.getChannelId());
}
if (source.getDiarizationEnabled() != null) {
mergeBuilder.diarizationEnabled(source.getDiarizationEnabled());
}
if (source.getSpeakerCount() != null) {
mergeBuilder.speakerCount(source.getSpeakerCount());
}
if (source.getDisfluencyRemovalEnabled() != null) {
mergeBuilder.disfluencyRemovalEnabled(source.getDisfluencyRemovalEnabled());
}
if (source.getTimestampAlignmentEnabled() != null) {
mergeBuilder.timestampAlignmentEnabled(source.getTimestampAlignmentEnabled());
}
if (source.getSpecialWordFilter() != null) {
mergeBuilder.specialWordFilter(source.getSpecialWordFilter());
}
if (source.getAudioEventDetectionEnabled() != null) {
mergeBuilder
.audioEventDetectionEnabled(source.getAudioEventDetectionEnabled());
}
return mergeBuilder.build();
}
}

@ -1,204 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.transcription;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.alibaba.cloud.ai.tongyi.audio.AudioTranscriptionModels;
import org.springframework.ai.model.ModelOptions;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
public class TongYiAudioTranscriptionOptions implements ModelOptions {
private String model = AudioTranscriptionModels.Paraformer_V1;
private List<String> fileUrls = new ArrayList<>();
private String phraseId = null;
private List<Integer> channelId = Collections.singletonList(0);
private Boolean diarizationEnabled = false;
private Integer speakerCount = null;
private Boolean disfluencyRemovalEnabled = false;
private Boolean timestampAlignmentEnabled = false;
private String specialWordFilter = "";
private Boolean audioEventDetectionEnabled = false;
public static TongYiAudioTranscriptionOptions.Builder builder() {
return new TongYiAudioTranscriptionOptions.Builder();
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public List<String> getFileUrls() {
return fileUrls;
}
public void setFileUrls(List<String> fileUrls) {
this.fileUrls = fileUrls;
}
public String getPhraseId() {
return phraseId;
}
public void setPhraseId(String phraseId) {
this.phraseId = phraseId;
}
public List<Integer> getChannelId() {
return channelId;
}
public void setChannelId(List<Integer> channelId) {
this.channelId = channelId;
}
public Boolean getDiarizationEnabled() {
return diarizationEnabled;
}
public void setDiarizationEnabled(Boolean diarizationEnabled) {
this.diarizationEnabled = diarizationEnabled;
}
public Integer getSpeakerCount() {
return speakerCount;
}
public void setSpeakerCount(Integer speakerCount) {
this.speakerCount = speakerCount;
}
public Boolean getDisfluencyRemovalEnabled() {
return disfluencyRemovalEnabled;
}
public void setDisfluencyRemovalEnabled(Boolean disfluencyRemovalEnabled) {
this.disfluencyRemovalEnabled = disfluencyRemovalEnabled;
}
public Boolean getTimestampAlignmentEnabled() {
return timestampAlignmentEnabled;
}
public void setTimestampAlignmentEnabled(Boolean timestampAlignmentEnabled) {
this.timestampAlignmentEnabled = timestampAlignmentEnabled;
}
public String getSpecialWordFilter() {
return specialWordFilter;
}
public void setSpecialWordFilter(String specialWordFilter) {
this.specialWordFilter = specialWordFilter;
}
public Boolean getAudioEventDetectionEnabled() {
return audioEventDetectionEnabled;
}
public void setAudioEventDetectionEnabled(Boolean audioEventDetectionEnabled) {
this.audioEventDetectionEnabled = audioEventDetectionEnabled;
}
/**
* Builder class for constructing TongYiAudioTranscriptionOptions instances.
*/
public static class Builder {
private final TongYiAudioTranscriptionOptions options = new TongYiAudioTranscriptionOptions();
public Builder withModel(String model) {
options.model = model;
return this;
}
public Builder withFileUrls(List<String> fileUrls) {
options.fileUrls = fileUrls;
return this;
}
public Builder withPhraseId(String phraseId) {
options.phraseId = phraseId;
return this;
}
public Builder withChannelId(List<Integer> channelId) {
options.channelId = channelId;
return this;
}
public Builder withDiarizationEnabled(Boolean diarizationEnabled) {
options.diarizationEnabled = diarizationEnabled;
return this;
}
public Builder withSpeakerCount(Integer speakerCount) {
options.speakerCount = speakerCount;
return this;
}
public Builder withDisfluencyRemovalEnabled(Boolean disfluencyRemovalEnabled) {
options.disfluencyRemovalEnabled = disfluencyRemovalEnabled;
return this;
}
public Builder withTimestampAlignmentEnabled(Boolean timestampAlignmentEnabled) {
options.timestampAlignmentEnabled = timestampAlignmentEnabled;
return this;
}
public Builder withSpecialWordFilter(String specialWordFilter) {
options.specialWordFilter = specialWordFilter;
return this;
}
public Builder withAudioEventDetectionEnabled(
Boolean audioEventDetectionEnabled) {
options.audioEventDetectionEnabled = audioEventDetectionEnabled;
return this;
}
public TongYiAudioTranscriptionOptions build() {
// Perform any necessary validation here before returning the built object
return options;
}
}
}

@ -1,73 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.transcription;
import com.alibaba.cloud.ai.tongyi.audio.AudioTranscriptionModels;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import static com.alibaba.cloud.ai.tongyi.common.constants.TongYiConstants.SCA_AI_CONFIGURATION;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
@ConfigurationProperties(TongYiAudioTranscriptionProperties.CONFIG_PREFIX)
public class TongYiAudioTranscriptionProperties {
/**
* Spring Cloud Alibaba AI configuration prefix.
*/
public static final String CONFIG_PREFIX = SCA_AI_CONFIGURATION + "audio.transcription";
/**
* Default TongYi Chat model.
*/
public static final String DEFAULT_AUDIO_MODEL_NAME = AudioTranscriptionModels.Paraformer_V1;
/**
* Enable TongYiQWEN ai audio client.
*/
private boolean enabled = true;
@NestedConfigurationProperty
private TongYiAudioTranscriptionOptions options = TongYiAudioTranscriptionOptions
.builder().withModel(DEFAULT_AUDIO_MODEL_NAME).build();
public TongYiAudioTranscriptionOptions getOptions() {
return this.options;
}
public void setOptions(TongYiAudioTranscriptionOptions options) {
this.options = options;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -1,57 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.transcription.api;
import com.alibaba.cloud.ai.tongyi.audio.transcription.TongYiAudioTranscriptionOptions;
import org.springframework.ai.model.ModelRequest;
import org.springframework.core.io.Resource;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
public class AudioTranscriptionPrompt implements ModelRequest<Resource> {
private Resource audioResource;
private TongYiAudioTranscriptionOptions transcriptionOptions;
public AudioTranscriptionPrompt(Resource resource) {
this.audioResource = resource;
}
public AudioTranscriptionPrompt(Resource resource, TongYiAudioTranscriptionOptions transcriptionOptions) {
this.audioResource = resource;
this.transcriptionOptions = transcriptionOptions;
}
@Override
public Resource getInstructions() {
return audioResource;
}
@Override
public TongYiAudioTranscriptionOptions getOptions() {
return transcriptionOptions;
}
}

@ -1,68 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.transcription.api;
import java.util.List;
import com.alibaba.cloud.ai.tongyi.metadata.audio.TongYiAudioTranscriptionResponseMetadata;
import org.springframework.ai.model.ModelResponse;
import org.springframework.ai.model.ResponseMetadata;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
public class AudioTranscriptionResponse implements ModelResponse<AudioTranscriptionResult> {
private List<AudioTranscriptionResult> resultList;
private TongYiAudioTranscriptionResponseMetadata transcriptionResponseMetadata;
public AudioTranscriptionResponse(List<AudioTranscriptionResult> result) {
this(result, TongYiAudioTranscriptionResponseMetadata.NULL);
}
public AudioTranscriptionResponse(List<AudioTranscriptionResult> result,
TongYiAudioTranscriptionResponseMetadata transcriptionResponseMetadata) {
this.resultList = List.copyOf(result);
this.transcriptionResponseMetadata = transcriptionResponseMetadata;
}
@Override
public AudioTranscriptionResult getResult() {
return this.resultList.get(0);
}
@Override
public List<AudioTranscriptionResult> getResults() {
return this.resultList;
}
@Override
public ResponseMetadata getMetadata() {
return this.transcriptionResponseMetadata;
}
}

@ -1,69 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.transcription.api;
import java.util.Objects;
import com.alibaba.cloud.ai.tongyi.metadata.audio.TongYiAudioTranscriptionMetadata;
import org.springframework.ai.model.ModelResult;
import org.springframework.ai.model.ResultMetadata;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class AudioTranscriptionResult implements ModelResult<String> {
private String text;
private TongYiAudioTranscriptionMetadata transcriptionMetadata;
public AudioTranscriptionResult(String text) {
this.text = text;
}
@Override
public String getOutput() {
return this.text;
}
@Override
public ResultMetadata getMetadata() {
return transcriptionMetadata != null ? transcriptionMetadata : TongYiAudioTranscriptionMetadata.NULL;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AudioTranscriptionResult that = (AudioTranscriptionResult) o;
return Objects.equals(text, that.text) && Objects.equals(transcriptionMetadata, that.transcriptionMetadata);
}
@Override
public int hashCode() {
return Objects.hash(text, transcriptionMetadata);
}
}

@ -1,473 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.chat;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.alibaba.cloud.ai.tongyi.common.exception.TongYiException;
import com.alibaba.dashscope.aigc.conversation.ConversationParam;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationOutput;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.tools.FunctionDefinition;
import com.alibaba.dashscope.tools.ToolCallBase;
import com.alibaba.dashscope.tools.ToolCallFunction;
import com.alibaba.dashscope.utils.ApiKeywords;
import com.alibaba.dashscope.utils.JsonUtils;
import io.reactivex.Flowable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.StreamingChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.AbstractFunctionCallSupport;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.util.CollectionUtils;
/**
* {@link ChatModel} and {@link StreamingChatModel} implementation for {@literal Alibaba DashScope}
* backed by {@link Generation}.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
* @see ChatModel
* @see com.alibaba.dashscope.aigc.generation
*/
public class TongYiChatModel extends
AbstractFunctionCallSupport<
com.alibaba.dashscope.common.Message,
ConversationParam,
GenerationResult>
implements ChatModel, StreamingChatModel {
private static final Logger logger = LoggerFactory.getLogger(TongYiChatModel.class);
/**
* DashScope generation client.
*/
private final Generation generation;
/**
* The TongYi models default chat completion api.
*/
private TongYiChatOptions defaultOptions;
/**
* Initializes an instance of the TongYiChatClient.
* @param generation DashScope generation client.
*/
public TongYiChatModel(Generation generation) {
this(generation,
TongYiChatOptions.builder()
.withTopP(0.8)
.withEnableSearch(true)
.withResultFormat(ConversationParam.ResultFormat.MESSAGE)
.build(),
null
);
}
/**
* Initializes an instance of the TongYiChatClient.
* @param generation DashScope generation client.
* @param options TongYi model params.
*/
public TongYiChatModel(Generation generation, TongYiChatOptions options) {
this(generation, options, null);
}
/**
* Create a TongYi models client.
* @param generation DashScope model generation client.
* @param options TongYi default chat completion api.
*/
public TongYiChatModel(Generation generation, TongYiChatOptions options,
FunctionCallbackContext functionCallbackContext) {
super(functionCallbackContext);
this.generation = generation;
this.defaultOptions = options;
}
/**
* Get default sca chat options.
*
* @return TongYiChatOptions default object.
*/
public TongYiChatOptions getDefaultOptions() {
return this.defaultOptions;
}
@Override
public ChatResponse call(Prompt prompt) {
ConversationParam params = toTongYiChatParams(prompt);
GenerationResult chatCompletions = this.callWithFunctionSupport(params);
List<org.springframework.ai.chat.model.Generation> generations =
chatCompletions
.getOutput()
.getChoices()
.stream()
.map(choice ->
new org.springframework.ai.chat.model.Generation(
choice
.getMessage()
.getContent()
).withGenerationMetadata(generateChoiceMetadata(choice)
))
.toList();
return new ChatResponse(generations);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
Flowable<GenerationResult> genRes;
ConversationParam tongYiChatParams = toTongYiChatParams(prompt);
// See https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.655fc11aRR0jj7#b9ad0a10cfhpe
// enable incremental output
tongYiChatParams.setIncrementalOutput(true);
try {
genRes = generation.streamCall(tongYiChatParams);
}
catch (NoApiKeyException | InputRequiredException e) {
logger.warn("TongYi chat client: " + e.getMessage());
throw new TongYiException(e.getMessage());
}
return Flux.from(genRes)
.flatMap(
message -> Flux.just(
message.getOutput()
.getChoices()
.get(0)
.getMessage()
.getContent())
.map(content -> {
var gen = new org.springframework.ai.chat.model.Generation(content)
.withGenerationMetadata(generateChoiceMetadata(
message.getOutput()
.getChoices()
.get(0)
));
return new ChatResponse(List.of(gen));
})
)
.publishOn(Schedulers.parallel());
}
/**
* Configuration properties to Qwen model params.
* Test access.
*
* @param prompt {@link Prompt}
* @return Qwen models params {@link ConversationParam}
*/
public ConversationParam toTongYiChatParams(Prompt prompt) {
Set<String> functionsForThisRequest = new HashSet<>();
List<com.alibaba.dashscope.common.Message> tongYiMessage = prompt
.getInstructions()
.stream()
.map(this::fromSpringAIMessage)
.toList();
ConversationParam chatParams = ConversationParam.builder()
.messages(tongYiMessage)
// models setting
// {@link HalfDuplexServiceParam#models}
.model(Generation.Models.QWEN_TURBO)
// {@link GenerationOutput}
.resultFormat(ConversationParam.ResultFormat.MESSAGE)
.build();
if (this.defaultOptions != null) {
chatParams = merge(chatParams, this.defaultOptions);
Set<String> defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions, !IS_RUNTIME_CALL);
functionsForThisRequest.addAll(defaultEnabledFunctions);
}
if (prompt.getOptions() != null) {
if (prompt.getOptions() instanceof ChatOptions runtimeOptions) {
TongYiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions,
ChatOptions.class, TongYiChatOptions.class);
chatParams = merge(updatedRuntimeOptions, chatParams);
Set<String> promptEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions,
IS_RUNTIME_CALL);
functionsForThisRequest.addAll(promptEnabledFunctions);
}
else {
throw new IllegalArgumentException("Prompt options are not of type ConversationParam:"
+ prompt.getOptions().getClass().getSimpleName());
}
}
// Add the enabled functions definitions to the request's tools parameter.
if (!CollectionUtils.isEmpty(functionsForThisRequest)) {
List<FunctionDefinition> tools = this.getFunctionTools(functionsForThisRequest);
// todo chatParams.setTools(tools)
}
return chatParams;
}
private ChatGenerationMetadata generateChoiceMetadata(GenerationOutput.Choice choice) {
return ChatGenerationMetadata.from(
String.valueOf(choice.getFinishReason()),
choice.getMessage().getContent()
);
}
private List<FunctionDefinition> getFunctionTools(Set<String> functionNames) {
return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> {
FunctionDefinition functionDefinition = FunctionDefinition.builder()
.name(functionCallback.getName())
.description(functionCallback.getDescription())
.parameters(JsonUtils.parametersToJsonObject(
ModelOptionsUtils.jsonToMap(functionCallback.getInputTypeSchema())
))
.build();
return functionDefinition;
}).toList();
}
private ConversationParam merge(ConversationParam tongYiParams, TongYiChatOptions scaChatParams) {
if (scaChatParams == null) {
return tongYiParams;
}
return ConversationParam.builder()
.messages(tongYiParams.getMessages())
.maxTokens((tongYiParams.getMaxTokens() != null) ? tongYiParams.getMaxTokens() : scaChatParams.getMaxTokens())
// When merge options. Because ConversationParams is must not null. So is setting.
.model(scaChatParams.getModel())
.resultFormat((tongYiParams.getResultFormat() != null) ? tongYiParams.getResultFormat() : scaChatParams.getResultFormat())
.enableSearch((tongYiParams.getEnableSearch() != null) ? tongYiParams.getEnableSearch() : scaChatParams.getEnableSearch())
.topK((tongYiParams.getTopK() != null) ? tongYiParams.getTopK() : scaChatParams.getTopK())
.topP((tongYiParams.getTopP() != null) ? tongYiParams.getTopP() : scaChatParams.getTopP())
.incrementalOutput((tongYiParams.getIncrementalOutput() != null) ? tongYiParams.getIncrementalOutput() : scaChatParams.getIncrementalOutput())
.temperature((tongYiParams.getTemperature() != null) ? tongYiParams.getTemperature() : scaChatParams.getTemperature())
.repetitionPenalty((tongYiParams.getRepetitionPenalty() != null) ? tongYiParams.getRepetitionPenalty() : scaChatParams.getRepetitionPenalty())
.seed((tongYiParams.getSeed() != null) ? tongYiParams.getSeed() : scaChatParams.getSeed())
.build();
}
private ConversationParam merge(TongYiChatOptions scaChatParams, ConversationParam tongYiParams) {
if (scaChatParams == null) {
return tongYiParams;
}
ConversationParam mergedTongYiParams = ConversationParam.builder()
.model(Generation.Models.QWEN_TURBO)
.messages(tongYiParams.getMessages())
.build();
mergedTongYiParams = merge(tongYiParams, scaChatParams);
if (scaChatParams.getMaxTokens() != null) {
mergedTongYiParams.setMaxTokens(scaChatParams.getMaxTokens());
}
if (scaChatParams.getStop() != null) {
mergedTongYiParams.setStopStrings(scaChatParams.getStop());
}
if (scaChatParams.getTemperature() != null) {
mergedTongYiParams.setTemperature(scaChatParams.getTemperature());
}
if (scaChatParams.getTopK() != null) {
mergedTongYiParams.setTopK(scaChatParams.getTopK());
}
if (scaChatParams.getTopP() != null) {
mergedTongYiParams.setTopP(scaChatParams.getTopP().doubleValue());
}
if (scaChatParams.getRepetitionPenalty() != null) {
mergedTongYiParams.setRepetitionPenalty(scaChatParams.getRepetitionPenalty());
}
if (scaChatParams.getIncrementalOutput() != null) {
mergedTongYiParams.setIncrementalOutput(scaChatParams.getIncrementalOutput());
}
return mergedTongYiParams;
}
private com.alibaba.dashscope.common.Message fromSpringAIMessage(Message message) {
return switch (message.getMessageType()) {
case USER -> com.alibaba.dashscope.common.Message.builder()
.role(Role.USER.getValue())
.content(message.getContent())
.build();
case SYSTEM -> com.alibaba.dashscope.common.Message.builder()
.role(Role.SYSTEM.getValue())
.content(message.getContent())
.build();
case ASSISTANT -> com.alibaba.dashscope.common.Message.builder()
.role(Role.ASSISTANT.getValue())
.content(message.getContent())
.build();
default -> throw new IllegalArgumentException("Unknown message type " + message.getMessageType());
};
}
@Override
protected ConversationParam doCreateToolResponseRequest(
ConversationParam previousRequest,
com.alibaba.dashscope.common.Message responseMessage,
List<com.alibaba.dashscope.common.Message> conversationHistory
) {
for (ToolCallBase toolCall : responseMessage.getToolCalls()) {
if (toolCall instanceof ToolCallFunction toolCallFunction) {
if (toolCallFunction.getFunction() != null) {
var functionName = toolCallFunction.getFunction().getName();
var functionArguments = toolCallFunction.getFunction().getArguments();
if (!this.functionCallbackRegister.containsKey(functionName)) {
throw new IllegalStateException("No function callback found for function name: " + functionName);
}
String functionResponse = this.functionCallbackRegister.get(functionName).call(functionArguments);
// Add the function response to the conversation.
conversationHistory
.add(com.alibaba.dashscope.common.Message.builder()
.content(functionResponse)
.role(Role.BOT.getValue())
.toolCallId(toolCall.getId())
.build()
);
}
}
}
ConversationParam newRequest = ConversationParam.builder().messages(conversationHistory).build();
// todo: No @JsonProperty fields.
newRequest = ModelOptionsUtils.merge(newRequest, previousRequest, ConversationParam.class);
return newRequest;
}
@Override
protected List<com.alibaba.dashscope.common.Message> doGetUserMessages(ConversationParam request) {
return request.getMessages();
}
@Override
protected com.alibaba.dashscope.common.Message doGetToolResponseMessage(GenerationResult response) {
var message = response.getOutput().getChoices().get(0).getMessage();
var assistantMessage = com.alibaba.dashscope.common.Message.builder().role(Role.ASSISTANT.getValue())
.content("").build();
assistantMessage.setToolCalls(message.getToolCalls());
return assistantMessage;
}
@Override
protected GenerationResult doChatCompletion(ConversationParam request) {
GenerationResult result;
try {
result = generation.call(request);
}
catch (NoApiKeyException | InputRequiredException e) {
throw new RuntimeException(e);
}
return result;
}
@Override
protected Flux<GenerationResult> doChatCompletionStream(ConversationParam request) {
final Flowable<GenerationResult> genRes;
try {
genRes = generation.streamCall(request);
}
catch (NoApiKeyException | InputRequiredException e) {
logger.warn("TongYi chat client: " + e.getMessage());
throw new TongYiException(e.getMessage());
}
return Flux.from(genRes);
}
@Override
protected boolean isToolFunctionCall(GenerationResult response) {
if (response == null || CollectionUtils.isEmpty(response.getOutput().getChoices())) {
return false;
}
var choice = response.getOutput().getChoices().get(0);
if (choice == null || choice.getFinishReason() == null) {
return false;
}
return Objects.equals(choice.getFinishReason(), ApiKeywords.TOOL_CALLS);
}
}

@ -1,473 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.chat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.util.Assert;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiChatOptions implements FunctionCallingOptions, ChatOptions {
/**
* TongYi Models.
* {@link Generation.Models}
*/
private String model = Generation.Models.QWEN_TURBO;
/**
* The random number seed used in generation, the user controls the randomness of the content generated by the model.
* seed supports unsigned 64-bit integers, with a default value of 1234.
* when using seed, the model will generate the same or similar results as much as possible, but there is currently no guarantee that the results will be exactly the same each time.
*/
private Integer seed = 1234;
/**
* Used to specify the maximum number of tokens that the model can generate when generating content,
* it defines the upper limit of generation but does not guarantee that this number will be generated every time.
* For qwen-turbo the maximum and default values are 1500 tokens.
* The qwen-max, qwen-max-1201, qwen-max-longcontext, and qwen-plus models have a maximum and default value of 2000 tokens.
*/
private Integer maxTokens = 1500;
/**
* The generation process kernel sampling method probability threshold,
* for example, takes the value of 0.8, only retains the smallest set of the most probable tokens with probabilities that add up to greater than or equal to 0.8 as the candidate set.
* The range of values is (0,1.0), the larger the value, the higher the randomness of generation; the lower the value, the higher the certainty of generation.
*/
private Double topP = 0.8;
/**
* The size of the sampling candidate set at the time of generation.
* For example, with a value of 50, only the 50 highest scoring tokens in a single generation will form a randomly sampled candidate set.
* The larger the value, the higher the randomness of the generation; the smaller the value, the higher the certainty of the generation.
* This parameter is not passed by default, and a value of None or when top_k is greater than 100 indicates that the top_k policy is not enabled,
* at which time, only the top_p policy is in effect.
*/
private Integer topK;
/**
* Used to control the repeatability of model generation.
* Increasing repetition_penalty reduces the repetition of model generation. 1.0 means no penalty.
*/
private Double repetitionPenalty = 1.1;
/**
* is used to control the degree of randomness and diversity.
* Specifically, the temperature value controls the extent to which the probability distribution of each candidate word is smoothed when generating text.
* Higher values of temperature reduce the peak of the probability distribution, allowing more low-probability words to be selected and generating more diverse results,
* while lower values of temperature enhance the peak of the probability distribution, making it easier for high-probability words to be selected and generating more certain results.
* Range: [0, 2), 0 is not recommended, meaningless.
* java version >= 2.5.1
*/
private Double temperature = 0.85;
/**
* The stop parameter is used to realize precise control of the content generation process, automatically stopping when the generated content is about to contain the specified string or token_ids,
* and the generated content does not contain the specified content.
* For example, if stop is specified as "Hello", it means stop when "Hello" will be generated; if stop is specified as [37763, 367], it means stop when "Observation" will be generated.
* The stop parameter can be passed as a list of arrays of strings or token_ids to support the scenario of using multiple stops.
* Explanation: Do not mix strings and token_ids in list mode, the element types should be the same in list mode.
*/
private List<String> stop;
/**
* Whether or not to use stream output. When outputting the result in stream mode, the interface returns the result as generator,
* you need to iterate to get the result, the default output is the whole sequence of the current generation for each output,
* the last output is the final result of all the generation, you can change the output mode to non-incremental output by the parameter incremental_output to False.
*/
private Boolean stream = false;
/**
* The model has a built-in Internet search service.
* This parameter controls whether the model refers to the use of Internet search results when generating text. The values are as follows:
* True: enable internet search, the model will use the search result as the reference information in the text generation process, but the model will "judge by itself" whether to use the internet search result based on its internal logic.
* False (default): Internet search is disabled.
*/
private Boolean enableSearch = false;
/**
* [text|message], defaults to text, when it is message,
* the output refers to the message result example.
* It is recommended to prioritize the use of message format.
*/
private String resultFormat = GenerationParam.ResultFormat.MESSAGE;
/**
* Control the streaming output mode, that is, the content will contain the content has been output;
* set to True, will open the incremental output mode, the output will not contain the content has been output,
* you need to splice the whole output, refer to the streaming output sample code.
*/
private Boolean incrementalOutput = false;
/**
* A list of tools that the model can optionally call.
* Currently only functions are supported, and even if multiple functions are entered, the model will only select one to generate the result.
*/
private List<String> tools;
@Override
public Float getTemperature() {
return this.temperature.floatValue();
}
public void setTemperature(Float temperature) {
this.temperature = temperature.doubleValue();
}
@Override
public Float getTopP() {
return this.topP.floatValue();
}
public void setTopP(Float topP) {
this.topP = topP.doubleValue();
}
@Override
public Integer getTopK() {
return this.topK;
}
public void setTopK(Integer topK) {
this.topK = topK;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public Integer getSeed() {
return seed;
}
public String getResultFormat() {
return resultFormat;
}
public void setResultFormat(String resultFormat) {
this.resultFormat = resultFormat;
}
public void setSeed(Integer seed) {
this.seed = seed;
}
public Integer getMaxTokens() {
return maxTokens;
}
public void setMaxTokens(Integer maxTokens) {
this.maxTokens = maxTokens;
}
public Float getRepetitionPenalty() {
return repetitionPenalty.floatValue();
}
public void setRepetitionPenalty(Float repetitionPenalty) {
this.repetitionPenalty = repetitionPenalty.doubleValue();
}
public List<String> getStop() {
return stop;
}
public void setStop(List<String> stop) {
this.stop = stop;
}
public Boolean getStream() {
return stream;
}
public void setStream(Boolean stream) {
this.stream = stream;
}
public Boolean getEnableSearch() {
return enableSearch;
}
public void setEnableSearch(Boolean enableSearch) {
this.enableSearch = enableSearch;
}
public Boolean getIncrementalOutput() {
return incrementalOutput;
}
public void setIncrementalOutput(Boolean incrementalOutput) {
this.incrementalOutput = incrementalOutput;
}
public List<String> getTools() {
return tools;
}
public void setTools(List<String> tools) {
this.tools = tools;
}
private List<FunctionCallback> functionCallbacks = new ArrayList<>();
private Set<String> functions = new HashSet<>();
@Override
public List<FunctionCallback> getFunctionCallbacks() {
return this.functionCallbacks;
}
@Override
public void setFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
this.functionCallbacks = functionCallbacks;
}
@Override
public Set<String> getFunctions() {
return this.functions;
}
@Override
public void setFunctions(Set<String> functions) {
this.functions = functions;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TongYiChatOptions that = (TongYiChatOptions) o;
return Objects.equals(model, that.model)
&& Objects.equals(seed, that.seed)
&& Objects.equals(maxTokens, that.maxTokens)
&& Objects.equals(topP, that.topP)
&& Objects.equals(topK, that.topK)
&& Objects.equals(repetitionPenalty, that.repetitionPenalty)
&& Objects.equals(temperature, that.temperature)
&& Objects.equals(stop, that.stop)
&& Objects.equals(stream, that.stream)
&& Objects.equals(enableSearch, that.enableSearch)
&& Objects.equals(resultFormat, that.resultFormat)
&& Objects.equals(incrementalOutput, that.incrementalOutput)
&& Objects.equals(tools, that.tools)
&& Objects.equals(functionCallbacks, that.functionCallbacks)
&& Objects.equals(functions, that.functions);
}
@Override
public int hashCode() {
return Objects.hash(
model,
seed,
maxTokens,
topP,
topK,
repetitionPenalty,
temperature,
stop,
stream,
enableSearch,
resultFormat,
incrementalOutput,
tools,
functionCallbacks,
functions
);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("TongYiChatOptions{");
sb.append(", model='").append(model).append('\'');
sb.append(", seed=").append(seed);
sb.append(", maxTokens=").append(maxTokens);
sb.append(", topP=").append(topP);
sb.append(", topK=").append(topK);
sb.append(", repetitionPenalty=").append(repetitionPenalty);
sb.append(", temperature=").append(temperature);
sb.append(", stop=").append(stop);
sb.append(", stream=").append(stream);
sb.append(", enableSearch=").append(enableSearch);
sb.append(", resultFormat='").append(resultFormat).append('\'');
sb.append(", incrementalOutput=").append(incrementalOutput);
sb.append(", tools=").append(tools);
sb.append(", functionCallbacks=").append(functionCallbacks);
sb.append(", functions=").append(functions);
sb.append('}');
return sb.toString();
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
protected TongYiChatOptions options;
public Builder() {
this.options = new TongYiChatOptions();
}
public Builder(TongYiChatOptions options) {
this.options = options;
}
public Builder withModel(String model) {
this.options.model = model;
return this;
}
public Builder withMaxTokens(Integer maxTokens) {
this.options.maxTokens = maxTokens;
return this;
}
public Builder withResultFormat(String rf) {
this.options.resultFormat = rf;
return this;
}
public Builder withEnableSearch(Boolean enableSearch) {
this.options.enableSearch = enableSearch;
return this;
}
public Builder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
this.options.functionCallbacks = functionCallbacks;
return this;
}
public Builder withFunctions(Set<String> functionNames) {
Assert.notNull(functionNames, "Function names must not be null");
this.options.functions = functionNames;
return this;
}
public Builder withFunction(String functionName) {
Assert.hasText(functionName, "Function name must not be empty");
this.options.functions.add(functionName);
return this;
}
public Builder withSeed(Integer seed) {
this.options.seed = seed;
return this;
}
public Builder withStop(List<String> stop) {
this.options.stop = stop;
return this;
}
public Builder withTemperature(Double temperature) {
this.options.temperature = temperature;
return this;
}
public Builder withTopP(Double topP) {
this.options.topP = topP;
return this;
}
public Builder withTopK(Integer topK) {
this.options.topK = topK;
return this;
}
public Builder withRepetitionPenalty(Double repetitionPenalty) {
this.options.repetitionPenalty = repetitionPenalty;
return this;
}
public Builder withIncrementalOutput(Boolean isIncrementalOutput) {
this.options.incrementalOutput = isIncrementalOutput;
return this;
}
public TongYiChatOptions build() {
return this.options;
}
}
}

@ -1,84 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.chat;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import static com.alibaba.cloud.ai.tongyi.common.constants.TongYiConstants.SCA_AI_CONFIGURATION;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
@ConfigurationProperties(TongYiChatProperties.CONFIG_PREFIX)
public class TongYiChatProperties {
/**
* Spring Cloud Alibaba AI configuration prefix.
*/
public static final String CONFIG_PREFIX = SCA_AI_CONFIGURATION + "chat";
/**
* Default TongYi Chat model.
*/
public static final String DEFAULT_DEPLOYMENT_NAME = Generation.Models.QWEN_TURBO;
/**
* Default temperature speed.
*/
private static final Double DEFAULT_TEMPERATURE = 0.8;
/**
* Enable TongYiQWEN ai chat client.
*/
private boolean enabled = true;
@NestedConfigurationProperty
private TongYiChatOptions options = TongYiChatOptions.builder()
.withModel(DEFAULT_DEPLOYMENT_NAME)
.withTemperature(DEFAULT_TEMPERATURE)
.withEnableSearch(true)
.withResultFormat(GenerationParam.ResultFormat.MESSAGE)
.build();
public TongYiChatOptions getOptions() {
return this.options;
}
public void setOptions(TongYiChatOptions options) {
this.options = options;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -1,44 +0,0 @@
/*
* Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.common.constants;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
*/
public final class TongYiConstants {
private TongYiConstants() {
}
/**
* Spring Cloud Alibaba AI configuration prefix.
*/
public static final String SCA_AI_CONFIGURATION = "spring.cloud.ai.tongyi.";
/**
* Spring Cloud Alibaba AI constants prefix.
*/
public static final String SCA_AI = "SPRING_CLOUD_ALIBABA_";
/**
* TongYi AI apikey env name.
*/
public static final String SCA_AI_TONGYI_API_KEY = SCA_AI + "TONGYI_API_KEY";
}

@ -1,38 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.common.exception;
/**
* TongYi models runtime exception.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiException extends RuntimeException {
public TongYiException(String message) {
super(message);
}
public TongYiException(String message, Throwable cause) {
super(message, cause);
}
}

@ -1,39 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.common.exception;
/**
* TongYi models images exception.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiImagesException extends TongYiException {
public TongYiImagesException(String message) {
super(message);
}
public TongYiImagesException(String message, Throwable cause) {
super(message, cause);
}
}

@ -1,85 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.embedding;
import java.util.List;
import com.alibaba.dashscope.embeddings.TextEmbeddingParam;
import org.springframework.ai.embedding.EmbeddingOptions;
/**
* @author why_ohh
* @author yuluo
* @author <a href="mailto:550588941@qq.com">why_ohh</a>
* @since 2023.0.1.0
*/
public final class TongYiEmbeddingOptions implements EmbeddingOptions {
private List<String> texts;
private TextEmbeddingParam.TextType textType;
public List<String> getTexts() {
return texts;
}
public void setTexts(List<String> texts) {
this.texts = texts;
}
public TextEmbeddingParam.TextType getTextType() {
return textType;
}
public void setTextType(TextEmbeddingParam.TextType textType) {
this.textType = textType;
}
public static Builder builder() {
return new Builder();
}
public final static class Builder {
private final TongYiEmbeddingOptions options;
private Builder() {
this.options = new TongYiEmbeddingOptions();
}
public Builder withtexts(List<String> texts) {
options.setTexts(texts);
return this;
}
public Builder withtextType(TextEmbeddingParam.TextType textType) {
options.setTextType(textType);
return this;
}
public TongYiEmbeddingOptions build() {
return options;
}
}
}

@ -1,176 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.embedding;
import java.util.List;
import java.util.stream.Collectors;
import com.alibaba.cloud.ai.tongyi.common.exception.TongYiException;
import com.alibaba.cloud.ai.tongyi.metadata.TongYiTextEmbeddingResponseMetadata;
import com.alibaba.dashscope.embeddings.TextEmbedding;
import com.alibaba.dashscope.embeddings.TextEmbeddingParam;
import com.alibaba.dashscope.embeddings.TextEmbeddingResult;
import com.alibaba.dashscope.embeddings.TextEmbeddingResultItem;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.MetadataMode;
import org.springframework.ai.embedding.AbstractEmbeddingModel;
import org.springframework.ai.embedding.Embedding;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.util.Assert;
/**
* {@link TongYiTextEmbeddingModel} implementation for {@literal Alibaba DashScope}.
*
* @author why_ohh
* @author yuluo
* @author <a href="mailto:550588941@qq.com">why_ohh</a>
* @since 2023.0.1.0
*/
public class TongYiTextEmbeddingModel extends AbstractEmbeddingModel {
private final Logger logger = LoggerFactory.getLogger(TongYiTextEmbeddingModel.class);
/**
* TongYi Text Embedding client.
*/
private final TextEmbedding textEmbedding;
/**
* {@link MetadataMode}.
*/
private final MetadataMode metadataMode;
private final TongYiEmbeddingOptions defaultOptions;
public TongYiTextEmbeddingModel(TextEmbedding textEmbedding) {
this(textEmbedding, MetadataMode.EMBED);
}
public TongYiTextEmbeddingModel(TextEmbedding textEmbedding, MetadataMode metadataMode) {
this(textEmbedding, metadataMode,
TongYiEmbeddingOptions.builder()
.withtextType(TextEmbeddingParam.TextType.DOCUMENT)
.build()
);
}
public TongYiTextEmbeddingModel(
TextEmbedding textEmbedding,
MetadataMode metadataMode,
TongYiEmbeddingOptions options
) {
Assert.notNull(textEmbedding, "textEmbedding must not be null");
Assert.notNull(metadataMode, "Metadata mode must not be null");
Assert.notNull(options, "TongYiEmbeddingOptions must not be null");
this.metadataMode = metadataMode;
this.textEmbedding = textEmbedding;
this.defaultOptions = options;
}
public TongYiEmbeddingOptions getDefaultOptions() {
return this.defaultOptions;
}
@Override
public List<Double> embed(Document document) {
return this.call(
new EmbeddingRequest(
List.of(document.getFormattedContent(this.metadataMode)),
null)
).getResults().stream()
.map(Embedding::getOutput)
.flatMap(List::stream)
.toList();
}
@Override
public EmbeddingResponse call(EmbeddingRequest request) {
TextEmbeddingParam embeddingParams = toEmbeddingParams(request);
logger.debug("Embedding request: {}", embeddingParams);
TextEmbeddingResult resp;
try {
resp = textEmbedding.call(embeddingParams);
}
catch (NoApiKeyException e) {
throw new TongYiException(e.getMessage());
}
return genEmbeddingResp(resp);
}
private EmbeddingResponse genEmbeddingResp(TextEmbeddingResult result) {
return new EmbeddingResponse(
genEmbeddingList(result.getOutput().getEmbeddings()),
TongYiTextEmbeddingResponseMetadata.from(result.getUsage())
);
}
private List<Embedding> genEmbeddingList(List<TextEmbeddingResultItem> embeddings) {
return embeddings.stream()
.map(embedding ->
new Embedding(
embedding.getEmbedding(),
embedding.getTextIndex()
))
.collect(Collectors.toList());
}
/**
* We recommend setting the model parameters by passing the embedding parameters through the code;
* yml configuration compatibility is not considered here.
* It is not recommended that users set parameters from yml,
* as this reduces the flexibility of the configuration.
* Because the model name keeps changing, strings are used here and constants are undefined:
* Model list: <a href="https://help.aliyun.com/zh/dashscope/developer-reference/text-embedding-quick-start">Text Embedding Model List</a>
* @param requestOptions Client params. {@link EmbeddingRequest}
* @return {@link TextEmbeddingParam}
*/
private TextEmbeddingParam toEmbeddingParams(EmbeddingRequest requestOptions) {
TextEmbeddingParam tongYiEmbeddingParams = TextEmbeddingParam.builder()
.texts(requestOptions.getInstructions())
.textType(defaultOptions.getTextType() != null ? defaultOptions.getTextType() : TextEmbeddingParam.TextType.DOCUMENT)
.model("text-embedding-v1")
.build();
try {
tongYiEmbeddingParams.validate();
}
catch (InputRequiredException e) {
throw new TongYiException(e.getMessage());
}
return tongYiEmbeddingParams;
}
}

@ -1,50 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.embedding;
import org.springframework.boot.context.properties.ConfigurationProperties;
import static com.alibaba.cloud.ai.tongyi.common.constants.TongYiConstants.SCA_AI_CONFIGURATION;
/**
* @author why_ohh
* @author yuluo
* @author <a href="mailto:550588941@qq.com">why_ohh</a>
* @since 2023.0.1.0
*/
@ConfigurationProperties(TongYiTextEmbeddingProperties.CONFIG_PREFIX)
public class TongYiTextEmbeddingProperties {
/**
* Prefix of TongYi Text Embedding properties.
*/
public static final String CONFIG_PREFIX = SCA_AI_CONFIGURATION + "embedding";
private boolean enabled = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -1,243 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.image;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.Base64;
import java.util.stream.Collectors;
import com.alibaba.cloud.ai.tongyi.common.exception.TongYiImagesException;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult;
import com.alibaba.dashscope.exception.NoApiKeyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.image.Image;
import org.springframework.ai.image.ImageGeneration;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.util.Assert;
import static com.alibaba.cloud.ai.tongyi.metadata.TongYiImagesResponseMetadata.from;
/**
* TongYiImagesClient is a class that implements the ImageClient interface. It provides a
* client for calling the TongYi AI image generation API.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiImagesModel implements ImageModel {
private final Logger logger = LoggerFactory.getLogger(TongYiImagesModel.class);
/**
* Gen Images API.
*/
private final ImageSynthesis imageSynthesis;
/**
* TongYi Gen images properties.
*/
private TongYiImagesOptions defaultOptions;
/**
* Adapt TongYi images api size properties.
*/
private final String sizeConnection = "*";
/**
* Get default images options.
*
* @return Default TongYiImagesOptions.
*/
public TongYiImagesOptions getDefaultOptions() {
return this.defaultOptions;
}
/**
* TongYiImagesClient constructor.
* @param imageSynthesis the image synthesis
* {@link ImageSynthesis}
*/
public TongYiImagesModel(ImageSynthesis imageSynthesis) {
this(imageSynthesis, TongYiImagesOptions.
builder()
.withModel(ImageSynthesis.Models.WANX_V1)
.withN(1)
.build()
);
}
/**
* TongYiImagesClient constructor.
* @param imageSynthesis {@link ImageSynthesis}
* @param imagesOptions {@link TongYiImagesOptions}
*/
public TongYiImagesModel(ImageSynthesis imageSynthesis, TongYiImagesOptions imagesOptions) {
Assert.notNull(imageSynthesis, "ImageSynthesis must not be null");
Assert.notNull(imagesOptions, "TongYiImagesOptions must not be null");
this.imageSynthesis = imageSynthesis;
this.defaultOptions = imagesOptions;
}
/**
* Call the TongYi images service.
* @param imagePrompt the image prompt.
* @return the image response.
* {@link ImageSynthesis#call(ImageSynthesisParam)}
*/
@Override
public ImageResponse call(ImagePrompt imagePrompt) {
ImageSynthesisResult result;
String prompt = imagePrompt.getInstructions().get(0).getText();
var imgParams = ImageSynthesisParam.builder()
.prompt("")
.model(ImageSynthesis.Models.WANX_V1)
.build();
if (this.defaultOptions != null) {
imgParams = merge(this.defaultOptions);
}
if (imagePrompt.getOptions() != null) {
imgParams = merge(toTingYiImageOptions(imagePrompt.getOptions()));
}
imgParams.setPrompt(prompt);
try {
result = imageSynthesis.call(imgParams);
}
catch (NoApiKeyException e) {
logger.error("TongYi models gen images failed: {}.", e.getMessage());
throw new TongYiImagesException(e.getMessage());
}
return convert(result);
}
public ImageSynthesisParam merge(TongYiImagesOptions target) {
var builder = ImageSynthesisParam.builder();
builder.model(this.defaultOptions.getModel() != null ? this.defaultOptions.getModel() : target.getModel());
builder.n(this.defaultOptions.getN() != null ? this.defaultOptions.getN() : target.getN());
builder.size((this.defaultOptions.getHeight() != null && this.defaultOptions.getWidth() != null)
? this.defaultOptions.getHeight() + "*" + this.defaultOptions.getWidth()
: target.getHeight() + "*" + target.getWidth()
);
// prompt is marked non-null but is null.
builder.prompt("");
return builder.build();
}
private ImageResponse convert(ImageSynthesisResult result) {
return new ImageResponse(
result.getOutput().getResults().stream()
.flatMap(value -> value.entrySet().stream())
.map(entry -> {
String key = entry.getKey();
String value = entry.getValue();
try {
String base64Image = convertImageToBase64(value);
Image image = new Image(value, base64Image);
return new ImageGeneration(image);
}
catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList()),
from(result)
);
}
public TongYiImagesOptions toTingYiImageOptions(ImageOptions runtimeImageOptions) {
var builder = TongYiImagesOptions.builder();
if (runtimeImageOptions != null) {
if (runtimeImageOptions.getN() != null) {
builder.withN(runtimeImageOptions.getN());
}
if (runtimeImageOptions.getModel() != null) {
builder.withModel(runtimeImageOptions.getModel());
}
if (runtimeImageOptions.getHeight() != null) {
builder.withHeight(runtimeImageOptions.getHeight());
}
if (runtimeImageOptions.getWidth() != null) {
builder.withWidth(runtimeImageOptions.getWidth());
}
// todo ImagesParams.
}
return builder.build();
}
/**
* Convert image to base64.
* @param imageUrl the image url.
* @return the base64 image.
* @throws Exception the exception.
*/
public String convertImageToBase64(String imageUrl) throws Exception {
var url = new URL(imageUrl);
var inputStream = url.openStream();
var outputStream = new ByteArrayOutputStream();
var buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
var imageBytes = outputStream.toByteArray();
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
inputStream.close();
outputStream.close();
return base64Image;
}
}

@ -1,188 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.image;
import java.util.Objects;
import com.alibaba.cloud.ai.tongyi.common.exception.TongYiImagesException;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
import org.springframework.ai.image.ImageOptions;
/**
* TongYi Image API options.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiImagesOptions implements ImageOptions {
/**
* Specify the model name, currently only wanx-v1 is supported.
*/
private String model = ImageSynthesis.Models.WANX_V1;
/**
* Gen images number.
*/
private Integer n;
/**
* The width of the generated images.
*/
private Integer width = 1024;
/**
* The height of the generated images.
*/
private Integer height = 1024;
@Override
public Integer getN() {
return this.n;
}
@Override
public String getModel() {
return this.model;
}
@Override
public Integer getWidth() {
return this.width;
}
@Override
public Integer getHeight() {
return this.height;
}
@Override
public String getResponseFormat() {
throw new TongYiImagesException("unimplemented!");
}
public void setModel(String model) {
this.model = model;
}
public void setN(Integer n) {
this.n = n;
}
public void setWidth(Integer width) {
this.width = width;
}
public void setHeight(Integer height) {
this.height = height;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TongYiImagesOptions that = (TongYiImagesOptions) o;
return Objects.equals(model, that.model)
&& Objects.equals(n, that.n)
&& Objects.equals(width, that.width)
&& Objects.equals(height, that.height);
}
@Override
public int hashCode() {
return Objects.hash(model, n, width, height);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("TongYiImagesOptions{");
sb.append("model='").append(model).append('\'');
sb.append(", n=").append(n);
sb.append(", width=").append(width);
sb.append(", height=").append(height);
sb.append('}');
return sb.toString();
}
public static Builder builder() {
return new Builder();
}
public final static class Builder {
private final TongYiImagesOptions options;
private Builder() {
this.options = new TongYiImagesOptions();
}
public Builder withN(Integer n) {
options.setN(n);
return this;
}
public Builder withModel(String model) {
options.setModel(model);
return this;
}
public Builder withWidth(Integer width) {
options.setWidth(width);
return this;
}
public Builder withHeight(Integer height) {
options.setHeight(height);
return this;
}
public TongYiImagesOptions build() {
return options;
}
}
}

@ -1,78 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.image;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import static com.alibaba.cloud.ai.tongyi.common.constants.TongYiConstants.SCA_AI_CONFIGURATION;
/**
* TongYi Image API properties.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
@ConfigurationProperties(TongYiImagesProperties.CONFIG_PREFIX)
public class TongYiImagesProperties {
/**
* Spring Cloud Alibaba AI configuration prefix.
*/
public static final String CONFIG_PREFIX = SCA_AI_CONFIGURATION + "images";
/**
* Default TongYi Chat model.
*/
public static final String DEFAULT_IMAGES_MODEL_NAME = ImageSynthesis.Models.WANX_V1;
/**
* Enable TongYiQWEN ai images client.
*/
private boolean enabled = true;
@NestedConfigurationProperty
private TongYiImagesOptions options = TongYiImagesOptions.builder()
.withModel(DEFAULT_IMAGES_MODEL_NAME)
.withN(1)
.build();
public TongYiImagesOptions getOptions() {
return this.options;
}
public void setOptions(TongYiImagesOptions options) {
this.options = options;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

@ -1,88 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.metadata;
import java.util.HashMap;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.chat.metadata.PromptMetadata;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.util.Assert;
/**
* {@link ChatResponseMetadata} implementation for {@literal Alibaba DashScope}.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiAiChatResponseMetadata extends HashMap<String, Object> implements ChatResponseMetadata {
protected static final String AI_METADATA_STRING = "{ @type: %1$s, id: %2$s, usage: %3$s, rateLimit: %4$s }";
@SuppressWarnings("all")
public static TongYiAiChatResponseMetadata from(GenerationResult chatCompletions,
PromptMetadata promptFilterMetadata) {
Assert.notNull(chatCompletions, "Alibaba ai ChatCompletions must not be null");
String id = chatCompletions.getRequestId();
TongYiAiUsage usage = TongYiAiUsage.from(chatCompletions);
return new TongYiAiChatResponseMetadata(
id,
usage,
promptFilterMetadata
);
}
private final String id;
private final Usage usage;
private final PromptMetadata promptMetadata;
protected TongYiAiChatResponseMetadata(String id, TongYiAiUsage usage, PromptMetadata promptMetadata) {
this.id = id;
this.usage = usage;
this.promptMetadata = promptMetadata;
}
public String getId() {
return this.id;
}
@Override
public Usage getUsage() {
return this.usage;
}
@Override
public PromptMetadata getPromptMetadata() {
return this.promptMetadata;
}
@Override
public String toString() {
return AI_METADATA_STRING.formatted(getClass().getTypeName(), getId(), getUsage(), getRateLimit());
}
}

@ -1,82 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.metadata;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.aigc.generation.GenerationUsage;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.util.Assert;
/**
* {@link Usage} implementation for {@literal Alibaba DashScope}.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiAiUsage implements Usage {
private final GenerationUsage usage;
public TongYiAiUsage(GenerationUsage usage) {
Assert.notNull(usage, "GenerationUsage must not be null");
this.usage = usage;
}
public static TongYiAiUsage from(GenerationResult chatCompletions) {
Assert.notNull(chatCompletions, "ChatCompletions must not be null");
return from(chatCompletions.getUsage());
}
public static TongYiAiUsage from(GenerationUsage usage) {
return new TongYiAiUsage(usage);
}
protected GenerationUsage getUsage() {
return this.usage;
}
@Override
public Long getPromptTokens() {
throw new UnsupportedOperationException("Unimplemented method 'getPromptTokens'");
}
@Override
public Long getGenerationTokens() {
return this.getUsage().getOutputTokens().longValue();
}
@Override
public Long getTotalTokens() {
return this.getUsage().getTotalTokens().longValue();
}
@Override
public String toString() {
return this.getUsage().toString();
}
}

@ -1,132 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.metadata;
import java.util.HashMap;
import java.util.Objects;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisTaskMetrics;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisUsage;
import org.springframework.ai.image.ImageResponseMetadata;
import org.springframework.util.Assert;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiImagesResponseMetadata extends HashMap<String, Object> implements ImageResponseMetadata {
private final Long created;
private String taskId;
private ImageSynthesisTaskMetrics metrics;
private ImageSynthesisUsage usage;
public static TongYiImagesResponseMetadata from(ImageSynthesisResult synthesisResult) {
Assert.notNull(synthesisResult, "TongYiAiImageResponse must not be null");
return new TongYiImagesResponseMetadata(
System.currentTimeMillis(),
synthesisResult.getOutput().getTaskMetrics(),
synthesisResult.getOutput().getTaskId(),
synthesisResult.getUsage()
);
}
protected TongYiImagesResponseMetadata(
Long created,
ImageSynthesisTaskMetrics metrics,
String taskId,
ImageSynthesisUsage usage
) {
this.taskId = taskId;
this.metrics = metrics;
this.created = created;
this.usage = usage;
}
public ImageSynthesisUsage getUsage() {
return usage;
}
public void setUsage(ImageSynthesisUsage usage) {
this.usage = usage;
}
@Override
public Long getCreated() {
return created;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public ImageSynthesisTaskMetrics getMetrics() {
return metrics;
}
void setMetrics(ImageSynthesisTaskMetrics metrics) {
this.metrics = metrics;
}
public Long created() {
return this.created;
}
@Override
public String toString() {
return "TongYiImagesResponseMetadata {" + "created=" + created + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TongYiImagesResponseMetadata that = (TongYiImagesResponseMetadata) o;
return Objects.equals(created, that.created)
&& Objects.equals(taskId, that.taskId)
&& Objects.equals(metrics, that.metrics);
}
@Override
public int hashCode() {
return Objects.hash(created, taskId, metrics);
}
}

@ -1,54 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.metadata;
import com.alibaba.dashscope.embeddings.TextEmbeddingUsage;
import org.springframework.ai.embedding.EmbeddingResponseMetadata;
/**
* @author why_ohh
* @author yuluo
* @author <a href="mailto:550588941@qq.com">why_ohh</a>
* @since 2023.0.1.0
*/
public class TongYiTextEmbeddingResponseMetadata extends EmbeddingResponseMetadata {
private Integer totalTokens;
protected TongYiTextEmbeddingResponseMetadata(Integer totalTokens) {
this.totalTokens = totalTokens;
}
public static TongYiTextEmbeddingResponseMetadata from(TextEmbeddingUsage usage) {
return new TongYiTextEmbeddingResponseMetadata(usage.getTotalTokens());
}
public Integer getTotalTokens() {
return totalTokens;
}
public void setTotalTokens(Integer totalTokens) {
this.totalTokens = totalTokens;
}
}

@ -1,131 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.metadata.audio;
import java.util.HashMap;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisUsage;
import com.alibaba.dashscope.audio.tts.timestamp.Sentence;
import org.springframework.ai.chat.metadata.EmptyRateLimit;
import org.springframework.ai.chat.metadata.RateLimit;
import org.springframework.ai.model.ResponseMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiAudioSpeechResponseMetadata extends HashMap<String, Object> implements ResponseMetadata {
private SpeechSynthesisUsage usage;
private String requestId;
private Sentence time;
protected static final String AI_METADATA_STRING = "{ @type: %1$s, requestsLimit: %2$s }";
/**
* NULL objects.
*/
public static final TongYiAudioSpeechResponseMetadata NULL = new TongYiAudioSpeechResponseMetadata() {
};
public static TongYiAudioSpeechResponseMetadata from(SpeechSynthesisResult result) {
Assert.notNull(result, "TongYi AI speech must not be null");
TongYiAudioSpeechResponseMetadata speechResponseMetadata = new TongYiAudioSpeechResponseMetadata();
return speechResponseMetadata;
}
public static TongYiAudioSpeechResponseMetadata from(String result) {
Assert.notNull(result, "TongYi AI speech must not be null");
TongYiAudioSpeechResponseMetadata speechResponseMetadata = new TongYiAudioSpeechResponseMetadata();
return speechResponseMetadata;
}
@Nullable
private RateLimit rateLimit;
public TongYiAudioSpeechResponseMetadata() {
this(null);
}
public TongYiAudioSpeechResponseMetadata(@Nullable RateLimit rateLimit) {
this.rateLimit = rateLimit;
}
@Nullable
public RateLimit getRateLimit() {
RateLimit rateLimit = this.rateLimit;
return rateLimit != null ? rateLimit : new EmptyRateLimit();
}
public TongYiAudioSpeechResponseMetadata withRateLimit(RateLimit rateLimit) {
this.rateLimit = rateLimit;
return this;
}
public TongYiAudioSpeechResponseMetadata withUsage(SpeechSynthesisUsage usage) {
this.usage = usage;
return this;
}
public TongYiAudioSpeechResponseMetadata withRequestId(String id) {
this.requestId = id;
return this;
}
public TongYiAudioSpeechResponseMetadata withSentence(Sentence sentence) {
this.time = sentence;
return this;
}
public SpeechSynthesisUsage getUsage() {
return usage;
}
public String getRequestId() {
return requestId;
}
public Sentence getTime() {
return time;
}
@Override
public String toString() {
return AI_METADATA_STRING.formatted(getClass().getName(), getRateLimit());
}
}

@ -1,43 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.metadata.audio;
import org.springframework.ai.model.ResultMetadata;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
public interface TongYiAudioTranscriptionMetadata extends ResultMetadata {
/**
* A constant instance of {@link TongYiAudioTranscriptionMetadata} that represents a null or empty metadata.
*/
TongYiAudioTranscriptionMetadata NULL = TongYiAudioTranscriptionMetadata.create();
/**
* Factory method for creating a new instance of {@link TongYiAudioTranscriptionMetadata}.
* @return a new instance of {@link TongYiAudioTranscriptionMetadata}
*/
static TongYiAudioTranscriptionMetadata create() {
return new TongYiAudioTranscriptionMetadata() {
};
}
}

@ -1,97 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.metadata.audio;
import java.util.HashMap;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionResult;
import com.google.gson.JsonObject;
import org.springframework.ai.chat.metadata.EmptyRateLimit;
import org.springframework.ai.chat.metadata.RateLimit;
import org.springframework.ai.model.ResponseMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
public class TongYiAudioTranscriptionResponseMetadata extends HashMap<String, Object> implements ResponseMetadata {
@Nullable
private RateLimit rateLimit;
private JsonObject usage;
protected static final String AI_METADATA_STRING = "{ @type: %1$s, rateLimit: %4$s }";
/**
* NULL objects.
*/
public static final TongYiAudioTranscriptionResponseMetadata NULL = new TongYiAudioTranscriptionResponseMetadata() {
};
protected TongYiAudioTranscriptionResponseMetadata() {
this(null, new JsonObject());
}
protected TongYiAudioTranscriptionResponseMetadata(JsonObject usage) {
this(null, usage);
}
protected TongYiAudioTranscriptionResponseMetadata(@Nullable RateLimit rateLimit, JsonObject usage) {
this.rateLimit = rateLimit;
this.usage = usage;
}
public static TongYiAudioTranscriptionResponseMetadata from(TranscriptionResult result) {
Assert.notNull(result, "TongYi Transcription must not be null");
return new TongYiAudioTranscriptionResponseMetadata(result.getUsage());
}
@Nullable
public RateLimit getRateLimit() {
return this.rateLimit != null ? this.rateLimit : new EmptyRateLimit();
}
public void setRateLimit(@Nullable RateLimit rateLimit) {
this.rateLimit = rateLimit;
}
public JsonObject getUsage() {
return usage;
}
public void setUsage(JsonObject usage) {
this.usage = usage;
}
@Override
public String toString() {
return AI_METADATA_STRING.formatted(getClass().getName(), getRateLimit());
}
}

@ -1,95 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech;
import java.nio.ByteBuffer;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.SpeechPrompt;
import com.alibaba.cloud.ai.tongyi.audio.speech.api.SpeechResponse;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import io.reactivex.Flowable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
class TongYiAudioSpeechClientTest {
@Mock
private SpeechSynthesizer speechSynthesizer;
private TongYiAudioSpeechModel client;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
client = new TongYiAudioSpeechModel(speechSynthesizer, new TongYiAudioSpeechOptions());
}
@Test
void shouldReturnSpeechResponseWhenCallWithText() {
ByteBuffer buffer = ByteBuffer.allocate(10);
when(speechSynthesizer.call(any())).thenReturn(buffer);
ByteBuffer result = client.call("test");
assertThat(result).isEqualTo(buffer);
}
@Test
void shouldReturnSpeechResponseWhenCallWithSpeechPrompt() {
SpeechPrompt prompt = new SpeechPrompt("test");
SpeechSynthesisResult synthesisResult = new SpeechSynthesisResult();
synthesisResult.setAudioFrame(ByteBuffer.allocate(10));
when(speechSynthesizer.call(any())).thenReturn(synthesisResult.getAudioFrame());
SpeechResponse result = client.call(prompt);
assertThat(result.getResult().getOutput()).isEqualTo(synthesisResult.getAudioFrame());
}
@Test
void shouldReturnFluxOfSpeechResponseWhenStreamWithSpeechPrompt() {
SpeechPrompt prompt = new SpeechPrompt("test");
SpeechSynthesisResult synthesisResult = new SpeechSynthesisResult();
synthesisResult.setAudioFrame(ByteBuffer.allocate(10));
when(speechSynthesizer.streamCall(any())).thenReturn(Flowable.just(synthesisResult));
Flux<SpeechResponse> result = client.stream(prompt);
StepVerifier.create(result)
.expectNextMatches(response -> response.getResult().getOutput().equals(synthesisResult.getAudioFrame()))
.verifyComplete();
}
}

@ -1,74 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.speech;
import com.alibaba.cloud.ai.tongyi.audio.AudioSpeechModels;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import com.alibaba.dashscope.utils.Constants;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.1.0
*/
class TongYiAudioSpeechOptionsTests {
@Test
public void testSpeechOptions() {
SpeechSynthesizer mockClient = Mockito.mock(SpeechSynthesizer.class);
Constants.apiKey = "test";
var speechClient = new TongYiAudioSpeechModel(mockClient,
TongYiAudioSpeechOptions.builder()
.withFormat(SpeechSynthesisAudioFormat.MP3)
.withRate(333f)
.withVolume(10)
.build()
);
var tongYiAudioSpeechOptions = speechClient.merge(null);
assertThat(tongYiAudioSpeechOptions.getModel()).isEqualTo(AudioSpeechModels.SAMBERT_ZHICHU_V1);
assertThat(tongYiAudioSpeechOptions.getFormat()).isEqualTo(SpeechSynthesisAudioFormat.MP3);
assertThat(tongYiAudioSpeechOptions.getRate()).isEqualTo(333f);
assertThat(tongYiAudioSpeechOptions.getVolume()).isEqualTo(10);
var modelParams = speechClient.toSpeechSynthesisParams(
TongYiAudioSpeechOptions.builder()
.withModel("test")
.withPitch(111f)
.withVolume(11)
.withSampleRate(1)
.build()
);
assertThat(modelParams).isInstanceOf(SpeechSynthesisParam.class);
assertThat(modelParams.getModel()).isEqualTo("test");
assertThat(modelParams.getVolume()).isEqualTo(11);
assertThat(modelParams.getSampleRate()).isNotEqualTo(2);
}
}

@ -1,73 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.audio.transcription;
import com.alibaba.cloud.ai.tongyi.audio.AudioTranscriptionModels;
import com.alibaba.dashscope.audio.asr.transcription.Transcription;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionParam;
import com.alibaba.dashscope.utils.Constants;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author xYLiu
* @author yuluo
* @since 2023.0.1.0
*/
class TongYiAudioTranscriptionOptionsTests {
@Test
void testTranscriptionOptions() {
Transcription mockClient = Mockito.mock(Transcription.class);
Constants.apiKey = "test";
var transcription = new TongYiAudioTranscriptionModel(
TongYiAudioTranscriptionOptions.builder().withDiarizationEnabled(false)
.withAudioEventDetectionEnabled(false)
.withTimestampAlignmentEnabled(false)
.withDisfluencyRemovalEnabled(false).build(),
mockClient);
var tongYiAudioTranscriptionOptions = transcription.merge(null);
assertThat(tongYiAudioTranscriptionOptions.getModel())
.isEqualTo(AudioTranscriptionModels.Paraformer_V1);
assertThat(tongYiAudioTranscriptionOptions.getDiarizationEnabled())
.isEqualTo(false);
assertThat(tongYiAudioTranscriptionOptions.getAudioEventDetectionEnabled())
.isEqualTo(false);
assertThat(tongYiAudioTranscriptionOptions.getTimestampAlignmentEnabled())
.isEqualTo(false);
assertThat(tongYiAudioTranscriptionOptions.getDisfluencyRemovalEnabled())
.isEqualTo(false);
var modelParams = transcription.toTranscriptionParam(TongYiAudioTranscriptionOptions
.builder()
.withModel("test")
.withSpeakerCount(2)
.build()
);
assertThat(modelParams).isInstanceOf(TranscriptionParam.class);
assertThat(modelParams.getModel()).isEqualTo("test");
assertThat(modelParams.getSpeakerCount()).isEqualTo(2);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save