fix: 扩展模型消息内容块数组配置

- 升级高级设置为消息级格式并兼容旧 system 配置

- 增加 VLM 图片首轮与文本追问的多轮连接验证
This commit is contained in:
2026-07-28 12:24:42 +08:00
parent dc35ddc3e4
commit f392c896f9
5 changed files with 227 additions and 45 deletions

View File

@@ -3,8 +3,8 @@ package tech.easyflow.agent.runtime;
import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
import com.easyagents.agent.runtime.model.AgentMessageContentFormat;
import com.easyagents.agent.runtime.model.AgentModelSpec;
import com.easyagents.agent.runtime.model.AgentSystemContentFormat;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.agent.entity.Agent;
@@ -68,33 +68,80 @@ public class AgentRuntimeCompilerModelConfigTest {
}
/**
* 验证模型 options 中的 system content 格式会编译到中立模型声明。
* 验证模型 options 中的消息 content 格式会编译到中立模型声明。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void modelSystemContentFormatShouldCompileFromOptions() throws Exception {
public void modelMessageContentFormatShouldCompileFromOptions() throws Exception {
Model model = model(Map.of("agentMessageContentFormat", "TEXT_PARTS"));
AgentRuntimeCompiler compiler = compiler(model);
AgentModelSpec spec = invokeModelSpec(compiler);
Assert.assertEquals(AgentMessageContentFormat.TEXT_PARTS, spec.getMessageContentFormat());
}
/**
* 验证旧 system content 数组配置会迁移为消息级数组格式。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void legacyTextPartsFormatShouldMigrateToMessageContentFormat() throws Exception {
Model model = model(Map.of("agentSystemContentFormat", "TEXT_PARTS"));
AgentRuntimeCompiler compiler = compiler(model);
AgentModelSpec spec = invokeModelSpec(compiler);
Assert.assertEquals(AgentSystemContentFormat.TEXT_PARTS, spec.getSystemContentFormat());
Assert.assertEquals(AgentMessageContentFormat.TEXT_PARTS, spec.getMessageContentFormat());
}
/**
* 验证未知 system content 格式安全回退到字符串
* 验证旧字符串配置会迁移为标准格式
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void unknownSystemContentFormatShouldFallbackToString() throws Exception {
Model model = model(Map.of("agentSystemContentFormat", "PARTS"));
public void legacyStringFormatShouldMigrateToStandard() throws Exception {
Model model = model(Map.of("agentSystemContentFormat", "STRING"));
AgentRuntimeCompiler compiler = compiler(model);
AgentModelSpec spec = invokeModelSpec(compiler);
Assert.assertEquals(AgentSystemContentFormat.STRING, spec.getSystemContentFormat());
Assert.assertEquals(AgentMessageContentFormat.STANDARD, spec.getMessageContentFormat());
}
/**
* 验证新旧配置并存时优先使用消息级配置。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void messageContentFormatShouldTakePrecedenceOverLegacyFormat() throws Exception {
Model model = model(Map.of(
"agentMessageContentFormat", "STANDARD",
"agentSystemContentFormat", "TEXT_PARTS"));
AgentRuntimeCompiler compiler = compiler(model);
AgentModelSpec spec = invokeModelSpec(compiler);
Assert.assertEquals(AgentMessageContentFormat.STANDARD, spec.getMessageContentFormat());
}
/**
* 验证未知消息 content 格式安全回退到标准格式。
*
* @throws Exception 反射调用失败时抛出
*/
@Test
public void unknownMessageContentFormatShouldFallbackToStandard() throws Exception {
Model model = model(Map.of("agentMessageContentFormat", "PARTS"));
AgentRuntimeCompiler compiler = compiler(model);
AgentModelSpec spec = invokeModelSpec(compiler);
Assert.assertEquals(AgentMessageContentFormat.STANDARD, spec.getMessageContentFormat());
}
/**

View File

@@ -88,7 +88,7 @@ public class AgentScopeChatModelConnectivityVerifierTest {
Flux.just(toolResponse(TEST_NONCE, null)));
ChatModelVerificationResult result = verifier(factory).verify(
model(false, "self-hosted"));
model(false, Map.of(), "self-hosted"));
Assert.assertEquals(ModelVerificationStatus.PASSED, result.getStatus());
Map<String, Object> bodyParams = factory.getFactoryAdditionalBodyParams().get(0);
@@ -173,6 +173,33 @@ public class AgentScopeChatModelConnectivityVerifierTest {
Assert.assertEquals((byte) 0x50, imageBytes[1]);
}
/**
* 验证严格内容块模式会先发送包含图片首轮和文本追问的完整上下文。
*/
@Test
public void shouldVerifyStrictMessageContentFormatWithVlmHistory() {
RecordingModelFactory factory = new RecordingModelFactory(
Flux.just(textResponse("你好")),
Flux.just(toolResponse(TEST_NONCE, VlmVerificationImage.VERIFICATION_CODE)));
ChatModelVerificationResult result = verifier(factory).verify(model(
true,
Map.of("agentMessageContentFormat", "TEXT_PARTS")));
Assert.assertEquals(ModelVerificationStatus.PASSED, result.getStatus());
Assert.assertEquals(List.of(0, 1), factory.getToolCounts());
List<Msg> history = factory.getMessageBatches().get(0);
Assert.assertEquals(List.of(
MsgRole.SYSTEM,
MsgRole.USER,
MsgRole.ASSISTANT,
MsgRole.USER),
history.stream().map(Msg::getRole).toList());
Assert.assertTrue(history.get(1).getContent().stream().anyMatch(ImageBlock.class::isInstance));
Assert.assertEquals("请直接回复“你好”,不要补充其他内容。",
history.get(3).getTextContent());
}
/**
* 创建待验证模型。
*
@@ -180,17 +207,31 @@ public class AgentScopeChatModelConnectivityVerifierTest {
* @return 测试模型
*/
private Model model(boolean supportImage) {
return model(supportImage, "gpustack");
return model(supportImage, Map.of());
}
/**
* 创建携带指定 options 的待验证模型。
*
* @param supportImage 是否支持图片
* @param options 模型扩展配置
* @return 测试模型
*/
private Model model(boolean supportImage, Map<String, Object> options) {
return model(supportImage, options, "gpustack");
}
/**
* 创建携带指定供应商类型的待验证模型。
*
* @param supportImage 是否支持图片
* @param options 模型扩展配置
* @param providerType 供应商类型
* @return 测试模型
*/
private Model model(boolean supportImage, String providerType) {
private Model model(boolean supportImage,
Map<String, Object> options,
String providerType) {
Model model = new Model();
model.setId(BigInteger.TEN);
model.setModelName("test-model");
@@ -198,6 +239,7 @@ public class AgentScopeChatModelConnectivityVerifierTest {
model.setRequestPath("/v1/chat/completions");
model.setApiKey("test-key");
model.setSupportImage(supportImage);
model.setOptions(options);
ModelProvider provider = new ModelProvider();
provider.setProviderType(providerType);
model.setModelProvider(provider);