From 831e51cb419867912ec086a716ab157bb7e65161 Mon Sep 17 00:00:00 2001
From: Wang
Date: Thu, 25 Dec 2025 09:41:45 +0800
Subject: [PATCH 1/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=85=AC=E5=85=B1?=
=?UTF-8?q?=E9=85=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/resources/application.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml b/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml
index 5200c02..406b99b 100644
--- a/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml
+++ b/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml
@@ -22,6 +22,8 @@ spring:
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
- optional:nacos:shared-database.yaml
- optional:nacos:shared-redis.yaml
+ - optional:nacos:shared-sa-token.yaml
+ - optional:nacos:shared-minio.yaml
cloud:
nacos:
discovery:
From f50cbe33609ef9f43ffb05f976253ff5685e883e Mon Sep 17 00:00:00 2001
From: Wang
Date: Thu, 25 Dec 2025 09:43:50 +0800
Subject: [PATCH 2/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=85=AC=E5=85=B1?=
=?UTF-8?q?=E9=85=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../teach/teacher/bootstrap/SeerTeacherApplicationBootStrap.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/seer-teacher/seer-teacher-service-bootstrap/src/main/java/com/seer/teach/teacher/bootstrap/SeerTeacherApplicationBootStrap.java b/seer-teacher/seer-teacher-service-bootstrap/src/main/java/com/seer/teach/teacher/bootstrap/SeerTeacherApplicationBootStrap.java
index 027de6c..e1d3f4d 100644
--- a/seer-teacher/seer-teacher-service-bootstrap/src/main/java/com/seer/teach/teacher/bootstrap/SeerTeacherApplicationBootStrap.java
+++ b/seer-teacher/seer-teacher-service-bootstrap/src/main/java/com/seer/teach/teacher/bootstrap/SeerTeacherApplicationBootStrap.java
@@ -25,7 +25,6 @@ public class SeerTeacherApplicationBootStrap {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SeerTeacherApplicationBootStrap.class);
Environment env = app.run(args).getEnvironment();
- log.info("spring.mvc.pathmatch.matching-strategy={}", env.getProperty("spring.mvc.pathmatch.matching-strategy"));
String port = env.getProperty("server.port", "8087");
String contextPath = env.getProperty("server.servlet.context-path", "/");
log.info("----------------------------------------------------------");
From 91914407ec8a3ce4a3316081a7810f63fc8412c3 Mon Sep 17 00:00:00 2001
From: Wang
Date: Sat, 27 Dec 2025 18:23:29 +0800
Subject: [PATCH 3/6] =?UTF-8?q?=E5=9F=BA=E4=BA=8Espring=20ai=E5=AE=9E?=
=?UTF-8?q?=E7=8E=B0dashscope,openpi=E7=9A=84chat=20client?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../teach/common/enums/ResultCodeEnum.java | 5 +-
.../common/constants/CommonConstant.java | 5 +
seer-dependencies/pom.xml | 44 +-
seer-teacher/pom.xml | 101 +--
seer-teacher/seer-teacher-ai/pom.xml | 55 ++
.../teach/ai/client/AiModelClientFactory.java | 12 +
.../ai/client/AiModelClientFactoryImpl.java | 43 +
.../client/model/AbstractAiModelClient.java | 43 +
.../teach/ai/client/model/AiModelClient.java | 78 ++
.../ai/client/model/config/ModelConfig.java | 13 +
.../model/dashscope/DashScopeModelClient.java | 191 ++++
.../client/model/local/LocalModelClient.java | 57 ++
.../model/openai/OpenAIModelClient.java | 165 ++++
.../teach/ai/service/AiChatModelService.java | 101 +++
seer-teacher/seer-teacher-common/pom.xml | 6 +
.../seer/teach/admin/util/PromptBuilder.java | 851 +++++++++---------
.../service/IAiScenarioConfigService.java | 4 +-
.../impl/AiScenarioConfigServiceImpl.java | 23 +
18 files changed, 1263 insertions(+), 534 deletions(-)
create mode 100644 seer-teacher/seer-teacher-ai/pom.xml
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactory.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactoryImpl.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/ModelConfig.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/local/LocalModelClient.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java
diff --git a/seer-common/common-enums/src/main/java/com/seer/teach/common/enums/ResultCodeEnum.java b/seer-common/common-enums/src/main/java/com/seer/teach/common/enums/ResultCodeEnum.java
index b4e278e..d4444a2 100644
--- a/seer-common/common-enums/src/main/java/com/seer/teach/common/enums/ResultCodeEnum.java
+++ b/seer-common/common-enums/src/main/java/com/seer/teach/common/enums/ResultCodeEnum.java
@@ -316,7 +316,10 @@ public enum ResultCodeEnum {
// 富文本模板相关错误码
RICH_TEXT_TEMPLATE_PREVIEW_FAILED(1100418, "预览失败"),
- RICH_TEXT_TEMPLATE_CONVERT_FAILED(1100419, "转换失败");
+ RICH_TEXT_TEMPLATE_CONVERT_FAILED(1100419, "转换失败"),
+
+
+ AI_MODEL_NOT_FOUND(12000, "未找到模型");
private int code;
private String msg;
diff --git a/seer-common/common/src/main/java/com/seer/teach/common/constants/CommonConstant.java b/seer-common/common/src/main/java/com/seer/teach/common/constants/CommonConstant.java
index 51e8961..951a7a1 100644
--- a/seer-common/common/src/main/java/com/seer/teach/common/constants/CommonConstant.java
+++ b/seer-common/common/src/main/java/com/seer/teach/common/constants/CommonConstant.java
@@ -70,4 +70,9 @@ public interface CommonConstant {
* 任务锁前缀
*/
String TASK_CANCEL_PREFIX = "ai_task_cancel:";
+
+ /**
+ * 启用
+ */
+ Integer ENABLE = 1;
}
\ No newline at end of file
diff --git a/seer-dependencies/pom.xml b/seer-dependencies/pom.xml
index ededf4c..9577480 100644
--- a/seer-dependencies/pom.xml
+++ b/seer-dependencies/pom.xml
@@ -15,8 +15,8 @@
4.3.0
2025.0.0.0
- 2.0.0-SNAPSHOT
- 1.0.0-M6.1
+ 1.1.2
+ 1.1.0.0-M5
@@ -68,7 +68,7 @@
33.5.0-jre
- 1.2.5.RELEASE
+ 2.0.8
2.8.14
@@ -186,6 +186,13 @@
import
+
+ org.springframework.ai
+ spring-ai-bom
+ ${spring-ai.version}
+ pom
+ import
+
@@ -646,6 +653,37 @@
+
+
+ com.alibaba.cloud.ai
+ spring-ai-alibaba-starter-dashscope
+ ${spring-ai-alibaba.version}
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-deepseek
+ ${spring-ai.version}
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-deepseek
+ ${spring-ai.version}
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-ollama
+ ${spring-ai.version}
+
+
+
+ com.alibaba.cloud.ai
+ spring-ai-alibaba-agent-framework
+ ${spring-ai-alibaba.version}
+
+
org.springframework.boot
diff --git a/seer-teacher/pom.xml b/seer-teacher/pom.xml
index 5d0c5f3..b390949 100644
--- a/seer-teacher/pom.xml
+++ b/seer-teacher/pom.xml
@@ -19,105 +19,6 @@
seer-teacher-service-bootstrap
seer-teacher-api
seer-teacher-service-admin
+ seer-teacher-ai
-
-
-
-
-
-
- com.github.houbb
- opencc4j
- 1.6.0
-
-
-
-
-
- io.minio
- minio
- 8.5.1
-
-
-
-
- com.google.protobuf
- protobuf-java
- 4.30.2
-
-
-
-
- commons-io
- commons-io
-
-
-
- commons-codec
- commons-codec
-
-
-
-
- com.baomidou
- mybatis-plus-spring-boot3-starter
-
-
-
- com.baomidou
- mybatis-plus-generator
-
-
-
-
- org.apache.velocity
- velocity-engine-core
-
-
-
- org.springframework.boot
- spring-boot-starter-aop
-
-
-
-
- com.alibaba.fastjson2
- fastjson2
-
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
-
- javax.validation
- validation-api
-
-
-
-
- cn.dev33
- sa-token-spring-boot3-starter
-
-
-
-
- org.springframework.retry
- spring-retry
-
-
-
-
- org.lionsoul
- ip2region
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/pom.xml b/seer-teacher/seer-teacher-ai/pom.xml
new file mode 100644
index 0000000..6af3ff8
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+ com.seer.teach
+ seer-teacher
+ 1.0.0-SNAPSHOT
+
+
+ seer-teacher-ai
+
+
+
+
+ ${project.groupId}
+ seer-teacher-service
+ ${project.version}
+
+
+
+ org.springframework.retry
+ spring-retry
+
+
+
+ com.alibaba.cloud.ai
+ spring-ai-alibaba-starter-dashscope
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-openai
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-deepseek
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-ollama
+
+
+
+ io.micrometer
+ micrometer-observation
+ 1.15.7
+ compile
+
+
+
+
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactory.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactory.java
new file mode 100644
index 0000000..53824cb
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactory.java
@@ -0,0 +1,12 @@
+package com.seer.teach.ai.client;
+
+import com.seer.teach.ai.client.model.AiModelClient;
+
+/**
+ *
+ */
+public interface AiModelClientFactory {
+
+ AiModelClient getClient(String platform);
+
+}
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactoryImpl.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactoryImpl.java
new file mode 100644
index 0000000..ded4ea8
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/AiModelClientFactoryImpl.java
@@ -0,0 +1,43 @@
+package com.seer.teach.ai.client;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.seer.teach.ai.client.model.AiModelClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 平台模型管理器接口
+ * 定义了平台特定的模型管理功能
+ *
+ * @since 2025-12-25
+ */
+@Slf4j
+@Service
+public class AiModelClientFactoryImpl implements AiModelClientFactory {
+
+ private final Map serviceMap = new ConcurrentHashMap<>();
+
+
+ public AiModelClientFactoryImpl(List platformServices){
+ if(CollectionUtil.isEmpty(platformServices)){
+ return;
+ }
+ for (AiModelClient platform : platformServices) {
+ register(platform);
+ }
+ }
+
+ public void register(AiModelClient platformManager) {
+ String platformName = platformManager.getPlatformName();
+ serviceMap.put(platformName, platformManager);
+ }
+
+ @Override
+ public AiModelClient getClient(String platform) {
+ return serviceMap.get(platform);
+ }
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java
new file mode 100644
index 0000000..f53bd21
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java
@@ -0,0 +1,43 @@
+package com.seer.teach.ai.client.model;
+
+import io.micrometer.observation.ObservationRegistry;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.model.tool.DefaultToolExecutionEligibilityPredicate;
+import org.springframework.ai.model.tool.ToolCallingManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.retry.support.RetryTemplate;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * 平台模型管理器抽象基类
+ * 提供通用的模型管理功能实现,减少代码重复
+ *
+ * @author Captain
+ * @since 2025-12-26
+ */
+@Slf4j
+public abstract class AbstractAiModelClient implements AiModelClient {
+
+ @Autowired(required = false)
+ protected ResponseErrorHandler responseErrorHandler;
+
+ @Autowired(required = false)
+ protected RestClient.Builder restClientBuilder;
+
+ @Autowired(required = false)
+ protected WebClient.Builder webClientBuilder;
+
+ @Autowired
+ protected RetryTemplate retryTemplate;
+
+ @Autowired
+ protected ToolCallingManager toolCallingManager;
+
+ @Autowired(required = false)
+ protected ObservationRegistry observationRegistry;
+
+ protected final DefaultToolExecutionEligibilityPredicate toolExecutionEligibilityPredicate = new DefaultToolExecutionEligibilityPredicate();
+
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java
new file mode 100644
index 0000000..0b1a2f0
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java
@@ -0,0 +1,78 @@
+package com.seer.teach.ai.client.model;
+
+import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisModel;
+import com.seer.teach.ai.client.model.config.ModelConfig;
+import org.springframework.ai.audio.tts.TextToSpeechModel;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.image.ImageModel;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ *
+ * @author Captain
+ * @since 2025-12-25
+ */
+@Service
+public interface AiModelClient {
+
+ String getPlatformName();
+
+ /**
+ * 获取聊天模型
+ *
+ * @param modelConfig 模型配置
+ * @return 大语言模型实例的Optional包装
+ */
+ default Optional createChatModel(ModelConfig modelConfig){
+ return Optional.empty();
+ }
+
+ default Optional createChatOptions(Map options){
+ return Optional.empty();
+ }
+
+ /**
+ * 获取图像模型
+ */
+ default Optional createImageModel(ModelConfig modelConfig){
+ return Optional.empty();
+ }
+
+ /**
+ * 获取语音合成模型(Speech Synthesis Model)
+ * 用于将文本转换为语音音频,仅dashscope平台支持
+ *
+ * @param modelConfig 模型配置
+ * @return 语音合成模型实例的Optional包装
+ */
+ default Optional createSpeechSynthesisModel(ModelConfig modelConfig) {
+ return Optional.empty();
+ }
+
+ /**
+ * 获取文本转语音模型(Text To Speech Model)
+ * 用于将文本转换为语音输出,支持多种平台
+ *
+ * @param modelConfig 模型配置
+ * @return 文本转语音模型实例的Optional包装
+ */
+ default Optional createTextToSpeechModel(ModelConfig modelConfig) {
+ return Optional.empty();
+ }
+
+ /**
+ * 获取嵌入模型(Embedding Model)
+ * 用于将文本转换为向量表示,支持多种平台
+ *
+ * @param modelConfig 模型配置
+ * @return 嵌入模型实例的Optional包装
+ */
+ default Optional createEmbeddingModel(ModelConfig modelConfig) {
+ return Optional.empty();
+ }
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/ModelConfig.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/ModelConfig.java
new file mode 100644
index 0000000..2dc7390
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/ModelConfig.java
@@ -0,0 +1,13 @@
+package com.seer.teach.ai.client.model.config;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Builder
+@Data
+public class ModelConfig {
+
+ private String model;
+ private String apiKey;
+ private String url;
+}
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java
new file mode 100644
index 0000000..8ec0fce
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java
@@ -0,0 +1,191 @@
+package com.seer.teach.ai.client.model.dashscope;
+
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatProperties;
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeConnectionProperties;
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingProperties;
+import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
+import com.alibaba.cloud.ai.dashscope.api.DashScopeAudioSpeechApi;
+import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
+import com.alibaba.cloud.ai.dashscope.audio.DashScopeAudioSpeechModel;
+import com.alibaba.cloud.ai.dashscope.audio.DashScopeAudioSpeechOptions;
+import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisModel;
+import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
+import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
+import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
+import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
+import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
+import com.seer.teach.ai.client.model.AbstractAiModelClient;
+import com.seer.teach.ai.client.model.config.ModelConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.document.MetadataMode;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.image.ImageModel;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * DashScope平台模型管理器
+ * 专门负责DashScope平台的AI模型管理
+ * 使用配置属性进行更灵活的模型初始化
+ *
+ * @author Captain
+ * @since 2025-12-25
+ */
+@Slf4j
+@Component
+public class DashScopeModelClient extends AbstractAiModelClient {
+
+ private DashScopeConnectionProperties connectionProperties;
+
+ @Autowired(required = false)
+ private DashScopeChatProperties chatProperties;
+
+ @Override
+ public String getPlatformName() {
+ return "dashscope";
+ }
+
+ @Override
+ public Optional createChatModel(ModelConfig modelConfig) {
+ DashScopeChatOptions chatOptions = DashScopeChatOptions.builder()
+ .withModel(modelConfig.getModel())
+ .build();
+
+ DashScopeApi dashScopeApi = createDashScopeApi(modelConfig.getApiKey(), modelConfig.getUrl());
+ return Optional.of(DashScopeChatModel.builder()
+ .dashScopeApi(dashScopeApi)
+ .retryTemplate(retryTemplate)
+ .toolCallingManager(toolCallingManager)
+ .defaultOptions(chatOptions)
+ .observationRegistry(observationRegistry)
+ .toolExecutionEligibilityPredicate(toolExecutionEligibilityPredicate)
+ .build());
+ }
+
+ @Override
+ public Optional createChatOptions(Map options) {
+ DashScopeChatOptions.DashScopeChatOptionsBuilder builder = DashScopeChatOptions.builder();
+ for (Map.Entry entry : options.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+
+ if (value == null) {
+ continue;
+ }
+ switch (key) {
+ case "temperature":
+ builder.withTemperature(((Number) value).doubleValue());
+ break;
+ case "maxTokens":
+ builder.withMaxToken(((Number) value).intValue());
+ break;
+ case "enableSearch":
+ builder.withEnableSearch((Boolean) value);
+ break;
+ case "seed":
+ builder.withSeed(((Number) value).intValue());
+ break;
+ case "topP":
+ builder.withTopP(((Number) value).doubleValue());
+ break;
+ case "topK":
+ builder.withTopK(((Number) value).intValue());
+ break;
+ case "enableThinking":
+ builder.withEnableThinking((Boolean) value);
+ break;
+ case "repetitionPenalty":
+ builder.withRepetitionPenalty(((Number) value).doubleValue());
+ default:
+ break;
+ }
+ }
+ DashScopeChatOptions chatOptions = builder.build();
+ return Optional.of(chatOptions);
+ }
+
+ @Override
+ public Optional createImageModel(ModelConfig modelConfig) {
+ DashScopeImageOptions imageOptions = DashScopeImageOptions.builder()
+ .withModel(modelConfig.getModel())
+ .build();
+
+ DashScopeImageApi imageApi = DashScopeImageApi.builder()
+ .apiKey(modelConfig.getApiKey())
+ .baseUrl(modelConfig.getUrl())
+ .restClientBuilder(restClientBuilder)
+ .responseErrorHandler(responseErrorHandler)
+ .build();
+ return Optional.of(new DashScopeImageModel(imageApi, imageOptions, retryTemplate));
+ }
+
+ @Override
+ public Optional createSpeechSynthesisModel(ModelConfig modelConfig) {
+ try {
+ DashScopeAudioSpeechApi audioSpeechApi = new DashScopeAudioSpeechApi(modelConfig.getApiKey(), modelConfig.getUrl());
+
+ DashScopeAudioSpeechOptions options = DashScopeAudioSpeechOptions.builder()
+ .model(modelConfig.getModel())
+ .requestText(DashScopeAudioSpeechApi.RequestTextType.PLAIN_TEXT)
+ .voice("longyingxiao")
+ .build();
+ DashScopeAudioSpeechModel speechSynthesisModel = new DashScopeAudioSpeechModel(audioSpeechApi, options, retryTemplate);
+
+ return Optional.of(speechSynthesisModel);
+ } catch (Exception e) {
+ log.error("创建DashScope语音合成模型失败", e);
+ return Optional.empty();
+ }
+ }
+
+
+ @Override
+ public Optional createEmbeddingModel(ModelConfig modelConfig) {
+ try {
+ DashScopeEmbeddingProperties embeddingProperties = new DashScopeEmbeddingProperties();
+ DashScopeApi dashScopeApi = createDashScopeApi(modelConfig.getApiKey(), modelConfig.getUrl());
+
+ DashScopeEmbeddingModel embeddingModel = new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, embeddingProperties.getOptions(), retryTemplate, observationRegistry);
+
+ return Optional.of(embeddingModel);
+ } catch (Exception e) {
+ log.error("创建DashScope嵌入模型失败", e);
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * 创建DashScope API实例
+ */
+ private DashScopeApi createDashScopeApi(String apiKey, String baseUrl) {
+ DashScopeApi.Builder builder = DashScopeApi.builder()
+ .apiKey(apiKey)
+ .baseUrl(baseUrl);
+
+ // 如果有可用的客户端构建器,添加它们
+ if (webClientBuilder != null) {
+ builder.webClientBuilder(webClientBuilder);
+ }
+ if (restClientBuilder != null) {
+ builder.restClientBuilder(restClientBuilder);
+ }
+ if (responseErrorHandler != null) {
+ builder.responseErrorHandler(responseErrorHandler);
+ }
+ return builder.build();
+ }
+
+ private DashScopeImageApi createDashScopeImageApi(String apiKey, String baseUrl) {
+ return DashScopeImageApi.builder()
+ .apiKey(apiKey)
+ .baseUrl(baseUrl)
+ .restClientBuilder(restClientBuilder)
+ .responseErrorHandler(responseErrorHandler)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/local/LocalModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/local/LocalModelClient.java
new file mode 100644
index 0000000..eb67aaa
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/local/LocalModelClient.java
@@ -0,0 +1,57 @@
+package com.seer.teach.ai.client.model.local;
+
+import com.seer.teach.ai.client.model.AbstractAiModelClient;
+import com.seer.teach.ai.client.model.config.ModelConfig;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.ollama.OllamaChatModel;
+import org.springframework.ai.ollama.api.OllamaApi;
+import org.springframework.ai.ollama.api.OllamaChatOptions;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 本地模型管理器
+ * 专门负责Ollama等本地AI模型管理
+ *
+ * @author Captain
+ * @since 2025-12-26
+ */
+@Component
+public class LocalModelClient extends AbstractAiModelClient {
+
+ @Override
+ public String getPlatformName() {
+ return "local";
+ }
+
+ @Override
+ public Optional createChatModel(ModelConfig modelConfig) {
+ OllamaChatOptions chatOptions = OllamaChatOptions.builder()
+ .model(modelConfig.getModel())
+ .build();
+
+ OllamaApi ollamaApi = OllamaApi.builder()
+ .baseUrl(modelConfig.getUrl())
+ .restClientBuilder(restClientBuilder)
+ .webClientBuilder(webClientBuilder)
+ .responseErrorHandler(responseErrorHandler)
+ .build();
+
+ return Optional.of(OllamaChatModel.builder()
+ .ollamaApi(ollamaApi)
+ .defaultOptions(chatOptions)
+ .retryTemplate(retryTemplate)
+ .toolCallingManager(toolCallingManager)
+ .observationRegistry(observationRegistry)
+ .toolExecutionEligibilityPredicate(toolExecutionEligibilityPredicate)
+ .build());
+ }
+
+ @Override
+ public Optional createChatOptions(Map options) {
+ return Optional.empty();
+ }
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java
new file mode 100644
index 0000000..17c9aa7
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java
@@ -0,0 +1,165 @@
+package com.seer.teach.ai.client.model.openai;
+
+import com.seer.teach.ai.client.model.AbstractAiModelClient;
+import com.seer.teach.ai.client.model.config.ModelConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.audio.tts.TextToSpeechModel;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.document.MetadataMode;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.image.ImageModel;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechProperties;
+import org.springframework.ai.model.openai.autoconfigure.OpenAiChatProperties;
+import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingProperties;
+import org.springframework.ai.openai.OpenAiAudioSpeechModel;
+import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.openai.OpenAiEmbeddingModel;
+import org.springframework.ai.openai.OpenAiImageModel;
+import org.springframework.ai.openai.OpenAiImageOptions;
+import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.ai.openai.api.OpenAiAudioApi;
+import org.springframework.ai.openai.api.OpenAiImageApi;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * OpenAI平台模型管理器
+ * 专门负责OpenAI平台的AI模型管理
+ *
+ * @author Captain
+ * @since 2025-12-26
+ */
+@Slf4j
+@Component
+public class OpenAIModelClient extends AbstractAiModelClient {
+
+ @Override
+ public String getPlatformName() {
+ return "openai";
+ }
+
+ @Override
+ public Optional createChatModel(ModelConfig modelConfig) {
+ OpenAiChatOptions chatOptions = OpenAiChatOptions.builder()
+ .model(modelConfig.getModel())
+ .build();
+
+ OpenAiApi openAiApi = createOpenAiApi(modelConfig.getApiKey(), modelConfig.getUrl());
+
+ return Optional.of(new OpenAiChatModel(openAiApi, chatOptions, toolCallingManager, retryTemplate, observationRegistry));
+ }
+
+ @Override
+ public Optional createChatOptions(Map options) {
+ OpenAiChatOptions.Builder builder = OpenAiChatOptions.builder();
+ builder.streamUsage(true);
+ for (Map.Entry entry : options.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (Objects.isNull(value)) {
+ continue;
+ }
+ switch (key) {
+ case "temperature":
+ builder.temperature(((Number) value).doubleValue());
+ break;
+ case "maxTokens":
+ builder.maxTokens(((Number) value).intValue());
+ break;
+ case "maxCompletionToken":
+ builder.maxCompletionTokens(((Number) value).intValue());
+ break;
+ case "topLogprobs":
+ builder.topLogprobs(((Number) value).intValue());
+ break;
+ case "logprobs":
+ builder.logprobs((Boolean) value);
+ break;
+ case "N":
+ case "n":
+ builder.N(((Number) value).intValue());
+ break;
+ case "reasoningEffort":
+ builder.reasoningEffort(value.toString());
+ break;
+ case "topP":
+ case "top_p":
+ builder.topP(((Number) value).doubleValue());
+ break;
+ case "frequencyPenalty":
+ builder.frequencyPenalty(((Number) value).doubleValue());
+ break;
+ case "presencePenalty":
+ builder.presencePenalty(((Number) value).doubleValue());
+ break;
+ default:
+ break;
+ }
+ }
+ return Optional.of(builder.build());
+ }
+
+ @Override
+ public Optional createImageModel(ModelConfig modelConfig) {
+ OpenAiImageOptions imageOptions = OpenAiImageOptions.builder()
+ .model(modelConfig.getModel())
+ .build();
+
+ OpenAiImageApi imageOpenAiApi = OpenAiImageApi.builder()
+ .baseUrl(modelConfig.getUrl())
+ .apiKey(new SimpleApiKey(modelConfig.getApiKey()))
+ .restClientBuilder(restClientBuilder)
+ .responseErrorHandler(responseErrorHandler)
+ .build();
+
+ return Optional.of(new OpenAiImageModel(imageOpenAiApi, imageOptions, retryTemplate, observationRegistry));
+ }
+
+ @Override
+ public Optional createTextToSpeechModel(ModelConfig modelConfig) {
+ try {
+ OpenAiAudioApi audioApi = OpenAiAudioApi.builder()
+ .baseUrl(modelConfig.getUrl())
+ .apiKey(new SimpleApiKey(modelConfig.getApiKey()))
+ .restClientBuilder(restClientBuilder)
+ .responseErrorHandler(responseErrorHandler)
+ .build();
+
+ OpenAiAudioSpeechProperties properties = new OpenAiAudioSpeechProperties();
+ TextToSpeechModel textToSpeechModel = new OpenAiAudioSpeechModel(audioApi, properties.getOptions(), retryTemplate);
+ return Optional.of(textToSpeechModel);
+ } catch (Exception e) {
+ log.error("创建OpenAI文本转语音模型失败", e);
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public Optional createEmbeddingModel(ModelConfig modelConfig) {
+ try {
+ OpenAiApi openAiApi = createOpenAiApi(modelConfig.getApiKey(), modelConfig.getUrl());
+ OpenAiEmbeddingProperties properties = new OpenAiEmbeddingProperties();
+ EmbeddingModel embeddingModel = new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED,properties.getOptions(), retryTemplate);
+ return Optional.of(embeddingModel);
+ } catch (Exception e) {
+ log.error("创建OpenAI嵌入模型失败", e);
+ return Optional.empty();
+ }
+ }
+
+ private OpenAiApi createOpenAiApi(String apiKey, String baseUrl) {
+ return OpenAiApi.builder()
+ .baseUrl(baseUrl)
+ .apiKey(new SimpleApiKey(apiKey))
+ .completionsPath(OpenAiChatProperties.DEFAULT_COMPLETIONS_PATH)
+ .embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH)
+ .restClientBuilder(restClientBuilder)
+ .webClientBuilder(webClientBuilder).build();
+ }
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java
new file mode 100644
index 0000000..3024b20
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java
@@ -0,0 +1,101 @@
+package com.seer.teach.ai.service;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.seer.teach.ai.client.AiModelClientFactory;
+import com.seer.teach.ai.client.model.AiModelClient;
+import com.seer.teach.ai.client.model.config.ModelConfig;
+import com.seer.teach.common.enums.ResultCodeEnum;
+import com.seer.teach.common.exception.CommonException;
+import com.seer.teach.common.utils.AssertUtils;
+import com.seer.teach.teacher.module.entity.AiApiKeyEntity;
+import com.seer.teach.teacher.module.entity.AiModelEntity;
+import com.seer.teach.teacher.module.entity.AiScenarioConfigEntity;
+import com.seer.teach.teacher.service.IAiApiKeyService;
+import com.seer.teach.teacher.service.IAiModelService;
+import com.seer.teach.teacher.service.IAiScenarioConfigService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AiChatModelService {
+
+ private final IAiScenarioConfigService aiScenarioConfigService;
+
+ private final IAiApiKeyService aiApiKeyService;
+
+ private final IAiModelService aiModelService;
+
+ private final AiModelClientFactory aiModelClientFactory;
+
+ public ChatResponse chatMessage(String scenarioCode, Object params){
+ AiScenarioConfigEntity scenarioConfig = aiScenarioConfigService.getOneByScenarioCode(scenarioCode);
+ log.info("scenarioConfig:{}", scenarioConfig);
+
+ AssertUtils.notNull(scenarioConfig, ResultCodeEnum.SCENARIO_NOT_FOUND);
+
+ AiModelClient client = aiModelClientFactory.getClient(scenarioConfig.getPlatform());
+
+ ModelConfig modelConfig = getModelConfig(scenarioConfig);
+
+ ChatModel chatModel = client.createChatModel(modelConfig).orElseThrow(() -> new CommonException(ResultCodeEnum.AI_MODEL_NOT_FOUND));
+
+ Optional options = client.createChatOptions(BeanUtil.beanToMap(scenarioConfig));
+ Prompt prompt = new Prompt(params.toString(), options.orElse(null));
+ return chatModel.call(prompt);
+ }
+
+ public Flux chatStream(String scenarioCode, Object params){
+ AiScenarioConfigEntity scenarioConfig = aiScenarioConfigService.getOneByScenarioCode(scenarioCode);
+ log.info("chatStream scenarioConfig:{}", scenarioConfig);
+
+ AssertUtils.notNull(scenarioConfig, ResultCodeEnum.SCENARIO_NOT_FOUND);
+
+ AiModelClient client = aiModelClientFactory.getClient(scenarioConfig.getPlatform());
+
+ ModelConfig modelConfig = getModelConfig(scenarioConfig);
+
+ ChatModel chatModel = client.createChatModel(modelConfig).orElseThrow(() -> new CommonException(ResultCodeEnum.AI_MODEL_NOT_FOUND));
+
+ Optional options = client.createChatOptions(BeanUtil.beanToMap(scenarioConfig));
+ Prompt prompt = new Prompt(params.toString(), options.orElse(null));
+ return chatModel.stream(prompt);
+ }
+
+
+ private ModelConfig getModelConfig(AiScenarioConfigEntity scenarioConfig) {
+ AiModelEntity module = aiModelService.getById(scenarioConfig.getModuleId());
+ AiApiKeyEntity apiKey = getApiKey(scenarioConfig);
+ return ModelConfig.builder()
+ .url(module.getUrl()).apiKey(apiKey.getApiKey()).model(module.getModelIdentifier()).build();
+ }
+
+ private AiApiKeyEntity getApiKey(AiScenarioConfigEntity aiScenarioConfig) {
+ List aiApiKeyList = aiApiKeyService.list(new LambdaQueryWrapper<>(AiApiKeyEntity.class).eq(AiApiKeyEntity::getPlatform, aiScenarioConfig.getPlatform()));
+ AiApiKeyEntity apiKey = null;
+ Optional any = aiApiKeyList.stream().filter(aiApiKey -> Objects.nonNull(aiScenarioConfig.getApiKeyId()) && aiScenarioConfig.getApiKeyId().intValue() == aiApiKey.getId()).findAny();
+ if (any.isPresent()) {
+ apiKey = any.get();
+ }
+ if (Objects.isNull(apiKey)) {
+ Optional apiKeyOptional = aiApiKeyList.stream().filter(aiApiKey -> Objects.nonNull(aiApiKey.getIsDefault()) && aiApiKey.getIsDefault() == 1).findAny();
+ if (apiKeyOptional.isPresent()) {
+ apiKey = apiKeyOptional.get();
+ }
+ }
+ return apiKey;
+ }
+
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-common/pom.xml b/seer-teacher/seer-teacher-common/pom.xml
index c32a679..50d116e 100644
--- a/seer-teacher/seer-teacher-common/pom.xml
+++ b/seer-teacher/seer-teacher-common/pom.xml
@@ -30,5 +30,11 @@
spring-webflux
+
+ org.springframework
+ spring-webmvc
+
+
+
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/util/PromptBuilder.java b/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/util/PromptBuilder.java
index 01dcee5..85d83f3 100644
--- a/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/util/PromptBuilder.java
+++ b/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/util/PromptBuilder.java
@@ -31,41 +31,38 @@ public class PromptBuilder {
public static String buildPromptForQuestionGeneration(ExampleQuestionsEntity exampleQuestion,
KnowledgePointEntity knowledgePoint,
Integer count) {
- StringBuilder prompt = new StringBuilder();
+ String optionsPart = ExampleQuestionTypesEnum.CHOICE.getCode().equals(exampleQuestion.getType()) ?
+ " - options: 选项数组(仅选择题需要,每个选项包含content和sort字段)\n\n" : "";
- prompt.append("你是一个教学题目的专家,请根据以下示例题目生成").append(count).append("道类似的题目。\n\n");
-
- prompt.append("知识点:").append(knowledgePoint.getKnowledgePointName()).append("\n");
- prompt.append("知识点总结:").append(knowledgePoint.getSummary()).append("\n\n");
- prompt.append("要求:\n");
- prompt.append("1. 题目必须基于相同的教学知识点\n");
- prompt.append("2. 题目难度应与示例题目相当\n");
- prompt.append("3. 题型必须与示例题目一致\n");
- prompt.append("4. 答案必须明确且唯一\n");
- prompt.append("5. 提供详细的解题过程或解析\n");
- prompt.append("6. 返回严格的JSON数组格式,每项包含以下字段:\n");
- prompt.append(" - question: 题干内容\n");
- prompt.append(" - answer: 答案内容\n");
- prompt.append(" - solution: 解析内容\n");
- if (ExampleQuestionTypesEnum.CHOICE.getCode().equals(exampleQuestion.getType())) {
- prompt.append(" - options: 选项数组(仅选择题需要,每个选项包含content和sort字段)\n\n");
- }
- prompt.append("输出格式示例:\n");
- prompt.append("[\n");
- prompt.append(" {\n");
- prompt.append(" \"question\": \"题目内容\",\n");
- prompt.append(" \"answer\": \"答案内容\",\n");
- prompt.append(" \"solution\": \"解析内容\",\n");
- if (ExampleQuestionTypesEnum.CHOICE.getCode().equals(exampleQuestion.getType())) {
- prompt.append(", \"options\": [\n");
- prompt.append(" {\"content\": \"选项A内容\", \"sort\": 1},\n");
- prompt.append(" {\"content\": \"选项B内容\", \"sort\": 2}\n");
- prompt.append(" ]\n");
- }
- prompt.append(" }\n");
- prompt.append("]\n");
-
- return prompt.toString();
+ return """
+ 你是一个教学题目的专家,请根据以下示例题目生成%d道类似的题目。
+
+ 知识点:%s
+ 知识点总结:%s
+
+ 要求:
+ 1. 题目必须基于相同的教学知识点
+ 2. 题目难度应与示例题目相当
+ 3. 题型必须与示例题目一致
+ 4. 答案必须明确且唯一
+ 5. 提供详细的解题过程或解析
+ 6. 返回严格的JSON数组格式,每项包含以下字段:
+ - question: 题干内容
+ - answer: 答案内容
+ - solution: 解析内容
+ %s
+ 输出格式示例:
+ [
+ {
+ "question": "题目内容",
+ "answer": "答案内容",
+ "solution": "解析内容",
+ %s
+ }
+ ]
+ """.formatted(count, knowledgePoint.getKnowledgePointName(), knowledgePoint.getSummary(), optionsPart,
+ ExampleQuestionTypesEnum.CHOICE.getCode().equals(exampleQuestion.getType()) ?
+ "\"options\": [\n {\"content\": \"选项A内容\", \"sort\": 1},\n {\"content\": \"选项B内容\", \"sort\": 2}\n ]" : "");
}
/**
@@ -77,104 +74,108 @@ public class PromptBuilder {
* @return 提示词
*/
public static String buildPromptForBankQuestionGeneration(KnowledgePointEntity knowledgePoints, Integer count, GenerateTaskReq req) {
- StringBuilder prompt = new StringBuilder();
- prompt.append("你是一位资深的'" + getGradeTypeName(req.getGradeType()) + "'教学专家,请根据以下知识点和示例题目为每一类型(1单选选择题 2多选选择题 4判断题 6填空题)的题目分别生成").append(count).append("道类似的题目。\n\n");
- prompt.append("知识点名称:").append(knowledgePoints.getKnowledgePointName()).append("\n");
- prompt.append("知识点总结:").append(req.getSummary()).append("\n");
- prompt.append("对应的年级:").append(getGradeNameList(req.getGradeIds())).append("\n");
- prompt.append("对应的学科:").append(getSubjectName(req.getSubjectId())).append("\n\n");
-
- prompt.append("要求:\n");
- prompt.append(" 1. 题目必须基于\"").append(knowledgePoints.getKnowledgePointName()).append("\"这个知识点,不能超出对应年纪和学生的理解范围\n");
- prompt.append(" 2. 题目难度应在1.00和5.00范围内,小数点后面两位小数,1.00为最简单,5.00为最难,且每道题的难度不能相同\n");
- prompt.append(" 3. 答案必须明确且唯一\n");
- prompt.append(" 4. 提供详细的解析过程,如果是数学,物理,化学,必须列出具体的解题步骤\n");
- prompt.append(" 5. 题干内容不能包含题目类型,如 (对/错),(多选)\n");
- prompt.append(" 6. 如果是选择题(单选选择题或则多选选择题),题干内容不能包含选项,如(题目内容,选项A内容,选项B内容)\n");
- prompt.append(" 7. 如果是判断题,答案(answer)只能是‘对’和‘错’,如果是选择题,答案只能是选项A,B,C,D,E... \n");
- prompt.append(" 8. 每一类型题目的数量必须是" + count + ",不能省略,如要求输出10道题目,不能只输出9道题目\n");
- prompt.append(" 9. 输出的格式必须是json格式,字段如下:\n");
- prompt.append(" - title: 题干内容\n");
- prompt.append(" - answer: 答案内容\n");
- prompt.append(" - solution: 解析内容,提供详细的解析过程,如果是数学,物理或则化学的,必须列出具体的解题步骤。如果是英语,尽量使用中文\n");
- prompt.append(" - type: 题型(1->单选选择题, 2->多选选择题, 4->判断题, 6->填空题)\n");
- prompt.append(" - difficulty: 题目难易度系数(1-5的整数,5为最难,每道题难度不能相同)\n");
- prompt.append(" - isNeedImage: 是否需要图片(0->不需要, 1->需要),如果是以图片的形式展示题目内容或者选项,则为1,否则为0\n");
- prompt.append(" - options: 选择题选项数组(仅题型为单选选择题或则多选选择题需要,每个选项包含content和sort字段,选项内容不能一样)\n");
-
// 判断是否是一、二年级的数学题目
boolean isLowerElementaryMath = isLowerElementaryMath(req.getGradeIds(), req.getSubjectId());
// 判断是否是三、四年级的英语题目
boolean isMiddleElementaryEnglish = isMiddleElementaryEnglish(req.getGradeIds(), req.getSubjectId());
- if (isNeedLatex(req.getSubjectId())) {
- prompt.append(" 10. 输出的题目内容,答案,解析,其中涉及到公式的,请使用Latex\n\n");
- }
+ String latexRequirement = isNeedLatex(req.getSubjectId()) ? " 10. 输出的题目内容,答案,解析,其中涉及到公式的,请使用Latex\n\n" : "";
- // 对三四年级英语题目添加特殊要求
- if (isMiddleElementaryEnglish) {
- prompt.append(" 10. 这是三四年级的英语题目,请尽量使用中文出题,题目内容和选项中涉及的英语内容应适合该年龄段学生理解\n\n");
- }
+ String englishRequirement = isMiddleElementaryEnglish ? " 10. 这是三四年级的英语题目,请尽量使用中文出题,题目内容和选项中涉及的英语内容应适合该年龄段学生理解\n\n" : "";
- prompt.append("输出格式示例:\n");
- prompt.append("[\n");
- prompt.append(" {\n");
- prompt.append(" \"title\": \"题目内容\",\n");
- prompt.append(" \"answer\": \"答案内容\",\n");
- prompt.append(" \"solution\": \"解析内容\",\n");
- prompt.append(" \"isNeedImage\": \"0\",\n");
- prompt.append(" \"type\": 1,\n");
- prompt.append(" \"difficulty\": 3.34,\n");
- prompt.append(" \"options\": [\n");
- prompt.append(" {\"content\": \"A. 选项A内容\", \"sort\": 1},\n");
- prompt.append(" {\"content\": \"B. 选项B内容\", \"sort\": 2},\n");
- prompt.append(" {\"content\": \"C. 选项C内容\", \"sort\": 3},\n");
- prompt.append(" {\"content\": \"D. 选项D内容\", \"sort\": 4}\n");
- prompt.append(" ]\n");
-
- prompt.append(" }\n");
- prompt.append("]\n");
-
- // 特殊要求:对于小学一、二年级的数学题目,强调以图片为主
- if (isLowerElementaryMath) {
- prompt.append("\n特别注意:这是一、二年级的题目,请尽量以图文并茂的形式展示题目内容和选项,使题目更适合低年级学生理解。\n");
- }
+ String lowerElementaryMathNote = isLowerElementaryMath ? "\n特别注意:这是一、二年级的题目,请尽量以图文并茂的形式展示题目内容和选项,使题目更适合低年级学生理解。\n" : "";
- // 特殊要求:对于三、四年级的英语题目,强调使用中文出题
- if (isMiddleElementaryEnglish) {
- prompt.append("\n特别注意:这是三、四年级的英语题目,请尽量使用中文出题,让题目更容易被学生理解。\n");
- }
+ String middleElementaryEnglishNote = isMiddleElementaryEnglish ? "\n特别注意:这是三、四年级的英语题目,请尽量使用中文出题,让题目更容易被学生理解。\n" : "";
- // 数学题目特殊要求:针对不同年级提供不同的解题步骤标准
+ String mathRequirements = "";
if (req.getSubjectId() == 2) { // 数学学科
- prompt.append("\n数学题目解析(solution字段)规范要求:\n");
+ mathRequirements = "\n数学题目解析(solution字段)规范要求:\n";
if(Objects.nonNull(req.getGradeId())){
// 初中三个年级的要求
if (req.getGradeId() == 7) { // 七年级(初一)
- prompt.append("七年级(初一)解析标准:\n");
- prompt.append("- 格式要求:从算术思维向代数思维过渡,强调基本概念、运算规则和解题格式的建立\n");
- prompt.append("- 解析步骤:读与标(阅读题目,标记关键数据) -> 联与析(联想相关数学概念) -> 算或解(执行计算或按步骤解方程) -> 验与答(初步检验答案,并规范作答)\n");
- prompt.append("- 示例题型:代数方程应用题,要求严格按照\"设->列->解->验->答\"的格式进行解答\n");
+ mathRequirements += "七年级(初一)解析标准:\n";
+ mathRequirements += "- 格式要求:从算术思维向代数思维过渡,强调基本概念、运算规则和解题格式的建立\n";
+ mathRequirements += "- 解析步骤:读与标(阅读题目,标记关键数据) -> 联与析(联想相关数学概念) -> 算或解(执行计算或按步骤解方程) -> 验与答(初步检验答案,并规范作答)\n";
+ mathRequirements += "- 示例题型:代数方程应用题,要求严格按照\"设->列->解->验->答\"的格式进行解答\n";
}
if (req.getGradeId() == 8) { // 八年级(初二)
- prompt.append("八年级(初二)解析标准:\n");
- prompt.append("- 格式要求:逻辑推理能力要求提高,几何证明和函数概念引入,解题需展现严密的因果链条\n");
- prompt.append("- 解析步骤:条件梳理(明确已知条件) -> 目标分析(明确待证明结论) -> 思路探寻(寻找定理、公式) -> 逻辑推演(清晰展示推理) -> 结论总结(给出最终结论)\n");
- prompt.append("- 示例题型:几何证明题,要求严格按照\"已知->求证->证明\"的格式进行解答\n");
+ mathRequirements += "八年级(初二)解析标准:\n";
+ mathRequirements += "- 格式要求:逻辑推理能力要求提高,几何证明和函数概念引入,解题需展现严密的因果链条\n";
+ mathRequirements += "- 解析步骤:条件梳理(明确已知条件) -> 目标分析(明确待证明结论) -> 思路探寻(寻找定理、公式) -> 逻辑推演(清晰展示推理) -> 结论总结(给出最终结论)\n";
+ mathRequirements += "- 示例题型:几何证明题,要求严格按照\"已知->求证->证明\"的格式进行解答\n";
}
if (req.getGradeId() == 9) { // 九年级(初三)
- prompt.append("九年级(初三)解析标准:\n");
- prompt.append("- 格式要求:知识高度综合,强调数学思想方法(分类讨论、数形结合、转化与化归、方程思想、模型思想)的应用,解法灵活多样\n");
- prompt.append("- 解析步骤:审题与建模(识别问题本质) -> 思路选择与规划(选择合适解题路径) -> 执行与讨论(计算或推理,必要时分类讨论) -> 检验与反思(检验答案合理性)\n");
- prompt.append("- 示例题型:二次函数综合题,要求体现数学思想方法的灵活应用\n");
+ mathRequirements += "九年级(初三)解析标准:\n";
+ mathRequirements += "- 格式要求:知识高度综合,强调数学思想方法(分类讨论、数形结合、转化与化归、方程思想、模型思想)的应用,解法灵活多样\n";
+ mathRequirements += "- 解析步骤:审题与建模(识别问题本质) -> 思路选择与规划(选择合适解题路径) -> 执行与讨论(计算或推理,必要时分类讨论) -> 检验与反思(检验答案合理性)\n";
+ mathRequirements += "- 示例题型:二次函数综合题,要求体现数学思想方法的灵活应用\n";
}
}
}
-
- return prompt.toString();
+
+ return """
+ 你是一位资深的'%s'教学专家,请根据以下知识点和示例题目为每一类型(1单选选择题 2多选选择题 4判断题 6填空题)的题目分别生成%d道类似的题目。
+
+ 知识点名称:%s
+ 知识点总结:%s
+ 对应的年级:%s
+ 对应的学科:%s
+
+ 要求:
+ 1. 题目必须基于"%s"这个知识点,不能超出对应年纪和学生的理解范围
+ 2. 题目难度应在1.00和5.00范围内,小数点后面两位小数,1.00为最简单,5.00为最难,且每道题的难度不能相同
+ 3. 答案必须明确且唯一
+ 4. 提供详细的解析过程,如果是数学,物理,化学,必须列出具体的解题步骤
+ 5. 题干内容不能包含题目类型,如 (对/错),(多选)
+ 6. 如果是选择题(单选选择题或则多选选择题),题干内容不能包含选项,如(题目内容,选项A内容,选项B内容)
+ 7. 如果是判断题,答案(answer)只能是‘对’和‘错’,如果是选择题,答案只能是选项A,B,C,D,E...
+ 8. 每一类型题目的数量必须是%d,不能省略,如要求输出10道题目,不能只输出9道题目
+ 9. 输出的格式必须是json格式,字段如下:
+ - title: 题干内容
+ - answer: 答案内容
+ - solution: 解析内容,提供详细的解析过程,如果是数学,物理或则化学的,必须列出具体的解题步骤。如果是英语,尽量使用中文
+ - type: 题型(1->单选选择题, 2->多选选择题, 4->判断题, 6->填空题)
+ - difficulty: 题目难易度系数(1-5的整数,5为最难,每道题难度不能相同)
+ - isNeedImage: 是否需要图片(0->不需要, 1->需要),如果是以图片的形式展示题目内容或者选项,则为1,否则为0
+ - options: 选择题选项数组(仅题型为单选选择题或则多选选择题需要,每个选项包含content和sort字段,选项内容不能一样)
+ %s%s
+ 输出格式示例:
+ [
+ {
+ "title": "题目内容",
+ "answer": "答案内容",
+ "solution": "解析内容",
+ "isNeedImage": "0",
+ "type": 1,
+ "difficulty": 3.34,
+ "options": [
+ {"content": "A. 选项A内容", "sort": 1},
+ {"content": "B. 选项B内容", "sort": 2},
+ {"content": "C. 选项C内容", "sort": 3},
+ {"content": "D. 选项D内容", "sort": 4}
+ ]
+ }
+ ]
+
+ %s%s%s
+ """.formatted(
+ getGradeTypeName(req.getGradeType()),
+ count,
+ knowledgePoints.getKnowledgePointName(),
+ req.getSummary(),
+ getGradeNameList(req.getGradeIds()),
+ getSubjectName(req.getSubjectId()),
+ knowledgePoints.getKnowledgePointName(),
+ count,
+ latexRequirement,
+ englishRequirement,
+ lowerElementaryMathNote,
+ middleElementaryEnglishNote,
+ mathRequirements
+ );
}
/**
@@ -184,60 +185,70 @@ public class PromptBuilder {
* @return 提示词
*/
public static String buildSplitPrompt(KnowledgePointEntity knowledgePoint, KnowSummaryEntity summary, KnowledgePointSplitReq req) {
- StringBuilder prompt = new StringBuilder();
-
- prompt.append("你是一位资深的教学专家,请根据以下知识点进行拆分,将其拆分为" + req.getCount() + "个更细粒度的知识点。\n\n");
-
- prompt.append("原始知识点名称:").append(knowledgePoint.getKnowledgePointName()).append("\n");
- prompt.append("原始知识点总结:").append(summary.getSummary()).append("\n");
- prompt.append("对应的年级:").append(getGradeNameList(req.getGradeIds())).append("\n");
- prompt.append("对应的学科:").append(getSubjectName(knowledgePoint.getSubjectId())).append("\n");
+ String specificNames = "";
if (CollectionUtil.isNotEmpty(req.getKnowledgePointNames())) {
- prompt.append("要求拆分为以下名称的知识点:").append("\n");
+ StringBuilder namesBuilder = new StringBuilder();
for (int i = 0; i < req.getKnowledgePointNames().size(); i++) {
- prompt.append(" ").append(i + 1).append(". ").append(req.getKnowledgePointNames().get(i)).append("\n");
+ namesBuilder.append(" ").append(i + 1).append(". ").append(req.getKnowledgePointNames().get(i)).append("\n");
}
+ specificNames = "要求拆分为以下名称的知识点:\n" + namesBuilder.toString();
}
- prompt.append("要求:\n");
- if (CollectionUtil.isNotEmpty(req.getKnowledgePointNames())) {
- prompt.append("1. 将原始知识点拆分为上面指定的").append(req.getCount()).append("个名称的知识点,每个知识点需要包含以下信息:\n");
- } else {
- prompt.append("1. 将原始知识点拆分为").append(req.getCount()).append("个更具体、更细粒度的知识点,每个知识点需要包含以下信息:\n");
- }
- prompt.append(" - knowledgePointName: 拆分后的知识点名称\n");
- prompt.append(" - summary: 知识点的简要总结\n");
- prompt.append(" - exampleQuestions: 该知识点的示例题目列表\n");
- prompt.append("2. 每个知识点不能超出对应年纪和学生的理解范围\n");
- prompt.append("3. 知识点总结必须符合教学逻辑(能直接被教师用于教学视频脚本),应包括:①基本定义;②在学科体系中的地位与作用;③与前后知识的衔接关系;④学习意义与实际应用;⑤常见误区与误解。少于 300 字。\n");
- prompt.append("4. exampleQuestions字段要求:\n");
- prompt.append(" - 每个题目包含type(题型)、question(题目内容)、options(选项,仅选择题需要)、answer(答案)、explanation(解析)字段\n");
- prompt.append(" - 题型包括:包含4种题型(1单选选择题 2多选选择题 4判断题 6填空题)\n");
- prompt.append(" - 每种题型1道题目\n\n");
+ String requirements = CollectionUtil.isNotEmpty(req.getKnowledgePointNames()) ?
+ String.format("1. 将原始知识点拆分为上面指定的%d个名称的知识点,每个知识点需要包含以下信息:\n", req.getCount()) :
+ String.format("1. 将原始知识点拆分为%d个更具体、更细粒度的知识点,每个知识点需要包含以下信息:\n", req.getCount());
- prompt.append("5. 返回严格的JSON数组格式,示例如下:\n");
- prompt.append("[\n");
- prompt.append(" {\n");
- prompt.append(" \"knowledgePointName\": \"拆分后的知识点名称\",\n");
- prompt.append(" \"summary\": \"知识点总结\",\n");
- prompt.append(" \"exampleQuestions\": [\n");
- prompt.append(" {\n");
- prompt.append(" \"type\": \"1\",\n");
- prompt.append(" \"question\": \"题目内容\",\n");
- prompt.append(" \"options\": [\n");
- prompt.append(" {\"content\": \"A. 选项A内容\", \"sort\": 1},\n");
- prompt.append(" {\"content\": \"B. 选项B内容\", \"sort\": 2},\n");
- prompt.append(" {\"content\": \"C. 选项C内容\", \"sort\": 3},\n");
- prompt.append(" {\"content\": \"D. 选项D内容\", \"sort\": 4}\n");
- prompt.append(" ]\n");
- prompt.append(" \"answer\": \"答案\",\n");
- prompt.append(" \"explanation\": \"解析内容\"\n");
- prompt.append(" }\n");
- prompt.append(" ]\n");
- prompt.append(" }\n");
- prompt.append("]\n");
-
- return prompt.toString();
+ return """
+ 你是一位资深的教学专家,请根据以下知识点进行拆分,将其拆分为%d个更细粒度的知识点。
+
+ 原始知识点名称:%s
+ 原始知识点总结:%s
+ 对应的年级:%s
+ 对应的学科:%s
+ %s
+ 要求:
+ %s
+ - knowledgePointName: 拆分后的知识点名称
+ - summary: 知识点的简要总结
+ - exampleQuestions: 该知识点的示例题目列表
+ 2. 每个知识点不能超出对应年纪和学生的理解范围
+ 3. 知识点总结必须符合教学逻辑(能直接被教师用于教学视频脚本),应包括:①基本定义;②在学科体系中的地位与作用;③与前后知识的衔接关系;④学习意义与实际应用;⑤常见误区与误解。少于 300 字。
+ 4. exampleQuestions字段要求:
+ - 每个题目包含type(题型)、question(题目内容)、options(选项,仅选择题需要)、answer(答案)、explanation(解析)字段
+ - 题型包括:包含4种题型(1单选选择题 2多选选择题 4判断题 6填空题)
+ - 每种题型1道题目
+
+
+ 5. 返回严格的JSON数组格式,示例如下:
+ [
+ {
+ "knowledgePointName": "拆分后的知识点名称",
+ "summary": "知识点总结",
+ "exampleQuestions": [
+ {
+ "type": "1",
+ "question": "题目内容",
+ "options": [
+ {"content": "A. 选项A内容", "sort": 1},
+ {"content": "B. 选项B内容", "sort": 2},
+ {"content": "C. 选项C内容", "sort": 3},
+ {"content": "D. 选项D内容", "sort": 4}
+ ]
+ "answer": "答案",
+ "explanation": "解析内容"
+ }
+ ]
+ }
+ ]
+ """.formatted(
+ req.getCount(),
+ knowledgePoint.getKnowledgePointName(),
+ summary.getSummary(),
+ getGradeNameList(req.getGradeIds()),
+ getSubjectName(knowledgePoint.getSubjectId()),
+ specificNames,
+ requirements
+ );
}
@@ -249,53 +260,59 @@ public class PromptBuilder {
* @return 提示词
*/
public static String buildCheckPrompt(KnowledgePointEntity knowledgePoint, KnowSummaryEntity summary, Collection gradeIds) {
- StringBuilder prompt = new StringBuilder();
-
- prompt.append("你是一位资深的教学专家,请根据以下知识点判断其内容是否正确和超纲。\n\n");
-
- prompt.append("知识点名称:").append(knowledgePoint.getKnowledgePointName()).append("\n");
- prompt.append("知识点总结:").append(summary != null ? summary.getSummary() : "").append("\n");
- prompt.append("对应的年级:").append(getGradeNameList(gradeIds)).append("\n");
- prompt.append("对应的学科:").append(getSubjectName(knowledgePoint.getSubjectId())).append("\n");
-
- prompt.append("请判断以下内容:\n");
- prompt.append("1. 该知识点内容是否正确\n");
- prompt.append("2. 该知识点是否超出指定年级学生的学习范围(超纲)\n");
- prompt.append("3. 如果超纲,请说明超纲原因,并给出适合的替换知识点名称和总结\n");
- prompt.append("4. 请提供该知识点的示例题目\n\n");
-
- prompt.append("要求:\n");
- prompt.append("1. 返回严格的JSON格式,包含以下字段:\n");
- prompt.append(" - isCorrect: 知识点内容是否正确 (boolean)\n");
- prompt.append(" - isOutOfScope: 是否超纲 (boolean)\n");
- prompt.append(" - outOfScopeReason: 超纲原因 (string, 如果没有超纲则为null)\n");
- prompt.append(" - suggestedKnowledgePointName: 建议的知识点名称 (string, 如果没有超纲则为null)\n");
- prompt.append(" - suggestedSummary: 建议的知识点总结 (string, 如果没有超纲则为null)\n");
- prompt.append(" - exampleQuestions: 示例题目列表 (array)\n");
- prompt.append("2. exampleQuestions字段要求:\n");
- prompt.append(" - 每个题目包含type(题型)、question(题目内容)、options(选项,仅选择题需要)、answer(答案)、explanation(解析)字段\n");
- prompt.append(" - 题型包括:包含4种题型(1单选选择题 2多选选择题 4判断题 6填空题)\n");
- prompt.append(" - 每种题型1道题目\n\n");
-
- prompt.append("输出格式示例:\n");
- prompt.append("{\n");
- prompt.append(" \"isCorrect\": true,\n");
- prompt.append(" \"isOutOfScope\": false,\n");
- prompt.append(" \"outOfScopeReason\": null,\n");
- prompt.append(" \"suggestedKnowledgePointName\": null,\n");
- prompt.append(" \"suggestedSummary\": null,\n");
- prompt.append(" \"exampleQuestions\": [\n");
- prompt.append(" {\n");
- prompt.append(" \"type\": \"1\",\n");
- prompt.append(" \"question\": \"题目内容\",\n");
- prompt.append(" \"options\": [\"选项A\", \"选项B\", \"选项C\", \"选项D\"],\n");
- prompt.append(" \"answer\": \"答案\",\n");
- prompt.append(" \"explanation\": \"解析内容\"\n");
- prompt.append(" }\n");
- prompt.append(" ]\n");
- prompt.append("}\n");
-
- return prompt.toString();
+ return """
+ 你是一位资深的教学专家,请根据以下知识点判断其内容是否正确和超纲。
+
+ 知识点名称:%s
+ 知识点总结:%s
+ 对应的年级:%s
+ 对应的学科:%s
+
+
+ 请判断以下内容:
+ 1. 该知识点内容是否正确
+ 2. 该知识点是否超出指定年级学生的学习范围(超纲)
+ 3. 如果超纲,请说明超纲原因,并给出适合的替换知识点名称和总结
+ 4. 请提供该知识点的示例题目
+
+
+ 要求:
+ 1. 返回严格的JSON格式,包含以下字段:
+ - isCorrect: 知识点内容是否正确 (boolean)
+ - isOutOfScope: 是否超纲 (boolean)
+ - outOfScopeReason: 超纲原因 (string, 如果没有超纲则为null)
+ - suggestedKnowledgePointName: 建议的知识点名称 (string, 如果没有超纲则为null)
+ - suggestedSummary: 建议的知识点总结 (string, 如果没有超纲则为null)
+ - exampleQuestions: 示例题目列表 (array)
+ 2. exampleQuestions字段要求:
+ - 每个题目包含type(题型)、question(题目内容)、options(选项,仅选择题需要)、answer(答案)、explanation(解析)字段
+ - 题型包括:包含4种题型(1单选选择题 2多选选择题 4判断题 6填空题)
+ - 每种题型1道题目
+
+
+ 输出格式示例:
+ {
+ "isCorrect": true,
+ "isOutOfScope": false,
+ "outOfScopeReason": null,
+ "suggestedKnowledgePointName": null,
+ "suggestedSummary": null,
+ "exampleQuestions": [
+ {
+ "type": "1",
+ "question": "题目内容",
+ "options": ["选项A", "选项B", "选项C", "选项D"],
+ "answer": "答案",
+ "explanation": "解析内容"
+ }
+ ]
+ }
+ """.formatted(
+ knowledgePoint.getKnowledgePointName(),
+ summary != null ? summary.getSummary() : "",
+ getGradeNameList(gradeIds),
+ getSubjectName(knowledgePoint.getSubjectId())
+ );
}
/**
@@ -306,100 +323,107 @@ public class PromptBuilder {
* @return 提示词
*/
public static String buildPromptForTeachTextGeneration(TeachGoalsEntity teachGoal, List methods, GenerateTaskReq req) {
- StringBuilder prompt = new StringBuilder();
- prompt.append("你是一位经验丰富的教师,请根据以下教学目标和教学重点,针对每种教学方法分别生成一份详细且有针对性的讲课稿。\n\n");
-
- // 添加教学目标信息
- prompt.append("教学目标: ").append(teachGoal.getGoal()).append("\n");
- prompt.append("教学重点: ").append(teachGoal.getPoint()).append("\n\n");
- if(Objects.nonNull(req.getGradeIds())){
- prompt.append("对应的年级:").append(getGradeName(req.getGradeId())).append("\n");
- }
- if(Objects.nonNull(req.getSubjectId())){
- prompt.append("对应的学科:").append(getSubjectName(req.getSubjectId())).append("\n\n");
- }
-
- // 添加教学方法信息
- prompt.append("教学方法列表:\n");
+ StringBuilder methodsBuilder = new StringBuilder();
for (int i = 0; i < methods.size(); i++) {
TeachMethodsEntity method = methods.get(i);
- prompt.append((i + 1)).append(". ").append(method.getName());
+ methodsBuilder.append((i + 1)).append(". ").append(method.getName());
if (method.getDescription() != null && !method.getDescription().isEmpty()) {
- prompt.append(": ").append(method.getDescription());
+ methodsBuilder.append(": ").append(method.getDescription());
}
- prompt.append("(methodId: ").append(method.getId()).append(")\n");
+ methodsBuilder.append("(methodId: ").append(method.getId()).append(")\n");
}
- prompt.append("\n");
- // 添加要求
- prompt.append("请根据以上教学目标、重点和教学方法生成详细的讲课稿,要求如下:\n");
- prompt.append("1. 内容详实,语言生动,适合课堂讲解\n");
- prompt.append("2. 结构清晰,逻辑性强\n");
- prompt.append("3. 包含导入、新授、巩固练习、小结等教学环节\n");
- prompt.append("4. 每种教学方法都需要生成对应的讲课稿\n");
- prompt.append("5. 字数不少于800字\n");
- prompt.append("6. 以JSON数组格式输出,每个元素包含sort(排序)、methodId(教学方法Id)和text(讲课稿内容)、isDefault(是否默认,1:是,0:否),videoContent(讲课稿内容对应的视频内容)字段\n\n");
-
- // 添加输出示例
- prompt.append("示例输出格式:\n");
- prompt.append("[\n");
- prompt.append(" {\n");
- prompt.append(" \"sort\": 1,\n");
- prompt.append(" \"methodId\": 385,\n");
- prompt.append(" \"isDefault\": 1,\n");
- prompt.append(" \"text\": \"这里是针对第一种教学方法的讲课稿内容...\",\n");
- prompt.append(" \"videoContent\": \"针对第一种教学方法的讲课稿内容对应的视频内容...\",\n");
- prompt.append(" },\n");
- prompt.append(" {\n");
- prompt.append(" \"sort\": 2,\n");
- prompt.append(" \"methodId\": 386,\n");
- prompt.append(" \"isDefault\": 0,\n");
- prompt.append(" \"text\": \"这里是针对第二种教学方法的讲课稿内容...\",\n");
- prompt.append(" \"videoContent\": \"针对第二种教学方法的讲课稿内容对应的视频内容...\",\n");
- prompt.append(" }\n");
- prompt.append(" {\n");
- prompt.append(" \"sort\": 3,\n");
- prompt.append(" \"isDefault\": 0,\n");
- prompt.append(" \"methodId\": 387,\n");
- prompt.append(" \"text\": \"这里是针对第三种教学方法的讲课稿内容...\",\n");
- prompt.append(" \"videoContent\": \"针对第三种教学方法的讲课稿内容对应的视频内容...\",\n");
- prompt.append("]");
-
- return prompt.toString();
+ return """
+ 你是一位经验丰富的教师,请根据以下教学目标和教学重点,针对每种教学方法分别生成一份详细且有针对性的讲课稿。
+
+ 教学目标: %s
+ 教学重点: %s
+ %s%s
+ 教学方法列表:
+ %s
+
+ 请根据以上教学目标、重点和教学方法生成详细的讲课稿,要求如下:
+ 1. 内容详实,语言生动,适合课堂讲解
+ 2. 结构清晰,逻辑性强
+ 3. 包含导入、新授、巩固练习、小结等教学环节
+ 4. 每种教学方法都需要生成对应的讲课稿
+ 5. 字数不少于800字
+ 6. 以JSON数组格式输出,每个元素包含sort(排序)、methodId(教学方法Id)和text(讲课稿内容)、isDefault(是否默认,1:是,0:否),videoContent(讲课稿内容对应的视频内容)字段
+
+
+ 示例输出格式:
+ [
+ {
+ "sort": 1,
+ "methodId": 385,
+ "isDefault": 1,
+ "text": "这里是针对第一种教学方法的讲课稿内容...",
+ "videoContent": "针对第一种教学方法的讲课稿内容对应的视频内容...",
+ },
+ {
+ "sort": 2,
+ "methodId": 386,
+ "isDefault": 0,
+ "text": "这里是针对第二种教学方法的讲课稿内容...",
+ "videoContent": "针对第二种教学方法的讲课稿内容对应的视频内容...",
+ }
+ {
+ "sort": 3,
+ "isDefault": 0,
+ "methodId": 387,
+ "text": "这里是针对第三种教学方法的讲课稿内容...",
+ "videoContent": "针对第三种教学方法的讲课稿内容对应的视频内容...",
+ }
+ ]
+ """.formatted(
+ teachGoal.getGoal(),
+ teachGoal.getPoint(),
+ Objects.nonNull(req.getGradeIds()) ? "对应的年级:" + getGradeName(req.getGradeId()) + "\n" : "",
+ Objects.nonNull(req.getSubjectId()) ? "对应的学科:" + getSubjectName(req.getSubjectId()) + "\n\n" : "",
+ methodsBuilder.toString()
+ );
}
public static String buildPromptForTeachGoalGeneration(KnowledgePointEntity knowledgePoint, Integer count, GenerateTaskReq req) {
- StringBuilder prompt = new StringBuilder();
- prompt.append("你是一位资深的'").append(getGradeTypeName(req.getGradeType())).append("'教学专家,请根据以下知识点生成").append(count).append("个教学目标(考点)。\n\n");
-
- // 添加知识点信息
- prompt.append("知识点名称:").append(knowledgePoint.getKnowledgePointName()).append("\n");
- prompt.append("知识点总结:").append(req.getSummary()).append("\n");
- prompt.append("对应的年级:").append(getGradeNameList(req.getGradeIds())).append("\n");
- prompt.append("对应的学科:").append(getSubjectName(req.getSubjectId())).append("\n\n");
-
- prompt.append("要求:\n");
- prompt.append(" 1. 教学目标必须基于\"").append(knowledgePoint.getKnowledgePointName()).append("\"这个知识点\n");
- prompt.append(" 2. 教学目标应适合对应年级的学生理解和掌握\n");
- prompt.append(" 3. 每个教学目标应包含具体的学习成果描述\n");
- prompt.append(" 4. 提供对应的教学重点\n");
- prompt.append(" 5. 可以提供推荐的视频教学链接(可选)\n");
- prompt.append(" 6. 每个教学目标需要有一个排序编号\n");
- prompt.append(" 7. 输出的格式必须是json格式,字段如下:\n");
- prompt.append(" - goal: 教学目标内容\n");
- prompt.append(" - point: 教学重点\n");
- prompt.append(" - sort: 排序编号\n\n");
-
- prompt.append("输出格式示例:\n");
- prompt.append("[\n");
- prompt.append(" {\n");
- prompt.append(" \"goal\": \"教学目标内容\",\n");
- prompt.append(" \"point\": \"教学重点\",\n");
- prompt.append(" \"sort\": 1,\n");
- prompt.append(" }\n");
- prompt.append("]\n");
-
- return prompt.toString();
+ return """
+ 你是一位资深的'%s'教学专家,请根据以下知识点生成%d个教学目标(考点)。
+
+ 知识点名称:%s
+ 知识点总结:%s
+ 对应的年级:%s
+ 对应的学科:%s
+
+
+ 要求:
+ 1. 教学目标必须基于"%s"这个知识点
+ 2. 教学目标应适合对应年级的学生理解和掌握
+ 3. 每个教学目标应包含具体的学习成果描述
+ 4. 提供对应的教学重点
+ 5. 可以提供推荐的视频教学链接(可选)
+ 6. 每个教学目标需要有一个排序编号
+ 7. 输出的格式必须是json格式,字段如下:
+ - goal: 教学目标内容
+ - point: 教学重点
+ - sort: 排序编号
+
+
+ 输出格式示例:
+ [
+ {
+ "goal": "教学目标内容",
+ "point": "教学重点",
+ "sort": 1,
+ }
+ ]
+ """.formatted(
+ getGradeTypeName(req.getGradeType()),
+ count,
+ knowledgePoint.getKnowledgePointName(),
+ req.getSummary(),
+ getGradeNameList(req.getGradeIds()),
+ getSubjectName(req.getSubjectId()),
+ knowledgePoint.getKnowledgePointName()
+ );
}
/**
@@ -409,69 +433,74 @@ public class PromptBuilder {
* @return 提示词
*/
public static String buildPromptForTeachTextVideoScriptGeneration(TeachTextsEntity text) {
- StringBuilder prompt = new StringBuilder();
-
- prompt.append("请根据以下视频内容描述,生成一个完整的Python脚本,该脚本能够直接生成高质量的教学视频。\n\n");
-
- prompt.append("## 视频内容描述:\n");
- prompt.append(text.getVideoContent());
- prompt.append("\n\n");
-
- prompt.append("## 核心要求:\n");
- prompt.append("1. **视频类型**: 生成的是纯视觉教学视频,不需要包含任何字幕或音频\n");
- prompt.append("2. **视频质量**: 必须是高质量的视觉呈现,包括清晰的图形、动画和视觉效果\n");
- prompt.append("3. **教学重点**: 通过视觉元素(图表、动画、代码演示、视觉示例等)清晰地传达教学内容\n\n");
-
- prompt.append("## Python代码要求:\n");
-
- prompt.append("### 1. 代码结构:\n");
- prompt.append("- 必须是完整的、可直接运行的独立脚本\n");
- prompt.append("- 包含所有必要的导入语句和依赖\n");
- prompt.append("- 包含主程序入口,可直接执行生成视频\n\n");
-
- prompt.append("### 2. 视觉生成要求:\n");
- prompt.append("- **使用专业的视觉库**: 如Matplotlib, Plotly, Seaborn, MoviePy, OpenCV, PIL等\n");
- prompt.append("- **高质量视觉效果**:\n");
- prompt.append(" - 清晰美观的图表和可视化\n");
- prompt.append(" - 流畅的动画和过渡效果\n");
- prompt.append(" - 合适的颜色搭配和视觉层次\n");
- prompt.append(" - 适当的文本标注(作为图形的一部分,而非字幕)\n");
- prompt.append("- **视觉叙事**: 通过视觉序列清晰地展示概念演变或步骤流程\n\n");
-
- prompt.append("### 3. 代码质量:\n");
- prompt.append("- **完整注释**:\n");
- prompt.append(" - 文件头部:说明视频主题、主要视觉元素和依赖\n");
- prompt.append(" - 函数/类:详细说明功能和参数\n");
- prompt.append(" - 关键步骤:解释视觉生成逻辑\n");
- prompt.append("- **错误处理**: 包含适当的异常处理和边界情况\n");
- prompt.append("- **模块化设计**: 将不同功能封装为函数或类\n\n");
-
- prompt.append("### 4. 依赖管理:\n");
- prompt.append("- 在文件开头通过注释明确列出所有第三方库\n");
- prompt.append("- 提供pip安装命令的注释\n\n");
-
- prompt.append("### 5. 特别注意事项:\n");
- prompt.append("- **不要生成音频处理代码**(如声音添加、音轨处理)\n");
- prompt.append("- **不要生成字幕相关代码**(如字幕叠加、文本时间轴)\n");
- prompt.append("- **专注于视觉表达**:通过图形、动画、图表等视觉元素传达信息\n");
- prompt.append("- **视频时长**:根据内容复杂度,生成合理时长的视频(通常在1-5分钟)\n\n");
-
- prompt.append("## 输出格式:\n");
- prompt.append("严格按以下JSON格式输出,不要包含任何解释或额外文本:\n");
- prompt.append("[\n");
- prompt.append(" {\n");
- prompt.append(" \"pythonScript\": \"完整的Python脚本代码,可直接运行生成高质量无字幕无音频的教学视频\"\n");
- prompt.append(" }\n");
- prompt.append("]\n\n");
-
- prompt.append("## 示例思考方向(根据具体内容调整):\n");
- prompt.append("- 如果是编程教学:生成代码执行过程的可视化动画\n");
- prompt.append("- 如果是数学教学:生成公式推导的逐步动画演示\n");
- prompt.append("- 如果是数据科学:生成数据处理流程的可视化\n");
- prompt.append("- 如果是算法教学:生成算法步骤的动态演示\n");
- prompt.append("- 如果是概念讲解:生成概念演变的分步图示\n");
-
- return prompt.toString();
+ return """
+ 请根据以下视频内容描述,生成一个完整的Python脚本,该脚本能够直接生成高质量的教学视频。
+
+ ## 视频内容描述:
+ %s
+
+
+ ## 核心要求:
+ 1. **视频类型**: 生成的是纯视觉教学视频,不需要包含任何字幕或音频
+ 2. **视频质量**: 必须是高质量的视觉呈现,包括清晰的图形、动画和视觉效果
+ 3. **教学重点**: 通过视觉元素(图表、动画、代码演示、视觉示例等)清晰地传达教学内容
+
+
+ ## Python代码要求:
+
+ ### 1. 代码结构:
+ - 必须是完整的、可直接运行的独立脚本
+ - 包含所有必要的导入语句和依赖
+ - 包含主程序入口,可直接执行生成视频
+
+
+ ### 2. 视觉生成要求:
+ - **使用专业的视觉库**: 如Matplotlib, Plotly, Seaborn, MoviePy, OpenCV, PIL等
+ - **高质量视觉效果**:
+ - 清晰美观的图表和可视化
+ - 流畅的动画和过渡效果
+ - 合适的颜色搭配和视觉层次
+ - 适当的文本标注(作为图形的一部分,而非字幕)
+ - **视觉叙事**: 通过视觉序列清晰地展示概念演变或步骤流程
+
+
+ ### 3. 代码质量:
+ - **完整注释**:
+ - 文件头部:说明视频主题、主要视觉元素和依赖
+ - 函数/类:详细说明功能和参数
+ - 关键步骤:解释视觉生成逻辑
+ - **错误处理**: 包含适当的异常处理和边界情况
+ - **模块化设计**: 将不同功能封装为函数或类
+
+
+ ### 4. 依赖管理:
+ - 在文件开头通过注释明确列出所有第三方库
+ - 提供pip安装命令的注释
+
+
+ ### 5. 特别注意事项:
+ - **不要生成音频处理代码**(如声音添加、音轨处理)
+ - **不要生成字幕相关代码**(如字幕叠加、文本时间轴)
+ - **专注于视觉表达**:通过图形、动画、图表等视觉元素传达信息
+ - **视频时长**:根据内容复杂度,生成合理时长的视频(通常在1-5分钟)
+
+
+ ## 输出格式:
+ 严格按以下JSON格式输出,不要包含任何解释或额外文本:
+ [
+ {
+ "pythonScript": "完整的Python脚本代码,可直接运行生成高质量无字幕无音频的教学视频"
+ }
+ ]
+
+
+ ## 示例思考方向(根据具体内容调整):
+ - 如果是编程教学:生成代码执行过程的可视化动画
+ - 如果是数学教学:生成公式推导的逐步动画演示
+ - 如果是数据科学:生成数据处理流程的可视化
+ - 如果是算法教学:生成算法步骤的动态演示
+ - 如果是概念讲解:生成概念演变的分步图示
+ """.formatted(text.getVideoContent());
}
private static boolean isNeedLatex(Integer subjectId) {
@@ -486,91 +515,57 @@ public class PromptBuilder {
* @return 题型描述
*/
private static String getQuestionTypeText(Integer type) {
- switch (type) {
- case 1:
- return "选择题";
- case 2:
- return "多选选择题";
- case 3:
- return "问答题";
- case 4:
- return "判断题";
- case 5:
- return "计算题";
- case 6:
- return "填空题";
- default:
- return "其他题型";
- }
+ return switch (type) {
+ case 1 -> "选择题";
+ case 2 -> "多选选择题";
+ case 3 -> "问答题";
+ case 4 -> "判断题";
+ case 5 -> "计算题";
+ case 6 -> "填空题";
+ default -> "其他题型";
+ };
}
private static String getGradeTypeName(Integer gradeType) {
- switch (gradeType) {
- case 1:
- return "小学";
- case 2:
- return "初中";
- case 3:
- return "高中";
- default:
- throw new RuntimeException("未知的学段类型");
- }
+ return switch (gradeType) {
+ case 1 -> "小学";
+ case 2 -> "初中";
+ case 3 -> "高中";
+ default -> throw new RuntimeException("未知的学段类型");
+ };
}
private static String getSubjectName(Integer subjectId) {
- switch (subjectId) {
- case 1:
- return "语文";
- case 2:
- return "数学";
- case 3:
- return "英语";
- case 4:
- return "品德与生活";
- case 5:
- return "科学";
- case 6:
- return "历史";
- case 7:
- return "地理";
- case 8:
- return "生物学";
- case 9:
- return "政治";
- case 10:
- return "物理";
- case 11:
- return "化学";
- default:
- throw new RuntimeException("未知的学科类型");
- }
+ return switch (subjectId) {
+ case 1 -> "语文";
+ case 2 -> "数学";
+ case 3 -> "英语";
+ case 4 -> "品德与生活";
+ case 5 -> "科学";
+ case 6 -> "历史";
+ case 7 -> "地理";
+ case 8 -> "生物学";
+ case 9 -> "政治";
+ case 10 -> "物理";
+ case 11 -> "化学";
+ default -> throw new RuntimeException("未知的学科类型");
+ };
}
private static String getGradeName(Integer gradeId) {
- switch (gradeId) {
- case 1:
- return "一年级";
- case 2:
- return "二年级";
- case 3:
- return "三年级";
- case 4:
- return "四年级";
- case 5:
- return "五年级";
- case 6:
- return "六年级";
- case 7:
- return "初一";
- case 8:
- return "初二";
- case 9:
- return "初三";
- case 10:
- return "高中";
- default:
- throw new RuntimeException("未知的学科类型");
- }
+ return switch (gradeId) {
+ case 1 -> "一年级";
+ case 2 -> "二年级";
+ case 3 -> "三年级";
+ case 4 -> "四年级";
+ case 5 -> "五年级";
+ case 6 -> "六年级";
+ case 7 -> "初一";
+ case 8 -> "初二";
+ case 9 -> "初三";
+ case 10 -> "高中";
+ default -> throw new RuntimeException("未知的学科类型");
+ };
}
private static String getGradeNameList(Collection gradeIds) {
diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/IAiScenarioConfigService.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/IAiScenarioConfigService.java
index 4e8d0d2..d11956e 100644
--- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/IAiScenarioConfigService.java
+++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/IAiScenarioConfigService.java
@@ -8,9 +8,9 @@ import com.seer.teach.teacher.module.entity.AiScenarioConfigEntity;
* AI场景配置表 服务类
*
*
- * @author System
- * @since 2025-10-27
*/
public interface IAiScenarioConfigService extends IService {
+
+ AiScenarioConfigEntity getOneByScenarioCode(String scenarioCode);
}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/AiScenarioConfigServiceImpl.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/AiScenarioConfigServiceImpl.java
index b51dcb6..b88ef98 100644
--- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/AiScenarioConfigServiceImpl.java
+++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/AiScenarioConfigServiceImpl.java
@@ -1,9 +1,16 @@
package com.seer.teach.teacher.service.impl;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.seer.teach.common.constants.CommonConstant;
+import com.seer.teach.common.enums.ResultCodeEnum;
+import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.teacher.module.entity.AiScenarioConfigEntity;
+import com.seer.teach.teacher.module.entity.AiScenarioEntity;
import com.seer.teach.teacher.module.mapper.AiScenarioConfigMapper;
import com.seer.teach.teacher.service.IAiScenarioConfigService;
+import com.seer.teach.teacher.service.IAiScenarioService;
+import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
@@ -14,7 +21,23 @@ import org.springframework.stereotype.Service;
* @author System
* @since 2025-10-27
*/
+@RequiredArgsConstructor
@Service
public class AiScenarioConfigServiceImpl extends ServiceImpl implements IAiScenarioConfigService {
+ private final IAiScenarioService aiScenarioService;
+
+
+ @Override
+ public AiScenarioConfigEntity getOneByScenarioCode(String scenarioCode) {
+ LambdaQueryWrapper scenarioWrapper = new LambdaQueryWrapper<>();
+ scenarioWrapper.eq(AiScenarioEntity::getScenarioCode, scenarioCode);
+ AiScenarioEntity aiScenario = aiScenarioService.getOne(scenarioWrapper);
+ AssertUtils.notNull(aiScenario, ResultCodeEnum.SCENARIO_NOT_FOUND);
+ LambdaQueryWrapper configWrapper = new LambdaQueryWrapper<>();
+ configWrapper.eq(AiScenarioConfigEntity::getScenarioId, aiScenario.getId());
+ configWrapper.eq(AiScenarioConfigEntity::getEnabled, CommonConstant.ENABLE);
+ configWrapper.orderByAsc(AiScenarioConfigEntity::getPriority);
+ return super.getOne(configWrapper);
+ }
}
\ No newline at end of file
From 9b90abf8e8e0a9868ec609481daf44ee97ded5cf Mon Sep 17 00:00:00 2001
From: Wang
Date: Sat, 27 Dec 2025 18:24:59 +0800
Subject: [PATCH 4/6] =?UTF-8?q?=E5=9F=BA=E4=BA=8Espring=20ai=E5=AE=9E?=
=?UTF-8?q?=E7=8E=B0dashscope,openpi=E7=9A=84chat=20client?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/com/seer/teach/ai/service/AiChatModelService.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java
index 3024b20..1e62043 100644
--- a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/service/AiChatModelService.java
@@ -40,7 +40,7 @@ public class AiChatModelService {
private final AiModelClientFactory aiModelClientFactory;
- public ChatResponse chatMessage(String scenarioCode, Object params){
+ public ChatResponse chatMessage(String scenarioCode, Object prompt){
AiScenarioConfigEntity scenarioConfig = aiScenarioConfigService.getOneByScenarioCode(scenarioCode);
log.info("scenarioConfig:{}", scenarioConfig);
@@ -53,8 +53,7 @@ public class AiChatModelService {
ChatModel chatModel = client.createChatModel(modelConfig).orElseThrow(() -> new CommonException(ResultCodeEnum.AI_MODEL_NOT_FOUND));
Optional options = client.createChatOptions(BeanUtil.beanToMap(scenarioConfig));
- Prompt prompt = new Prompt(params.toString(), options.orElse(null));
- return chatModel.call(prompt);
+ return chatModel.call(new Prompt(prompt.toString(), options.orElse(null)));
}
public Flux chatStream(String scenarioCode, Object params){
From 3e1e89c213588898e500787a87979a313c4697e4 Mon Sep 17 00:00:00 2001
From: Wang
Date: Mon, 29 Dec 2025 17:00:47 +0800
Subject: [PATCH 5/6] =?UTF-8?q?=E5=9F=BA=E4=BA=8Espring-ai=E7=94=9F?=
=?UTF-8?q?=E6=88=90=E6=95=99=E5=AD=A6=E7=9B=AE=E6=A0=87?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../teach/admin/SeerAdminApplication.java | 2 +-
seer-admin/src/main/resources/application.yml | 6 +-
seer-dependencies/pom.xml | 19 +++-
seer-teacher/seer-teacher-ai/pom.xml | 7 +-
.../client/model/AbstractAiModelClient.java | 6 +-
.../teach/ai/client/model/AiModelClient.java | 12 ---
.../model/config/DashScopeClientConfig.java | 99 +++++++++++++++++++
.../model/dashscope/DashScopeModelClient.java | 26 ++---
.../model/openai/OpenAIModelClient.java | 12 +--
.../seer-teacher-service-admin/pom.xml | 6 ++
.../impl/AbstractGenerationService.java | 15 ++-
11 files changed, 153 insertions(+), 57 deletions(-)
create mode 100644 seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/DashScopeClientConfig.java
diff --git a/seer-admin/src/main/java/com/seer/teach/admin/SeerAdminApplication.java b/seer-admin/src/main/java/com/seer/teach/admin/SeerAdminApplication.java
index 121946b..367799f 100644
--- a/seer-admin/src/main/java/com/seer/teach/admin/SeerAdminApplication.java
+++ b/seer-admin/src/main/java/com/seer/teach/admin/SeerAdminApplication.java
@@ -15,7 +15,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@Slf4j
@EnableFeignClients(basePackages = "com.seer.teach.*.api")
-@SpringBootApplication(scanBasePackages = "com.seer",exclude = QuartzAutoConfiguration.class)
+@SpringBootApplication(scanBasePackages = "com.seer",exclude = {QuartzAutoConfiguration.class})
@EnableTransactionManagement
@EnableAspectJAutoProxy
@EnableAsync
diff --git a/seer-admin/src/main/resources/application.yml b/seer-admin/src/main/resources/application.yml
index d4574a2..56d71aa 100644
--- a/seer-admin/src/main/resources/application.yml
+++ b/seer-admin/src/main/resources/application.yml
@@ -11,13 +11,8 @@ spring:
main:
allow-bean-definition-overriding: true
allow-circular-references: true
- mvc:
-
profiles:
active: dev
- mvc:
- pathmatch:
- matching-strategy: ant_path_matcher
flyway:
enabled: true
locations: classpath:db/mysql
@@ -39,6 +34,7 @@ spring:
server-addr: 192.168.0.39:8848 # 配置中心地址
file-extension: yaml # 配置文件后缀(yaml/properties)
namespace: ${spring.profiles.active}
+
#日志
logging:
config: classpath:logback-${spring.profiles.active}.xml
diff --git a/seer-dependencies/pom.xml b/seer-dependencies/pom.xml
index 9577480..38a7611 100644
--- a/seer-dependencies/pom.xml
+++ b/seer-dependencies/pom.xml
@@ -16,7 +16,7 @@
2025.0.0.0
1.1.2
- 1.1.0.0-M5
+ 1.1.0.0-RC2
@@ -660,6 +660,12 @@
${spring-ai-alibaba.version}
+
+ com.alibaba.cloud.ai
+ spring-ai-alibaba-dashscope
+ ${spring-ai-alibaba.version}
+
+
org.springframework.ai
spring-ai-starter-model-deepseek
@@ -668,8 +674,17 @@
org.springframework.ai
- spring-ai-starter-model-deepseek
+ spring-ai-deepseek
${spring-ai.version}
+ compile
+ true
+
+
+
+ org.springframework.ai
+ spring-ai-openai
+ ${spring-ai.version}
+ compile
diff --git a/seer-teacher/seer-teacher-ai/pom.xml b/seer-teacher/seer-teacher-ai/pom.xml
index 6af3ff8..ac91d20 100644
--- a/seer-teacher/seer-teacher-ai/pom.xml
+++ b/seer-teacher/seer-teacher-ai/pom.xml
@@ -26,17 +26,18 @@
com.alibaba.cloud.ai
- spring-ai-alibaba-starter-dashscope
+ spring-ai-alibaba-dashscope
org.springframework.ai
- spring-ai-starter-model-openai
+ spring-ai-openai
+ compile
org.springframework.ai
- spring-ai-starter-model-deepseek
+ spring-ai-deepseek
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java
index f53bd21..2506171 100644
--- a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AbstractAiModelClient.java
@@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.model.tool.DefaultToolExecutionEligibilityPredicate;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
@@ -24,9 +25,11 @@ public abstract class AbstractAiModelClient implements AiModelClient {
protected ResponseErrorHandler responseErrorHandler;
@Autowired(required = false)
+ @Qualifier("aiRestClient")
protected RestClient.Builder restClientBuilder;
@Autowired(required = false)
+ @Qualifier("aiWebClient")
protected WebClient.Builder webClientBuilder;
@Autowired
@@ -35,8 +38,7 @@ public abstract class AbstractAiModelClient implements AiModelClient {
@Autowired
protected ToolCallingManager toolCallingManager;
- @Autowired(required = false)
- protected ObservationRegistry observationRegistry;
+ protected ObservationRegistry observationRegistry = ObservationRegistry.create();
protected final DefaultToolExecutionEligibilityPredicate toolExecutionEligibilityPredicate = new DefaultToolExecutionEligibilityPredicate();
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java
index 0b1a2f0..59035ad 100644
--- a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/AiModelClient.java
@@ -1,6 +1,5 @@
package com.seer.teach.ai.client.model;
-import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisModel;
import com.seer.teach.ai.client.model.config.ModelConfig;
import org.springframework.ai.audio.tts.TextToSpeechModel;
import org.springframework.ai.chat.model.ChatModel;
@@ -43,17 +42,6 @@ public interface AiModelClient {
return Optional.empty();
}
- /**
- * 获取语音合成模型(Speech Synthesis Model)
- * 用于将文本转换为语音音频,仅dashscope平台支持
- *
- * @param modelConfig 模型配置
- * @return 语音合成模型实例的Optional包装
- */
- default Optional createSpeechSynthesisModel(ModelConfig modelConfig) {
- return Optional.empty();
- }
-
/**
* 获取文本转语音模型(Text To Speech Model)
* 用于将文本转换为语音输出,支持多种平台
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/DashScopeClientConfig.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/DashScopeClientConfig.java
new file mode 100644
index 0000000..737fa4b
--- /dev/null
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/config/DashScopeClientConfig.java
@@ -0,0 +1,99 @@
+package com.seer.teach.ai.client.model.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.codec.json.Jackson2JsonDecoder;
+import org.springframework.http.codec.json.Jackson2JsonEncoder;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * DashScope API 配置类
+ * 用于配置restClient以正确处理聊天完成请求
+ */
+@Slf4j
+@Configuration
+public class DashScopeClientConfig {
+
+ /**
+ * 创建用于DashScope API的RestClient
+ */
+ @Bean
+ public RestClient aiRestClient() {
+ ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
+ .modules(new JavaTimeModule())
+ .build();
+
+ MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
+
+ return RestClient.builder()
+ .requestInterceptor(new LoggingInterceptor())
+ .messageConverters(converters -> converters.add(converter))
+ .build();
+ }
+
+ /**
+ * 创建用于DashScope API的WebClient并启用日志
+ */
+ @Bean
+ public WebClient aiWebClient() {
+ ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
+ .modules(new JavaTimeModule())
+ .build();
+
+ return WebClient.builder()
+ .codecs(configurer -> configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper)))
+ .codecs(configurer -> configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper)))
+ .filter(ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
+ log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
+ clientRequest.headers().forEach((name, values) ->
+ values.forEach(value -> log.info("{}={}", name, value)));
+ return Mono.just(clientRequest);
+ }))
+ .filter(ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
+ log.info("Response Status: {}", clientResponse.statusCode());
+ clientResponse.headers().asHttpHeaders().forEach((name, values) ->
+ values.forEach(value -> log.info("{}={}", name, value)));
+ return Mono.just(clientResponse);
+ }))
+ .build();
+ }
+
+
+ public static class LoggingInterceptor implements ClientHttpRequestInterceptor {
+
+ @Override
+ public ClientHttpResponse intercept(
+ HttpRequest request,
+ byte[] body,
+ ClientHttpRequestExecution execution) throws IOException {
+
+ // 记录请求信息
+ log.info("HTTP请求方法: {}", request.getMethod());
+ log.info("HTTP请求URL: {}", request.getURI());
+ log.info("HTTP请求头: {}", request.getHeaders());
+ if (body.length > 0) {
+ log.info("HTTP请求体: {}", new String(body, StandardCharsets.UTF_8));
+ }
+
+ // 执行请求
+ ClientHttpResponse response = execution.execute(request, body);
+
+ return response;
+ }
+ }
+}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java
index 8ec0fce..6e93ce1 100644
--- a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/dashscope/DashScopeModelClient.java
@@ -1,14 +1,10 @@
package com.seer.teach.ai.client.model.dashscope;
-import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatProperties;
-import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeConnectionProperties;
-import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingProperties;
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.api.DashScopeAudioSpeechApi;
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
import com.alibaba.cloud.ai.dashscope.audio.DashScopeAudioSpeechModel;
import com.alibaba.cloud.ai.dashscope.audio.DashScopeAudioSpeechOptions;
-import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
@@ -17,12 +13,11 @@ import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
import com.seer.teach.ai.client.model.AbstractAiModelClient;
import com.seer.teach.ai.client.model.config.ModelConfig;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.audio.tts.TextToSpeechModel;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
-import org.springframework.ai.document.MetadataMode;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
@@ -40,14 +35,9 @@ import java.util.Optional;
@Component
public class DashScopeModelClient extends AbstractAiModelClient {
- private DashScopeConnectionProperties connectionProperties;
-
- @Autowired(required = false)
- private DashScopeChatProperties chatProperties;
-
@Override
public String getPlatformName() {
- return "dashscope";
+ return "aliyun_bailian";
}
@Override
@@ -125,17 +115,15 @@ public class DashScopeModelClient extends AbstractAiModelClient {
}
@Override
- public Optional createSpeechSynthesisModel(ModelConfig modelConfig) {
+ public Optional createTextToSpeechModel(ModelConfig modelConfig) {
try {
DashScopeAudioSpeechApi audioSpeechApi = new DashScopeAudioSpeechApi(modelConfig.getApiKey(), modelConfig.getUrl());
DashScopeAudioSpeechOptions options = DashScopeAudioSpeechOptions.builder()
.model(modelConfig.getModel())
- .requestText(DashScopeAudioSpeechApi.RequestTextType.PLAIN_TEXT)
.voice("longyingxiao")
.build();
DashScopeAudioSpeechModel speechSynthesisModel = new DashScopeAudioSpeechModel(audioSpeechApi, options, retryTemplate);
-
return Optional.of(speechSynthesisModel);
} catch (Exception e) {
log.error("创建DashScope语音合成模型失败", e);
@@ -147,10 +135,9 @@ public class DashScopeModelClient extends AbstractAiModelClient {
@Override
public Optional createEmbeddingModel(ModelConfig modelConfig) {
try {
- DashScopeEmbeddingProperties embeddingProperties = new DashScopeEmbeddingProperties();
DashScopeApi dashScopeApi = createDashScopeApi(modelConfig.getApiKey(), modelConfig.getUrl());
- DashScopeEmbeddingModel embeddingModel = new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, embeddingProperties.getOptions(), retryTemplate, observationRegistry);
+ DashScopeEmbeddingModel embeddingModel = new DashScopeEmbeddingModel(dashScopeApi);
return Optional.of(embeddingModel);
} catch (Exception e) {
@@ -162,11 +149,10 @@ public class DashScopeModelClient extends AbstractAiModelClient {
/**
* 创建DashScope API实例
*/
- private DashScopeApi createDashScopeApi(String apiKey, String baseUrl) {
+ private DashScopeApi createDashScopeApi(String apiKey, String completionsPath) {
DashScopeApi.Builder builder = DashScopeApi.builder()
.apiKey(apiKey)
- .baseUrl(baseUrl);
-
+ .baseUrl("https://dashscope.aliyuncs.com");
// 如果有可用的客户端构建器,添加它们
if (webClientBuilder != null) {
builder.webClientBuilder(webClientBuilder);
diff --git a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java
index 17c9aa7..229f54c 100644
--- a/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java
+++ b/seer-teacher/seer-teacher-ai/src/main/java/com/seer/teach/ai/client/model/openai/OpenAIModelClient.java
@@ -6,13 +6,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.audio.tts.TextToSpeechModel;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
-import org.springframework.ai.document.MetadataMode;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.model.SimpleApiKey;
-import org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechProperties;
-import org.springframework.ai.model.openai.autoconfigure.OpenAiChatProperties;
-import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingProperties;
import org.springframework.ai.openai.OpenAiAudioSpeechModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
@@ -131,8 +127,7 @@ public class OpenAIModelClient extends AbstractAiModelClient {
.responseErrorHandler(responseErrorHandler)
.build();
- OpenAiAudioSpeechProperties properties = new OpenAiAudioSpeechProperties();
- TextToSpeechModel textToSpeechModel = new OpenAiAudioSpeechModel(audioApi, properties.getOptions(), retryTemplate);
+ TextToSpeechModel textToSpeechModel = new OpenAiAudioSpeechModel(audioApi);
return Optional.of(textToSpeechModel);
} catch (Exception e) {
log.error("创建OpenAI文本转语音模型失败", e);
@@ -144,8 +139,7 @@ public class OpenAIModelClient extends AbstractAiModelClient {
public Optional createEmbeddingModel(ModelConfig modelConfig) {
try {
OpenAiApi openAiApi = createOpenAiApi(modelConfig.getApiKey(), modelConfig.getUrl());
- OpenAiEmbeddingProperties properties = new OpenAiEmbeddingProperties();
- EmbeddingModel embeddingModel = new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED,properties.getOptions(), retryTemplate);
+ EmbeddingModel embeddingModel = new OpenAiEmbeddingModel(openAiApi);
return Optional.of(embeddingModel);
} catch (Exception e) {
log.error("创建OpenAI嵌入模型失败", e);
@@ -157,8 +151,6 @@ public class OpenAIModelClient extends AbstractAiModelClient {
return OpenAiApi.builder()
.baseUrl(baseUrl)
.apiKey(new SimpleApiKey(apiKey))
- .completionsPath(OpenAiChatProperties.DEFAULT_COMPLETIONS_PATH)
- .embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH)
.restClientBuilder(restClientBuilder)
.webClientBuilder(webClientBuilder).build();
}
diff --git a/seer-teacher/seer-teacher-service-admin/pom.xml b/seer-teacher/seer-teacher-service-admin/pom.xml
index b8c6c34..7978baf 100644
--- a/seer-teacher/seer-teacher-service-admin/pom.xml
+++ b/seer-teacher/seer-teacher-service-admin/pom.xml
@@ -38,6 +38,12 @@
${project.version}
+
+ ${project.groupId}
+ seer-teacher-ai
+ ${project.version}
+
+
com.squareup.okhttp3
okhttp
diff --git a/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/service/impl/AbstractGenerationService.java b/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/service/impl/AbstractGenerationService.java
index a4a3b97..c528da6 100644
--- a/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/service/impl/AbstractGenerationService.java
+++ b/seer-teacher/seer-teacher-service-admin/src/main/java/com/seer/teach/admin/service/impl/AbstractGenerationService.java
@@ -2,11 +2,14 @@ package com.seer.teach.admin.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.seer.teach.ai.service.AiChatModelService;
import com.seer.teach.common.constants.CommonConstant;
import com.seer.teach.common.entity.BaseEntity;
import com.seer.teach.teacher.service.AiModelCallService;
import com.seer.teach.teacher.service.platform.LlmResponse;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.model.Generation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.support.TransactionTemplate;
@@ -16,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Function;
/**
@@ -41,6 +45,9 @@ public abstract class AbstractGenerationService {
@Autowired
protected TransactionTemplate transactionTemplate;
+ @Autowired
+ protected AiChatModelService chatModelService;
+
/**
* 检查当前线程是否被中断
*
@@ -78,6 +85,10 @@ public abstract class AbstractGenerationService {
*/
protected LlmResponse callLLM(String scenarioCode, String prompt, Map params, Integer userId) {
log.info("调用大模型,场景编码: {}, 提示词长度: {}", scenarioCode, prompt.length());
+ //ChatResponse chatResponse = chatModelService.chatMessage(scenarioCode, prompt);
+ LlmResponse response = new LlmResponse();
+ //Generation result = chatResponse.getResult();
+
return aiModelCallService.callModel(scenarioCode, prompt, params, userId);
}
@@ -160,8 +171,8 @@ public abstract class AbstractGenerationService {
* @param saver 保存器函数
*/
protected Integer executeBatchProcess(R request,
- Function> generator,
- java.util.function.Consumer> saver) {
+ Function> generator,
+ java.util.function.Consumer> saver) {
// 检查任务是否被取消
String taskId = getTaskIdFromRequest(request);
if (isThreadInterrupted() || isTaskCancelled(taskId)) {
From da1f49001d551d47946ea1c6c4a61123401c4148 Mon Sep 17 00:00:00 2001
From: Wang
Date: Mon, 29 Dec 2025 17:52:31 +0800
Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9seer-gateway=E7=9A=84?=
=?UTF-8?q?=E7=BD=91=E5=85=B3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
seer-gateway/pom.xml | 2 +-
.../src/main/resources/application.yml | 39 +++++++++++++++++++
.../src/main/resources/logback-dev.xml | 4 +-
3 files changed, 42 insertions(+), 3 deletions(-)
diff --git a/seer-gateway/pom.xml b/seer-gateway/pom.xml
index 86c8eb8..d408cd2 100644
--- a/seer-gateway/pom.xml
+++ b/seer-gateway/pom.xml
@@ -21,7 +21,7 @@
org.springframework.cloud
- spring-cloud-starter-gateway
+ spring-cloud-starter-gateway-server-webflux
diff --git a/seer-gateway/src/main/resources/application.yml b/seer-gateway/src/main/resources/application.yml
index c59d0a2..f84994c 100644
--- a/seer-gateway/src/main/resources/application.yml
+++ b/seer-gateway/src/main/resources/application.yml
@@ -32,6 +32,45 @@ spring:
server-addr: 192.168.0.39:8848 # 配置中心地址
file-extension: yaml # 配置文件后缀(yaml/properties)
namespace: ${spring.profiles.active}
+ gateway:
+ discovery:
+ locator:
+ enabled: true # 开启通过服务发现创建路由
+ lower-case-service-id: true # 服务名小写
+ routes:
+ - id: seer-admin # 路由的编号
+ uri: lb://seer-admin
+ predicates:
+ - Path=/seer/admin/**
+ - id: seer-teacher
+ uri: lb://seer-teacher
+ predicates:
+ - Path=/seer/teacher/**
+ - id: seer-iot
+ uri: lb://seer-iot
+ predicates:
+ - Path=/seer/iot/**
+ - id: seer-mall
+ uri: lb://seer-mall
+ predicates:
+ - Path=/seer/mall/**
+ - id: seer-pay
+ uri: lb://seer-pay
+ predicates:
+ - Path=/seer/pay/**
+ - id: seer-user
+ uri: lb://seer-user
+ predicates:
+ - Path=/seer/user/**
+ - id: seer-mp
+ uri: lb://seer-mp
+ predicates:
+ - Path=/seer/mp/**
+ - id: seer-open-api
+ uri: lb://seer-open-api
+ predicates:
+ - Path=/seer/open/rest/**
+
config:
import:
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
diff --git a/seer-gateway/src/main/resources/logback-dev.xml b/seer-gateway/src/main/resources/logback-dev.xml
index 86a3500..6c86e72 100644
--- a/seer-gateway/src/main/resources/logback-dev.xml
+++ b/seer-gateway/src/main/resources/logback-dev.xml
@@ -78,12 +78,12 @@
-
+
-
+