发布 v1.10 #5
@@ -2,6 +2,7 @@
|
|||||||
package tech.easyflow.ai.entity;
|
package tech.easyflow.ai.entity;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.easyagents.core.model.chat.ChatMessageContentFormat;
|
||||||
import com.easyagents.core.model.chat.ChatModel;
|
import com.easyagents.core.model.chat.ChatModel;
|
||||||
import com.easyagents.core.model.embedding.EmbeddingModel;
|
import com.easyagents.core.model.embedding.EmbeddingModel;
|
||||||
import com.easyagents.core.model.rerank.RerankModel;
|
import com.easyagents.core.model.rerank.RerankModel;
|
||||||
@@ -27,6 +28,9 @@ import tech.easyflow.ai.entity.base.ModelBase;
|
|||||||
import tech.easyflow.common.util.StringUtil;
|
import tech.easyflow.common.util.StringUtil;
|
||||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实体类。
|
* 实体类。
|
||||||
*
|
*
|
||||||
@@ -118,6 +122,7 @@ public class Model extends ModelBase {
|
|||||||
ollamaChatConfig.setProvider(getModelProvider().getProviderName());
|
ollamaChatConfig.setProvider(getModelProvider().getProviderName());
|
||||||
ollamaChatConfig.setSupportImage(getSupportImage());
|
ollamaChatConfig.setSupportImage(getSupportImage());
|
||||||
ollamaChatConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
ollamaChatConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
||||||
|
ollamaChatConfig.setMessageContentFormat(resolveMessageContentFormat());
|
||||||
return new OllamaChatModel(ollamaChatConfig);
|
return new OllamaChatModel(ollamaChatConfig);
|
||||||
case "deepseek":
|
case "deepseek":
|
||||||
DeepseekConfig deepseekConfig = new DeepseekConfig();
|
DeepseekConfig deepseekConfig = new DeepseekConfig();
|
||||||
@@ -131,6 +136,7 @@ public class Model extends ModelBase {
|
|||||||
deepseekConfig.setNeedReasoningContentForToolMessage(Boolean.TRUE);
|
deepseekConfig.setNeedReasoningContentForToolMessage(Boolean.TRUE);
|
||||||
deepseekConfig.setSupportImage(getSupportImage());
|
deepseekConfig.setSupportImage(getSupportImage());
|
||||||
deepseekConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
deepseekConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
||||||
|
deepseekConfig.setMessageContentFormat(resolveMessageContentFormat());
|
||||||
if (getSupportTool() != null) {
|
if (getSupportTool() != null) {
|
||||||
deepseekConfig.setSupportToolMessage(getSupportTool());
|
deepseekConfig.setSupportToolMessage(getSupportTool());
|
||||||
}
|
}
|
||||||
@@ -144,6 +150,7 @@ public class Model extends ModelBase {
|
|||||||
openAIChatConfig.setRequestPath(checkAndGetRequestPath());
|
openAIChatConfig.setRequestPath(checkAndGetRequestPath());
|
||||||
openAIChatConfig.setSupportImage(getSupportImage());
|
openAIChatConfig.setSupportImage(getSupportImage());
|
||||||
openAIChatConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
openAIChatConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
||||||
|
openAIChatConfig.setMessageContentFormat(resolveMessageContentFormat());
|
||||||
if (getSupportTool() != null) {
|
if (getSupportTool() != null) {
|
||||||
openAIChatConfig.setSupportToolMessage(getSupportTool());
|
openAIChatConfig.setSupportToolMessage(getSupportTool());
|
||||||
}
|
}
|
||||||
@@ -151,6 +158,37 @@ public class Model extends ModelBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析模型高级配置中的 OpenAI-compatible 消息 content 格式。
|
||||||
|
* 新配置优先,旧 system 配置用于兼容历史数据。
|
||||||
|
*
|
||||||
|
* @return 消息 content 格式;缺失或非法时返回标准格式
|
||||||
|
*/
|
||||||
|
private ChatMessageContentFormat resolveMessageContentFormat() {
|
||||||
|
Map<String, Object> modelOptions = getOptions();
|
||||||
|
if (modelOptions == null || modelOptions.isEmpty()) {
|
||||||
|
return ChatMessageContentFormat.STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object rawFormat = modelOptions.get("agentMessageContentFormat");
|
||||||
|
if (rawFormat == null || String.valueOf(rawFormat).isBlank()) {
|
||||||
|
rawFormat = modelOptions.get("agentSystemContentFormat");
|
||||||
|
}
|
||||||
|
if (rawFormat == null || String.valueOf(rawFormat).isBlank()) {
|
||||||
|
return ChatMessageContentFormat.STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedFormat = String.valueOf(rawFormat).trim().toUpperCase(Locale.ROOT);
|
||||||
|
if ("STRING".equals(normalizedFormat)) {
|
||||||
|
return ChatMessageContentFormat.STANDARD;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return ChatMessageContentFormat.valueOf(normalizedFormat);
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
return ChatMessageContentFormat.STANDARD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public RerankModel toRerankModel() {
|
public RerankModel toRerankModel() {
|
||||||
switch (modelProvider.getProviderType().toLowerCase()) {
|
switch (modelProvider.getProviderType().toLowerCase()) {
|
||||||
case "gitee":
|
case "gitee":
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package tech.easyflow.ai.entity;
|
||||||
|
|
||||||
|
import com.easyagents.core.message.SystemMessage;
|
||||||
|
import com.easyagents.core.message.UserMessage;
|
||||||
|
import com.easyagents.core.model.chat.BaseChatModel;
|
||||||
|
import com.easyagents.core.model.chat.ChatConfig;
|
||||||
|
import com.easyagents.core.model.chat.ChatMessageContentFormat;
|
||||||
|
import com.easyagents.core.model.client.OpenAIChatMessageSerializer;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型高级配置向工作流聊天模型透传的消息格式测试。
|
||||||
|
*/
|
||||||
|
public class ModelMessageContentFormatTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证 OpenAI-compatible、DeepSeek 与 Ollama 模型都会接收内容块数组配置。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldApplyTextPartsFormatToOpenAiCompatibleModels() {
|
||||||
|
Map<String, Object> options = Map.of("agentMessageContentFormat", "TEXT_PARTS");
|
||||||
|
|
||||||
|
Assert.assertEquals(ChatMessageContentFormat.TEXT_PARTS,
|
||||||
|
chatConfig(model("custom", options)).getMessageContentFormat());
|
||||||
|
Assert.assertEquals(ChatMessageContentFormat.TEXT_PARTS,
|
||||||
|
chatConfig(model("deepseek", options)).getMessageContentFormat());
|
||||||
|
Assert.assertEquals(ChatMessageContentFormat.TEXT_PARTS,
|
||||||
|
chatConfig(model("ollama", options)).getMessageContentFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证旧 system 内容块配置会迁移到消息级内容块格式。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldMigrateLegacySystemTextPartsFormat() {
|
||||||
|
Model model = model("custom", Map.of("agentSystemContentFormat", "TEXT_PARTS"));
|
||||||
|
|
||||||
|
Assert.assertEquals(ChatMessageContentFormat.TEXT_PARTS,
|
||||||
|
chatConfig(model).getMessageContentFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证新旧配置并存时使用新的消息级配置。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldPreferCurrentMessageFormatSetting() {
|
||||||
|
Model model = model("custom", Map.of(
|
||||||
|
"agentMessageContentFormat", "STANDARD",
|
||||||
|
"agentSystemContentFormat", "TEXT_PARTS"));
|
||||||
|
|
||||||
|
Assert.assertEquals(ChatMessageContentFormat.STANDARD,
|
||||||
|
chatConfig(model).getMessageContentFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证非法配置会安全回退到标准格式。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldFallbackToStandardForUnknownFormat() {
|
||||||
|
Model model = model("custom", Map.of("agentMessageContentFormat", "PARTS"));
|
||||||
|
|
||||||
|
Assert.assertEquals(ChatMessageContentFormat.STANDARD,
|
||||||
|
chatConfig(model).getMessageContentFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证工作流选择内容块模式模型后,请求消息按数组格式序列化。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldSerializeWorkflowModelMessagesAsTextParts() {
|
||||||
|
ChatConfig config = chatConfig(model(
|
||||||
|
"custom", Map.of("agentMessageContentFormat", "TEXT_PARTS")));
|
||||||
|
|
||||||
|
List<Map<String, Object>> messages = new OpenAIChatMessageSerializer().serializeMessages(
|
||||||
|
List.of(SystemMessage.of("系统提示"), new UserMessage("用户问题")),
|
||||||
|
config);
|
||||||
|
|
||||||
|
assertTextPart(messages.get(0), "系统提示");
|
||||||
|
assertTextPart(messages.get(1), "用户问题");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定供应商和高级配置的聊天模型记录。
|
||||||
|
*
|
||||||
|
* @param providerType 供应商类型
|
||||||
|
* @param options 模型高级配置
|
||||||
|
* @return 模型记录
|
||||||
|
*/
|
||||||
|
private Model model(String providerType, Map<String, Object> options) {
|
||||||
|
ModelProvider provider = new ModelProvider();
|
||||||
|
provider.setProviderType(providerType);
|
||||||
|
provider.setProviderName(providerType);
|
||||||
|
|
||||||
|
Model model = new Model();
|
||||||
|
model.setModelProvider(provider);
|
||||||
|
model.setEndpoint("https://model.example.com");
|
||||||
|
model.setApiKey("sk-test");
|
||||||
|
model.setModelName("test-model");
|
||||||
|
model.setRequestPath("/v1/chat/completions");
|
||||||
|
model.setOptions(options);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模型生成的聊天配置。
|
||||||
|
*
|
||||||
|
* @param model 模型记录
|
||||||
|
* @return 聊天配置
|
||||||
|
*/
|
||||||
|
private ChatConfig chatConfig(Model model) {
|
||||||
|
return ((BaseChatModel<?>) model.toChatModel()).getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断言消息 content 只包含指定文本内容块。
|
||||||
|
*
|
||||||
|
* @param message 已序列化消息
|
||||||
|
* @param expectedText 预期文本
|
||||||
|
*/
|
||||||
|
private void assertTextPart(Map<String, Object> message, String expectedText) {
|
||||||
|
List<?> content = (List<?>) message.get("content");
|
||||||
|
Assert.assertEquals(1, content.size());
|
||||||
|
Map<?, ?> textPart = (Map<?, ?>) content.get(0);
|
||||||
|
Assert.assertEquals("text", textPart.get("type"));
|
||||||
|
Assert.assertEquals(expectedText, textPart.get("text"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user