feat: bump srping-ai to 0.8.1, tongyi sdk to 2.12.0 and adapt function call (#3673)

Signed-off-by: yuluo-yx <yuluo08290126@gmail.com>
pull/3683/head
YuLuo 10 months ago committed by GitHub
parent cd07571081
commit 2e3af06901
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -27,8 +27,8 @@
<rocketmq.version>5.1.4</rocketmq.version>
<!-- Spring AI -->
<spring.ai.version>0.8.0</spring.ai.version>
<dashscope-sdk-java.version>2.10.1</dashscope-sdk-java.version>
<spring.ai.version>0.8.1</spring.ai.version>
<dashscope-sdk-java.version>2.12.0</dashscope-sdk-java.version>
<!-- Maven Plugin Versions -->
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
@ -265,6 +265,13 @@
</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>

@ -2,11 +2,11 @@
## Project description
The Spring Cloud Alibaba AI module is based on [Spring AI 0.8.0](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.
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
- Current completion of spring-ai chat api.
## Application access
@ -14,7 +14,6 @@ The Spring Cloud Alibaba AI module is based on [Spring AI 0.8.0](https://docs.sp
1. Add the following dependencies to the project POM. XML:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
@ -24,8 +23,7 @@ The Spring Cloud Alibaba AI module is based on [Spring AI 0.8.0](https://docs.sp
2. Add the following configuration to the application. Yml configuration file:
```yaml
```yml
spring:
cloud:
ai:
@ -34,50 +32,23 @@ The Spring Cloud Alibaba AI module is based on [Spring AI 0.8.0](https://docs.sp
options:
# api_key is invalied.
api-key: sk-a3d73b1709bf4a178c28ed7c8b3b5a45
system-user: "You are a helpful assistant."
```
3. Add the following code:
```java
controller:
@GetMapping("/example")
public Map<String, String> completion(
@RequestParam(value = "message", defaultValue = "Tell me a joke")
String message
) {
return tongyiService.completion(message);
}
service:
@Resource
private MessageManager msgManager;
private final ChatClient chatClient;
@Autowired
public TongYiServiceImpl(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Override
public Map<String, String> completion(String message) {
Message userMsg = Message.builder()
.role(Role.USER.getValue())
.content(message)
.build();
msgManager.add(userMsg);
return Map.of(message, chatClient.call(message));
}
```yml
spring:
cloud:
ai:
tongyi:
chat:
options:
# api_key is invalied.
api-key: sk-a3d73b1709bf4a178c28ed7c8b3b5a45
```
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:
@ -106,4 +77,12 @@ cd `resources/static`open index.html file by local browser, input your questi
## Configuration item description
More configuration refer:
https://help.aliyun.com/zh/dashscope/developer-reference/api-details
## More examples:
This example consists of 5 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.

@ -2,11 +2,11 @@
## 项目说明
Spring Cloud Alibaba AI 模块基于 [Spring AI 0.8.0](https://docs.spring.io/spring-ai/reference/0.8-SNAPSHOT/index.html) 项目 API 完成通义系列大模型的接入。本项目演示如何使用 `spring-cloud-starter-alibaba-ai` 完成 Spring Cloud 微服务应用与通义系列模型的整合。
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
- 目前只完成 spring-ai chat completion api 的接入。
## 应用接入
@ -32,7 +32,6 @@ Spring Cloud Alibaba AI 模块基于 [Spring AI 0.8.0](https://docs.spring.io/sp
options:
# api_key is invalied.
api-key: sk-a3d73b1709bf4a178c28ed7c8b3b5a45
system-user: "You are a helpful assistant."
```
3. 添加如下代码:
@ -40,41 +39,42 @@ Spring Cloud Alibaba AI 模块基于 [Spring AI 0.8.0](https://docs.spring.io/sp
```java
controller:
@Autowired
@Qualifier("tongYiSimpleServiceImpl")
private TongYiService tongYiSimpleService;
@GetMapping("/example")
public Map<String, String> completion(
public String completion(
@RequestParam(value = "message", defaultValue = "Tell me a joke")
String message
) {
return tongyiService.completion(message);
return tongYiSimpleService.completion(message);
}
service:
@Resource
private MessageManager msgManager;
private final ChatClient chatClient;
@Autowired
public TongYiServiceImpl(ChatClient chatClient) {
public TongYiSimpleServiceImpl(ChatClient chatClient, StreamingChatClient streamingChatClient) {
this.chatClient = chatClient;
this.streamingChatClient = streamingChatClient;
}
@Override
public Map<String, String> completion(String message) {
public String completion(String message) {
Message userMsg = Message.builder()
.role(Role.USER.getValue())
.content(message)
.build();
msgManager.add(userMsg);
Prompt prompt = new Prompt(new UserMessage(message));
return Map.of(message, chatClient.call(message));
return chatClient.call(prompt).getResult().getOutput().getContent();
}
```
至此,便完成了最简单的模型接入!和本 example 中的代码略有不同,但 example 中的代码无需修改。可完成对应功能。
4. 启动应用
本 Example 项目支持如下两种启动方式:
@ -102,4 +102,12 @@ Spring Cloud Alibaba AI 模块基于 [Spring AI 0.8.0](https://docs.spring.io/sp
## 配置项说明
更多配置项参考:
https://help.aliyun.com/zh/dashscope/developer-reference/api-details
## 更多 example 示例:
本 example 由 5 个示例组成,由不同的 serviceimpl 实现,您可以参考每个 serviceimpl 中的 readme 文件,在 controller 中使用 @RestController 作为入口点,您可以使用浏览器或者 curl 工具对接口进行请求。完成对应功能接入的练习。
> example 已完成功能,无需修改代码。只在对应的 example 中替换 apikey 即可,项目中提供的 apikey 无效。

@ -21,7 +21,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
@SpringBootApplication

@ -18,9 +18,13 @@ package com.alibaba.cloud.ai.example.tongyi.controller;
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 org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -28,8 +32,11 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* TongYi models Spring Cloud Alibaba Controller.
*
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
@RestController
@ -38,7 +45,8 @@ import org.springframework.web.bind.annotation.RestController;
public class TongYiController {
@Autowired
private TongYiService tongyiService;
@Qualifier("tongYiSimpleServiceImpl")
private TongYiService tongYiSimpleService;
@GetMapping("/example")
public String completion(
@ -46,7 +54,7 @@ public class TongYiController {
String message
) {
return tongyiService.completion(message);
return tongYiSimpleService.completion(message);
}
@GetMapping("/stream")
@ -55,7 +63,55 @@ public class TongYiController {
String message
) {
return tongyiService.streamCompletion(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);
}
}

@ -0,0 +1,57 @@
/*
* 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-RC1
*/
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 + '}';
}
}

@ -14,25 +14,24 @@
* limitations under the License.
*/
package com.alibaba.cloud.ai.tongyi.aot;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
package com.alibaba.cloud.ai.example.tongyi.models;
/**
* The TongYiRuntimeHints class is responsible for registering runtime hints for TongYiAI.
*
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
public class TongYiRuntimeHints implements RuntimeHintsRegistrar {
public class Completion {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
private final String completion;
// todo
public Completion(String completion) {
this.completion = completion;
}
public String getCompletion() {
return completion;
}
}

@ -0,0 +1,69 @@
/*
* 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.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;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
public abstract class AbstractTongYiServiceImpl implements TongYiService {
@Override
public String completion(String message) {
return null;
}
@Override
public Map<String, String> streamCompletion(String message) {
return null;
}
@Override
public ActorsFilms genOutputParse(String actor) {
return null;
}
@Override
public AssistantMessage genPromptTemplates(String adjective, String topic) {
return null;
}
@Override
public AssistantMessage genRole(String message, String name, String voice) {
return null;
}
@Override
public Completion stuffCompletion(String message, boolean stuffit) {
return null;
}
}

@ -18,15 +18,68 @@ package com.alibaba.cloud.ai.example.tongyi.service;
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;
/**
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
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);
}

@ -0,0 +1,39 @@
# 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!
```

@ -14,42 +14,45 @@
* limitations under the License.
*/
package com.alibaba.cloud.ai.example.tongyi.service.impl;
package com.alibaba.cloud.ai.example.tongyi.service.impl.helloworld;
import java.util.Map;
import com.alibaba.cloud.ai.example.tongyi.service.AbstractTongYiServiceImpl;
import com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.MessageManager;
import com.alibaba.dashscope.common.Role;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.StreamingChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Simple example.
* 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
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
@Slf4j
@Service
public class TongYiServiceImpl implements TongYiService {
public class TongYiSimpleServiceImpl extends AbstractTongYiServiceImpl {
@Resource
private MessageManager msgManager;
private static final Logger logger = LoggerFactory.getLogger(TongYiService.class);
private final ChatClient chatClient;
private final StreamingChatClient streamingChatClient;
@Autowired
public TongYiServiceImpl(ChatClient chatClient, StreamingChatClient streamingChatClient) {
public TongYiSimpleServiceImpl(ChatClient chatClient, StreamingChatClient streamingChatClient) {
this.chatClient = chatClient;
this.streamingChatClient = streamingChatClient;
@ -58,13 +61,9 @@ public class TongYiServiceImpl implements TongYiService {
@Override
public String completion(String message) {
Message userMsg = Message.builder()
.role(Role.USER.getValue())
.content(message)
.build();
msgManager.add(userMsg);
Prompt prompt = new Prompt(new UserMessage(message));
return chatClient.call(message);
return chatClient.call(prompt).getResult().getOutput().getContent();
}
@Override

@ -0,0 +1,137 @@
# Spring Cloud Alibaba AI Hello World
`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"
]
}
```

@ -0,0 +1,78 @@
/*
* 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 com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.parser.BeanOutputParser;
import org.springframework.stereotype.Service;
/**
* The BeanOutputParser generates an OpenAI 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-RC1
*/
@Slf4j
@Service
public class TongYiOutputParseServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiService.class);
private final ChatClient chatClient;
public TongYiOutputParseServiceImpl(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Override
public ActorsFilms genOutputParse(String actor) {
var outputParser = new BeanOutputParser<>(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 = chatClient.call(prompt).getResult();
// {@link BeanOutputParser#getFormat}
// simple solve.
String content = generation.getOutput().getContent()
.replace("```json", "")
.replace("```", "");
return outputParser.parse(content);
}
}

@ -0,0 +1,65 @@
# Spring Cloud Alibaba AI Hello World
`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": []
}
```

@ -0,0 +1,67 @@
/*
* 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 com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
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-RC1
*/
@Slf4j
@Service
public class TongYiPromptTemplateServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiService.class);
private final ChatClient chatClient;
@Value("classpath:/prompts/joke-prompt.st")
private Resource jokeResource;
public TongYiPromptTemplateServiceImpl(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Override
public AssistantMessage genPromptTemplates(String adjective, String topic) {
PromptTemplate promptTemplate = new PromptTemplate(jokeResource);
Prompt prompt = promptTemplate.create(Map.of("adjective", adjective, "topic", topic));
return chatClient.call(prompt).getResult().getOutput();
}
}

@ -0,0 +1,80 @@
# Spring Cloud Alibaba AI Hello World
`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":[]}
```

@ -0,0 +1,79 @@
/*
* 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 com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.UserMessage;
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-RC1
*/
@Slf4j
@Service
public class TongYiRolesServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiService.class);
private final ChatClient chatClient;
public TongYiRolesServiceImpl(ChatClient chatClient) {
this.chatClient = chatClient;
}
@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 chatClient.call(prompt).getResult().getOutput();
}
}

@ -0,0 +1,67 @@
# Spring Cloud Alibaba AI Hello World
`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."}
```

@ -0,0 +1,82 @@
/*
* 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 com.alibaba.cloud.ai.example.tongyi.service.TongYiService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.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-RC1
*/
@Slf4j
@Service
public class TongYiStuffServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiService.class);
private final ChatClient chatClient;
public TongYiStuffServiceImpl(ChatClient chatClient) {
this.chatClient = chatClient;
}
@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 = chatClient.call(prompt).getResult();
return new Completion(generation.getOutput().getContent());
}
}

@ -26,4 +26,3 @@ spring:
chat:
options:
api-key: sk-a3d73b1709bf4a178c28ed7c8b3b5a45
system-user: "You are a helpful assistant."

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

@ -0,0 +1,4 @@
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}.

@ -0,0 +1,7 @@
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,3 +1,19 @@
<!--
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>

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

@ -38,6 +38,7 @@
<artifactId>spring-ai-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
@ -60,6 +61,25 @@
<optional>true</optional>
</dependency>
<!-- test use -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- test dependencies -->
<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>
</dependencies>
<repositories>

@ -16,10 +16,7 @@
package com.alibaba.cloud.ai.tongyi;
import com.alibaba.cloud.ai.tongyi.client.TongYiChatClient;
import com.alibaba.cloud.ai.tongyi.constant.TongYiConstants;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.MessageManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -48,15 +45,7 @@ public class TongYiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MessageManager msgManager(TongYiChatProperties chatProperties) {
MessageManager messageManager = new MessageManager(10);
messageManager.add(
Message.builder()
.role(TongYiConstants.Role.SYSTEM)
.content(chatProperties.getOptions().getSystemUser())
.build()
);
public MessageManager msgManager() {
return new MessageManager(10);
}

@ -0,0 +1,492 @@
/*
* 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.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.alibaba.cloud.ai.tongyi.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.MessageManager;
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.ApiKey;
import com.alibaba.dashscope.utils.ApiKeywords;
import com.alibaba.dashscope.utils.Constants;
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.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.StreamingChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
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.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
/**
* {@link ChatClient} and {@link StreamingChatClient} implementation for {@literal Alibaba DashScope}
* backed by {@link Generation}.
*
* @author yuluo
* @since 2023.0.0.0-RC1
* @see ChatClient
* @see com.alibaba.dashscope.aigc.generation
*/
public class TongYiChatClient extends
AbstractFunctionCallSupport<
com.alibaba.dashscope.common.Message,
ConversationParam,
GenerationResult>
implements ChatClient, StreamingChatClient {
private static final Logger logger = LoggerFactory.getLogger(TongYiChatClient.class);
/**
* DashScope generation client.
*/
private final Generation generation;
/**
* The TongYi models default chat completion api.
*/
private TongYiChatOptions defaultOptions;
/**
* User role message manager.
*/
@Autowired
private MessageManager msgManager;
/**
* Initializes an instance of the TongYiChatClient.
* @param generation DashScope generation client.
*/
public TongYiChatClient(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 TongYiChatClient(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 TongYiChatClient(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);
// TongYi models context loader.
com.alibaba.dashscope.common.Message message = new com.alibaba.dashscope.common.Message();
message.setRole(Role.USER.getValue());
message.setContent(prompt.getContents());
msgManager.add(message);
params.setMessages(msgManager.get());
logger.trace("TongYi ConversationOptions: {}", params);
GenerationResult chatCompletions = this.callWithFunctionSupport(params);
logger.trace("TongYi ConversationOptions: {}", params);
msgManager.add(chatCompletions);
List<org.springframework.ai.chat.Generation> generations =
chatCompletions
.getOutput()
.getChoices()
.stream()
.map(choice ->
new org.springframework.ai.chat.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
// 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.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}
*/
ConversationParam toTongYiChatParams(Prompt prompt) {
try {
Constants.apiKey = getKey();
}
catch (NoApiKeyException e) {
logger.warn(e.getMessage());
throw new TongYiException(e.getMessage());
}
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.getTopK() != null) {
mergedTongYiParams.setTopK(scaChatParams.getTopK());
}
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());
};
}
/**
* Get TongYi model api_key .
* todo: Get key from env and env_file.
* @return api_key.
*/
private String getKey() throws NoApiKeyException {
String apiKey;
if (Objects.nonNull(this.defaultOptions.getApiKey())) {
apiKey = this.defaultOptions.getApiKey();
}
else {
apiKey = ApiKey.getApiKey(null);
}
return apiKey;
}
@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();
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 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);
}
}

@ -16,20 +16,27 @@
package com.alibaba.cloud.ai.tongyi;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.alibaba.cloud.ai.tongyi.constant.TongYiConstants;
import com.alibaba.dashscope.aigc.generation.models.QwenParam;
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
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
public class TongYiChatOptions implements ChatOptions {
public class TongYiChatOptions implements FunctionCallingOptions, ChatOptions {
/**
* TongYi model api key.
@ -37,9 +44,10 @@ public class TongYiChatOptions implements ChatOptions {
private String apiKey;
/**
* TongYi model name, {@link TongYiConstants}.
* TongYi Models.
* {@link Generation.Models}
*/
private String model = TongYiConstants.Model.QWEN_TURBO;
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.
@ -95,7 +103,7 @@ public class TongYiChatOptions implements ChatOptions {
* 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<Integer> stop;
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,
@ -117,7 +125,7 @@ public class TongYiChatOptions implements ChatOptions {
* the output refers to the message result example.
* It is recommended to prioritize the use of message format.
*/
private String resultFormat = QwenParam.ResultFormat.MESSAGE;
private String resultFormat = GenerationParam.ResultFormat.MESSAGE;
/**
* Control the streaming output mode, that is, the content will contain the content has been output;
@ -132,15 +140,12 @@ public class TongYiChatOptions implements ChatOptions {
*/
private List<String> tools;
private String SystemUser = "You are a helpful assistant.";
@Override
public Float getTemperature() {
return this.temperature.floatValue();
}
@Override
public void setTemperature(Float temperature) {
this.temperature = temperature.doubleValue();
@ -152,7 +157,6 @@ public class TongYiChatOptions implements ChatOptions {
return this.topP.floatValue();
}
@Override
public void setTopP(Float topP) {
this.topP = topP.doubleValue();
@ -164,20 +168,11 @@ public class TongYiChatOptions implements ChatOptions {
return this.topK;
}
@Override
public void setTopK(Integer topK) {
this.topK = topK;
}
public String getSystemUser() {
return SystemUser;
}
public void setSystemUser(String systemUser) {
SystemUser = systemUser;
}
public String getApiKey() {
return apiKey;
@ -238,12 +233,12 @@ public class TongYiChatOptions implements ChatOptions {
this.repetitionPenalty = repetitionPenalty.doubleValue();
}
public List<Integer> getStop() {
public List<String> getStop() {
return stop;
}
public void setStop(List<Integer> stop) {
public void setStop(List<String> stop) {
this.stop = stop;
}
@ -288,14 +283,45 @@ public class TongYiChatOptions implements ChatOptions {
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(apiKey, that.apiKey)
@ -307,11 +333,13 @@ public class TongYiChatOptions implements ChatOptions {
&& Objects.equals(repetitionPenalty, that.repetitionPenalty)
&& Objects.equals(temperature, that.temperature)
&& Objects.equals(stop, that.stop)
&& Objects.equals(resultFormat, that.resultFormat)
&& 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(tools, that.tools)
&& Objects.equals(functionCallbacks, that.functionCallbacks)
&& Objects.equals(functions, that.functions);
}
@Override
@ -328,31 +356,39 @@ public class TongYiChatOptions implements ChatOptions {
temperature,
stop,
stream,
resultFormat,
enableSearch,
resultFormat,
incrementalOutput,
tools
tools,
functionCallbacks,
functions
);
}
@Override
public String toString() {
return "TongYiProperties {" + "apiKey='" + apiKey + '\''
+ ", model=" + model
+ ", seed=" + seed
+ ", maxTokens=" + maxTokens
+ ", topP=" + topP
+ ", topK=" + topK
+ ", repetitionPenalty=" + repetitionPenalty
+ ", temperature=" + temperature
+ ", stop=" + stop
+ ", resultFormat=" + resultFormat
+ ", stream=" + stream
+ ", enableSearch=" + enableSearch
+ ", incrementalOutput=" + incrementalOutput
+ ", tools=" + tools
+ '}';
final StringBuilder sb = new StringBuilder("TongYiChatOptions{");
sb.append("apiKey='").append(apiKey).append('\'');
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() {
@ -394,12 +430,29 @@ public class TongYiChatOptions implements ChatOptions {
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<Integer> stop) {
public Builder withStop(List<String> stop) {
this.options.stop = stop;
return this;
}

@ -16,7 +16,7 @@
package com.alibaba.cloud.ai.tongyi;
import com.alibaba.cloud.ai.tongyi.constant.TongYiConstants;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.models.QwenParam;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -24,7 +24,8 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
/**
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
@ConfigurationProperties(TongYiChatProperties.CONFIG_PREFIX)
@ -38,7 +39,7 @@ public class TongYiChatProperties {
/**
* Default TongYi Chat model.
*/
public static final String DEFAULT_DEPLOYMENT_NAME = TongYiConstants.Model.QWEN_TURBO;
public static final String DEFAULT_DEPLOYMENT_NAME = Generation.Models.QWEN_TURBO;
/**
* Default temperature speed.

@ -1,214 +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.client;
import java.util.List;
import java.util.Objects;
import com.alibaba.cloud.ai.tongyi.TongYiChatOptions;
import com.alibaba.cloud.ai.tongyi.exception.TongYiException;
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.aigc.generation.models.QwenParam;
import com.alibaba.dashscope.common.MessageManager;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.Constants;
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.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.StreamingChatClient;
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* {@link ChatClient} and {@link StreamingChatClient} implementation for {@literal Alibaba DashScope}
* backed by {@link Generation}.
* @author yuluo
* @since 2023.0.0.0
*/
public class TongYiChatClient implements ChatClient, StreamingChatClient {
private static final Logger logger = LoggerFactory.getLogger(TongYiChatClient.class);
/**
* DashScope generation client.
*/
private final Generation generation;
/**
* The TongYi models default chat completion api.
*/
private TongYiChatOptions defaultOptions;
/**
* User role message manager.
*/
@Autowired
private MessageManager msgManager;
/**
* Initializes an instance of the TongYiChatClient.
* @param generation DashScope generation client.
*/
public TongYiChatClient(Generation generation) {
this(generation,
TongYiChatOptions.builder()
.withTopP(0.8)
.withEnableSearch(true)
.withResultFormat(QwenParam.ResultFormat.MESSAGE)
.build()
);
}
/**
* Create a TongYi models client.
* @param generation DashScope model generation client.
* @param options TongYi default chat completion api.
*/
public TongYiChatClient(Generation generation, TongYiChatOptions options) {
this.generation = generation;
this.defaultOptions = options;
}
@Override
public ChatResponse call(Prompt prompt) {
GenerationResult res = null;
try {
res = generation.call(toTongYiChatParams(prompt));
msgManager.add(res);
}
catch (NoApiKeyException | InputRequiredException e) {
logger.warn("TongYi chat client: " + e.getMessage());
throw new TongYiException(e.getMessage());
}
List<org.springframework.ai.chat.Generation> generations =
res
.getOutput()
.getChoices()
.stream()
.map(choice ->
new org.springframework.ai.chat.Generation(
choice
.getMessage()
.getContent()
).withGenerationMetadata(generateChoiceMetadata(choice)
))
.toList();
return new ChatResponse(generations);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
Flowable<GenerationResult> genRes = null;
try {
genRes = generation.streamCall(toTongYiChatParams(prompt));
}
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.Generation(content)
.withGenerationMetadata(generateChoiceMetadata(
message.getOutput()
.getChoices()
.get(0)
));
return new ChatResponse(List.of(gen));
})
)
.publishOn(Schedulers.parallel());
}
/**
* Configuration properties to Qwen model params.
* @param prompt {@link Prompt}
* @return Qwen models params {@link QwenParam}
*/
private QwenParam toTongYiChatParams(Prompt prompt) {
Constants.apiKey = getKey();
return QwenParam.builder()
.model(this.defaultOptions.getModel())
.messages(msgManager.get())
.resultFormat(this.defaultOptions.getResultFormat())
.topP(this.defaultOptions.getTopP().doubleValue())
.topK(this.defaultOptions.getTopK())
.enableSearch(this.defaultOptions.getEnableSearch())
.seed(this.defaultOptions.getSeed())
.maxTokens(this.defaultOptions.getMaxTokens())
.repetitionPenalty(this.defaultOptions.getRepetitionPenalty())
.temperature(this.defaultOptions.getTemperature())
.incrementalOutput(this.defaultOptions.getIncrementalOutput())
.prompt(prompt.getContents())
.build();
}
private ChatGenerationMetadata generateChoiceMetadata(GenerationOutput.Choice choice) {
return ChatGenerationMetadata.from(
String.valueOf(choice.getFinishReason()),
choice.getMessage().getContent()
);
}
/**
* Get TongYi model api_key .
* todo: Get key from env and env_file.
* @return api_key.
*/
private String getKey() {
String apiKey = null;
if (Objects.nonNull(this.defaultOptions.getApiKey())) {
apiKey = this.defaultOptions.getApiKey();
}
return apiKey;
}
}

@ -1,99 +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.constant;
import com.alibaba.dashscope.aigc.generation.Generation;
/**
* TongYi models constants.
*
* @author yuluo
* @since 2023.0.0.0
*/
public final class TongYiConstants {
private TongYiConstants() {
}
public static final class Model {
private Model() {
}
/**
* Tongyi Thousand Questions mega-language model supports input in different languages such as Chinese and English.
*/
public static final String QWEN_TURBO = Generation.Models.QWEN_TURBO;
/**
* Enhanced version of Tongyi Thousand Questions mega-language model to support input in different languages such as Chinese and English.
*/
public static final String QWEN_PLUS = Generation.Models.QWEN_PLUS;
/**
* Tongyi Qianqi 100 billion level super large-scale language model, support Chinese, English and other different language input.
* As the model is upgraded, qwen-max will be updated and upgraded on a rolling basis. If you wish to use a stable version, please use qwen-max-1201.
*/
public static final String QWEN_MAX = Generation.Models.QWEN_MAX;
/**
* Tongyi Qianqi 100 billion level mega-scale language model, supporting different language inputs such as Chinese and English.
* The model is a snapshot stabilized version of qwen-max, and is expected to be maintained until one month after the release time of the next snapshot version (TBD).
*/
public static final String QWEN_MAX_1021 = "qwen-max-1201";
/**
* Tongyi Thousand Questions is a 100 billion level super large-scale language model that supports different language inputs such as Chinese and English.
*/
public static final String QWEN_MAX_LONG_CONTEXT = "qwen-max-longcontext";
}
public static final class Role {
private Role() {
}
/**
* system: indicates a system-level message that can only be used for the first entry in the conversation history (messages[0]).
* The use of the system role is optional and, if present, must be at the beginning of the list
*/
public static final String SYSTEM = "system";
/**
* user and assistant: represent the dialog between the user and the model.
* They should appear alternately in the dialog to simulate the actual dialog flow.
*/
public static final String USER = "user";
/**
* user and assistant: represent the dialog between the user and the model.
* They should appear alternately in the dialog to simulate the actual dialog flow.
*/
public static final String ASSISTANT = "assistant";
/**
* When using the function_call function, if you want to pass in the result of a function,
* this message takes the form {"content": "Boston is raining.", "name": "get_current_weather", "role": "tool"}, where name is the name of the function,
* which needs to be consistent with the tool_calls[i].function.name parameter in the previous round's response, and content is the output of the function.
* Examples are given for multiple rounds of calls in the reference code.
*/
public static final String TOOL = "tool";
}
}

@ -20,7 +20,8 @@ package com.alibaba.cloud.ai.tongyi.exception;
* TongYi models runtime exception.
*
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
public class TongYiException extends RuntimeException {

@ -27,7 +27,8 @@ import org.springframework.util.Assert;
* {@link ChatResponseMetadata} implementation for {@literal Alibaba DashScope}.
*
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
public class TongYiAiChatResponseMetadata implements ChatResponseMetadata {

@ -26,7 +26,8 @@ import org.springframework.util.Assert;
* {@link Usage} implementation for {@literal Alibaba DashScope}.
*
* @author yuluo
* @since 2023.0.0.0
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
public class TongYiAiUsage implements Usage {
@ -57,21 +58,25 @@ public class TongYiAiUsage implements Usage {
@Override
public Long getPromptTokens() {
return null;
throw new UnsupportedOperationException("Unimplemented method 'getPromptTokens'");
}
@Override
public Long getGenerationTokens() {
return this.getUsage().getOutputTokens().longValue();
}
@Override
public Long getTotalTokens() {
return this.getUsage().getInputTokens().longValue() + this.getUsage().getInputTokens().longValue();
return this.getUsage().getTotalTokens().longValue();
}
@Override
public String toString() {
return this.getUsage().toString();
}
}

@ -0,0 +1,67 @@
/*
* 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 com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.utils.Constants;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.ai.chat.prompt.Prompt;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0-RC1
*/
public class TongYiChatOptionsTests {
@Test
public void createRequestWithChatOptions() {
Generation mockClient = Mockito.mock(Generation.class);
Constants.apiKey = "test";
// Test start.
var tongYiChatClient = new TongYiChatClient(mockClient,
TongYiChatOptions.builder().withModel(Generation.Models.QWEN_TURBO).withTemperature(88.8).build());
var tongYiChatParams = tongYiChatClient.toTongYiChatParams(new Prompt("This is a test message"));
assertThat(tongYiChatParams.getMessages()).hasSize(1);
assertThat(tongYiChatParams.getModel()).isEqualTo(Generation.Models.QWEN_TURBO);
assertThat(tongYiChatParams.getTemperature()).isEqualTo(88.8f);
tongYiChatClient = new TongYiChatClient(mockClient,
TongYiChatOptions.builder().withModel(Generation.Models.QWEN_MAX).withTemperature(77.7).build());
tongYiChatParams = tongYiChatClient.toTongYiChatParams(new Prompt("This is a test message"));
assertThat(tongYiChatParams.getMessages()).hasSize(1);
assertThat(tongYiChatParams.getModel()).isEqualTo(Generation.Models.QWEN_MAX);
assertThat(tongYiChatParams.getTemperature()).isEqualTo(77.7f);
// Test end.
}
}

@ -0,0 +1,38 @@
#
# 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.
#
# HTTP call example
curl --location 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation' \
--header 'Authorization: Bearer sk-a3d73b1709bf4a178c28ed7c8b3b5a45' \
--header 'Content-Type: application/json' \
--data '{
"model": "qwen-turbo",
"input":{
"messages":[
{
"role": "system",
"content": "You are a helpful AI assistant.You are an AI assistant that helps people find information.Your name is yuluo.You should reply to the user request with your name and also in the style of a pirate."
},
{
"role": "user",
"content": "who are you?"
}
]
},
"parameters": {
"result_format": "message"
}
}'
Loading…
Cancel
Save