fix: 使用发布快照校验图片输入能力

- 正式聊天和模型编译统一读取发布快照中的图片能力

- 草稿态保留实时模型能力并为旧快照提供安全关闭策略
This commit is contained in:
2026-07-30 15:05:42 +08:00
parent 1b40829135
commit ff5f90121b
5 changed files with 340 additions and 7 deletions

View File

@@ -0,0 +1,167 @@
package tech.easyflow.agent.runtime;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.web.server.ResponseStatusException;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.service.ModelService;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
/**
* Agent 正式与草稿聊天图片能力边界测试。
*/
public class AgentRunServiceImageCapabilityTest {
/**
* 验证正式聊天允许快照已启用的图片能力,且不读取实时模型能力。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void formalChatShouldUseEnabledPublishedCapability() throws Exception {
ModelService modelService = Mockito.mock(ModelService.class);
AgentRunService service = service(modelService);
Agent agent = publishedAgent(true);
invokeAssertImageCapability(service, agent);
Mockito.verifyNoInteractions(modelService);
}
/**
* 验证正式聊天拒绝快照未启用的图片能力,且不读取实时模型能力。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void formalChatShouldRejectDisabledPublishedCapability() throws Exception {
ModelService modelService = Mockito.mock(ModelService.class);
AgentRunService service = service(modelService);
Agent agent = publishedAgent(false);
ResponseStatusException error = expectImageCapabilityError(service, agent);
Assert.assertEquals(400, error.getStatusCode().value());
Mockito.verifyNoInteractions(modelService);
}
/**
* 验证旧快照缺少图片能力字段时安全拒绝图片。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void formalChatShouldFailClosedForLegacySnapshot() throws Exception {
ModelService modelService = Mockito.mock(ModelService.class);
AgentRunService service = service(modelService);
Agent agent = new Agent();
agent.setModelId(BigInteger.TEN);
agent.setPublishedSnapshotJson(Map.of(
"modelSummary", Map.of("modelName", "legacy-model")));
ResponseStatusException error = expectImageCapabilityError(service, agent);
Assert.assertEquals(400, error.getStatusCode().value());
Mockito.verifyNoInteractions(modelService);
}
/**
* 验证草稿聊天继续读取实时模型图片能力。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void draftChatShouldUseLiveModelCapability() throws Exception {
Model model = new Model();
model.setSupportImage(true);
ModelService modelService = Mockito.mock(ModelService.class);
Mockito.when(modelService.getModelInstance(BigInteger.TEN)).thenReturn(model);
AgentRunService service = service(modelService);
Agent agent = new Agent();
agent.setModelId(BigInteger.TEN);
invokeAssertImageCapability(service, agent);
Mockito.verify(modelService).getModelInstance(BigInteger.TEN);
}
/**
* 创建已注入模型服务的运行服务。
*
* @param modelService 模型服务
* @return Agent 运行服务
* @throws Exception 字段注入失败时抛出
*/
private AgentRunService service(ModelService modelService) throws Exception {
AgentRunService service = new AgentRunService();
Field field = AgentRunService.class.getDeclaredField("modelService");
field.setAccessible(true);
field.set(service, modelService);
return service;
}
/**
* 创建携带图片能力的正式运行 Agent。
*
* @param supportImage 是否支持图片
* @return 正式运行 Agent
*/
private Agent publishedAgent(boolean supportImage) {
Agent agent = new Agent();
agent.setModelId(BigInteger.TEN);
agent.setPublishedSnapshotJson(Map.of(
"modelSummary", Map.of(
"supportImage", supportImage,
"supportImageB64Only", false)));
return agent;
}
/**
* 调用图片能力校验。
*
* @param service Agent 运行服务
* @param agent Agent 运行视图
* @throws Exception 反射调用失败或能力校验失败时抛出
*/
private void invokeAssertImageCapability(AgentRunService service,
Agent agent) throws Exception {
Method method = AgentRunService.class.getDeclaredMethod(
"assertImageCapability", Agent.class, List.class);
method.setAccessible(true);
try {
method.invoke(service, agent, List.of("image-upload-id"));
} catch (InvocationTargetException error) {
if (error.getCause() instanceof Exception cause) {
throw cause;
}
throw error;
}
}
/**
* 调用图片能力校验并返回预期的业务错误。
*
* @param service Agent 运行服务
* @param agent Agent 运行视图
* @return 图片能力错误
* @throws Exception 反射调用失败时抛出
*/
private ResponseStatusException expectImageCapabilityError(AgentRunService service,
Agent agent) throws Exception {
try {
invokeAssertImageCapability(service, agent);
Assert.fail("expected ResponseStatusException");
return null;
} catch (ResponseStatusException error) {
return error;
}
}
}

