feat: 增加智能体模型 HTTP 传输兼容策略
- 按模型地址自动选择 HTTP/1.1 或 HTTP/2 优先策略 - 复用并托管 AgentScope HTTP Transport 生命周期 - 补充协议解析、复用与 Provider 边界测试
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
|
||||
import io.agentscope.core.model.transport.HttpTransport;
|
||||
import io.agentscope.core.model.transport.HttpTransportConfig;
|
||||
import io.agentscope.core.model.transport.HttpTransportFactory;
|
||||
import io.agentscope.core.model.transport.HttpVersion;
|
||||
import io.agentscope.core.model.transport.JdkHttpTransport;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 按 HTTP 版本策略提供进程级共享的 AgentScope Transport。
|
||||
*/
|
||||
public final class AgentHttpTransportProvider {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AgentHttpTransportProvider.class);
|
||||
private static final AgentHttpTransportProvider SHARED = new AgentHttpTransportProvider();
|
||||
|
||||
private final Map<AgentHttpVersionPolicy, HttpTransport> transports = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 创建 Transport 提供器。
|
||||
*/
|
||||
private AgentHttpTransportProvider() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程级共享提供器。
|
||||
*
|
||||
* @return 共享提供器
|
||||
*/
|
||||
public static AgentHttpTransportProvider shared() {
|
||||
return SHARED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定策略与基础 URL 对应的共享 Transport。
|
||||
*
|
||||
* @param policy HTTP 版本策略
|
||||
* @param baseUrl 最终生效的模型基础 URL
|
||||
* @return 共享 Transport
|
||||
*/
|
||||
public HttpTransport getTransport(AgentHttpVersionPolicy policy, String baseUrl) {
|
||||
AgentHttpVersionPolicy effectivePolicy = resolveEffectivePolicy(policy, baseUrl);
|
||||
if (effectivePolicy == AgentHttpVersionPolicy.AUTO) {
|
||||
return HttpTransportFactory.getDefault();
|
||||
}
|
||||
return transports.computeIfAbsent(effectivePolicy, this::createTransport);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析请求实际使用的 HTTP 策略。
|
||||
*
|
||||
* <p>明文 HTTP 固定使用 HTTP/1.1,避免 JDK 客户端发起 h2c Upgrade;HTTPS
|
||||
* 保持 HTTP/2 优先并允许底层通过 ALPN 回退。显式策略始终覆盖 URL 判断。</p>
|
||||
*
|
||||
* @param policy 配置的 HTTP 版本策略
|
||||
* @param baseUrl 最终生效的模型基础 URL
|
||||
* @return 实际生效策略;无法识别 URL 时返回 AUTO
|
||||
*/
|
||||
public static AgentHttpVersionPolicy resolveEffectivePolicy(AgentHttpVersionPolicy policy, String baseUrl) {
|
||||
AgentHttpVersionPolicy safePolicy = policy == null ? AgentHttpVersionPolicy.AUTO : policy;
|
||||
if (safePolicy != AgentHttpVersionPolicy.AUTO) {
|
||||
return safePolicy;
|
||||
}
|
||||
if (baseUrl == null || baseUrl.isBlank()) {
|
||||
LOG.warn("Agent HTTP AUTO policy cannot infer protocol because base URL is missing; fallback to default transport");
|
||||
return AgentHttpVersionPolicy.AUTO;
|
||||
}
|
||||
try {
|
||||
String scheme = URI.create(baseUrl.trim()).getScheme();
|
||||
if (scheme == null) {
|
||||
return AgentHttpVersionPolicy.AUTO;
|
||||
}
|
||||
String normalizedScheme = scheme.toLowerCase(Locale.ROOT);
|
||||
if ("http".equals(normalizedScheme)) {
|
||||
return AgentHttpVersionPolicy.HTTP_1_1;
|
||||
}
|
||||
if ("https".equals(normalizedScheme)) {
|
||||
return AgentHttpVersionPolicy.HTTP_2_PREFERRED;
|
||||
}
|
||||
LOG.warn("Agent HTTP AUTO policy does not support URL scheme '{}'; fallback to default transport",
|
||||
normalizedScheme);
|
||||
return AgentHttpVersionPolicy.AUTO;
|
||||
} catch (IllegalArgumentException exception) {
|
||||
LOG.warn("Agent HTTP AUTO policy cannot parse base URL; fallback to default transport");
|
||||
return AgentHttpVersionPolicy.AUTO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并注册受 AgentScope 生命周期管理的 JDK Transport。
|
||||
*
|
||||
* @param policy HTTP 版本策略
|
||||
* @return 新 Transport
|
||||
*/
|
||||
private HttpTransport createTransport(AgentHttpVersionPolicy policy) {
|
||||
HttpVersion httpVersion = resolveHttpVersion(policy);
|
||||
HttpTransportConfig config = HttpTransportConfig.builder()
|
||||
.httpVersion(httpVersion)
|
||||
.build();
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.version(httpVersion.toJdkHttpVersion())
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
.connectTimeout(config.getConnectTimeout())
|
||||
.build();
|
||||
HttpTransport transport = new JdkHttpTransport(httpClient, config);
|
||||
// 注册后由 AgentScope JVM shutdown hook 统一关闭,避免每次 Agent 运行创建连接池。
|
||||
HttpTransportFactory.register(transport);
|
||||
return transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将中立策略映射为 AgentScope HTTP 版本。
|
||||
*
|
||||
* @param policy HTTP 版本策略
|
||||
* @return AgentScope HTTP 版本
|
||||
*/
|
||||
static HttpVersion resolveHttpVersion(AgentHttpVersionPolicy policy) {
|
||||
if (policy == AgentHttpVersionPolicy.HTTP_1_1) {
|
||||
return HttpVersion.HTTP_1_1;
|
||||
}
|
||||
return HttpVersion.HTTP_2;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRuntimeException;
|
||||
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
|
||||
import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
|
||||
import com.easyagents.agent.runtime.model.AgentModelFactory;
|
||||
import com.easyagents.agent.runtime.model.AgentModelProviderType;
|
||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||
@@ -24,6 +25,24 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
|
||||
private static final String ARK_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
|
||||
private static final String SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1";
|
||||
|
||||
private final AgentHttpTransportProvider httpTransportProvider;
|
||||
|
||||
/**
|
||||
* 使用进程级共享 Transport 提供器创建模型工厂。
|
||||
*/
|
||||
public AgentScopeModelFactory() {
|
||||
this(AgentHttpTransportProvider.shared());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定 Transport 提供器创建模型工厂。
|
||||
*
|
||||
* @param httpTransportProvider Transport 提供器
|
||||
*/
|
||||
AgentScopeModelFactory(AgentHttpTransportProvider httpTransportProvider) {
|
||||
this.httpTransportProvider = httpTransportProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model create(AgentModelSpec modelSpec, AgentGenerationOptions generationOptions) {
|
||||
if (modelSpec == null) {
|
||||
@@ -31,6 +50,7 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
|
||||
}
|
||||
GenerateOptions options = toGenerateOptions(modelSpec, generationOptions);
|
||||
AgentModelProviderType providerType = modelSpec.getProviderType();
|
||||
validateHttpTransportSupport(providerType, modelSpec.getHttpVersionPolicy());
|
||||
if (providerType == AgentModelProviderType.OLLAMA) {
|
||||
return buildOllama(modelSpec, options);
|
||||
}
|
||||
@@ -124,12 +144,14 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildOpenAiCompatible(AgentModelSpec modelSpec, GenerateOptions options, String defaultBaseUrl) {
|
||||
String baseUrl = resolveBaseUrl(modelSpec, defaultBaseUrl);
|
||||
OpenAIChatModel.Builder builder = OpenAIChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(resolveBaseUrl(modelSpec, defaultBaseUrl))
|
||||
.baseUrl(baseUrl)
|
||||
.endpointPath(modelSpec.getEndpointPath())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl))
|
||||
.generateOptions(options);
|
||||
return builder.build();
|
||||
}
|
||||
@@ -176,12 +198,14 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildDeepSeek(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
String baseUrl = resolveBaseUrl(modelSpec, DEEPSEEK_BASE_URL);
|
||||
OpenAIChatModel.Builder builder = OpenAIChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(resolveBaseUrl(modelSpec, DEEPSEEK_BASE_URL))
|
||||
.baseUrl(baseUrl)
|
||||
.endpointPath(modelSpec.getEndpointPath())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl))
|
||||
.formatter(new DeepSeekFormatter())
|
||||
.generateOptions(options);
|
||||
return builder.build();
|
||||
@@ -195,12 +219,14 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildGlm(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
String baseUrl = resolveBaseUrl(modelSpec, GLM_BASE_URL);
|
||||
OpenAIChatModel.Builder builder = OpenAIChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(resolveBaseUrl(modelSpec, GLM_BASE_URL))
|
||||
.baseUrl(baseUrl)
|
||||
.endpointPath(modelSpec.getEndpointPath())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl))
|
||||
.formatter(new GLMFormatter())
|
||||
.generateOptions(options);
|
||||
return builder.build();
|
||||
@@ -231,16 +257,39 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
|
||||
*/
|
||||
private Model buildDashScope(AgentModelSpec modelSpec, AgentGenerationOptions generationOptions, GenerateOptions options) {
|
||||
Boolean thinkingEnabled = generationOptions == null ? null : generationOptions.getThinkingEnabled();
|
||||
String baseUrl = modelSpec.getBaseUrl();
|
||||
return DashScopeChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(modelSpec.getBaseUrl())
|
||||
.baseUrl(baseUrl)
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.enableThinking(thinkingEnabled)
|
||||
.httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl))
|
||||
.defaultOptions(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验当前 Provider 是否支持显式 Agent HTTP Transport。
|
||||
*
|
||||
* @param providerType 模型供应商类型
|
||||
* @param policy HTTP 版本策略
|
||||
*/
|
||||
private void validateHttpTransportSupport(AgentModelProviderType providerType,
|
||||
AgentHttpVersionPolicy policy) {
|
||||
if (policy == null || policy == AgentHttpVersionPolicy.AUTO) {
|
||||
return;
|
||||
}
|
||||
if (providerType == AgentModelProviderType.ANTHROPIC
|
||||
|| providerType == AgentModelProviderType.GEMINI
|
||||
|| providerType == AgentModelProviderType.OLLAMA) {
|
||||
throw new AgentRuntimeException(
|
||||
"Agent HTTP transport policy " + policy
|
||||
+ " is not supported by provider " + providerType
|
||||
+ "; use AUTO for this provider.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先使用调用方传入的基础 URL,未传入时使用供应商默认地址。
|
||||
*
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.easyagents.agent.runtime.model;
|
||||
|
||||
/**
|
||||
* Agent 模型调用使用的 HTTP 版本策略。
|
||||
*/
|
||||
public enum AgentHttpVersionPolicy {
|
||||
|
||||
/** 按基础 URL 协议自动选择:HTTP 使用 1.1,HTTPS 优先使用 2。 */
|
||||
AUTO,
|
||||
|
||||
/** 强制使用 HTTP/1.1。 */
|
||||
HTTP_1_1,
|
||||
|
||||
/** 优先使用 HTTP/2,并允许 JDK 客户端按协议能力回退。 */
|
||||
HTTP_2_PREFERRED
|
||||
}
|
||||
@@ -13,6 +13,9 @@ public class AgentModelSpec {
|
||||
private String baseUrl;
|
||||
private String endpointPath;
|
||||
private String apiKey;
|
||||
private boolean supportImage;
|
||||
private boolean supportImageBase64Only;
|
||||
private AgentHttpVersionPolicy httpVersionPolicy = AgentHttpVersionPolicy.AUTO;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
@@ -105,6 +108,60 @@ public class AgentModelSpec {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型是否支持图片输入。
|
||||
*
|
||||
* @return 支持图片时返回 true
|
||||
*/
|
||||
public boolean isSupportImage() {
|
||||
return supportImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型是否支持图片输入。
|
||||
*
|
||||
* @param supportImage 是否支持图片
|
||||
*/
|
||||
public void setSupportImage(boolean supportImage) {
|
||||
this.supportImage = supportImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型是否只接受 Base64 图片。
|
||||
*
|
||||
* @return 仅接受 Base64 时返回 true
|
||||
*/
|
||||
public boolean isSupportImageBase64Only() {
|
||||
return supportImageBase64Only;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型是否只接受 Base64 图片。
|
||||
*
|
||||
* @param supportImageBase64Only 是否只接受 Base64 图片
|
||||
*/
|
||||
public void setSupportImageBase64Only(boolean supportImageBase64Only) {
|
||||
this.supportImageBase64Only = supportImageBase64Only;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Agent 模型调用的 HTTP 版本策略。
|
||||
*
|
||||
* @return HTTP 版本策略
|
||||
*/
|
||||
public AgentHttpVersionPolicy getHttpVersionPolicy() {
|
||||
return httpVersionPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Agent 模型调用的 HTTP 版本策略。
|
||||
*
|
||||
* @param httpVersionPolicy HTTP 版本策略
|
||||
*/
|
||||
public void setHttpVersionPolicy(AgentHttpVersionPolicy httpVersionPolicy) {
|
||||
this.httpVersionPolicy = httpVersionPolicy == null ? AgentHttpVersionPolicy.AUTO : httpVersionPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user