feat: 完善多模型配置与运行切换
将模型连接统一持久化管理,并支持 Agent 运行中切换模型以及中断后选择模型继续。补充配置校验、事务与前后端交互测试。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package tech.easyflow.manuagent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -373,7 +374,7 @@ class DatabaseAndEventIntegrationTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证模型新增、保留旧密钥更新、默认分配及密钥解密读取。
|
||||
* 验证多个模型持久化、首模型自动默认、保留旧密钥更新以及默认模型切换。
|
||||
*
|
||||
* @throws Exception Mapper XML 初始化失败时抛出
|
||||
*/
|
||||
@@ -386,24 +387,19 @@ class DatabaseAndEventIntegrationTest {
|
||||
when(users.requireUserId("admin")).thenReturn(userId);
|
||||
AppProperties properties = new AppProperties(
|
||||
temporaryDirectory,
|
||||
temporaryDirectory.resolve("deepseek.key"),
|
||||
temporaryDirectory.resolve("dashscope.key"),
|
||||
"integration-master-key",
|
||||
"admin",
|
||||
"admin",
|
||||
"https://default.example.test",
|
||||
"default-model",
|
||||
131_072,
|
||||
"runtime:test",
|
||||
"bridge",
|
||||
Duration.ofMinutes(1));
|
||||
ModelService service = new ModelService(
|
||||
modelConfigMapper(),
|
||||
modelAssignmentMapper(),
|
||||
mock(AppUserMapper.class),
|
||||
agentRunMapper(),
|
||||
users,
|
||||
new KeyCipher(properties),
|
||||
properties,
|
||||
new ObjectMapper());
|
||||
Map<String, Object> capabilities = Map.of(
|
||||
"toolCalling", true, "reasoning", true, "contextWindow", 65_536);
|
||||
@@ -420,16 +416,61 @@ class DatabaseAndEventIntegrationTest {
|
||||
"测试模型更新", "https://model.example.test", "model-v2", "",
|
||||
Map.of("timeoutSeconds", 120), capabilities),
|
||||
() -> "admin");
|
||||
service.setDefault(created.id(), () -> "admin");
|
||||
ModelService.ModelSecret secret = service.defaultModelSecret();
|
||||
ModelService.ModelView second = service.save(
|
||||
null,
|
||||
new ModelService.ModelInput(
|
||||
"第二测试模型", "https://second-model.example.test", "model-second", "secret-5678",
|
||||
Map.of("timeoutSeconds", 90), capabilities),
|
||||
() -> "admin");
|
||||
|
||||
assertThat(created.defaultModel()).isTrue();
|
||||
assertThat(second.defaultModel()).isFalse();
|
||||
|
||||
service.setDefault(second.id(), () -> "admin");
|
||||
ModelService.ModelSecret firstSecret = service.requireRuntimeModel(created.id());
|
||||
ModelService.ModelSecret defaultSecret = service.defaultModelSecret();
|
||||
|
||||
assertThat(updated.name()).isEqualTo("测试模型更新");
|
||||
assertThat(updated.apiKeyHint()).endsWith("1234");
|
||||
assertThat(secret.apiKey()).isEqualTo("secret-1234");
|
||||
assertThat(secret.modelId()).isEqualTo("model-v2");
|
||||
assertThat(secret.contextWindow()).isEqualTo(65_536);
|
||||
assertThat(firstSecret.apiKey()).isEqualTo("secret-1234");
|
||||
assertThat(firstSecret.modelId()).isEqualTo("model-v2");
|
||||
assertThat(defaultSecret.apiKey()).isEqualTo("secret-5678");
|
||||
assertThat(defaultSecret.modelId()).isEqualTo("model-second");
|
||||
assertThat(defaultSecret.contextWindow()).isEqualTo(65_536);
|
||||
assertThat(jdbc().sql("SELECT COUNT(*) FROM app.model_assignment WHERE model_config_id = :id")
|
||||
.param("id", created.id()).query(Long.class).single()).isEqualTo(3L);
|
||||
.param("id", second.id()).query(Long.class).single()).isEqualTo(3L);
|
||||
|
||||
assertThatThrownBy(() -> service.setEnabled(second.id(), false))
|
||||
.isInstanceOfSatisfying(tech.easyflow.manuagent.common.ApiException.class, exception ->
|
||||
assertThat(exception.code()).isEqualTo("DEFAULT_MODEL_REQUIRED"));
|
||||
ModelService.ModelView disabled = service.setEnabled(created.id(), false);
|
||||
assertThat(disabled.enabled()).isFalse();
|
||||
|
||||
// 已完成 Run 仍属于历史审计事实;即使模型已经停用,也必须阻止真删除。
|
||||
UUID historyProjectId = UUID.randomUUID();
|
||||
UUID historyRunId = UUID.randomUUID();
|
||||
jdbc().sql("""
|
||||
INSERT INTO app.project(id, company_name, project_name, agui_thread_id, application_level, created_by)
|
||||
VALUES (:id, '模型历史企业', '模型历史测试', :threadId, 'ADVANCED', :userId)
|
||||
""").param("id", historyProjectId).param("threadId", "thread-" + historyProjectId)
|
||||
.param("userId", userId).update();
|
||||
jdbc().sql("""
|
||||
INSERT INTO app.agent_run(
|
||||
id, project_id, model_config_id, trigger_type, status, trace_id, ended_at)
|
||||
VALUES (:id, :projectId, :modelId, 'INITIAL', 'COMPLETED', :traceId, CURRENT_TIMESTAMP)
|
||||
""").param("id", historyRunId).param("projectId", historyProjectId)
|
||||
.param("modelId", created.id()).param("traceId", "trace-" + historyRunId).update();
|
||||
|
||||
assertThatThrownBy(() -> service.delete(created.id()))
|
||||
.isInstanceOfSatisfying(tech.easyflow.manuagent.common.ApiException.class, exception ->
|
||||
assertThat(exception.code()).isEqualTo("MODEL_HISTORY_EXISTS"));
|
||||
|
||||
// 清理本测试创建的引用后,再验证从未被历史 Run 使用的模型仍可按原有规则删除。
|
||||
jdbc().sql("DELETE FROM app.agent_run WHERE id = :id").param("id", historyRunId).update();
|
||||
jdbc().sql("DELETE FROM app.project WHERE id = :id").param("id", historyProjectId).update();
|
||||
service.delete(created.id());
|
||||
assertThat(jdbc().sql("SELECT COUNT(*) FROM app.model_config WHERE id = :id")
|
||||
.param("id", created.id()).query(Long.class).single()).isZero();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,14 +483,10 @@ class DatabaseAndEventIntegrationTest {
|
||||
SkillConfigMapper mapper = skillConfigMapper();
|
||||
AppProperties properties = new AppProperties(
|
||||
temporaryDirectory,
|
||||
temporaryDirectory.resolve("deepseek.key"),
|
||||
temporaryDirectory.resolve("dashscope.key"),
|
||||
"integration-master-key",
|
||||
"admin",
|
||||
"admin",
|
||||
"https://default.example.test",
|
||||
"default-model",
|
||||
131_072,
|
||||
"runtime:test",
|
||||
"bridge",
|
||||
Duration.ofMinutes(1));
|
||||
|
||||
Reference in New Issue
Block a user