Commit f2ee2008e6d84bf9d30113592a239803ff0fa5d5
1 parent
588c9fe3
【功能新增】AI:增加 MilvusVectorStore 向量库的接入
Showing
5 changed files
with
75 additions
and
22 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java
| @@ -17,7 +17,7 @@ import org.springframework.ai.chat.model.ChatModel; | @@ -17,7 +17,7 @@ import org.springframework.ai.chat.model.ChatModel; | ||
| 17 | import org.springframework.ai.embedding.EmbeddingModel; | 17 | import org.springframework.ai.embedding.EmbeddingModel; |
| 18 | import org.springframework.ai.image.ImageModel; | 18 | import org.springframework.ai.image.ImageModel; |
| 19 | import org.springframework.ai.vectorstore.VectorStore; | 19 | import org.springframework.ai.vectorstore.VectorStore; |
| 20 | -import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; | 20 | +import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; |
| 21 | import org.springframework.stereotype.Service; | 21 | import org.springframework.stereotype.Service; |
| 22 | import org.springframework.validation.annotation.Validated; | 22 | import org.springframework.validation.annotation.Validated; |
| 23 | 23 | ||
| @@ -114,8 +114,7 @@ public class AiModelServiceImpl implements AiModelService { | @@ -114,8 +114,7 @@ public class AiModelServiceImpl implements AiModelService { | ||
| 114 | } | 114 | } |
| 115 | 115 | ||
| 116 | @Override | 116 | @Override |
| 117 | - public List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, | ||
| 118 | - String platform) { | 117 | + public List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, String platform) { |
| 119 | return modelMapper.selectListByStatusAndType(status, type, platform); | 118 | return modelMapper.selectListByStatusAndType(status, type, platform); |
| 120 | } | 119 | } |
| 121 | 120 | ||
| @@ -163,9 +162,10 @@ public class AiModelServiceImpl implements AiModelService { | @@ -163,9 +162,10 @@ public class AiModelServiceImpl implements AiModelService { | ||
| 163 | platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); | 162 | platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); |
| 164 | 163 | ||
| 165 | // 创建或获取 VectorStore 对象 | 164 | // 创建或获取 VectorStore 对象 |
| 166 | -// return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields); | ||
| 167 | - return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields); | ||
| 168 | -// return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields); | 165 | + // return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields); |
| 166 | + // return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields); | ||
| 167 | + // return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields); | ||
| 168 | + return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields); | ||
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | } | 171 | } |
| 172 | \ No newline at end of file | 172 | \ No newline at end of file |
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
| @@ -70,7 +70,6 @@ | @@ -70,7 +70,6 @@ | ||
| 70 | <groupId>${spring-ai.groupId}</groupId> | 70 | <groupId>${spring-ai.groupId}</groupId> |
| 71 | <artifactId>spring-ai-qdrant-store</artifactId> | 71 | <artifactId>spring-ai-qdrant-store</artifactId> |
| 72 | <version>${spring-ai.version}</version> | 72 | <version>${spring-ai.version}</version> |
| 73 | -<!-- <optional>true</optional>--> | ||
| 74 | </dependency> | 73 | </dependency> |
| 75 | 74 | ||
| 76 | <dependency> | 75 | <dependency> |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| @@ -11,8 +11,12 @@ import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel | @@ -11,8 +11,12 @@ import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel | ||
| 11 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 11 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 12 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | 12 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 13 | import lombok.extern.slf4j.Slf4j; | 13 | import lombok.extern.slf4j.Slf4j; |
| 14 | +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties; | ||
| 15 | +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties; | ||
| 14 | import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties; | 16 | import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties; |
| 15 | import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; | 17 | import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; |
| 18 | +import org.springframework.ai.embedding.BatchingStrategy; | ||
| 19 | +import org.springframework.ai.embedding.TokenCountBatchingStrategy; | ||
| 16 | import org.springframework.ai.openai.OpenAiChatModel; | 20 | import org.springframework.ai.openai.OpenAiChatModel; |
| 17 | import org.springframework.ai.openai.OpenAiChatOptions; | 21 | import org.springframework.ai.openai.OpenAiChatOptions; |
| 18 | import org.springframework.ai.openai.api.OpenAiApi; | 22 | import org.springframework.ai.openai.api.OpenAiApi; |
| @@ -22,7 +26,6 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; | @@ -22,7 +26,6 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; | ||
| 22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | 26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 23 | import org.springframework.boot.context.properties.EnableConfigurationProperties; | 27 | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| 24 | import org.springframework.context.annotation.Bean; | 28 | import org.springframework.context.annotation.Bean; |
| 25 | -import org.springframework.context.annotation.Lazy; | ||
| 26 | 29 | ||
| 27 | /** | 30 | /** |
| 28 | * 芋道 AI 自动配置 | 31 | * 芋道 AI 自动配置 |
| @@ -33,6 +36,7 @@ import org.springframework.context.annotation.Lazy; | @@ -33,6 +36,7 @@ import org.springframework.context.annotation.Lazy; | ||
| 33 | @EnableConfigurationProperties({YudaoAiProperties.class, | 36 | @EnableConfigurationProperties({YudaoAiProperties.class, |
| 34 | QdrantVectorStoreProperties.class, // 解析 Qdrant 配置 | 37 | QdrantVectorStoreProperties.class, // 解析 Qdrant 配置 |
| 35 | RedisVectorStoreProperties.class, // 解析 Redis 配置 | 38 | RedisVectorStoreProperties.class, // 解析 Redis 配置 |
| 39 | + MilvusVectorStoreProperties.class, MilvusServiceClientProperties.class // 解析 Milvus 配置 | ||
| 36 | }) | 40 | }) |
| 37 | @Slf4j | 41 | @Slf4j |
| 38 | public class YudaoAiAutoConfiguration { | 42 | public class YudaoAiAutoConfiguration { |
| @@ -193,18 +197,16 @@ public class YudaoAiAutoConfiguration { | @@ -193,18 +197,16 @@ public class YudaoAiAutoConfiguration { | ||
| 193 | return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); | 197 | return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); |
| 194 | } | 198 | } |
| 195 | 199 | ||
| 196 | - // ========== rag 相关 ========== | ||
| 197 | - // TODO @xin 免费版本 | ||
| 198 | -// @Bean | ||
| 199 | -// @Lazy // TODO 芋艿:临时注释,避免无法启动」 | ||
| 200 | -// public TransformersEmbeddingModel transformersEmbeddingClient() { | ||
| 201 | -// return new TransformersEmbeddingModel(MetadataMode.EMBED); | ||
| 202 | -// } | 200 | + // ========== RAG 相关 ========== |
| 203 | 201 | ||
| 204 | @Bean | 202 | @Bean |
| 205 | - @Lazy // TODO 芋艿:临时注释,避免无法启动 | ||
| 206 | public TokenCountEstimator tokenCountEstimator() { | 203 | public TokenCountEstimator tokenCountEstimator() { |
| 207 | return new JTokkitTokenCountEstimator(); | 204 | return new JTokkitTokenCountEstimator(); |
| 208 | } | 205 | } |
| 209 | 206 | ||
| 207 | + @Bean | ||
| 208 | + public BatchingStrategy batchingStrategy() { | ||
| 209 | + return new TokenCountBatchingStrategy(); | ||
| 210 | + } | ||
| 211 | + | ||
| 210 | } | 212 | } |
| 211 | \ No newline at end of file | 213 | \ No newline at end of file |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
| @@ -5,7 +5,6 @@ import cn.hutool.core.lang.Assert; | @@ -5,7 +5,6 @@ import cn.hutool.core.lang.Assert; | ||
| 5 | import cn.hutool.core.lang.Singleton; | 5 | import cn.hutool.core.lang.Singleton; |
| 6 | import cn.hutool.core.lang.func.Func0; | 6 | import cn.hutool.core.lang.func.Func0; |
| 7 | import cn.hutool.core.util.ArrayUtil; | 7 | import cn.hutool.core.util.ArrayUtil; |
| 8 | -import cn.hutool.core.util.ReflectUtil; | ||
| 9 | import cn.hutool.core.util.RuntimeUtil; | 8 | import cn.hutool.core.util.RuntimeUtil; |
| 10 | import cn.hutool.core.util.StrUtil; | 9 | import cn.hutool.core.util.StrUtil; |
| 11 | import cn.hutool.extra.spring.SpringUtil; | 10 | import cn.hutool.extra.spring.SpringUtil; |
| @@ -29,6 +28,7 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; | @@ -29,6 +28,7 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; | ||
| 29 | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; | 28 | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; |
| 30 | import com.azure.ai.openai.OpenAIClientBuilder; | 29 | import com.azure.ai.openai.OpenAIClientBuilder; |
| 31 | import io.micrometer.observation.ObservationRegistry; | 30 | import io.micrometer.observation.ObservationRegistry; |
| 31 | +import io.milvus.client.MilvusServiceClient; | ||
| 32 | import io.qdrant.client.QdrantClient; | 32 | import io.qdrant.client.QdrantClient; |
| 33 | import io.qdrant.client.QdrantGrpcClient; | 33 | import io.qdrant.client.QdrantGrpcClient; |
| 34 | import lombok.SneakyThrows; | 34 | import lombok.SneakyThrows; |
| @@ -38,6 +38,10 @@ import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionPr | @@ -38,6 +38,10 @@ import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionPr | ||
| 38 | import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; | 38 | import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; |
| 39 | import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; | 39 | import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; |
| 40 | import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; | 40 | import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; |
| 41 | +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientConnectionDetails; | ||
| 42 | +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties; | ||
| 43 | +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration; | ||
| 44 | +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties; | ||
| 41 | import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration; | 45 | import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration; |
| 42 | import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties; | 46 | import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties; |
| 43 | import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration; | 47 | import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration; |
| @@ -47,6 +51,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; | @@ -47,6 +51,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; | ||
| 47 | import org.springframework.ai.azure.openai.AzureOpenAiChatModel; | 51 | import org.springframework.ai.azure.openai.AzureOpenAiChatModel; |
| 48 | import org.springframework.ai.chat.model.ChatModel; | 52 | import org.springframework.ai.chat.model.ChatModel; |
| 49 | import org.springframework.ai.document.MetadataMode; | 53 | import org.springframework.ai.document.MetadataMode; |
| 54 | +import org.springframework.ai.embedding.BatchingStrategy; | ||
| 50 | import org.springframework.ai.embedding.EmbeddingModel; | 55 | import org.springframework.ai.embedding.EmbeddingModel; |
| 51 | import org.springframework.ai.image.ImageModel; | 56 | import org.springframework.ai.image.ImageModel; |
| 52 | import org.springframework.ai.ollama.OllamaChatModel; | 57 | import org.springframework.ai.ollama.OllamaChatModel; |
| @@ -66,6 +71,7 @@ import org.springframework.ai.stabilityai.StabilityAiImageModel; | @@ -66,6 +71,7 @@ import org.springframework.ai.stabilityai.StabilityAiImageModel; | ||
| 66 | import org.springframework.ai.stabilityai.api.StabilityAiApi; | 71 | import org.springframework.ai.stabilityai.api.StabilityAiApi; |
| 67 | import org.springframework.ai.vectorstore.SimpleVectorStore; | 72 | import org.springframework.ai.vectorstore.SimpleVectorStore; |
| 68 | import org.springframework.ai.vectorstore.VectorStore; | 73 | import org.springframework.ai.vectorstore.VectorStore; |
| 74 | +import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; | ||
| 69 | import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; | 75 | import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; |
| 70 | import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; | 76 | import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; |
| 71 | import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; | 77 | import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; |
| @@ -247,6 +253,9 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -247,6 +253,9 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 247 | if (type == RedisVectorStore.class) { | 253 | if (type == RedisVectorStore.class) { |
| 248 | return buildRedisVectorStore(embeddingModel, metadataFields); | 254 | return buildRedisVectorStore(embeddingModel, metadataFields); |
| 249 | } | 255 | } |
| 256 | + if (type == MilvusVectorStore.class) { | ||
| 257 | + return buildMilvusVectorStore(embeddingModel); | ||
| 258 | + } | ||
| 250 | throw new IllegalArgumentException(StrUtil.format("未知类型({})", type)); | 259 | throw new IllegalArgumentException(StrUtil.format("未知类型({})", type)); |
| 251 | }); | 260 | }); |
| 252 | } | 261 | } |
| @@ -482,8 +491,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -482,8 +491,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 482 | QdrantClient qdrantClient = new QdrantClient(grpcClientBuilder.build()); | 491 | QdrantClient qdrantClient = new QdrantClient(grpcClientBuilder.build()); |
| 483 | // 创建 QdrantVectorStore 对象 | 492 | // 创建 QdrantVectorStore 对象 |
| 484 | QdrantVectorStore vectorStore = configuration.vectorStore(embeddingModel, properties, qdrantClient, | 493 | QdrantVectorStore vectorStore = configuration.vectorStore(embeddingModel, properties, qdrantClient, |
| 485 | - getObservationRegistry(), getCustomObservationConvention(), | ||
| 486 | - ReflectUtil.invoke(configuration, "batchingStrategy")); | 494 | + getObservationRegistry(), getCustomObservationConvention(), getBatchingStrategy()); |
| 487 | // 初始化索引 | 495 | // 初始化索引 |
| 488 | vectorStore.afterPropertiesSet(); | 496 | vectorStore.afterPropertiesSet(); |
| 489 | return vectorStore; | 497 | return vectorStore; |
| @@ -516,13 +524,48 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -516,13 +524,48 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 516 | })) | 524 | })) |
| 517 | .observationRegistry(getObservationRegistry().getObject()) | 525 | .observationRegistry(getObservationRegistry().getObject()) |
| 518 | .customObservationConvention(getCustomObservationConvention().getObject()) | 526 | .customObservationConvention(getCustomObservationConvention().getObject()) |
| 519 | - .batchingStrategy(ReflectUtil.invoke(configuration, "batchingStrategy")) | 527 | + .batchingStrategy(getBatchingStrategy()) |
| 520 | .build(); | 528 | .build(); |
| 521 | // 初始化索引 | 529 | // 初始化索引 |
| 522 | redisVectorStore.afterPropertiesSet(); | 530 | redisVectorStore.afterPropertiesSet(); |
| 523 | return redisVectorStore; | 531 | return redisVectorStore; |
| 524 | } | 532 | } |
| 525 | 533 | ||
| 534 | + /** | ||
| 535 | + * 参考 {@link MilvusVectorStoreAutoConfiguration} 的 vectorStore 方法 | ||
| 536 | + */ | ||
| 537 | + @SneakyThrows | ||
| 538 | + private MilvusVectorStore buildMilvusVectorStore(EmbeddingModel embeddingModel) { | ||
| 539 | + MilvusVectorStoreAutoConfiguration configuration = new MilvusVectorStoreAutoConfiguration(); | ||
| 540 | + // 获取配置属性 | ||
| 541 | + MilvusVectorStoreProperties serverProperties = SpringUtil.getBean(MilvusVectorStoreProperties.class); | ||
| 542 | + MilvusServiceClientProperties clientProperties = SpringUtil.getBean(MilvusServiceClientProperties.class); | ||
| 543 | + | ||
| 544 | + // 创建 MilvusServiceClient 对象 | ||
| 545 | + MilvusServiceClient milvusClient = configuration.milvusClient(serverProperties, clientProperties, | ||
| 546 | + new MilvusServiceClientConnectionDetails() { | ||
| 547 | + | ||
| 548 | + @Override | ||
| 549 | + public String getHost() { | ||
| 550 | + return clientProperties.getHost(); | ||
| 551 | + } | ||
| 552 | + | ||
| 553 | + @Override | ||
| 554 | + public int getPort() { | ||
| 555 | + return clientProperties.getPort(); | ||
| 556 | + } | ||
| 557 | + | ||
| 558 | + } | ||
| 559 | + ); | ||
| 560 | + // 创建 MilvusVectorStore 对象 | ||
| 561 | + MilvusVectorStore vectorStore = configuration.vectorStore(milvusClient, embeddingModel, serverProperties, | ||
| 562 | + getBatchingStrategy(), getObservationRegistry(), getCustomObservationConvention()); | ||
| 563 | + | ||
| 564 | + // 初始化索引 | ||
| 565 | + vectorStore.afterPropertiesSet(); | ||
| 566 | + return vectorStore; | ||
| 567 | + } | ||
| 568 | + | ||
| 526 | private static ObjectProvider<ObservationRegistry> getObservationRegistry() { | 569 | private static ObjectProvider<ObservationRegistry> getObservationRegistry() { |
| 527 | return new ObjectProvider<>() { | 570 | return new ObjectProvider<>() { |
| 528 | 571 | ||
| @@ -543,4 +586,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -543,4 +586,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 543 | }; | 586 | }; |
| 544 | } | 587 | } |
| 545 | 588 | ||
| 589 | + private static BatchingStrategy getBatchingStrategy() { | ||
| 590 | + return SpringUtil.getBean(BatchingStrategy.class); | ||
| 591 | + } | ||
| 592 | + | ||
| 546 | } | 593 | } |
yudao-server/src/main/resources/application.yaml
| @@ -157,8 +157,13 @@ spring: | @@ -157,8 +157,13 @@ spring: | ||
| 157 | collection-name: knowledge_segment # Qdrant 中向量集合的名称:用于存储向量数据的集合标识符,所有相关的向量操作都会在这个集合中进行 | 157 | collection-name: knowledge_segment # Qdrant 中向量集合的名称:用于存储向量数据的集合标识符,所有相关的向量操作都会在这个集合中进行 |
| 158 | host: 127.0.0.1 | 158 | host: 127.0.0.1 |
| 159 | port: 6334 | 159 | port: 6334 |
| 160 | - use-tls: false | ||
| 161 | - api-key: | 160 | + milvus: |
| 161 | + initialize-schema: true | ||
| 162 | + database-name: default # Milvus 中数据库的名称 | ||
| 163 | + collection-name: knowledge_segment # Milvus 中集合的名称:用于存储向量数据的集合标识符,所有相关的向量操作都会在这个集合中进行 | ||
| 164 | + client: | ||
| 165 | + host: 127.0.0.1 | ||
| 166 | + port: 19530 | ||
| 162 | qianfan: # 文心一言 | 167 | qianfan: # 文心一言 |
| 163 | api-key: x0cuLZ7XsaTCU08vuJWO87Lg | 168 | api-key: x0cuLZ7XsaTCU08vuJWO87Lg |
| 164 | secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK | 169 | secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK |