Commit a7e5aaec3bf155a8c451fc031dbdaa5ff4d283d2
1 parent
11747099
【代码新增】AI:轨迹流动的接入
Showing
11 changed files
with
184 additions
and
4 deletions
.image/common/ai-feature.png
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; | @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; | ||
| 7 | import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; | 7 | import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; |
| 8 | import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; | 8 | import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 9 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | 9 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 10 | +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; | ||
| 10 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 11 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 11 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | 12 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 12 | import lombok.extern.slf4j.Slf4j; | 13 | import lombok.extern.slf4j.Slf4j; |
| @@ -92,6 +93,32 @@ public class YudaoAiAutoConfiguration { | @@ -92,6 +93,32 @@ public class YudaoAiAutoConfiguration { | ||
| 92 | } | 93 | } |
| 93 | 94 | ||
| 94 | @Bean | 95 | @Bean |
| 96 | + @ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true") | ||
| 97 | + public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) { | ||
| 98 | + YudaoAiProperties.SiliconFlowProperties properties = yudaoAiProperties.getSiliconflow(); | ||
| 99 | + return buildSiliconFlowChatClient(properties); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) { | ||
| 103 | + if (StrUtil.isEmpty(properties.getModel())) { | ||
| 104 | + properties.setModel(SiliconFlowChatModel.MODEL_DEFAULT); | ||
| 105 | + } | ||
| 106 | + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() | ||
| 107 | + .openAiApi(OpenAiApi.builder() | ||
| 108 | + .baseUrl(SiliconFlowChatModel.BASE_URL) | ||
| 109 | + .apiKey(properties.getApiKey()) | ||
| 110 | + .build()) | ||
| 111 | + .defaultOptions(OpenAiChatOptions.builder() | ||
| 112 | + .model(properties.getModel()) | ||
| 113 | + .temperature(properties.getTemperature()) | ||
| 114 | + .maxTokens(properties.getMaxTokens()) | ||
| 115 | + .topP(properties.getTopP()) | ||
| 116 | + .build()) | ||
| 117 | + .build(); | ||
| 118 | + return new SiliconFlowChatModel(openAiChatModel); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + @Bean | ||
| 95 | @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true") | 122 | @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true") |
| 96 | public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) { | 123 | public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) { |
| 97 | YudaoAiProperties.HunYuanProperties properties = yudaoAiProperties.getHunyuan(); | 124 | YudaoAiProperties.HunYuanProperties properties = yudaoAiProperties.getHunyuan(); |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
| @@ -32,6 +32,12 @@ public class YudaoAiProperties { | @@ -32,6 +32,12 @@ public class YudaoAiProperties { | ||
| 32 | private HunYuanProperties hunyuan; | 32 | private HunYuanProperties hunyuan; |
| 33 | 33 | ||
| 34 | /** | 34 | /** |
| 35 | + * 硅基流动 | ||
| 36 | + */ | ||
| 37 | + @SuppressWarnings("SpellCheckingInspection") | ||
| 38 | + private SiliconFlowProperties siliconflow; | ||
| 39 | + | ||
| 40 | + /** | ||
| 35 | * 讯飞星火 | 41 | * 讯飞星火 |
| 36 | */ | 42 | */ |
| 37 | @SuppressWarnings("SpellCheckingInspection") | 43 | @SuppressWarnings("SpellCheckingInspection") |
| @@ -89,6 +95,19 @@ public class YudaoAiProperties { | @@ -89,6 +95,19 @@ public class YudaoAiProperties { | ||
| 89 | } | 95 | } |
| 90 | 96 | ||
| 91 | @Data | 97 | @Data |
| 98 | + public static class SiliconFlowProperties { | ||
| 99 | + | ||
| 100 | + private String enable; | ||
| 101 | + private String apiKey; | ||
| 102 | + | ||
| 103 | + private String model; | ||
| 104 | + private Double temperature; | ||
| 105 | + private Integer maxTokens; | ||
| 106 | + private Double topP; | ||
| 107 | + | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + @Data | ||
| 92 | public static class XingHuoProperties { | 111 | public static class XingHuoProperties { |
| 93 | 112 | ||
| 94 | private String enable; | 113 | private String enable; |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
| @@ -21,6 +21,7 @@ public enum AiPlatformEnum { | @@ -21,6 +21,7 @@ public enum AiPlatformEnum { | ||
| 21 | XING_HUO("XingHuo", "星火"), // 讯飞 | 21 | XING_HUO("XingHuo", "星火"), // 讯飞 |
| 22 | DOU_BAO("DouBao", "豆包"), // 字节 | 22 | DOU_BAO("DouBao", "豆包"), // 字节 |
| 23 | HUN_YUAN("HunYuan", "混元"), // 腾讯 | 23 | HUN_YUAN("HunYuan", "混元"), // 腾讯 |
| 24 | + SILICON_FLOW("SiliconFlow", "硅基流动"), // 硅基流动 | ||
| 24 | 25 | ||
| 25 | // ========== 国外平台 ========== | 26 | // ========== 国外平台 ========== |
| 26 | 27 |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
| @@ -13,6 +13,7 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; | @@ -13,6 +13,7 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; | ||
| 13 | import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; | 13 | import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; |
| 14 | import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; | 14 | import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 15 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | 15 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 16 | +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; | ||
| 16 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 17 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 17 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | 18 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 18 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAutoConfiguration; | 19 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAutoConfiguration; |
| @@ -80,6 +81,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -80,6 +81,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 80 | return buildDouBaoChatModel(apiKey); | 81 | return buildDouBaoChatModel(apiKey); |
| 81 | case HUN_YUAN: | 82 | case HUN_YUAN: |
| 82 | return buildHunYuanChatModel(apiKey, url); | 83 | return buildHunYuanChatModel(apiKey, url); |
| 84 | + case SILICON_FLOW: | ||
| 85 | + return buildSiliconFlowChatModel(apiKey); | ||
| 83 | case ZHI_PU: | 86 | case ZHI_PU: |
| 84 | return buildZhiPuChatModel(apiKey, url); | 87 | return buildZhiPuChatModel(apiKey, url); |
| 85 | case XING_HUO: | 88 | case XING_HUO: |
| @@ -110,6 +113,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -110,6 +113,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 110 | return SpringUtil.getBean(DouBaoChatModel.class); | 113 | return SpringUtil.getBean(DouBaoChatModel.class); |
| 111 | case HUN_YUAN: | 114 | case HUN_YUAN: |
| 112 | return SpringUtil.getBean(HunYuanChatModel.class); | 115 | return SpringUtil.getBean(HunYuanChatModel.class); |
| 116 | + case SILICON_FLOW: | ||
| 117 | + return SpringUtil.getBean(SiliconFlowChatModel.class); | ||
| 113 | case ZHI_PU: | 118 | case ZHI_PU: |
| 114 | return SpringUtil.getBean(ZhiPuAiChatModel.class); | 119 | return SpringUtil.getBean(ZhiPuAiChatModel.class); |
| 115 | case XING_HUO: | 120 | case XING_HUO: |
| @@ -291,6 +296,15 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -291,6 +296,15 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 291 | } | 296 | } |
| 292 | 297 | ||
| 293 | /** | 298 | /** |
| 299 | + * 可参考 {@link YudaoAiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)} | ||
| 300 | + */ | ||
| 301 | + private ChatModel buildSiliconFlowChatModel(String apiKey) { | ||
| 302 | + YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties() | ||
| 303 | + .setApiKey(apiKey); | ||
| 304 | + return new YudaoAiAutoConfiguration().buildSiliconFlowChatClient(properties); | ||
| 305 | + } | ||
| 306 | + | ||
| 307 | + /** | ||
| 294 | * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法 | 308 | * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法 |
| 295 | */ | 309 | */ |
| 296 | private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { | 310 | private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.core.model.siliconflow; | ||
| 2 | + | ||
| 3 | +import lombok.RequiredArgsConstructor; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import org.springframework.ai.chat.model.ChatModel; | ||
| 6 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 7 | +import org.springframework.ai.chat.prompt.ChatOptions; | ||
| 8 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 9 | +import org.springframework.ai.openai.OpenAiChatModel; | ||
| 10 | +import reactor.core.publisher.Flux; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 硅基流动 {@link ChatModel} 实现类 | ||
| 14 | + * | ||
| 15 | + * 1. API 文档:<a href="https://docs.siliconflow.cn/cn/api-reference/chat-completions/chat-completions">API 文档</a> | ||
| 16 | + * | ||
| 17 | + * @author fansili | ||
| 18 | + */ | ||
| 19 | +@Slf4j | ||
| 20 | +@RequiredArgsConstructor | ||
| 21 | +public class SiliconFlowChatModel implements ChatModel { | ||
| 22 | + | ||
| 23 | + public static final String BASE_URL = "https://api.siliconflow.cn"; | ||
| 24 | + | ||
| 25 | + public static final String MODEL_DEFAULT = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 兼容 OpenAI 接口,进行复用 | ||
| 29 | + */ | ||
| 30 | + private final OpenAiChatModel openAiChatModel; | ||
| 31 | + | ||
| 32 | + @Override | ||
| 33 | + public ChatResponse call(Prompt prompt) { | ||
| 34 | + return openAiChatModel.call(prompt); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + @Override | ||
| 38 | + public Flux<ChatResponse> stream(Prompt prompt) { | ||
| 39 | + return openAiChatModel.stream(prompt); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + @Override | ||
| 43 | + public ChatOptions getDefaultOptions() { | ||
| 44 | + return openAiChatModel.getDefaultOptions(); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | +} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
| @@ -33,6 +33,7 @@ public class AiUtils { | @@ -33,6 +33,7 @@ public class AiUtils { | ||
| 33 | case DOU_BAO: // 复用 OpenAI 客户端 | 33 | case DOU_BAO: // 复用 OpenAI 客户端 |
| 34 | case HUN_YUAN: // 复用 OpenAI 客户端 | 34 | case HUN_YUAN: // 复用 OpenAI 客户端 |
| 35 | case XING_HUO: // 复用 OpenAI 客户端 | 35 | case XING_HUO: // 复用 OpenAI 客户端 |
| 36 | + case SILICON_FLOW: // 复用 OpenAI 客户端 | ||
| 36 | return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); | 37 | return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); |
| 37 | case AZURE_OPENAI: | 38 | case AZURE_OPENAI: |
| 38 | // TODO 芋艿:貌似没 model 字段???! | 39 | // TODO 芋艿:貌似没 model 字段???! |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DouBaoChatModelTests.java
| 1 | package cn.iocoder.yudao.framework.ai.chat; | 1 | package cn.iocoder.yudao.framework.ai.chat; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; | ||
| 4 | import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; | 3 | import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; |
| 5 | import org.junit.jupiter.api.Disabled; | 4 | import org.junit.jupiter.api.Disabled; |
| 6 | import org.junit.jupiter.api.Test; | 5 | import org.junit.jupiter.api.Test; |
| @@ -18,7 +17,7 @@ import java.util.ArrayList; | @@ -18,7 +17,7 @@ import java.util.ArrayList; | ||
| 18 | import java.util.List; | 17 | import java.util.List; |
| 19 | 18 | ||
| 20 | /** | 19 | /** |
| 21 | - * {@link DeepSeekChatModel} 集成测试 | 20 | + * {@link DouBaoChatModel} 集成测试 |
| 22 | * | 21 | * |
| 23 | * @author 芋道源码 | 22 | * @author 芋道源码 |
| 24 | */ | 23 | */ |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/HunYuanChatModelTests.java
| 1 | package cn.iocoder.yudao.framework.ai.chat; | 1 | package cn.iocoder.yudao.framework.ai.chat; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; | ||
| 4 | import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; | 3 | import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 5 | import org.junit.jupiter.api.Disabled; | 4 | import org.junit.jupiter.api.Disabled; |
| 6 | import org.junit.jupiter.api.Test; | 5 | import org.junit.jupiter.api.Test; |
| @@ -18,7 +17,7 @@ import java.util.ArrayList; | @@ -18,7 +17,7 @@ import java.util.ArrayList; | ||
| 18 | import java.util.List; | 17 | import java.util.List; |
| 19 | 18 | ||
| 20 | /** | 19 | /** |
| 21 | - * {@link DeepSeekChatModel} 集成测试 | 20 | + * {@link HunYuanChatModel} 集成测试 |
| 22 | * | 21 | * |
| 23 | * @author 芋道源码 | 22 | * @author 芋道源码 |
| 24 | */ | 23 | */ |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.chat; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; | ||
| 4 | +import org.junit.jupiter.api.Disabled; | ||
| 5 | +import org.junit.jupiter.api.Test; | ||
| 6 | +import org.springframework.ai.chat.messages.Message; | ||
| 7 | +import org.springframework.ai.chat.messages.SystemMessage; | ||
| 8 | +import org.springframework.ai.chat.messages.UserMessage; | ||
| 9 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 10 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 11 | +import org.springframework.ai.openai.OpenAiChatModel; | ||
| 12 | +import org.springframework.ai.openai.OpenAiChatOptions; | ||
| 13 | +import org.springframework.ai.openai.api.OpenAiApi; | ||
| 14 | +import reactor.core.publisher.Flux; | ||
| 15 | + | ||
| 16 | +import java.util.ArrayList; | ||
| 17 | +import java.util.List; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * {@link SiliconFlowChatModel} 集成测试 | ||
| 21 | + * | ||
| 22 | + * @author 芋道源码 | ||
| 23 | + */ | ||
| 24 | +public class SiliconFlowChatModelTests { | ||
| 25 | + | ||
| 26 | + private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() | ||
| 27 | + .openAiApi(OpenAiApi.builder() | ||
| 28 | + .baseUrl(SiliconFlowChatModel.BASE_URL) | ||
| 29 | + .apiKey("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // apiKey | ||
| 30 | + .build()) | ||
| 31 | + .defaultOptions(OpenAiChatOptions.builder() | ||
| 32 | + .model(SiliconFlowChatModel.MODEL_DEFAULT) // 模型 | ||
| 33 | +// .model("deepseek-ai/DeepSeek-R1") // 模型(deepseek-ai/DeepSeek-R1)可用赠费 | ||
| 34 | +// .model("Pro/deepseek-ai/DeepSeek-R1") // 模型(Pro/deepseek-ai/DeepSeek-R1)需要付费 | ||
| 35 | + .temperature(0.7) | ||
| 36 | + .build()) | ||
| 37 | + .build(); | ||
| 38 | + | ||
| 39 | + private final SiliconFlowChatModel chatModel = new SiliconFlowChatModel(openAiChatModel); | ||
| 40 | + | ||
| 41 | + @Test | ||
| 42 | + @Disabled | ||
| 43 | + public void testCall() { | ||
| 44 | + // 准备参数 | ||
| 45 | + List<Message> messages = new ArrayList<>(); | ||
| 46 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 47 | + messages.add(new UserMessage("1 + 1 = ?")); | ||
| 48 | + | ||
| 49 | + // 调用 | ||
| 50 | + ChatResponse response = chatModel.call(new Prompt(messages)); | ||
| 51 | + // 打印结果 | ||
| 52 | + System.out.println(response); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + @Test | ||
| 56 | + @Disabled | ||
| 57 | + public void testStream() { | ||
| 58 | + // 准备参数 | ||
| 59 | + List<Message> messages = new ArrayList<>(); | ||
| 60 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 61 | + messages.add(new UserMessage("1 + 1 = ?")); | ||
| 62 | + | ||
| 63 | + // 调用 | ||
| 64 | + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); | ||
| 65 | + // 打印结果 | ||
| 66 | + flux.doOnNext(System.out::println).then().block(); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | +} |
yudao-server/src/main/resources/application.yaml
| @@ -192,6 +192,10 @@ yudao: | @@ -192,6 +192,10 @@ yudao: | ||
| 192 | enable: true | 192 | enable: true |
| 193 | api-key: sk-abc | 193 | api-key: sk-abc |
| 194 | model: hunyuan-turbo | 194 | model: hunyuan-turbo |
| 195 | + siliconflow: # 硅基流动 | ||
| 196 | + enable: true | ||
| 197 | + api-key: sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz | ||
| 198 | + model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B | ||
| 195 | xinghuo: # 讯飞星火 | 199 | xinghuo: # 讯飞星火 |
| 196 | enable: true | 200 | enable: true |
| 197 | appKey: 75b161ed2aef4719b275d6e7f2a4d4cd | 201 | appKey: 75b161ed2aef4719b275d6e7f2a4d4cd |