Merge remote-tracking branch 'origin/master' into dev-chenjiajian
This commit is contained in:
commit
92ad9f88a0
@ -15,7 +15,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@EnableFeignClients(basePackages = "com.seer.teach.*.api")
|
@EnableFeignClients(basePackages = "com.seer.teach.*.api")
|
||||||
@SpringBootApplication(scanBasePackages = "com.seer",exclude = QuartzAutoConfiguration.class)
|
@SpringBootApplication(scanBasePackages = "com.seer",exclude = {QuartzAutoConfiguration.class})
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
@EnableAspectJAutoProxy
|
@EnableAspectJAutoProxy
|
||||||
@EnableAsync
|
@EnableAsync
|
||||||
|
|||||||
@ -11,13 +11,8 @@ spring:
|
|||||||
main:
|
main:
|
||||||
allow-bean-definition-overriding: true
|
allow-bean-definition-overriding: true
|
||||||
allow-circular-references: true
|
allow-circular-references: true
|
||||||
mvc:
|
|
||||||
|
|
||||||
profiles:
|
profiles:
|
||||||
active: dev
|
active: dev
|
||||||
mvc:
|
|
||||||
pathmatch:
|
|
||||||
matching-strategy: ant_path_matcher
|
|
||||||
flyway:
|
flyway:
|
||||||
enabled: true
|
enabled: true
|
||||||
locations: classpath:db/mysql
|
locations: classpath:db/mysql
|
||||||
@ -39,6 +34,7 @@ spring:
|
|||||||
server-addr: 192.168.0.39:8848 # 配置中心地址
|
server-addr: 192.168.0.39:8848 # 配置中心地址
|
||||||
file-extension: yaml # 配置文件后缀(yaml/properties)
|
file-extension: yaml # 配置文件后缀(yaml/properties)
|
||||||
namespace: ${spring.profiles.active}
|
namespace: ${spring.profiles.active}
|
||||||
|
|
||||||
#日志
|
#日志
|
||||||
logging:
|
logging:
|
||||||
config: classpath:logback-${spring.profiles.active}.xml
|
config: classpath:logback-${spring.profiles.active}.xml
|
||||||
|
|||||||
@ -316,7 +316,10 @@ public enum ResultCodeEnum {
|
|||||||
|
|
||||||
// 富文本模板相关错误码
|
// 富文本模板相关错误码
|
||||||
RICH_TEXT_TEMPLATE_PREVIEW_FAILED(1100418, "预览失败"),
|
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 int code;
|
||||||
private String msg;
|
private String msg;
|
||||||
|
|||||||
@ -70,4 +70,9 @@ public interface CommonConstant {
|
|||||||
* 任务锁前缀
|
* 任务锁前缀
|
||||||
*/
|
*/
|
||||||
String TASK_CANCEL_PREFIX = "ai_task_cancel:";
|
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-openfeign.version>4.3.0</spring-cloud-openfeign.version>
|
||||||
<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.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.version>1.1.2</spring-ai.version>
|
||||||
<spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
|
<spring-ai-alibaba.version>1.1.0.0-RC2</spring-ai-alibaba.version>
|
||||||
|
|
||||||
|
|
||||||
<!-- 认证 -->
|
<!-- 认证 -->
|
||||||
@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<guava.version>33.5.0-jre</guava.version>
|
<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>
|
<springdoc-openapi.version>2.8.14</springdoc-openapi.version>
|
||||||
@ -186,6 +186,13 @@
|
|||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</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 -->
|
<!-- Lombok -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -646,6 +653,52 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud.ai</groupId>
|
||||||
|
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||||
|
<version>${spring-ai-alibaba.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud.ai</groupId>
|
||||||
|
<artifactId>spring-ai-alibaba-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-deepseek</artifactId>
|
||||||
|
<version>${spring-ai.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-openai</artifactId>
|
||||||
|
<version>${spring-ai.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</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>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@ -32,6 +32,45 @@ spring:
|
|||||||
server-addr: 192.168.0.39:8848 # 配置中心地址
|
server-addr: 192.168.0.39:8848 # 配置中心地址
|
||||||
file-extension: yaml # 配置文件后缀(yaml/properties)
|
file-extension: yaml # 配置文件后缀(yaml/properties)
|
||||||
namespace: ${spring.profiles.active}
|
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:
|
config:
|
||||||
import:
|
import:
|
||||||
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
|
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
|
|||||||
@ -78,12 +78,12 @@
|
|||||||
<logger name="org.springframework.data.redis" level="error"/>
|
<logger name="org.springframework.data.redis" level="error"/>
|
||||||
<logger name="com.alibaba.nacos.shaded.io.grpc" level="error"/>
|
<logger name="com.alibaba.nacos.shaded.io.grpc" level="error"/>
|
||||||
<logger name="com.alibaba.nacos.client.naming" level="WARN"/>
|
<logger name="com.alibaba.nacos.client.naming" level="WARN"/>
|
||||||
<logger name="org.springframework.cloud.gateway.route" level="error"/>
|
<logger name="org.springframework.cloud.gateway.route" level="debug"/>
|
||||||
<logger name="org.springframework.core.env" level="error"/>
|
<logger name="org.springframework.core.env" level="error"/>
|
||||||
<logger name="java.util.concurrent" level="error"/>
|
<logger name="java.util.concurrent" level="error"/>
|
||||||
|
|
||||||
<!--将整个项目的日志设置为debug级别-->
|
<!--将整个项目的日志设置为debug级别-->
|
||||||
<root level="info">
|
<root level="debug">
|
||||||
<appender-ref ref="FILE_ERROR"/>
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
<appender-ref ref="FILE_WARN"/>
|
<appender-ref ref="FILE_WARN"/>
|
||||||
<appender-ref ref="FILE_INFO"/>
|
<appender-ref ref="FILE_INFO"/>
|
||||||
|
|||||||
@ -19,105 +19,6 @@
|
|||||||
<module>seer-teacher-service-bootstrap</module>
|
<module>seer-teacher-service-bootstrap</module>
|
||||||
<module>seer-teacher-api</module>
|
<module>seer-teacher-api</module>
|
||||||
<module>seer-teacher-service-admin</module>
|
<module>seer-teacher-service-admin</module>
|
||||||
|
<module>seer-teacher-ai</module>
|
||||||
</modules>
|
</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>
|
</project>
|
||||||
56
seer-teacher/seer-teacher-ai/pom.xml
Normal file
56
seer-teacher/seer-teacher-ai/pom.xml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?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-dashscope</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-openai</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-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,45 @@
|
|||||||
|
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.beans.factory.annotation.Qualifier;
|
||||||
|
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)
|
||||||
|
@Qualifier("aiRestClient")
|
||||||
|
protected RestClient.Builder restClientBuilder;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
@Qualifier("aiWebClient")
|
||||||
|
protected WebClient.Builder webClientBuilder;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected RetryTemplate retryTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected ToolCallingManager toolCallingManager;
|
||||||
|
|
||||||
|
protected ObservationRegistry observationRegistry = ObservationRegistry.create();
|
||||||
|
|
||||||
|
protected final DefaultToolExecutionEligibilityPredicate toolExecutionEligibilityPredicate = new DefaultToolExecutionEligibilityPredicate();
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package com.seer.teach.ai.client.model;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文本转语音模型(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,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,177 @@
|
|||||||
|
package com.seer.teach.ai.client.model.dashscope;
|
||||||
|
|
||||||
|
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.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.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.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 {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPlatformName() {
|
||||||
|
return "aliyun_bailian";
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<TextToSpeechModel> createTextToSpeechModel(ModelConfig modelConfig) {
|
||||||
|
try {
|
||||||
|
DashScopeAudioSpeechApi audioSpeechApi = new DashScopeAudioSpeechApi(modelConfig.getApiKey(), modelConfig.getUrl());
|
||||||
|
|
||||||
|
DashScopeAudioSpeechOptions options = DashScopeAudioSpeechOptions.builder()
|
||||||
|
.model(modelConfig.getModel())
|
||||||
|
.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 {
|
||||||
|
DashScopeApi dashScopeApi = createDashScopeApi(modelConfig.getApiKey(), modelConfig.getUrl());
|
||||||
|
|
||||||
|
DashScopeEmbeddingModel embeddingModel = new DashScopeEmbeddingModel(dashScopeApi);
|
||||||
|
|
||||||
|
return Optional.of(embeddingModel);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建DashScope嵌入模型失败", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建DashScope API实例
|
||||||
|
*/
|
||||||
|
private DashScopeApi createDashScopeApi(String apiKey, String completionsPath) {
|
||||||
|
DashScopeApi.Builder builder = DashScopeApi.builder()
|
||||||
|
.apiKey(apiKey)
|
||||||
|
.baseUrl("https://dashscope.aliyuncs.com");
|
||||||
|
// 如果有可用的客户端构建器,添加它们
|
||||||
|
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,157 @@
|
|||||||
|
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.embedding.EmbeddingModel;
|
||||||
|
import org.springframework.ai.image.ImageModel;
|
||||||
|
import org.springframework.ai.model.SimpleApiKey;
|
||||||
|
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();
|
||||||
|
|
||||||
|
TextToSpeechModel textToSpeechModel = new OpenAiAudioSpeechModel(audioApi);
|
||||||
|
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());
|
||||||
|
EmbeddingModel embeddingModel = new OpenAiEmbeddingModel(openAiApi);
|
||||||
|
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))
|
||||||
|
.restClientBuilder(restClientBuilder)
|
||||||
|
.webClientBuilder(webClientBuilder).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
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 prompt){
|
||||||
|
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));
|
||||||
|
return chatModel.call(new Prompt(prompt.toString(), options.orElse(null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
<artifactId>spring-webflux</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webmvc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@ -38,6 +38,12 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>${project.groupId}</groupId>
|
||||||
|
<artifactId>seer-teacher-ai</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
|
|||||||
@ -2,11 +2,14 @@ package com.seer.teach.admin.service.impl;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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.constants.CommonConstant;
|
||||||
import com.seer.teach.common.entity.BaseEntity;
|
import com.seer.teach.common.entity.BaseEntity;
|
||||||
import com.seer.teach.teacher.service.AiModelCallService;
|
import com.seer.teach.teacher.service.AiModelCallService;
|
||||||
import com.seer.teach.teacher.service.platform.LlmResponse;
|
import com.seer.teach.teacher.service.platform.LlmResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
@ -16,6 +19,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,6 +45,9 @@ public abstract class AbstractGenerationService<T, R, E extends BaseEntity> {
|
|||||||
@Autowired
|
@Autowired
|
||||||
protected TransactionTemplate transactionTemplate;
|
protected TransactionTemplate transactionTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected AiChatModelService chatModelService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查当前线程是否被中断
|
* 检查当前线程是否被中断
|
||||||
*
|
*
|
||||||
@ -78,6 +85,10 @@ public abstract class AbstractGenerationService<T, R, E extends BaseEntity> {
|
|||||||
*/
|
*/
|
||||||
protected LlmResponse callLLM(String scenarioCode, String prompt, Map<String, Object> params, Integer userId) {
|
protected LlmResponse callLLM(String scenarioCode, String prompt, Map<String, Object> params, Integer userId) {
|
||||||
log.info("调用大模型,场景编码: {}, 提示词长度: {}", scenarioCode, prompt.length());
|
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);
|
return aiModelCallService.callModel(scenarioCode, prompt, params, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,41 +31,38 @@ public class PromptBuilder {
|
|||||||
public static String buildPromptForQuestionGeneration(ExampleQuestionsEntity exampleQuestion,
|
public static String buildPromptForQuestionGeneration(ExampleQuestionsEntity exampleQuestion,
|
||||||
KnowledgePointEntity knowledgePoint,
|
KnowledgePointEntity knowledgePoint,
|
||||||
Integer count) {
|
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");
|
知识点:%s
|
||||||
prompt.append("知识点总结:").append(knowledgePoint.getSummary()).append("\n\n");
|
知识点总结:%s
|
||||||
prompt.append("要求:\n");
|
|
||||||
prompt.append("1. 题目必须基于相同的教学知识点\n");
|
要求:
|
||||||
prompt.append("2. 题目难度应与示例题目相当\n");
|
1. 题目必须基于相同的教学知识点
|
||||||
prompt.append("3. 题型必须与示例题目一致\n");
|
2. 题目难度应与示例题目相当
|
||||||
prompt.append("4. 答案必须明确且唯一\n");
|
3. 题型必须与示例题目一致
|
||||||
prompt.append("5. 提供详细的解题过程或解析\n");
|
4. 答案必须明确且唯一
|
||||||
prompt.append("6. 返回严格的JSON数组格式,每项包含以下字段:\n");
|
5. 提供详细的解题过程或解析
|
||||||
prompt.append(" - question: 题干内容\n");
|
6. 返回严格的JSON数组格式,每项包含以下字段:
|
||||||
prompt.append(" - answer: 答案内容\n");
|
- question: 题干内容
|
||||||
prompt.append(" - solution: 解析内容\n");
|
- answer: 答案内容
|
||||||
if (ExampleQuestionTypesEnum.CHOICE.getCode().equals(exampleQuestion.getType())) {
|
- solution: 解析内容
|
||||||
prompt.append(" - options: 选项数组(仅选择题需要,每个选项包含content和sort字段)\n\n");
|
%s
|
||||||
|
输出格式示例:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"question": "题目内容",
|
||||||
|
"answer": "答案内容",
|
||||||
|
"solution": "解析内容",
|
||||||
|
%s
|
||||||
}
|
}
|
||||||
prompt.append("输出格式示例:\n");
|
]
|
||||||
prompt.append("[\n");
|
""".formatted(count, knowledgePoint.getKnowledgePointName(), knowledgePoint.getSummary(), optionsPart,
|
||||||
prompt.append(" {\n");
|
ExampleQuestionTypesEnum.CHOICE.getCode().equals(exampleQuestion.getType()) ?
|
||||||
prompt.append(" \"question\": \"题目内容\",\n");
|
"\"options\": [\n {\"content\": \"选项A内容\", \"sort\": 1},\n {\"content\": \"选项B内容\", \"sort\": 2}\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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,104 +74,108 @@ public class PromptBuilder {
|
|||||||
* @return 提示词
|
* @return 提示词
|
||||||
*/
|
*/
|
||||||
public static String buildPromptForBankQuestionGeneration(KnowledgePointEntity knowledgePoints, Integer count, GenerateTaskReq req) {
|
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 isLowerElementaryMath = isLowerElementaryMath(req.getGradeIds(), req.getSubjectId());
|
||||||
|
|
||||||
// 判断是否是三、四年级的英语题目
|
// 判断是否是三、四年级的英语题目
|
||||||
boolean isMiddleElementaryEnglish = isMiddleElementaryEnglish(req.getGradeIds(), req.getSubjectId());
|
boolean isMiddleElementaryEnglish = isMiddleElementaryEnglish(req.getGradeIds(), req.getSubjectId());
|
||||||
|
|
||||||
if (isNeedLatex(req.getSubjectId())) {
|
String latexRequirement = isNeedLatex(req.getSubjectId()) ? " 10. 输出的题目内容,答案,解析,其中涉及到公式的,请使用Latex\n\n" : "";
|
||||||
prompt.append(" 10. 输出的题目内容,答案,解析,其中涉及到公式的,请使用Latex\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对三四年级英语题目添加特殊要求
|
String englishRequirement = isMiddleElementaryEnglish ? " 10. 这是三四年级的英语题目,请尽量使用中文出题,题目内容和选项中涉及的英语内容应适合该年龄段学生理解\n\n" : "";
|
||||||
if (isMiddleElementaryEnglish) {
|
|
||||||
prompt.append(" 10. 这是三四年级的英语题目,请尽量使用中文出题,题目内容和选项中涉及的英语内容应适合该年龄段学生理解\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt.append("输出格式示例:\n");
|
String lowerElementaryMathNote = isLowerElementaryMath ? "\n特别注意:这是一、二年级的题目,请尽量以图文并茂的形式展示题目内容和选项,使题目更适合低年级学生理解。\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");
|
String middleElementaryEnglishNote = isMiddleElementaryEnglish ? "\n特别注意:这是三、四年级的英语题目,请尽量使用中文出题,让题目更容易被学生理解。\n" : "";
|
||||||
prompt.append("]\n");
|
|
||||||
|
|
||||||
// 特殊要求:对于小学一、二年级的数学题目,强调以图片为主
|
String mathRequirements = "";
|
||||||
if (isLowerElementaryMath) {
|
|
||||||
prompt.append("\n特别注意:这是一、二年级的题目,请尽量以图文并茂的形式展示题目内容和选项,使题目更适合低年级学生理解。\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 特殊要求:对于三、四年级的英语题目,强调使用中文出题
|
|
||||||
if (isMiddleElementaryEnglish) {
|
|
||||||
prompt.append("\n特别注意:这是三、四年级的英语题目,请尽量使用中文出题,让题目更容易被学生理解。\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数学题目特殊要求:针对不同年级提供不同的解题步骤标准
|
|
||||||
if (req.getSubjectId() == 2) { // 数学学科
|
if (req.getSubjectId() == 2) { // 数学学科
|
||||||
prompt.append("\n数学题目解析(solution字段)规范要求:\n");
|
mathRequirements = "\n数学题目解析(solution字段)规范要求:\n";
|
||||||
if(Objects.nonNull(req.getGradeId())){
|
if(Objects.nonNull(req.getGradeId())){
|
||||||
// 初中三个年级的要求
|
// 初中三个年级的要求
|
||||||
if (req.getGradeId() == 7) { // 七年级(初一)
|
if (req.getGradeId() == 7) { // 七年级(初一)
|
||||||
prompt.append("七年级(初一)解析标准:\n");
|
mathRequirements += "七年级(初一)解析标准:\n";
|
||||||
prompt.append("- 格式要求:从算术思维向代数思维过渡,强调基本概念、运算规则和解题格式的建立\n");
|
mathRequirements += "- 格式要求:从算术思维向代数思维过渡,强调基本概念、运算规则和解题格式的建立\n";
|
||||||
prompt.append("- 解析步骤:读与标(阅读题目,标记关键数据) -> 联与析(联想相关数学概念) -> 算或解(执行计算或按步骤解方程) -> 验与答(初步检验答案,并规范作答)\n");
|
mathRequirements += "- 解析步骤:读与标(阅读题目,标记关键数据) -> 联与析(联想相关数学概念) -> 算或解(执行计算或按步骤解方程) -> 验与答(初步检验答案,并规范作答)\n";
|
||||||
prompt.append("- 示例题型:代数方程应用题,要求严格按照\"设->列->解->验->答\"的格式进行解答\n");
|
mathRequirements += "- 示例题型:代数方程应用题,要求严格按照\"设->列->解->验->答\"的格式进行解答\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.getGradeId() == 8) { // 八年级(初二)
|
if (req.getGradeId() == 8) { // 八年级(初二)
|
||||||
prompt.append("八年级(初二)解析标准:\n");
|
mathRequirements += "八年级(初二)解析标准:\n";
|
||||||
prompt.append("- 格式要求:逻辑推理能力要求提高,几何证明和函数概念引入,解题需展现严密的因果链条\n");
|
mathRequirements += "- 格式要求:逻辑推理能力要求提高,几何证明和函数概念引入,解题需展现严密的因果链条\n";
|
||||||
prompt.append("- 解析步骤:条件梳理(明确已知条件) -> 目标分析(明确待证明结论) -> 思路探寻(寻找定理、公式) -> 逻辑推演(清晰展示推理) -> 结论总结(给出最终结论)\n");
|
mathRequirements += "- 解析步骤:条件梳理(明确已知条件) -> 目标分析(明确待证明结论) -> 思路探寻(寻找定理、公式) -> 逻辑推演(清晰展示推理) -> 结论总结(给出最终结论)\n";
|
||||||
prompt.append("- 示例题型:几何证明题,要求严格按照\"已知->求证->证明\"的格式进行解答\n");
|
mathRequirements += "- 示例题型:几何证明题,要求严格按照\"已知->求证->证明\"的格式进行解答\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.getGradeId() == 9) { // 九年级(初三)
|
if (req.getGradeId() == 9) { // 九年级(初三)
|
||||||
prompt.append("九年级(初三)解析标准:\n");
|
mathRequirements += "九年级(初三)解析标准:\n";
|
||||||
prompt.append("- 格式要求:知识高度综合,强调数学思想方法(分类讨论、数形结合、转化与化归、方程思想、模型思想)的应用,解法灵活多样\n");
|
mathRequirements += "- 格式要求:知识高度综合,强调数学思想方法(分类讨论、数形结合、转化与化归、方程思想、模型思想)的应用,解法灵活多样\n";
|
||||||
prompt.append("- 解析步骤:审题与建模(识别问题本质) -> 思路选择与规划(选择合适解题路径) -> 执行与讨论(计算或推理,必要时分类讨论) -> 检验与反思(检验答案合理性)\n");
|
mathRequirements += "- 解析步骤:审题与建模(识别问题本质) -> 思路选择与规划(选择合适解题路径) -> 执行与讨论(计算或推理,必要时分类讨论) -> 检验与反思(检验答案合理性)\n";
|
||||||
prompt.append("- 示例题型:二次函数综合题,要求体现数学思想方法的灵活应用\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 提示词
|
* @return 提示词
|
||||||
*/
|
*/
|
||||||
public static String buildSplitPrompt(KnowledgePointEntity knowledgePoint, KnowSummaryEntity summary, KnowledgePointSplitReq req) {
|
public static String buildSplitPrompt(KnowledgePointEntity knowledgePoint, KnowSummaryEntity summary, KnowledgePointSplitReq req) {
|
||||||
StringBuilder prompt = new StringBuilder();
|
String specificNames = "";
|
||||||
|
|
||||||
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");
|
|
||||||
if (CollectionUtil.isNotEmpty(req.getKnowledgePointNames())) {
|
if (CollectionUtil.isNotEmpty(req.getKnowledgePointNames())) {
|
||||||
prompt.append("要求拆分为以下名称的知识点:").append("\n");
|
StringBuilder namesBuilder = new StringBuilder();
|
||||||
for (int i = 0; i < req.getKnowledgePointNames().size(); i++) {
|
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");
|
String requirements = CollectionUtil.isNotEmpty(req.getKnowledgePointNames()) ?
|
||||||
if (CollectionUtil.isNotEmpty(req.getKnowledgePointNames())) {
|
String.format("1. 将原始知识点拆分为上面指定的%d个名称的知识点,每个知识点需要包含以下信息:\n", req.getCount()) :
|
||||||
prompt.append("1. 将原始知识点拆分为上面指定的").append(req.getCount()).append("个名称的知识点,每个知识点需要包含以下信息:\n");
|
String.format("1. 将原始知识点拆分为%d个更具体、更细粒度的知识点,每个知识点需要包含以下信息:\n", req.getCount());
|
||||||
} else {
|
|
||||||
prompt.append("1. 将原始知识点拆分为").append(req.getCount()).append("个更具体、更细粒度的知识点,每个知识点需要包含以下信息:\n");
|
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": "解析内容"
|
||||||
}
|
}
|
||||||
prompt.append(" - knowledgePointName: 拆分后的知识点名称\n");
|
]
|
||||||
prompt.append(" - summary: 知识点的简要总结\n");
|
}
|
||||||
prompt.append(" - exampleQuestions: 该知识点的示例题目列表\n");
|
]
|
||||||
prompt.append("2. 每个知识点不能超出对应年纪和学生的理解范围\n");
|
""".formatted(
|
||||||
prompt.append("3. 知识点总结必须符合教学逻辑(能直接被教师用于教学视频脚本),应包括:①基本定义;②在学科体系中的地位与作用;③与前后知识的衔接关系;④学习意义与实际应用;⑤常见误区与误解。少于 300 字。\n");
|
req.getCount(),
|
||||||
prompt.append("4. exampleQuestions字段要求:\n");
|
knowledgePoint.getKnowledgePointName(),
|
||||||
prompt.append(" - 每个题目包含type(题型)、question(题目内容)、options(选项,仅选择题需要)、answer(答案)、explanation(解析)字段\n");
|
summary.getSummary(),
|
||||||
prompt.append(" - 题型包括:包含4种题型(1单选选择题 2多选选择题 4判断题 6填空题)\n");
|
getGradeNameList(req.getGradeIds()),
|
||||||
prompt.append(" - 每种题型1道题目\n\n");
|
getSubjectName(knowledgePoint.getSubjectId()),
|
||||||
|
specificNames,
|
||||||
prompt.append("5. 返回严格的JSON数组格式,示例如下:\n");
|
requirements
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -249,53 +260,59 @@ public class PromptBuilder {
|
|||||||
* @return 提示词
|
* @return 提示词
|
||||||
*/
|
*/
|
||||||
public static String buildCheckPrompt(KnowledgePointEntity knowledgePoint, KnowSummaryEntity summary, Collection<Integer> gradeIds) {
|
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");
|
1. 该知识点内容是否正确
|
||||||
prompt.append("2. 该知识点是否超出指定年级学生的学习范围(超纲)\n");
|
2. 该知识点是否超出指定年级学生的学习范围(超纲)
|
||||||
prompt.append("3. 如果超纲,请说明超纲原因,并给出适合的替换知识点名称和总结\n");
|
3. 如果超纲,请说明超纲原因,并给出适合的替换知识点名称和总结
|
||||||
prompt.append("4. 请提供该知识点的示例题目\n\n");
|
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");
|
1. 返回严格的JSON格式,包含以下字段:
|
||||||
prompt.append(" \"isCorrect\": true,\n");
|
- isCorrect: 知识点内容是否正确 (boolean)
|
||||||
prompt.append(" \"isOutOfScope\": false,\n");
|
- isOutOfScope: 是否超纲 (boolean)
|
||||||
prompt.append(" \"outOfScopeReason\": null,\n");
|
- outOfScopeReason: 超纲原因 (string, 如果没有超纲则为null)
|
||||||
prompt.append(" \"suggestedKnowledgePointName\": null,\n");
|
- suggestedKnowledgePointName: 建议的知识点名称 (string, 如果没有超纲则为null)
|
||||||
prompt.append(" \"suggestedSummary\": null,\n");
|
- suggestedSummary: 建议的知识点总结 (string, 如果没有超纲则为null)
|
||||||
prompt.append(" \"exampleQuestions\": [\n");
|
- exampleQuestions: 示例题目列表 (array)
|
||||||
prompt.append(" {\n");
|
2. exampleQuestions字段要求:
|
||||||
prompt.append(" \"type\": \"1\",\n");
|
- 每个题目包含type(题型)、question(题目内容)、options(选项,仅选择题需要)、answer(答案)、explanation(解析)字段
|
||||||
prompt.append(" \"question\": \"题目内容\",\n");
|
- 题型包括:包含4种题型(1单选选择题 2多选选择题 4判断题 6填空题)
|
||||||
prompt.append(" \"options\": [\"选项A\", \"选项B\", \"选项C\", \"选项D\"],\n");
|
- 每种题型1道题目
|
||||||
prompt.append(" \"answer\": \"答案\",\n");
|
|
||||||
prompt.append(" \"explanation\": \"解析内容\"\n");
|
|
||||||
prompt.append(" }\n");
|
|
||||||
prompt.append(" ]\n");
|
|
||||||
prompt.append("}\n");
|
|
||||||
|
|
||||||
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 提示词
|
* @return 提示词
|
||||||
*/
|
*/
|
||||||
public static String buildPromptForTeachTextGeneration(TeachGoalsEntity teachGoal, List<TeachMethodsEntity> methods, GenerateTaskReq req) {
|
public static String buildPromptForTeachTextGeneration(TeachGoalsEntity teachGoal, List<TeachMethodsEntity> methods, GenerateTaskReq req) {
|
||||||
StringBuilder prompt = new StringBuilder();
|
StringBuilder methodsBuilder = 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");
|
|
||||||
for (int i = 0; i < methods.size(); i++) {
|
for (int i = 0; i < methods.size(); i++) {
|
||||||
TeachMethodsEntity method = methods.get(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()) {
|
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");
|
|
||||||
|
|
||||||
// 添加要求
|
return """
|
||||||
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");
|
|
||||||
|
|
||||||
// 添加输出示例
|
教学目标: %s
|
||||||
prompt.append("示例输出格式:\n");
|
教学重点: %s
|
||||||
prompt.append("[\n");
|
%s%s
|
||||||
prompt.append(" {\n");
|
教学方法列表:
|
||||||
prompt.append(" \"sort\": 1,\n");
|
%s
|
||||||
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();
|
请根据以上教学目标、重点和教学方法生成详细的讲课稿,要求如下:
|
||||||
|
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) {
|
public static String buildPromptForTeachGoalGeneration(KnowledgePointEntity knowledgePoint, Integer count, GenerateTaskReq req) {
|
||||||
StringBuilder prompt = new StringBuilder();
|
return """
|
||||||
prompt.append("你是一位资深的'").append(getGradeTypeName(req.getGradeType())).append("'教学专家,请根据以下知识点生成").append(count).append("个教学目标(考点)。\n\n");
|
你是一位资深的'%s'教学专家,请根据以下知识点生成%d个教学目标(考点)。
|
||||||
|
|
||||||
// 添加知识点信息
|
知识点名称:%s
|
||||||
prompt.append("知识点名称:").append(knowledgePoint.getKnowledgePointName()).append("\n");
|
知识点总结:%s
|
||||||
prompt.append("知识点总结:").append(req.getSummary()).append("\n");
|
对应的年级:%s
|
||||||
prompt.append("对应的年级:").append(getGradeNameList(req.getGradeIds())).append("\n");
|
对应的学科:%s
|
||||||
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");
|
1. 教学目标必须基于"%s"这个知识点
|
||||||
prompt.append(" {\n");
|
2. 教学目标应适合对应年级的学生理解和掌握
|
||||||
prompt.append(" \"goal\": \"教学目标内容\",\n");
|
3. 每个教学目标应包含具体的学习成果描述
|
||||||
prompt.append(" \"point\": \"教学重点\",\n");
|
4. 提供对应的教学重点
|
||||||
prompt.append(" \"sort\": 1,\n");
|
5. 可以提供推荐的视频教学链接(可选)
|
||||||
prompt.append(" }\n");
|
6. 每个教学目标需要有一个排序编号
|
||||||
prompt.append("]\n");
|
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 提示词
|
* @return 提示词
|
||||||
*/
|
*/
|
||||||
public static String buildPromptForTeachTextVideoScriptGeneration(TeachTextsEntity text) {
|
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");
|
1. **视频类型**: 生成的是纯视觉教学视频,不需要包含任何字幕或音频
|
||||||
prompt.append("2. **视频质量**: 必须是高质量的视觉呈现,包括清晰的图形、动画和视觉效果\n");
|
2. **视频质量**: 必须是高质量的视觉呈现,包括清晰的图形、动画和视觉效果
|
||||||
prompt.append("3. **教学重点**: 通过视觉元素(图表、动画、代码演示、视觉示例等)清晰地传达教学内容\n\n");
|
3. **教学重点**: 通过视觉元素(图表、动画、代码演示、视觉示例等)清晰地传达教学内容
|
||||||
|
|
||||||
prompt.append("## Python代码要求:\n");
|
|
||||||
|
|
||||||
prompt.append("### 1. 代码结构:\n");
|
## Python代码要求:
|
||||||
prompt.append("- 必须是完整的、可直接运行的独立脚本\n");
|
|
||||||
prompt.append("- 包含所有必要的导入语句和依赖\n");
|
|
||||||
prompt.append("- 包含主程序入口,可直接执行生成视频\n\n");
|
|
||||||
|
|
||||||
prompt.append("### 2. 视觉生成要求:\n");
|
### 1. 代码结构:
|
||||||
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");
|
### 2. 视觉生成要求:
|
||||||
prompt.append("- 在文件开头通过注释明确列出所有第三方库\n");
|
- **使用专业的视觉库**: 如Matplotlib, Plotly, Seaborn, MoviePy, OpenCV, PIL等
|
||||||
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");
|
### 3. 代码质量:
|
||||||
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();
|
### 4. 依赖管理:
|
||||||
|
- 在文件开头通过注释明确列出所有第三方库
|
||||||
|
- 提供pip安装命令的注释
|
||||||
|
|
||||||
|
|
||||||
|
### 5. 特别注意事项:
|
||||||
|
- **不要生成音频处理代码**(如声音添加、音轨处理)
|
||||||
|
- **不要生成字幕相关代码**(如字幕叠加、文本时间轴)
|
||||||
|
- **专注于视觉表达**:通过图形、动画、图表等视觉元素传达信息
|
||||||
|
- **视频时长**:根据内容复杂度,生成合理时长的视频(通常在1-5分钟)
|
||||||
|
|
||||||
|
|
||||||
|
## 输出格式:
|
||||||
|
严格按以下JSON格式输出,不要包含任何解释或额外文本:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pythonScript": "完整的Python脚本代码,可直接运行生成高质量无字幕无音频的教学视频"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
## 示例思考方向(根据具体内容调整):
|
||||||
|
- 如果是编程教学:生成代码执行过程的可视化动画
|
||||||
|
- 如果是数学教学:生成公式推导的逐步动画演示
|
||||||
|
- 如果是数据科学:生成数据处理流程的可视化
|
||||||
|
- 如果是算法教学:生成算法步骤的动态演示
|
||||||
|
- 如果是概念讲解:生成概念演变的分步图示
|
||||||
|
""".formatted(text.getVideoContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNeedLatex(Integer subjectId) {
|
private static boolean isNeedLatex(Integer subjectId) {
|
||||||
@ -486,91 +515,57 @@ public class PromptBuilder {
|
|||||||
* @return 题型描述
|
* @return 题型描述
|
||||||
*/
|
*/
|
||||||
private static String getQuestionTypeText(Integer type) {
|
private static String getQuestionTypeText(Integer type) {
|
||||||
switch (type) {
|
return switch (type) {
|
||||||
case 1:
|
case 1 -> "选择题";
|
||||||
return "选择题";
|
case 2 -> "多选选择题";
|
||||||
case 2:
|
case 3 -> "问答题";
|
||||||
return "多选选择题";
|
case 4 -> "判断题";
|
||||||
case 3:
|
case 5 -> "计算题";
|
||||||
return "问答题";
|
case 6 -> "填空题";
|
||||||
case 4:
|
default -> "其他题型";
|
||||||
return "判断题";
|
};
|
||||||
case 5:
|
|
||||||
return "计算题";
|
|
||||||
case 6:
|
|
||||||
return "填空题";
|
|
||||||
default:
|
|
||||||
return "其他题型";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getGradeTypeName(Integer gradeType) {
|
private static String getGradeTypeName(Integer gradeType) {
|
||||||
switch (gradeType) {
|
return switch (gradeType) {
|
||||||
case 1:
|
case 1 -> "小学";
|
||||||
return "小学";
|
case 2 -> "初中";
|
||||||
case 2:
|
case 3 -> "高中";
|
||||||
return "初中";
|
default -> throw new RuntimeException("未知的学段类型");
|
||||||
case 3:
|
};
|
||||||
return "高中";
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("未知的学段类型");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getSubjectName(Integer subjectId) {
|
private static String getSubjectName(Integer subjectId) {
|
||||||
switch (subjectId) {
|
return switch (subjectId) {
|
||||||
case 1:
|
case 1 -> "语文";
|
||||||
return "语文";
|
case 2 -> "数学";
|
||||||
case 2:
|
case 3 -> "英语";
|
||||||
return "数学";
|
case 4 -> "品德与生活";
|
||||||
case 3:
|
case 5 -> "科学";
|
||||||
return "英语";
|
case 6 -> "历史";
|
||||||
case 4:
|
case 7 -> "地理";
|
||||||
return "品德与生活";
|
case 8 -> "生物学";
|
||||||
case 5:
|
case 9 -> "政治";
|
||||||
return "科学";
|
case 10 -> "物理";
|
||||||
case 6:
|
case 11 -> "化学";
|
||||||
return "历史";
|
default -> throw new RuntimeException("未知的学科类型");
|
||||||
case 7:
|
};
|
||||||
return "地理";
|
|
||||||
case 8:
|
|
||||||
return "生物学";
|
|
||||||
case 9:
|
|
||||||
return "政治";
|
|
||||||
case 10:
|
|
||||||
return "物理";
|
|
||||||
case 11:
|
|
||||||
return "化学";
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("未知的学科类型");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getGradeName(Integer gradeId) {
|
private static String getGradeName(Integer gradeId) {
|
||||||
switch (gradeId) {
|
return switch (gradeId) {
|
||||||
case 1:
|
case 1 -> "一年级";
|
||||||
return "一年级";
|
case 2 -> "二年级";
|
||||||
case 2:
|
case 3 -> "三年级";
|
||||||
return "二年级";
|
case 4 -> "四年级";
|
||||||
case 3:
|
case 5 -> "五年级";
|
||||||
return "三年级";
|
case 6 -> "六年级";
|
||||||
case 4:
|
case 7 -> "初一";
|
||||||
return "四年级";
|
case 8 -> "初二";
|
||||||
case 5:
|
case 9 -> "初三";
|
||||||
return "五年级";
|
case 10 -> "高中";
|
||||||
case 6:
|
default -> throw new RuntimeException("未知的学科类型");
|
||||||
return "六年级";
|
};
|
||||||
case 7:
|
|
||||||
return "初一";
|
|
||||||
case 8:
|
|
||||||
return "初二";
|
|
||||||
case 9:
|
|
||||||
return "初三";
|
|
||||||
case 10:
|
|
||||||
return "高中";
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("未知的学科类型");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getGradeNameList(Collection<Integer> gradeIds) {
|
private static String getGradeNameList(Collection<Integer> gradeIds) {
|
||||||
|
|||||||
@ -25,7 +25,6 @@ public class SeerTeacherApplicationBootStrap {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication app = new SpringApplication(SeerTeacherApplicationBootStrap.class);
|
SpringApplication app = new SpringApplication(SeerTeacherApplicationBootStrap.class);
|
||||||
Environment env = app.run(args).getEnvironment();
|
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 port = env.getProperty("server.port", "8087");
|
||||||
String contextPath = env.getProperty("server.servlet.context-path", "/");
|
String contextPath = env.getProperty("server.servlet.context-path", "/");
|
||||||
log.info("----------------------------------------------------------");
|
log.info("----------------------------------------------------------");
|
||||||
|
|||||||
@ -23,6 +23,8 @@ spring:
|
|||||||
- optional:nacos:shared-database.yaml
|
- optional:nacos:shared-database.yaml
|
||||||
- optional:nacos:shared-minio.yaml
|
- optional:nacos:shared-minio.yaml
|
||||||
- optional:nacos:shared-redis.yaml
|
- optional:nacos:shared-redis.yaml
|
||||||
|
- optional:nacos:shared-sa-token.yaml
|
||||||
|
- optional:nacos:shared-minio.yaml
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
discovery:
|
discovery:
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import com.seer.teach.teacher.module.entity.AiScenarioConfigEntity;
|
|||||||
* AI场景配置表 服务类
|
* AI场景配置表 服务类
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author System
|
|
||||||
* @since 2025-10-27
|
|
||||||
*/
|
*/
|
||||||
public interface IAiScenarioConfigService extends IService<AiScenarioConfigEntity> {
|
public interface IAiScenarioConfigService extends IService<AiScenarioConfigEntity> {
|
||||||
|
|
||||||
|
|
||||||
|
AiScenarioConfigEntity getOneByScenarioCode(String scenarioCode);
|
||||||
}
|
}
|
||||||
@ -1,9 +1,16 @@
|
|||||||
package com.seer.teach.teacher.service.impl;
|
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.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.AiScenarioConfigEntity;
|
||||||
|
import com.seer.teach.teacher.module.entity.AiScenarioEntity;
|
||||||
import com.seer.teach.teacher.module.mapper.AiScenarioConfigMapper;
|
import com.seer.teach.teacher.module.mapper.AiScenarioConfigMapper;
|
||||||
import com.seer.teach.teacher.service.IAiScenarioConfigService;
|
import com.seer.teach.teacher.service.IAiScenarioConfigService;
|
||||||
|
import com.seer.teach.teacher.service.IAiScenarioService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,7 +21,23 @@ import org.springframework.stereotype.Service;
|
|||||||
* @author System
|
* @author System
|
||||||
* @since 2025-10-27
|
* @since 2025-10-27
|
||||||
*/
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
@Service
|
@Service
|
||||||
public class AiScenarioConfigServiceImpl extends ServiceImpl<AiScenarioConfigMapper, AiScenarioConfigEntity> implements IAiScenarioConfigService {
|
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