View File

@@ -144,6 +144,62 @@ public class AgentRuntimeCompilerModelConfigTest {
Assert.assertEquals(AgentMessageContentFormat.STANDARD, spec.getMessageContentFormat());
}
/**
* 验证正式运行的图片能力严格使用发布快照。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void publishedImageCapabilityShouldOverrideLiveModel() throws Exception {
Model model = model(Map.of());
model.setSupportImage(false);
model.setSupportImageB64Only(false);
Agent agent = agent(Map.of(
"modelSummary", Map.of(
"supportImage", true,
"supportImageB64Only", true)));
AgentModelSpec spec = invokeModelSpec(compiler(model), agent);
Assert.assertTrue(spec.isSupportImage());
Assert.assertTrue(spec.isSupportImageBase64Only());
}
/**
* 验证旧发布快照缺少能力字段时关闭图片能力。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void missingPublishedImageCapabilityShouldFailClosed() throws Exception {
Model model = model(Map.of());
model.setSupportImage(true);
model.setSupportImageB64Only(true);
Agent agent = agent(Map.of("modelSummary", Map.of("modelName", "legacy-model")));
AgentModelSpec spec = invokeModelSpec(compiler(model), agent);
Assert.assertFalse(spec.isSupportImage());
Assert.assertFalse(spec.isSupportImageBase64Only());
}
/**
* 验证草稿运行继续使用实时模型图片能力。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void draftImageCapabilityShouldUseLiveModel() throws Exception {
Model model = model(Map.of());
model.setSupportImage(true);
model.setSupportImageB64Only(false);
AgentModelSpec spec = invokeModelSpec(compiler(model));
Assert.assertTrue(spec.isSupportImage());
Assert.assertFalse(spec.isSupportImageBase64Only());
}
/**
* 验证 EasyFlow 忽略新旧消息数阈值,仅保留 Token 压缩配置。
*
@@ -230,13 +286,37 @@ public class AgentRuntimeCompilerModelConfigTest {
* @throws Exception 反射调用失败时抛出
*/
private AgentModelSpec invokeModelSpec(AgentRuntimeCompiler compiler) throws Exception {
Agent agent = new Agent();
agent.setModelId(BigInteger.TEN);
return invokeModelSpec(compiler, agent(Map.of()));
}
/**
* 调用私有模型声明编译方法。
*
* @param compiler 编译器
* @param agent Agent 运行视图
* @return 模型声明
* @throws Exception 反射调用失败时抛出
*/
private AgentModelSpec invokeModelSpec(AgentRuntimeCompiler compiler,
Agent agent) throws Exception {
Method method = AgentRuntimeCompiler.class.getDeclaredMethod("buildModelSpec", Agent.class);
method.setAccessible(true);
return (AgentModelSpec) method.invoke(compiler, agent);
}
/**
* 创建测试 Agent。
*
* @param publishedSnapshot 发布快照;空映射表示草稿态
* @return 测试 Agent
*/
private Agent agent(Map<String, Object> publishedSnapshot) {
Agent agent = new Agent();
agent.setModelId(BigInteger.TEN);
agent.setPublishedSnapshotJson(publishedSnapshot);
return agent;
}
/**
* 调用私有记忆策略编译方法。
*