Commit c5db930603bdbcb38d832020524da3d4f3352bb9

Authored by YunaiV
1 parent 7dfa7a15

【优化】AI:依赖从文心一言,使用 spring ai 替代接入

Showing 16 changed files with 89 additions and 841 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
... ... @@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 7 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions;
8 8 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
9 9 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
10   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
11 10 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
12 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
13 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -33,6 +32,7 @@ import org.springframework.ai.chat.prompt.ChatOptions;
33 32 import org.springframework.ai.chat.prompt.Prompt;
34 33 import org.springframework.ai.ollama.api.OllamaOptions;
35 34 import org.springframework.ai.openai.OpenAiChatOptions;
  35 +import org.springframework.ai.qianfan.QianFanChatOptions;
36 36 import org.springframework.stereotype.Service;
37 37 import org.springframework.transaction.annotation.Transactional;
38 38 import reactor.core.publisher.Flux;
... ... @@ -191,8 +191,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
191 191 case OLLAMA:
192 192 return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
193 193 case YI_YAN:
194   - // TODO @fan:增加一个 model
195   - return new YiYanChatOptions().setTemperature(temperatureF).setMaxOutputTokens(maxTokens);
  194 + // TODO 芋艿:貌似 model 只要一设置,就报错
  195 +// return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
  196 + return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build();
196 197 case XING_HUO:
197 198 return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)
198 199 .setMaxTokens(maxTokens);
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
... ... @@ -43,6 +43,13 @@
43 43 <artifactId>yudao-common</artifactId>
44 44 </dependency>
45 45  
  46 + <!-- TODO 芋艿:等 spring-ai 官方发布后,需要把 groupId 改下 -->
  47 + <dependency>
  48 + <groupId>group.springframework.ai</groupId>
  49 + <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
  50 + <version>1.1.0</version>
  51 + </dependency>
  52 +
