基于spring ai实现dashscope,openpi的chat client
This commit is contained in:
parent
f50cbe3360
commit
91914407ec
@ -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;
|
||||
|
||||
@ -70,4 +70,9 @@ public interface CommonConstant {
|
||||
* 任务锁前缀
|
||||
*/
|
||||
String TASK_CANCEL_PREFIX = "ai_task_cancel:";
|
||||
|
||||
/**
|
||||
* 启用
|
||||
*/
|
||||
Integer ENABLE = 1;
|
||||
}
|
||||
@ -15,8 +15,8 @@
|
||||
<spring-cloud-openfeign.version>4.3.0</spring-cloud-openfeign.version>
|
||||
<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>
|
||||
|
||||
<spring-ai.version>2.0.0-SNAPSHOT</spring-ai.version>
|
||||
<spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
|
||||
<spring-ai.version>1.1.2</spring-ai.version>
|
||||
<spring-ai-alibaba.version>1.1.0.0-M5</spring-ai-alibaba.version>
|
||||
|
||||
|
||||
<!-- 认证 -->
|
||||
@ -68,7 +68,7 @@
|
||||
|
||||
<guava.version>33.5.0-jre</guava.version>
|
||||
<!-- 异步处理 -->
|
||||
<spring-retry.version>1.2.5.RELEASE</spring-retry.version>
|
||||
<spring-retry.version>2.0.8</spring-retry.version>
|
||||
|
||||
<!-- 接口文档 -->
|
||||
<springdoc-openapi.version>2.8.14</springdoc-openapi.version>
|
||||
@ -186,6 +186,13 @@
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
@ -646,6 +653,37 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||
<version>${spring-ai-alibaba.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-ollama</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
|
||||
<version>${spring-ai-alibaba.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 测试依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@ -19,105 +19,6 @@
|
||||
<module>seer-teacher-service-bootstrap</module>
|
||||
<module>seer-teacher-api</module>
|
||||
<module>seer-teacher-service-admin</module>
|
||||
<module>seer-teacher-ai</module>
|
||||
</modules>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!--繁体转简体-->
|
||||
<dependency>
|
||||
<groupId>com.github.houbb</groupId>
|
||||
<artifactId>opencc4j</artifactId>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--MinIO依赖-->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>8.5.1</version>
|
||||
</dependency>
|
||||
|
||||
<!--protobaffer-->
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>4.30.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 加密库 -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--MybatisPlus-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- 代码自动生成器依赖-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-generator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--代码生成器需要的引擎-->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
</dependency>
|
||||
<!--AOP编程jar包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- fastjson2 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
<!--参数校验-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- IP地址检索 -->
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
55
seer-teacher/seer-teacher-ai/pom.xml
Normal file
55
seer-teacher/seer-teacher-ai/pom.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.seer.teach</groupId>
|
||||
<artifactId>seer-teacher</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>seer-teacher-ai</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seer-teacher-service</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-ollama</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-observation</artifactId>
|
||||
<version>1.15.7</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -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<String, AiModelClient> serviceMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public AiModelClientFactoryImpl(List<AiModelClient> 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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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<ChatModel> createChatModel(ModelConfig modelConfig){
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<ChatOptions> createChatOptions(Map<String, Object> options){
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图像模型
|
||||
*/
|
||||
default Optional<ImageModel> createImageModel(ModelConfig modelConfig){
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语音合成模型(Speech Synthesis Model)
|
||||
* 用于将文本转换为语音音频,仅dashscope平台支持
|
||||
*
|
||||
* @param modelConfig 模型配置
|
||||
* @return 语音合成模型实例的Optional包装
|
||||
*/
|
||||
default Optional<SpeechSynthesisModel> createSpeechSynthesisModel(ModelConfig modelConfig) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文本转语音模型(Text To Speech Model)
|
||||
* 用于将文本转换为语音输出,支持多种平台
|
||||
*
|
||||
* @param modelConfig 模型配置
|
||||
* @return 文本转语音模型实例的Optional包装
|
||||
*/
|
||||
default Optional<TextToSpeechModel> createTextToSpeechModel(ModelConfig modelConfig) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取嵌入模型(Embedding Model)
|
||||
* 用于将文本转换为向量表示,支持多种平台
|
||||
*
|
||||
* @param modelConfig 模型配置
|
||||
* @return 嵌入模型实例的Optional包装
|
||||
*/
|
||||
default Optional<EmbeddingModel> createEmbeddingModel(ModelConfig modelConfig) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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<ChatModel> 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<ChatOptions> createChatOptions(Map<String, Object> options) {
|
||||
DashScopeChatOptions.DashScopeChatOptionsBuilder builder = DashScopeChatOptions.builder();
|
||||
for (Map.Entry<String, Object> 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<ImageModel> 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<SpeechSynthesisModel> 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<EmbeddingModel> 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();
|
||||
}
|
||||
}
|
||||
@ -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<ChatModel> 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<ChatOptions> createChatOptions(Map<String, Object> options) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@ -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<ChatModel> 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<ChatOptions> createChatOptions(Map<String, Object> options) {
|
||||
OpenAiChatOptions.Builder builder = OpenAiChatOptions.builder();
|
||||
builder.streamUsage(true);
|
||||
for (Map.Entry<String, Object> 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<ImageModel> 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<TextToSpeechModel> 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<EmbeddingModel> 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();
|
||||
}
|
||||
}
|
||||
@ -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<ChatOptions> options = client.createChatOptions(BeanUtil.beanToMap(scenarioConfig));
|
||||
Prompt prompt = new Prompt(params.toString(), options.orElse(null));
|
||||
return chatModel.call(prompt);
|
||||
}
|
||||
|
||||
public Flux<ChatResponse> 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<ChatOptions> 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<AiApiKeyEntity> aiApiKeyList = aiApiKeyService.list(new LambdaQueryWrapper<>(AiApiKeyEntity.class).eq(AiApiKeyEntity::getPlatform, aiScenarioConfig.getPlatform()));
|
||||
AiApiKeyEntity apiKey = null;
|
||||
Optional<AiApiKeyEntity> 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<AiApiKeyEntity> apiKeyOptional = aiApiKeyList.stream().filter(aiApiKey -> Objects.nonNull(aiApiKey.getIsDefault()) && aiApiKey.getIsDefault() == 1).findAny();
|
||||
if (apiKeyOptional.isPresent()) {
|
||||
apiKey = apiKeyOptional.get();
|
||||
}
|
||||
}
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
}
|
||||
@ -30,5 +30,11 @@
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -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");
|
||||
return """
|
||||
你是一个教学题目的专家,请根据以下示例题目生成%d道类似的题目。
|
||||
|
||||
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");
|
||||
知识点:%s
|
||||
知识点总结:%s
|
||||
|
||||
return prompt.toString();
|
||||
要求:
|
||||
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");
|
||||
String lowerElementaryMathNote = isLowerElementaryMath ? "\n特别注意:这是一、二年级的题目,请尽量以图文并茂的形式展示题目内容和选项,使题目更适合低年级学生理解。\n" : "";
|
||||
|
||||
prompt.append(" }\n");
|
||||
prompt.append("]\n");
|
||||
String middleElementaryEnglishNote = isMiddleElementaryEnglish ? "\n特别注意:这是三、四年级的英语题目,请尽量使用中文出题,让题目更容易被学生理解。\n" : "";
|
||||
|
||||
// 特殊要求:对于小学一、二年级的数学题目,强调以图片为主
|
||||
if (isLowerElementaryMath) {
|
||||
prompt.append("\n特别注意:这是一、二年级的题目,请尽量以图文并茂的形式展示题目内容和选项,使题目更适合低年级学生理解。\n");
|
||||
}
|
||||
|
||||
// 特殊要求:对于三、四年级的英语题目,强调使用中文出题
|
||||
if (isMiddleElementaryEnglish) {
|
||||
prompt.append("\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 """
|
||||
你是一位资深的教学专家,请根据以下知识点进行拆分,将其拆分为%d个更细粒度的知识点。
|
||||
|
||||
return prompt.toString();
|
||||
原始知识点名称:%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<Integer> gradeIds) {
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
return """
|
||||
你是一位资深的教学专家,请根据以下知识点判断其内容是否正确和超纲。
|
||||
|
||||
prompt.append("你是一位资深的教学专家,请根据以下知识点判断其内容是否正确和超纲。\n\n");
|
||||
知识点名称:%s
|
||||
知识点总结:%s
|
||||
对应的年级:%s
|
||||
对应的学科:%s
|
||||
|
||||
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");
|
||||
请判断以下内容:
|
||||
1. 该知识点内容是否正确
|
||||
2. 该知识点是否超出指定年级学生的学习范围(超纲)
|
||||
3. 如果超纲,请说明超纲原因,并给出适合的替换知识点名称和总结
|
||||
4. 请提供该知识点的示例题目
|
||||
|
||||
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");
|
||||
要求:
|
||||
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道题目
|
||||
|
||||
return prompt.toString();
|
||||
|
||||
输出格式示例:
|
||||
{
|
||||
"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<TeachMethodsEntity> 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");
|
||||
return """
|
||||
你是一位经验丰富的教师,请根据以下教学目标和教学重点,针对每种教学方法分别生成一份详细且有针对性的讲课稿。
|
||||
|
||||
// 添加输出示例
|
||||
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("]");
|
||||
教学目标: %s
|
||||
教学重点: %s
|
||||
%s%s
|
||||
教学方法列表:
|
||||
%s
|
||||
|
||||
return prompt.toString();
|
||||
请根据以上教学目标、重点和教学方法生成详细的讲课稿,要求如下:
|
||||
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");
|
||||
return """
|
||||
你是一位资深的'%s'教学专家,请根据以下知识点生成%d个教学目标(考点)。
|
||||
|
||||
// 添加知识点信息
|
||||
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");
|
||||
知识点名称:%s
|
||||
知识点总结:%s
|
||||
对应的年级:%s
|
||||
对应的学科:%s
|
||||
|
||||
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");
|
||||
要求:
|
||||
1. 教学目标必须基于"%s"这个知识点
|
||||
2. 教学目标应适合对应年级的学生理解和掌握
|
||||
3. 每个教学目标应包含具体的学习成果描述
|
||||
4. 提供对应的教学重点
|
||||
5. 可以提供推荐的视频教学链接(可选)
|
||||
6. 每个教学目标需要有一个排序编号
|
||||
7. 输出的格式必须是json格式,字段如下:
|
||||
- goal: 教学目标内容
|
||||
- point: 教学重点
|
||||
- sort: 排序编号
|
||||
|
||||
return prompt.toString();
|
||||
|
||||
输出格式示例:
|
||||
[
|
||||
{
|
||||
"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();
|
||||
return """
|
||||
请根据以下视频内容描述,生成一个完整的Python脚本,该脚本能够直接生成高质量的教学视频。
|
||||
|
||||
prompt.append("请根据以下视频内容描述,生成一个完整的Python脚本,该脚本能够直接生成高质量的教学视频。\n\n");
|
||||
## 视频内容描述:
|
||||
%s
|
||||
|
||||
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");
|
||||
## 核心要求:
|
||||
1. **视频类型**: 生成的是纯视觉教学视频,不需要包含任何字幕或音频
|
||||
2. **视频质量**: 必须是高质量的视觉呈现,包括清晰的图形、动画和视觉效果
|
||||
3. **教学重点**: 通过视觉元素(图表、动画、代码演示、视觉示例等)清晰地传达教学内容
|
||||
|
||||
prompt.append("## Python代码要求:\n");
|
||||
|
||||
prompt.append("### 1. 代码结构:\n");
|
||||
prompt.append("- 必须是完整的、可直接运行的独立脚本\n");
|
||||
prompt.append("- 包含所有必要的导入语句和依赖\n");
|
||||
prompt.append("- 包含主程序入口,可直接执行生成视频\n\n");
|
||||
## Python代码要求:
|
||||
|
||||
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");
|
||||
### 1. 代码结构:
|
||||
- 必须是完整的、可直接运行的独立脚本
|
||||
- 包含所有必要的导入语句和依赖
|
||||
- 包含主程序入口,可直接执行生成视频
|
||||
|
||||
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");
|
||||
### 2. 视觉生成要求:
|
||||
- **使用专业的视觉库**: 如Matplotlib, Plotly, Seaborn, MoviePy, OpenCV, PIL等
|
||||
- **高质量视觉效果**:
|
||||
- 清晰美观的图表和可视化
|
||||
- 流畅的动画和过渡效果
|
||||
- 合适的颜色搭配和视觉层次
|
||||
- 适当的文本标注(作为图形的一部分,而非字幕)
|
||||
- **视觉叙事**: 通过视觉序列清晰地展示概念演变或步骤流程
|
||||
|
||||
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");
|
||||
### 3. 代码质量:
|
||||
- **完整注释**:
|
||||
- 文件头部:说明视频主题、主要视觉元素和依赖
|
||||
- 函数/类:详细说明功能和参数
|
||||
- 关键步骤:解释视觉生成逻辑
|
||||
- **错误处理**: 包含适当的异常处理和边界情况
|
||||
- **模块化设计**: 将不同功能封装为函数或类
|
||||
|
||||
prompt.append("## 示例思考方向(根据具体内容调整):\n");
|
||||
prompt.append("- 如果是编程教学:生成代码执行过程的可视化动画\n");
|
||||
prompt.append("- 如果是数学教学:生成公式推导的逐步动画演示\n");
|
||||
prompt.append("- 如果是数据科学:生成数据处理流程的可视化\n");
|
||||
prompt.append("- 如果是算法教学:生成算法步骤的动态演示\n");
|
||||
prompt.append("- 如果是概念讲解:生成概念演变的分步图示\n");
|
||||
|
||||
return prompt.toString();
|
||||
### 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<Integer> gradeIds) {
|
||||
|
||||
@ -8,9 +8,9 @@ import com.seer.teach.teacher.module.entity.AiScenarioConfigEntity;
|
||||
* AI场景配置表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author System
|
||||
* @since 2025-10-27
|
||||
*/
|
||||
public interface IAiScenarioConfigService extends IService<AiScenarioConfigEntity> {
|
||||
|
||||
|
||||
AiScenarioConfigEntity getOneByScenarioCode(String scenarioCode);
|
||||
}
|
||||
@ -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<AiScenarioConfigMapper, AiScenarioConfigEntity> implements IAiScenarioConfigService {
|
||||
|
||||
private final IAiScenarioService aiScenarioService;
|
||||
|
||||
|
||||
@Override
|
||||
public AiScenarioConfigEntity getOneByScenarioCode(String scenarioCode) {
|
||||
LambdaQueryWrapper<AiScenarioEntity> scenarioWrapper = new LambdaQueryWrapper<>();
|
||||
scenarioWrapper.eq(AiScenarioEntity::getScenarioCode, scenarioCode);
|
||||
AiScenarioEntity aiScenario = aiScenarioService.getOne(scenarioWrapper);
|
||||
AssertUtils.notNull(aiScenario, ResultCodeEnum.SCENARIO_NOT_FOUND);
|
||||
LambdaQueryWrapper<AiScenarioConfigEntity> configWrapper = new LambdaQueryWrapper<>();
|
||||
configWrapper.eq(AiScenarioConfigEntity::getScenarioId, aiScenario.getId());
|
||||
configWrapper.eq(AiScenarioConfigEntity::getEnabled, CommonConstant.ENABLE);
|
||||
configWrapper.orderByAsc(AiScenarioConfigEntity::getPriority);
|
||||
return super.getOne(configWrapper);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user