feat: 增加 system 消息格式配置
- 模型管理支持选择字符串或内容块数组格式 - 验证连接携带 system 消息并复用正式运行时模型配置 - 补充配置映射与验证消息角色测试
This commit is contained in:
@@ -3,6 +3,7 @@ package tech.easyflow.agent.runtime;
|
|||||||
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
|
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
|
||||||
import com.easyagents.agent.runtime.model.AgentModelProviderType;
|
import com.easyagents.agent.runtime.model.AgentModelProviderType;
|
||||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||||
|
import com.easyagents.agent.runtime.model.AgentSystemContentFormat;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import tech.easyflow.ai.entity.Model;
|
import tech.easyflow.ai.entity.Model;
|
||||||
@@ -60,6 +61,7 @@ public final class AgentModelSpecMapper {
|
|||||||
spec.setSupportImage(Boolean.TRUE.equals(model.getSupportImage()));
|
spec.setSupportImage(Boolean.TRUE.equals(model.getSupportImage()));
|
||||||
spec.setSupportImageBase64Only(Boolean.TRUE.equals(model.getSupportImageB64Only()));
|
spec.setSupportImageBase64Only(Boolean.TRUE.equals(model.getSupportImageB64Only()));
|
||||||
spec.setHttpVersionPolicy(parseHttpVersionPolicy(model));
|
spec.setHttpVersionPolicy(parseHttpVersionPolicy(model));
|
||||||
|
spec.setSystemContentFormat(parseSystemContentFormat(model));
|
||||||
spec.getMetadata().put("modelId", model.getId());
|
spec.getMetadata().put("modelId", model.getId());
|
||||||
if (providerType != null && !providerType.isBlank()) {
|
if (providerType != null && !providerType.isBlank()) {
|
||||||
spec.getMetadata().put("sourceProviderType", providerType);
|
spec.getMetadata().put("sourceProviderType", providerType);
|
||||||
@@ -90,6 +92,29 @@ public final class AgentModelSpecMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析模型配置中的 Agent system content 格式。
|
||||||
|
*
|
||||||
|
* @param model 模型配置
|
||||||
|
* @return system content 格式,缺失或非法时返回 STRING
|
||||||
|
*/
|
||||||
|
private static AgentSystemContentFormat parseSystemContentFormat(Model model) {
|
||||||
|
Object rawFormat = model.getOptions() == null
|
||||||
|
? null
|
||||||
|
: model.getOptions().get("agentSystemContentFormat");
|
||||||
|
if (rawFormat == null || String.valueOf(rawFormat).isBlank()) {
|
||||||
|
return AgentSystemContentFormat.STRING;
|
||||||
|
}
|
||||||
|
String normalizedFormat = String.valueOf(rawFormat).trim().toUpperCase(Locale.ROOT);
|
||||||
|
try {
|
||||||
|
return AgentSystemContentFormat.valueOf(normalizedFormat);
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
LOG.warn("Invalid Agent system content format '{}' for model {}, fallback to STRING",
|
||||||
|
rawFormat, model.getId());
|
||||||
|
return AgentSystemContentFormat.STRING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 AgentScope 支持的模型供应商类型。
|
* 解析 AgentScope 支持的模型供应商类型。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ public class AgentScopeChatModelConnectivityVerifier implements ChatModelConnect
|
|||||||
/** 为未识别关闭思考扩展参数的推理模型保留足够的小额输出预算。 */
|
/** 为未识别关闭思考扩展参数的推理模型保留足够的小额输出预算。 */
|
||||||
private static final int MAX_TOKENS = 256;
|
private static final int MAX_TOKENS = 256;
|
||||||
private static final Duration DEFAULT_PHASE_TIMEOUT = Duration.ofSeconds(60);
|
private static final Duration DEFAULT_PHASE_TIMEOUT = Duration.ofSeconds(60);
|
||||||
|
private static final String VERIFICATION_SYSTEM_PROMPT =
|
||||||
|
"You are verifying model connectivity. Follow the user's instruction exactly.";
|
||||||
|
|
||||||
/** AgentScope 模型工厂。 */
|
/** AgentScope 模型工厂。 */
|
||||||
private final AgentModelFactory<io.agentscope.core.model.Model> modelFactory;
|
private final AgentModelFactory<io.agentscope.core.model.Model> modelFactory;
|
||||||
@@ -128,7 +130,11 @@ public class AgentScopeChatModelConnectivityVerifier implements ChatModelConnect
|
|||||||
generationOptions.setMaxTokens(MAX_TOKENS);
|
generationOptions.setMaxTokens(MAX_TOKENS);
|
||||||
io.agentscope.core.model.Model agentScopeModel = modelFactory.create(modelSpec, generationOptions);
|
io.agentscope.core.model.Model agentScopeModel = modelFactory.create(modelSpec, generationOptions);
|
||||||
|
|
||||||
Msg message = messageAdapter.toMsg(verificationMessage);
|
List<Msg> messages = List.of(
|
||||||
|
messageAdapter.toMsg(AgentMessage.text(
|
||||||
|
AgentMessageRole.SYSTEM,
|
||||||
|
VERIFICATION_SYSTEM_PROMPT)),
|
||||||
|
messageAdapter.toMsg(verificationMessage));
|
||||||
GenerateOptions requestOptions = GenerateOptions.builder()
|
GenerateOptions requestOptions = GenerateOptions.builder()
|
||||||
.stream(stream)
|
.stream(stream)
|
||||||
.maxTokens(MAX_TOKENS)
|
.maxTokens(MAX_TOKENS)
|
||||||
@@ -138,7 +144,7 @@ public class AgentScopeChatModelConnectivityVerifier implements ChatModelConnect
|
|||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
List<ChatResponse> responses = agentScopeModel
|
List<ChatResponse> responses = agentScopeModel
|
||||||
.stream(List.of(message), List.of(), requestOptions)
|
.stream(messages, List.of(), requestOptions)
|
||||||
.timeout(phaseTimeout)
|
.timeout(phaseTimeout)
|
||||||
.collectList()
|
.collectList()
|
||||||
.block(phaseTimeout.plusSeconds(1));
|
.block(phaseTimeout.plusSeconds(1));
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
|
|||||||
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
|
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
|
||||||
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
|
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
|
||||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||||
|
import com.easyagents.agent.runtime.model.AgentSystemContentFormat;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import tech.easyflow.agent.entity.Agent;
|
import tech.easyflow.agent.entity.Agent;
|
||||||
@@ -66,6 +67,36 @@ public class AgentRuntimeCompilerModelConfigTest {
|
|||||||
Assert.assertEquals(AgentHttpVersionPolicy.AUTO, spec.getHttpVersionPolicy());
|
Assert.assertEquals(AgentHttpVersionPolicy.AUTO, spec.getHttpVersionPolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证模型 options 中的 system content 格式会编译到中立模型声明。
|
||||||
|
*
|
||||||
|
* @throws Exception 反射调用失败时抛出
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void modelSystemContentFormatShouldCompileFromOptions() 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证未知 system content 格式安全回退到字符串。
|
||||||
|
*
|
||||||
|
* @throws Exception 反射调用失败时抛出
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void unknownSystemContentFormatShouldFallbackToString() throws Exception {
|
||||||
|
Model model = model(Map.of("agentSystemContentFormat", "PARTS"));
|
||||||
|
AgentRuntimeCompiler compiler = compiler(model);
|
||||||
|
|
||||||
|
AgentModelSpec spec = invokeModelSpec(compiler);
|
||||||
|
|
||||||
|
Assert.assertEquals(AgentSystemContentFormat.STRING, spec.getSystemContentFormat());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证 EasyFlow 忽略新旧消息数阈值,仅保留 Token 压缩配置。
|
* 验证 EasyFlow 忽略新旧消息数阈值,仅保留 Token 压缩配置。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.easyagents.agent.runtime.model.AgentModelSpec;
|
|||||||
import io.agentscope.core.message.Base64Source;
|
import io.agentscope.core.message.Base64Source;
|
||||||
import io.agentscope.core.message.ImageBlock;
|
import io.agentscope.core.message.ImageBlock;
|
||||||
import io.agentscope.core.message.Msg;
|
import io.agentscope.core.message.Msg;
|
||||||
|
import io.agentscope.core.message.MsgRole;
|
||||||
import io.agentscope.core.message.TextBlock;
|
import io.agentscope.core.message.TextBlock;
|
||||||
import io.agentscope.core.model.ChatResponse;
|
import io.agentscope.core.model.ChatResponse;
|
||||||
import io.agentscope.core.model.GenerateOptions;
|
import io.agentscope.core.model.GenerateOptions;
|
||||||
@@ -48,6 +49,7 @@ public class AgentScopeChatModelConnectivityVerifierTest {
|
|||||||
Assert.assertEquals("HTTP_1_1", result.getEffectiveHttpVersion());
|
Assert.assertEquals("HTTP_1_1", result.getEffectiveHttpVersion());
|
||||||
Assert.assertEquals(List.of(false, true), factory.getFactoryStreams());
|
Assert.assertEquals(List.of(false, true), factory.getFactoryStreams());
|
||||||
Assert.assertEquals(List.of(false, true), factory.getRequestStreams());
|
Assert.assertEquals(List.of(false, true), factory.getRequestStreams());
|
||||||
|
Assert.assertEquals(List.of(MsgRole.SYSTEM, MsgRole.USER), factory.getMessageRoles().get(0));
|
||||||
Assert.assertEquals(List.of(false, false), factory.getEnableThinkingValues());
|
Assert.assertEquals(List.of(false, false), factory.getEnableThinkingValues());
|
||||||
Assert.assertEquals(List.of(false, false), factory.getChatTemplateThinkingValues());
|
Assert.assertEquals(List.of(false, false), factory.getChatTemplateThinkingValues());
|
||||||
}
|
}
|
||||||
@@ -190,6 +192,8 @@ public class AgentScopeChatModelConnectivityVerifierTest {
|
|||||||
private final List<Boolean> requestStreams = new ArrayList<>();
|
private final List<Boolean> requestStreams = new ArrayList<>();
|
||||||
/** 模型请求收到的消息。 */
|
/** 模型请求收到的消息。 */
|
||||||
private final List<Msg> messages = new ArrayList<>();
|
private final List<Msg> messages = new ArrayList<>();
|
||||||
|
/** 各阶段请求消息的角色顺序。 */
|
||||||
|
private final List<List<MsgRole>> messageRoles = new ArrayList<>();
|
||||||
/** OpenAI-compatible 请求中的思考开关。 */
|
/** OpenAI-compatible 请求中的思考开关。 */
|
||||||
private final List<Object> enableThinkingValues = new ArrayList<>();
|
private final List<Object> enableThinkingValues = new ArrayList<>();
|
||||||
/** GPUStack 模板参数中的思考开关。 */
|
/** GPUStack 模板参数中的思考开关。 */
|
||||||
@@ -241,7 +245,8 @@ public class AgentScopeChatModelConnectivityVerifierTest {
|
|||||||
List<ToolSchema> tools,
|
List<ToolSchema> tools,
|
||||||
GenerateOptions options) {
|
GenerateOptions options) {
|
||||||
requestStreams.add(Boolean.TRUE.equals(options.getStream()));
|
requestStreams.add(Boolean.TRUE.equals(options.getStream()));
|
||||||
messages.add(inputMessages.get(0));
|
messages.add(inputMessages.get(inputMessages.size() - 1));
|
||||||
|
messageRoles.add(inputMessages.stream().map(Msg::getRole).toList());
|
||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +289,15 @@ public class AgentScopeChatModelConnectivityVerifierTest {
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取各阶段请求消息的角色顺序。
|
||||||
|
*
|
||||||
|
* @return 消息角色顺序
|
||||||
|
*/
|
||||||
|
private List<List<MsgRole>> getMessageRoles() {
|
||||||
|
return messageRoles;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 OpenAI-compatible 请求中的思考开关。
|
* 获取 OpenAI-compatible 请求中的思考开关。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ import {
|
|||||||
} from '#/views/ai/model/modelUtils/model-ability-utils';
|
} from '#/views/ai/model/modelUtils/model-ability-utils';
|
||||||
|
|
||||||
type AgentHttpVersionPolicy = 'AUTO' | 'HTTP_1_1' | 'HTTP_2_PREFERRED';
|
type AgentHttpVersionPolicy = 'AUTO' | 'HTTP_1_1' | 'HTTP_2_PREFERRED';
|
||||||
|
type AgentSystemContentFormat = 'STRING' | 'TEXT_PARTS';
|
||||||
|
|
||||||
interface ModelOptions {
|
interface ModelOptions {
|
||||||
agentHttpVersionPolicy: AgentHttpVersionPolicy;
|
agentHttpVersionPolicy: AgentHttpVersionPolicy;
|
||||||
|
agentSystemContentFormat: AgentSystemContentFormat;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +104,7 @@ const formData = reactive<FormData>({
|
|||||||
supportToolMessage: true,
|
supportToolMessage: true,
|
||||||
options: {
|
options: {
|
||||||
agentHttpVersionPolicy: 'AUTO',
|
agentHttpVersionPolicy: 'AUTO',
|
||||||
|
agentSystemContentFormat: 'STRING',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,6 +114,11 @@ const agentHttpVersionOptions = [
|
|||||||
{ label: 'HTTP/2 优先', value: 'HTTP_2_PREFERRED' },
|
{ label: 'HTTP/2 优先', value: 'HTTP_2_PREFERRED' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const agentSystemContentFormatOptions = [
|
||||||
|
{ label: '字符串(默认)', value: 'STRING' },
|
||||||
|
{ label: '内容块数组', value: 'TEXT_PARTS' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
const normalizeAgentHttpVersionPolicy = (
|
const normalizeAgentHttpVersionPolicy = (
|
||||||
value?: unknown,
|
value?: unknown,
|
||||||
): AgentHttpVersionPolicy => {
|
): AgentHttpVersionPolicy => {
|
||||||
@@ -120,6 +128,12 @@ const normalizeAgentHttpVersionPolicy = (
|
|||||||
return 'AUTO';
|
return 'AUTO';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeAgentSystemContentFormat = (
|
||||||
|
value?: unknown,
|
||||||
|
): AgentSystemContentFormat => {
|
||||||
|
return value === 'TEXT_PARTS' ? 'TEXT_PARTS' : 'STRING';
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeModelOptions = (options?: unknown): ModelOptions => {
|
const normalizeModelOptions = (options?: unknown): ModelOptions => {
|
||||||
const source =
|
const source =
|
||||||
options && typeof options === 'object' && !Array.isArray(options)
|
options && typeof options === 'object' && !Array.isArray(options)
|
||||||
@@ -130,6 +144,9 @@ const normalizeModelOptions = (options?: unknown): ModelOptions => {
|
|||||||
agentHttpVersionPolicy: normalizeAgentHttpVersionPolicy(
|
agentHttpVersionPolicy: normalizeAgentHttpVersionPolicy(
|
||||||
source.agentHttpVersionPolicy,
|
source.agentHttpVersionPolicy,
|
||||||
),
|
),
|
||||||
|
agentSystemContentFormat: normalizeAgentSystemContentFormat(
|
||||||
|
source.agentSystemContentFormat,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,6 +465,19 @@ const save = async () => {
|
|||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
<ElFormItem v-if="!hasSpecialModelType" label="System 消息格式">
|
||||||
|
<ElSelect
|
||||||
|
v-model="formData.options.agentSystemContentFormat"
|
||||||
|
aria-label="System 消息格式"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="item in agentSystemContentFormatOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
</div>
|
</div>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user