46 53 <!-- 阿里云 通义千问 -->
47 54 <dependency>
48 55 <groupId>com.alibaba</groupId>
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
... ... @@ -11,9 +11,6 @@ import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi;
11 11 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
12 12 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
13 13 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
14   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
15   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
16   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
17 14 import lombok.extern.slf4j.Slf4j;
18 15 import org.springframework.boot.autoconfigure.AutoConfiguration;
19 16 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
... ... @@ -79,27 +76,6 @@ public class YudaoAiAutoConfiguration {
79 76 }
80 77  
81 78 @Bean
82   - @ConditionalOnProperty(value = "yudao.ai.yiyan.enable", havingValue = "true")
83   - public YiYanChatClient yiYanChatClient(YudaoAiProperties yudaoAiProperties) {
84   - YudaoAiProperties.YiYanProperties yiYanProperties = yudaoAiProperties.getYiyan();
85   - // 转换配置
86   - YiYanChatOptions yiYanOptions = new YiYanChatOptions();
87   -// yiYanOptions.setTopK(yiYanProperties.getTopK()); TODO 芋艿:后续弄
88   - yiYanOptions.setTopP(yiYanProperties.getTopP());
89   - yiYanOptions.setTemperature(yiYanProperties.getTemperature());
90   - yiYanOptions.setMaxOutputTokens(yiYanProperties.getMaxTokens());
91   - return new YiYanChatClient(
92   - new YiYanApi(
93   - yiYanProperties.getAppKey(),
94   - yiYanProperties.getSecretKey(),
95   - yiYanProperties.getModel(),
96   - yiYanProperties.getRefreshTokenSecondTime()
97   - ),
98   - yiYanOptions
99   - );
100   - }
101   -
102   - @Bean
103 79 @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true")
104 80 public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) {
105 81 YudaoAiProperties.MidjourneyProperties config = yudaoAiProperties.getMidjourney();
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.ai.config;
3 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 4 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal;
5 5 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
6   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatModel;
7 6 import lombok.Data;
8 7 import lombok.experimental.Accessors;
9 8 import org.springframework.ai.autoconfigure.openai.OpenAiImageProperties;
... ... @@ -23,7 +22,6 @@ public class YudaoAiProperties {
23 22  
24 23 private QianWenProperties qianwen;
25 24 private XingHuoProperties xinghuo;
26   - private YiYanProperties yiyan;
27 25 private OpenAiImageProperties openAiImage;
28 26 private MidjourneyProperties midjourney;
29 27 private SunoProperties suno;
... ... @@ -63,7 +61,6 @@ public class YudaoAiProperties {
63 61 }
64 62  
65 63 @Data
66   - @Accessors(chain = true)
67 64 public static class XingHuoProperties extends ChatProperties {
68 65  
69 66 private String appId;
... ... @@ -74,28 +71,6 @@ public class YudaoAiProperties {
74 71 }
75 72  
76 73 @Data
77   - @Accessors(chain = true)
78   - public static class YiYanProperties extends ChatProperties {
79   -
80   - /**
81   - * appKey
82   - */
83   - private String appKey;
84   - /**
85   - * secretKey
86   - */
87   - private String secretKey;
88   - /**
89   - * 模型
90   - */
91   - private YiYanChatModel model = YiYanChatModel.ERNIE4_3_5_8K;
92   - /**
93   - * token 刷新时间(默认 86400 = 24小时)
94   - */
95   - private int refreshTokenSecondTime = 86400;
96   - }
97   -
98   - @Data
99 74 public static class MidjourneyProperties {
100 75  
101 76 private String enable;
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
... ... @@ -16,10 +16,11 @@ import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal;
16 16 import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi;
17 17 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
18 18 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
19   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
20   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
21 19 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
22 20 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
  21 +import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
  22 +import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties;
  23 +import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties;
23 24 import org.springframework.ai.chat.model.StreamingChatModel;
24 25 import org.springframework.ai.image.ImageModel;
25 26 import org.springframework.ai.ollama.OllamaChatModel;
... ... @@ -29,8 +30,12 @@ import org.springframework.ai.openai.OpenAiImageModel;
29 30 import org.springframework.ai.openai.api.ApiUtils;
30 31 import org.springframework.ai.openai.api.OpenAiApi;
31 32 import org.springframework.ai.openai.api.OpenAiImageApi;
  33 +import org.springframework.ai.qianfan.QianFanChatModel;
  34 +import org.springframework.ai.qianfan.api.QianFanApi;
32 35 import org.springframework.ai.stabilityai.StabilityAiImageModel;
33 36 import org.springframework.ai.stabilityai.api.StabilityAiApi;
  37 +import org.springframework.retry.support.RetryTemplate;
  38 +import org.springframework.web.client.ResponseErrorHandler;
34 39 import org.springframework.web.client.RestClient;
35 40  
36 41 import java.util.List;
... ... @@ -75,7 +80,7 @@ public class AiClientFactoryImpl implements AiClientFactory {
75 80 case OLLAMA:
76 81 return SpringUtil.getBean(OllamaChatModel.class);
77 82 case YI_YAN:
78   - return SpringUtil.getBean(YiYanChatClient.class);
  83 + return SpringUtil.getBean(QianFanChatModel.class);
79 84 case XING_HUO:
80 85 return SpringUtil.getBean(XingHuoChatClient.class);
81 86 case QIAN_WEN:
... ... @@ -153,15 +158,16 @@ public class AiClientFactoryImpl implements AiClientFactory {
153 158 }
154 159  
155 160 /**
156   - * 可参考 {@link YudaoAiAutoConfiguration#yiYanChatClient(YudaoAiProperties)}
  161 + * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
157 162 */
158   - private static YiYanChatClient buildYiYanChatClient(String key) {
  163 + private static QianFanChatModel buildYiYanChatClient(String key) {
  164 + // TODO 芋艿:貌似目前设置,request 势必会报错
159 165 List<String> keys = StrUtil.split(key, '|');
160 166 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
161 167 String appKey = keys.get(0);
162 168 String secretKey = keys.get(1);
163   - YiYanApi yiYanApi = new YiYanApi(appKey, secretKey, YiYanApi.DEFAULT_CHAT_MODEL, 0);
164   - return new YiYanChatClient(yiYanApi);
  169 + QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
  170 + return new QianFanChatModel(qianFanApi);
165 171 }
166 172  
167 173 /**
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/tongyi/QianWenChatClient.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.ai.core.model.tongyi;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.exception.ChatException;
4 4 import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi;
5   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.exception.YiYanApiException;
6 5 import com.alibaba.dashscope.aigc.generation.GenerationResult;
7 6 import com.alibaba.dashscope.aigc.generation.models.QwenParam;
8 7 import com.alibaba.dashscope.common.Message;
... ... @@ -59,7 +58,7 @@ public class QianWenChatClient implements ChatModel, StreamingChatModel {
59 58 public final RetryTemplate retryTemplate = RetryTemplate.builder()
60 59 // 最大重试次数 10
61 60 .maxAttempts(10)
62   - .retryOn(YiYanApiException.class)
  61 + .retryOn(Exception.class) // TODO 芋艿:临时这么写
63 62 // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms
64 63 .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000))
65 64 .withListener(new RetryListener() {
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/YiYanChatClient.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan;
2   -
3   -import cn.hutool.core.bean.BeanUtil;
4   -import cn.iocoder.yudao.framework.ai.core.exception.ChatException;
5   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
6   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatCompletionRequest;
7   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatCompletionResponse;
8   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.exception.YiYanApiException;
9   -import lombok.extern.slf4j.Slf4j;
10   -import org.springframework.ai.chat.client.ChatClient;
11   -import org.springframework.ai.chat.messages.Message;
12   -import org.springframework.ai.chat.messages.MessageType;
13   -import org.springframework.ai.chat.model.ChatModel;
14   -import org.springframework.ai.chat.model.ChatResponse;
15   -import org.springframework.ai.chat.model.Generation;
16   -import org.springframework.ai.chat.model.StreamingChatModel;
17   -import org.springframework.ai.chat.prompt.ChatOptions;
18   -import org.springframework.ai.chat.prompt.Prompt;
19   -import org.springframework.http.ResponseEntity;
20   -import org.springframework.retry.RetryCallback;
21   -import org.springframework.retry.RetryContext;
22   -import org.springframework.retry.RetryListener;
23   -import org.springframework.retry.support.RetryTemplate;
24   -import org.springframework.util.Assert;
25   -import reactor.core.publisher.Flux;
26   -
27   -import java.time.Duration;
28   -import java.util.List;
29   -import java.util.stream.Collectors;
30   -
31   -/**
32   - * 文心一言的 {@link ChatClient} 实现类
33   - *
34   - * @author fansili
35   - */
36   -@Slf4j
37   -public class YiYanChatClient implements ChatModel, StreamingChatModel {
38   -
39   - private final YiYanApi yiYanApi;
40   -
41   - private YiYanChatOptions defaultOptions;
42   -
43   - // TODO @fan:参考 OpenAiChatClient 调整下 retryTemplate;使用 RetryUtils.DEFAULT_RETRY_TEMPLATE;加允许传入?
44   -
45   - public YiYanChatClient(YiYanApi yiYanApi) {
46   - this.yiYanApi = yiYanApi;
47   - // TODO @fan:这个情况,是不是搞个 defaultOptions;OpenAiChatOptions.builder().withModel(OpenAiApi.DEFAULT_CHAT_MODEL).withTemperature(0.7f).build()
48   - }
49   -
50   - public YiYanChatClient(YiYanApi yiYanApi, YiYanChatOptions defaultOptions) {
51   - Assert.notNull(yiYanApi, "OllamaApi must not be null");
52   - Assert.notNull(defaultOptions, "DefaultOptions must not be null");
53   - this.yiYanApi = yiYanApi;
54   - this.defaultOptions = defaultOptions;
55   - }
56   -
57   - public final RetryTemplate retryTemplate = RetryTemplate.builder()
58   - .maxAttempts(10)
59   - .retryOn(YiYanApiException.class)
60   - .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000))
61   - .withListener(new RetryListener() {
62   -
63   - @Override
64   - public <T, E extends Throwable> void onError(RetryContext context,
65   - RetryCallback<T, E> callback, Throwable throwable) {
66   - log.warn("重试异常:" + context.getRetryCount(), throwable);
67   - }
68   -
69   - })
70   - .build();
71   -
72   - @Override
73   - public ChatResponse call(Prompt prompt) {
74   - YiYanChatCompletionRequest request = createRequest(prompt, false);
75   - return this.retryTemplate.execute(ctx -> {
76   - // 发送请求
77   - ResponseEntity<YiYanChatCompletionResponse> response = yiYanApi.chatCompletionEntity(request);
78   - // 获取结果封装 ChatResponse
79   - YiYanChatCompletionResponse chatCompletion = response.getBody();
80   - if (chatCompletion == null) {
81   - log.warn("No chat completion returned for prompt: {}", prompt);
82   - return new ChatResponse(List.of());
83   - } else {
84   - // TODO @fan:chatResponseMetadata,参考 OpenAiChatResponseMetadata.from(completionEntity.getBody())
85   - return new ChatResponse(List.of(new Generation(chatCompletion.getResult())));
86   - }
87   - });
88   - }
89   -
90   - @Override
91   - public ChatOptions getDefaultOptions() {
92   - // TODO 芋艿:需要跟进下
93   - throw new UnsupportedOperationException();
94   - }
95   -
96   - @Override
97   - public Flux<ChatResponse> stream(Prompt prompt) {
98   - YiYanChatCompletionRequest request = this.createRequest(prompt, true);
99   - return this.retryTemplate.execute(ctx -> {
100   - // 调用 callWithFunctionSupport 发送请求
101   - Flux<YiYanChatCompletionResponse> response = this.yiYanApi.chatCompletionStream(request);
102   - return response.map(chunk -> {
103   - // TODO @fan:ChatResponseMetadata chatResponseMetadata
104   - return new ChatResponse(List.of(new Generation(chunk.getResult())));
105   - });
106   - });
107   - }
108   -
109   - private YiYanChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
110   - // 参考 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t 文档,system 是独立字段
111   - // 1.1 获取 user 和 assistant
112   - List<YiYanChatCompletionRequest.Message> messageList = prompt.getInstructions().stream()
113   - // 过滤 system
114   - .filter(msg -> MessageType.SYSTEM != msg.getMessageType())
115   - .map(message -> new YiYanChatCompletionRequest.Message()
116   - .setRole(message.getMessageType().getValue()).setContent(message.getContent())
117   - ).toList();
118   - // 1.2 获取 system
119   - String systemPrompt = prompt.getInstructions().stream()
120   - .filter(message -> MessageType.SYSTEM == message.getMessageType())
121   - .map(Message::getContent)
122   - .collect(Collectors.joining());
123   -
124   - // 3. 创建 request
125   - YiYanChatCompletionRequest request = new YiYanChatCompletionRequest(messageList);
126   - // 复制 YiYanOptions 属性,到 request 中(这里 options 属性和 request 基本保持一致)
127   - YiYanChatOptions useOptions = getYiYanOptions(prompt);
128   - BeanUtil.copyProperties(useOptions, request);
129   - request.setTopP(useOptions.getTopP())
130   - .setMaxOutputTokens(useOptions.getMaxOutputTokens())
131   - .setTemperature(useOptions.getTemperature())
132   - .setSystem(systemPrompt)
133   - .setStream(stream);
134   - return request;
135   - }
136   -
137   - // TODO @fan:Options 的处理,参考下 OpenAiChatClient 的 createRequest
138   - private YiYanChatOptions getYiYanOptions(Prompt prompt) {
139   - // 两个都为null 则没有配置文件
140   - if (defaultOptions == null && prompt.getOptions() == null) {
141   - // TODO @fan:IllegalArgumentException 参数更好哈
142   - throw new ChatException("ChatOptions 未配置参数!");
143   - }
144   - // 优先使用 Prompt 里面的 ChatOptions
145   - ChatOptions options = defaultOptions;
146   - if (prompt.getOptions() != null) {
147   - options = (ChatOptions) prompt.getOptions();
148   - }
149   - // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断
150   - if (!(options instanceof YiYanChatOptions)) {
151   - // TODO @fan:IllegalArgumentException 参数更好哈
152   - // TODO @fan:需要兼容 ChatOptionsBuilder 创建出来的
153   - throw new ChatException("Prompt 传入的不是 YiYanOptions!");
154   - }
155   - // 转换 YiYanOptions
156   - return (YiYanChatOptions) options;
157   - }
158   -
159   -}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/YiYanChatOptions.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan;
2   -
3   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatCompletionRequest;
4   -import lombok.Data;
5   -import org.springframework.ai.chat.prompt.ChatOptions;
6   -
7   -import java.util.List;
8   -
9   -/**
10   - * 文心一言的 {@link ChatOptions} 实现类
11   - *
12   - * 字段说明:参考 <a href="https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t">ERNIE-4.0-8K</a>
13   - *
14   - * @author fansili
15   - */
16   -@Data
17   -public class YiYanChatOptions implements ChatOptions {
18   -
19   - /**
20   - * functions 函数
21   - */
22   - private List<YiYanChatCompletionRequest.Function> functions;
23   - /**
24   - * temperature
25   - */
26   - private Float temperature;
27   - /**
28   - * topP
29   - */
30   - private Float topP;
31   - /**
32   - * 通过对已生成的token增加惩罚,减少重复生成的现象
33   - */
34   - private Float penaltyScore;
35   - /**
36   - * stream 模式请求
37   - */
38   - private Boolean stream;
39   - /**
40   - * system 提示
41   - */
42   - private String system;
43   - /**
44   - * 生成停止标识,当模型生成结果以stop中某个元素结尾时,停止文本生成
45   - */
46   - private List<String> stop;
47   - /**
48   - * 是否强制关闭实时搜索功能
49   - */
50   - private Boolean disableSearch;
51   - /**
52   - * 是否开启上角标返回
53   - */
54   - private Boolean enableCitation;
55   - /**
56   - * 输出最大 token
57   - */
58   - private Integer maxOutputTokens;
59   - /**
60   - * 响应格式 text、json_object
61   - */
62   - private String responseFormat;
63   - /**
64   - * 用户id
65   - */
66   - private String userId;
67   - /**
68   - * 在函数调用场景下,提示大模型选择指定的函数(非强制),说明:指定的函数名必须在functions中存在
69   - * tip: ERNIE-4.0-8K 模型没有这个字段
70   - */
71   - private String toolChoice;
72   -
73   - @Override
74   - public Float getTemperature() {
75   - return this.temperature;
76   - }
77   -
78   - @Override
79   - public Float getTopP() {
80   - return topP;
81   - }
82   -
83   - /**
84   - * 百度么有 topK
85   - */
86   - @Override
87   - public Integer getTopK() {
88   - return null;
89   - }
90   -
91   -}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/api/YiYanApi.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan.api;
2   -
3   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.exception.YiYanApiException;
4   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5   -import org.springframework.http.HttpStatusCode;
6   -import org.springframework.http.ResponseEntity;
7   -import org.springframework.web.reactive.function.client.WebClient;
8   -import reactor.core.publisher.Flux;
9   -import reactor.core.publisher.Mono;
10   -
11   -/**
12   - * 文心一言 API
13   - *
14   - * @author fansili
15   - */
16   -public class YiYanApi {
17   -
18   - private static final String DEFAULT_BASE_URL = "https://aip.baidubce.com";
19   -
20   - private static final String AUTH_2_TOKEN_URI = "/oauth/2.0/token";
21   -
22   - public static final YiYanChatModel DEFAULT_CHAT_MODEL = YiYanChatModel.ERNIE4_0;
23   -
24   - private final String appKey;
25   - private final String secretKey;
26   - /**
27   - * TODO fan:这个是不是要有个刷新机制哈;如果目前不需要,可以删除掉 refreshTokenSecondTime;整体更简洁;
28   - */
29   - private final String token;
30   - /**
31   - * token 刷新时间(秒)
32   - */
33   - private int refreshTokenSecondTime;
34   - /**
35   - * 发送请求 webClient
36   - */
37   - private final WebClient webClient;
38   - /**
39   - * 使用的模型
40   - */
41   - private final YiYanChatModel useChatModel;
42   -
43   - // TODO fan:看看是不是去掉 refreshTokenSecondTime 字段
44   - public YiYanApi(String appKey, String secretKey, YiYanChatModel useChatModel, int refreshTokenSecondTime) {
45   - this.appKey = appKey;
46   - this.secretKey = secretKey;
47   - this.useChatModel = useChatModel;
48   - this.refreshTokenSecondTime = refreshTokenSecondTime;
49   - this.webClient = WebClient.builder().baseUrl(DEFAULT_BASE_URL).build();
50   - // 获取访问令牌
51   - token = getToken();
52   - }
53   -
54   - /**
55   - * 获得访问令牌
56   - *
57   - * @see <a href="https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5">文档地址</>
58   - * @return 访问令牌
59   - */
60   - private String getToken() {
61   - ResponseEntity<YiYanAuthResponse> response = this.webClient.post()
62   - .uri(uriBuilder -> uriBuilder.path(AUTH_2_TOKEN_URI)
63   - .queryParam("grant_type", "client_credentials")
64   - .queryParam("client_id", appKey)
65   - .queryParam("client_secret", secretKey)
66   - .build()
67   - )
68   - .retrieve()
69   - .toEntity(YiYanAuthResponse.class)
70   - .block();
71   - // 检查请求状态
72   - // TODO @fan:可以使用 response.getStatusCode().is2xxSuccessful()
73   - if (HttpStatusCode.valueOf(200) != response.getStatusCode()
74   - || response.getBody() == null) {
75   - // TODO @fan:可以使用 IllegalStateException 替代;另外,最好打印下返回;方便排错;
76   - throw new YiYanApiException("一言认证失败! api:https://aip.baidubce.com/oauth/2.0/token 请检查 client_id、client_secret 是否正确!");
77   - }
78   - return response.getBody().getAccess_token();
79   - }
80   -
81   - public ResponseEntity<YiYanChatCompletionResponse> chatCompletionEntity(YiYanChatCompletionRequest request) {
82   - // TODO: 2024/3/10 小范 这里错误信息返回的结构不一样
83   -// {"error_code":17,"error_msg":"Open api daily request limit reached"}
84   - return this.webClient.post()
85   - .uri(uriBuilder
86   - -> uriBuilder.path(useChatModel.getUri())
87   - .queryParam("access_token", token)
88   - .build())
89   - .body(Mono.just(JsonUtils.toJsonString(request)), String.class)
90   - .retrieve()
91   - .toEntity(YiYanChatCompletionResponse.class)
92   - .block();
93   - }
94   -
95   - public Flux<YiYanChatCompletionResponse> chatCompletionStream(YiYanChatCompletionRequest request) {
96   - return this.webClient.post()
97   - .uri(uriBuilder
98   - -> uriBuilder.path(useChatModel.getUri())
99   - .queryParam("access_token", token)
100   - .build())
101   - .body(Mono.just(request), YiYanChatCompletionRequest.class)
102   - .retrieve()
103   - .bodyToFlux(YiYanChatCompletionResponse.class);
104   - }
105   -
106   -}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/api/YiYanAuthResponse.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan.api;
2   -
3   -import lombok.Data;
4   -
5   -// TODO @fan:字段驼峰;字段注释都可以删除,贴个链接就好;
6   -/**
7   - * 获取文心一言的 access_token 的 Response
8   - *
9   - * @author fansili
10   - */
11   -@Data
12   -public class YiYanAuthResponse {
13   -
14   - /**
15   - * 访问凭证
16   - */
17   - private String access_token;
18   - /**
19   - * 有效期,Access Token的有效期。
20   - * 说明:单位是秒,有效期30天
21   - */
22   - private int expires_in;
23   - /**
24   - * 错误码,说明:响应失败时返回该字段,成功时不返回
25   - */
26   - private String error;
27   - /**
28   - * 错误描述信息,帮助理解和解决发生的错误
29   - * 说明:响应失败时返回该字段,成功时不返回
30   - */
31   - private String error_description;
32   - /**
33   - * 暂时未使用,可忽略
34   - */
35   - private String session_key;
36   - /**
37   - * 暂时未使用,可忽略
38   - */
39   - private String refresh_token;
40   - /**
41   - * 暂时未使用,可忽略
42   - */
43   - private String scope;
44   - /**
45   - * 暂时未使用,可忽略
46   - */
47   - private String session_secret;
48   -}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/api/YiYanChatCompletionRequest.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan.api;
2   -
3   -import com.fasterxml.jackson.annotation.JsonProperty;
4   -import lombok.Data;
5   -
6   -import java.util.List;
7   -
8   -/**
9   - * 文心一言 Completion Request
10   - *
11   - * 百度千帆文档:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/jlil56u11
12   - *
13   - * @author fansili
14   - */
15   -@Data
16   -public class YiYanChatCompletionRequest {
17   -
18   - public YiYanChatCompletionRequest(List<Message> messages) {
19   - this.messages = messages;
20   - }
21   -
22   - /**
23   - * 聊天上下文信息
24   - */
25   - private List<Message> messages;
26   - /**
27   - * functions 函数
28   - */
29   - private List<Function> functions;
30   - /**
31   - * temperature
32   - */
33   - private Float temperature;
34   - /**
35   - * topP
36   - */
37   - @JsonProperty("top_p")
38   - private Float topP;
39   - /**
40   - * 通过对已生成的token增加惩罚,减少重复生成的现象
41   - */
42   - @JsonProperty("penalty_score")
43   - private Float penaltyScore;
44   - /**
45   - * stream 模式
46   - */
47   - private Boolean stream;
48   - /**
49   - * system 预设角色
50   - */
51   - private String system;
52   - /**
53   - * 生成停止标识,当模型生成结果以stop中某个元素结尾时,停止文本生成
54   - */
55   - private List<String> stop;
56   - /**
57   - * 是否强制关闭实时搜索功能
58   - */
59   - @JsonProperty("disable_search")
60   - private Boolean disableSearch;
61   - /**
62   - * 是否开启上角标返回
63   - */
64   - @JsonProperty("enable_citation")
65   - private Boolean enableCitation;
66   - /**
67   - * 最大输出 token 数
68   - */
69   - @JsonProperty("max_output_tokens")
70   - private Integer maxOutputTokens;
71   - /**
72   - * 返回格式 text、json_object
73   - */
74   - @JsonProperty("response_format")
75   - private String responseFormat;
76   - /**
77   - * 用户 id
78   - */
79   - @JsonProperty("user_id")
80   - private String userId;
81   - /**
82   - * 在函数调用场景下,提示大模型选择指定的函数(非强制),说明:指定的函数名必须在functions中存在
83   - * tip: ERNIE-4.0-8K 模型没有这个字段
84   - */
85   - @JsonProperty("tool_choice")
86   - private String toolChoice;
87   -
88   -
89   - @Data
90   - public static class Message {
91   -
92   - private String role;
93   -
94   - private String content;
95   -
96   - }
97   -
98   - @Data
99   - public static class ToolChoice {
100   - /**
101   - * 指定工具类型,function
102   - * 必填: 是
103   - */
104   - private String type;
105   - /**
106   - * 指定要使用的函数
107   - * 必填: 是
108   - */
109   - private Function function;
110   - /**
111   - * 指定要使用的函数名
112   - * 必填: 是
113   - */
114   - private String name;
115   - }
116   -
117   - @Data
118   - public static class Function {
119   - /**
120   - * 函数名
121   - * 必填: 是
122   - */
123   - private String name;
124   - /**
125   - * 函数描述
126   - * 必填: 是
127   - */
128   - private String description;
129   - /**
130   - * 函数请求参数,说明:
131   - * (1)JSON Schema 格式,参考JSON Schema描述
132   - * (2)如果函数没有请求参数,parameters值格式如下:
133   - * {"type": "object","properties": {}}
134   - * 必填: 是
135   - */
136   - private String parameters;
137   - /**
138   - * 函数响应参数,JSON Schema 格式,参考JSON Schema描述
139   - * 必填: 否
140   - */
141   - private String responses;
142   - /**
143   - * function调用的一些历史示例,说明:
144   - * (1)可以提供正例(正常触发)和反例(无需触发)的example
145   - * ·正例:从历史请求数据中获取
146   - * ·反例:
147   - * 当role = user,不会触发请求的query
148   - * 当role = assistant,有固定的格式。function_call的name为空,arguments是空对象:"{}",thought可以填固定的:"我不需要调用任何工具"
149   - * (2)兼容之前的 List(example) 格式
150   - */
151   - private String examples;
152   - }
153   -
154   -}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/api/YiYanChatCompletionResponse.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan.api;
2   -
3   -import lombok.Data;
4   -
5   -/**
6   - * 文心一言 Completion Response
7   - *
8   - * 百度链接: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t
9   - *
10   - * @author fansili
11   - */
12   -@Data
13   -public class YiYanChatCompletionResponse {
14   -
15   - /**
16   - * 本轮对话的id
17   - */
18   - private String id;
19   - /**
20   - * 回包类型,chat.completion:多轮对话返回
21   - */
22   - private String object;
23   - /**
24   - * 时间戳
25   - */
26   - private int created;
27   - /**
28   - * 表示当前子句的序号。只有在流式接口模式下会返回该字段
29   - */
30   - private int sentence_id;
31   - /**
32   - * 表示当前子句是否是最后一句。只有在流式接口模式下会返回该字段
33   - */
34   - private boolean is_end;
35   - /**
36   - * 当前生成的结果是否被截断
37   - */
38   - private boolean is_truncated;
39   - /**
40   - * 输出内容标识,说明:
41   - * · normal:输出内容完全由大模型生成,未触发截断、替换
42   - * · stop:输出结果命中入参stop中指定的字段后被截断
43   - * · length:达到了最大的token数,根据EB返回结果is_truncated来截断
44   - * · content_filter:输出内容被截断、兜底、替换为**等
45   - */
46   - private String finish_reason;
47   - /**
48   - * 搜索数据,当请求参数enable_citation为true并且触发搜索时,会返回该字段
49   - */
50   - private String search_info;
51   - /**
52   - * 对话返回结果
53   - */
54   - private String result;
55   - /**
56   - * 表示用户输入是否存在安全,是否关闭当前会话,清理历史会话信息
57   - * true:是,表示用户输入存在安全风险,建议关闭当前会话,清理历史会话信息
58   - * false:否,表示用户输入无安全风险
59   - */
60   - private boolean need_clear_history;
61   - /**
62   - * 说明:
63   - * · 0:正常返回
64   - * · 其他:非正常
65   - */
66   - private int flag;
67   - /**
68   - * 当need_clear_history为true时,此字段会告知第几轮对话有敏感信息,如果是当前问题,ban_round=-1
69   - */
70   - private int ban_round;
71   - /**
72   - * token统计信息
73   - */
74   - private Usage usage;
75   -
76   - @Data
77   - public static class Usage {
78   - /**
79   - * 问题tokens数
80   - */
81   - private int prompt_tokens;
82   - /**
83   - * 回答tokens数
84   - */
85   - private int completion_tokens;
86   - /**
87   - * tokens总数
88   - */
89   - private int total_tokens;
90   - }
91   -
92   -}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/api/YiYanChatModel.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan.api;
2   -
3   -import lombok.AllArgsConstructor;
4   -import lombok.Getter;
5   -
6   -/**
7   - * 文心一言的模型枚举
8   - *
9   - * 可参考 <a href="https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t">百度文档</>
10   - *
11   - * @author fansili
12   - */
13   -@Getter
14   -@AllArgsConstructor
15   -public enum YiYanChatModel {
16   -
17   - ERNIE4_0("ERNIE 4.0", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro"),
18   - ERNIE4_3_5_8K("ERNIE-3.5-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"),
19   - ERNIE4_3_5_8K_0205("ERNIE-3.5-8K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-0205"),
20   - ERNIE4_3_5_8K_1222("ERNIE-3.5-8K-1222", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-1222"),
21   - ERNIE4_BOT_8K("ERNIE-Bot-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie_bot_8k"),
22   - ERNIE4_3_5_4K_0205("ERNIE-3.5-4K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-4k-0205"),
23   - ;
24   -
25   - /**
26   - * 模型名
27   - */
28   - private final String model;
29   - /**
30   - * API URL
31   - */
32   - private final String uri;
33   -
34   - public static YiYanChatModel valueOfModel(String model) {
35   - for (YiYanChatModel modelEnum : YiYanChatModel.values()) {
36   - if (modelEnum.getModel().equals(model)) {
37   - return modelEnum;
38   - }
39   - }
40   - throw new IllegalArgumentException("Invalid MessageType value: " + model);
41   - }
42   -}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/exception/YiYanApiException.java deleted
1   -package cn.iocoder.yudao.framework.ai.core.model.yiyan.exception;
2   -
3   -/**
4   - * 一言 api 调用异常
5   - */
6   -public class YiYanApiException extends RuntimeException {
7   -
8   - public YiYanApiException(String message) {
9   - super(message);
10   - }
11   -
12   - public YiYanApiException(String message, Throwable cause) {
13   - super(message, cause);
14   - }
15   -
16   -}
17 0 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java
1 1 package cn.iocoder.yudao.framework.ai.chat;
2 2  
3   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
4   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
5   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
6   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatModel;
7   -import org.junit.Before;
8   -import org.junit.Test;
9   -import org.springframework.ai.chat.messages.Message;
10   -import org.springframework.ai.chat.messages.SystemMessage;
11   -import org.springframework.ai.chat.messages.UserMessage;
12   -import org.springframework.ai.chat.model.ChatResponse;
13   -import org.springframework.ai.chat.prompt.Prompt;
14   -import reactor.core.publisher.Flux;
15   -
16   -import java.util.ArrayList;
17   -import java.util.List;
18   -import java.util.Scanner;
  3 +//import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
  4 +//import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
  5 +//import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
  6 +//import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatModel;
  7 +//import org.junit.Before;
  8 +//import org.junit.Test;
  9 +//import org.springframework.ai.chat.messages.Message;
  10 +//import org.springframework.ai.chat.messages.SystemMessage;
  11 +//import org.springframework.ai.chat.messages.UserMessage;
  12 +//import org.springframework.ai.chat.model.ChatResponse;
  13 +//import org.springframework.ai.chat.prompt.Prompt;
  14 +//import reactor.core.publisher.Flux;
  15 +//
  16 +//import java.util.ArrayList;
  17 +//import java.util.List;
  18 +//import java.util.Scanner;
19 19  
20 20 // TODO 芋艿:整理单测
21 21 /**
... ... @@ -26,49 +26,49 @@ import java.util.Scanner;
26 26 */
27 27 public class YiYanChatTests {
28 28  
29   - private YiYanChatClient yiYanChatClient;
30   -
31   - @Before
32   - public void setup() {
33   - YiYanApi yiYanApi = new YiYanApi(
34   - "x0cuLZ7XsaTCU08vuJWO87Lg",
35   - "R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK",
36   - YiYanChatModel.ERNIE4_3_5_8K,
37   - 86400
38   - );
39   - YiYanChatOptions yiYanOptions = new YiYanChatOptions();
40   - yiYanOptions.setMaxOutputTokens(2048);
41   - yiYanOptions.setTopP(0.6f);
42   - yiYanOptions.setTemperature(0.85f);
43   - yiYanChatClient = new YiYanChatClient(
44   - yiYanApi,
45   - yiYanOptions
46   - );
47   - }
48   -
49   - @Test
50   - public void callTest() {
51   -
52   - // tip: 百度的message 有特殊规则(最后一个message为当前请求的信息,前面的message为历史对话信息)
53   - // tip: 地址 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/jlil56u11
54   - List<Message> messages = new ArrayList<>();
55   - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景,所有问题都采用文言文回答。"));
56   - messages.add(new UserMessage("长沙怎么样?"));
57   -
58   - ChatResponse call = yiYanChatClient.call(new Prompt(messages));
59   - System.err.println(call.getResult());
60   - }
61   -
62   - @Test
63   - public void streamTest() {
64   - List<Message> messages = new ArrayList<>();
65   - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景,所有问题都采用文言文回答。"));
66   - messages.add(new UserMessage("长沙怎么样?"));
67   -
68   - Flux<ChatResponse> fluxResponse = yiYanChatClient.stream(new Prompt(messages));
69   - fluxResponse.subscribe(chatResponse -> System.err.print(chatResponse.getResult().getOutput().getContent()));
70   - // 阻止退出
71   - Scanner scanner = new Scanner(System.in);
72   - scanner.nextLine();
73   - }
  29 +// private YiYanChatClient yiYanChatClient;
  30 +//
  31 +// @Before
  32 +// public void setup() {
  33 +// YiYanApi yiYanApi = new YiYanApi(
  34 +// "x0cuLZ7XsaTCU08vuJWO87Lg",
  35 +// "R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK",
  36 +// YiYanChatModel.ERNIE4_3_5_8K,
  37 +// 86400
  38 +// );
  39 +// YiYanChatOptions yiYanOptions = new YiYanChatOptions();
  40 +// yiYanOptions.setMaxOutputTokens(2048);
  41 +// yiYanOptions.setTopP(0.6f);
  42 +// yiYanOptions.setTemperature(0.85f);
  43 +// yiYanChatClient = new YiYanChatClient(
  44 +// yiYanApi,
  45 +// yiYanOptions
  46 +// );
  47 +// }
  48 +//
  49 +// @Test
  50 +// public void callTest() {
  51 +//
  52 +// // tip: 百度的message 有特殊规则(最后一个message为当前请求的信息,前面的message为历史对话信息)
  53 +// // tip: 地址 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/jlil56u11
  54 +// List<Message> messages = new ArrayList<>();
  55 +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景,所有问题都采用文言文回答。"));
  56 +// messages.add(new UserMessage("长沙怎么样?"));
  57 +//
  58 +// ChatResponse call = yiYanChatClient.call(new Prompt(messages));
  59 +// System.err.println(call.getResult());
  60 +// }
  61 +//
  62 +// @Test
  63 +// public void streamTest() {
  64 +// List<Message> messages = new ArrayList<>();
  65 +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景,所有问题都采用文言文回答。"));
  66 +// messages.add(new UserMessage("长沙怎么样?"));
  67 +//
  68 +// Flux<ChatResponse> fluxResponse = yiYanChatClient.stream(new Prompt(messages));
  69 +// fluxResponse.subscribe(chatResponse -> System.err.print(chatResponse.getResult().getOutput().getContent()));
  70 +// // 阻止退出
  71 +// Scanner scanner = new Scanner(System.in);
  72 +// scanner.nextLine();
  73 +// }
74 74 }
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -160,19 +160,11 @@ spring:
160 160 gemini:
161 161 project-id: 1 # TODO 芋艿:缺配置
162 162 location: 2
  163 + qianfan: # 文心一言
  164 + api-key: x0cuLZ7XsaTCU08vuJWO87Lg
  165 + secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
163 166  
164 167 yudao.ai:
165   - yiyan:
166   - enable: true
167   - aiPlatform: YI_YAN # TODO @fan:建议每个都独立配置属性类
168   - max-tokens: 1500
169   - temperature: 0.85
170   - topP: 0.8
171   - topK: 0
172   - appKey: x0cuLZ7XsaTCU08vuJWO87Lg
173   - secretKey: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
174   - refreshTokenSecondTime: 86400
175   - model: ERNIE4_3_5_8K
176 168 xinghuo:
177 169 enable: true
178 170 aiPlatform: XING_HUO # TODO @fan:建议每个都独立配置属性类
... ...