Refactor: 重构 config,request,options 相关代码,引入 RequestBody

This commit is contained in:
LuanY77
2025-07-17 18:48:40 +08:00
parent cfa947a0d7
commit bcb07b908a
8 changed files with 405 additions and 47 deletions

View File

@@ -12,7 +12,7 @@ import java.util.Objects;
* @since TODO
*/
public class ModelConfig {
public class ModelConfig implements RequestBodyConvertible {
protected String apiUrl;
@@ -28,6 +28,8 @@ public class ModelConfig {
protected Map<String, String> headersConfig = new LinkedHashMap<>();
protected static final String MODEL_KEY = "model";
public ModelConfig() {
}
@@ -49,7 +51,7 @@ public class ModelConfig {
this.headersConfig = headersConfig;
}
protected ModelConfig(Builder builder) {
protected ModelConfig(Builder<?> builder) {
this.apiUrl = builder.apiUrl;
this.endPoint = builder.endPoint;
this.apiKey = builder.apiKey;
@@ -59,6 +61,24 @@ public class ModelConfig {
this.headersConfig.putAll(builder.headersConfig);
}
@Override
public RequestBody toRequestBody() {
return RequestBody.of()
.putIfNotNull(MODEL_KEY, model);
}
/**
* 解析完整的API URL
*
* @return 完整的API URL
*/
public String resolveUrl() {
String cleanApiUrl = apiUrl.endsWith("/") ? apiUrl.substring(0, apiUrl.length() - 1) : apiUrl;
String cleanEndpoint = endPoint.startsWith("/") ? endPoint.substring(1) : endPoint;
return cleanApiUrl + "/" + cleanEndpoint;
}
public String getApiUrl() {
return apiUrl;
}
@@ -119,11 +139,7 @@ public class ModelConfig {
this.headersConfig.remove(key);
}
public Builder builder() {
return new Builder();
}
public static class Builder {
public static abstract class Builder<B extends Builder<B>> {
protected String apiUrl;
protected String endPoint;
@@ -138,48 +154,51 @@ public class ModelConfig {
protected Map<String, String> headersConfig = new LinkedHashMap<>();
public Builder apiUrl(String apiUrl) {
protected abstract B self();
public B apiUrl(String apiUrl) {
this.apiUrl = apiUrl;
return this;
return self();
}
public Builder endPoint(String endPoint) {
public B endPoint(String endPoint) {
this.endPoint = endPoint;
return this;
return self();
}
public Builder apiKey(String apiKey) {
public B apiKey(String apiKey) {
this.apiKey = apiKey;
return this;
return self();
}
public Builder provider(String provider) {
public B provider(String provider) {
this.provider = provider;
return this;
return self();
}
public Builder model(String model) {
public B model(String model) {
this.model = model;
return this;
return self();
}
public Builder timeout(Duration timeout) {
public B timeout(Duration timeout) {
this.timeout = timeout;
return this;
return self();
}
public Builder headersConfig(Map<String, String> headersConfig) {
public B headersConfig(Map<String, String> headersConfig) {
this.headersConfig = headersConfig;
return this;
return self();
}
public ModelConfig build() {
protected void checkRequiredFields() {
Objects.requireNonNull(apiUrl, "API URL must not be null");
Objects.requireNonNull(endPoint, "End Point must not be null");
Objects.requireNonNull(apiKey, "API Key must not be null");
Objects.requireNonNull(provider, "Provider must not be null");
Objects.requireNonNull(model, "Model must not be null");
return new ModelConfig(this);
}
public abstract ModelConfig build();
}
}

View File

@@ -7,5 +7,5 @@ package com.yomahub.liteflow.ai.model;
* @since TODO
*/
public interface ModelOptions {
public interface ModelOptions extends RequestBodyConvertible {
}

View File

@@ -7,5 +7,6 @@ package com.yomahub.liteflow.ai.model;
* @since TODO
*/
public interface ModelRequest {
public interface ModelRequest extends RequestBodyConvertible {
}

View File

@@ -0,0 +1,168 @@
package com.yomahub.liteflow.ai.model;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* Http 请求体,三个核心功能
* 1. merge
* 2. toJsonString
* 3. put
*
* @author 苍镜月
* @since TODO
*/
public class RequestBody {
private final Map<String, Object> data;
public RequestBody() {
this.data = new LinkedHashMap<>();
}
public RequestBody(Map<String, Object> data) {
this.data = new LinkedHashMap<>(data);
}
/**
* 合并其他 RequestBody 的数据到当前 RequestBody
*
* @param other 其他 RequestBody 实例
* @return 当前 RequestBody 实例
*/
public RequestBody merge(RequestBody other) {
if (Objects.nonNull(other)) {
this.data.putAll(other.data);
}
return this;
}
/**
* 将内部数据转换为 Json 字符串
*
* @return 格式化的 Json 字符串
*/
public String toJsonString() {
return JSON.toJSONString(this.data, JSONWriter.Feature.PrettyFormat);
}
/**
* 创建一个空的 RequestBody 实例的静态工厂方法。
*
* @return 一个新的空 {@code RequestBody} 实例
*/
public static RequestBody of() {
return new RequestBody();
}
/**
* 使用一个已有的 Map 创建 RequestBody 实例的静态工厂方法。
*
* @param data 初始数据 Map
* @return 一个包含初始数据的新 {@code RequestBody} 实例
*/
public static RequestBody of(Map<String, Object> data) {
return new RequestBody(data);
}
/**
* 创建一个包含单个键值对的 RequestBody 实例的静态工厂方法。
* <p>
* 这是最便捷的创建并初始化实例的方式。
*
* @param key 初始键
* @param value 初始值
* @return 一个包含初始键值对的新 {@code RequestBody} 实例
*/
public static RequestBody of(String key, Object value) {
return new RequestBody().put(key, value);
}
/**
* 向请求体中添加一个键值对。
*
* @param key 键
* @param value 值
* @return 当前实例
*/
public RequestBody put(String key, Object value) {
this.data.put(key, value);
return this;
}
/**
* 当值不为null的情况下向请求体中添加一个键值对。
*
* @param key 键
* @param value 要检查并添加的值
* @return 当前实例
*/
public RequestBody putIfNotNull(String key, Object value) {
if (!isNullOrEmpty(value)) {
this.data.put(key, value);
}
return this;
}
/**
* 当值不为空的情况下,向请求体中添加一个键值对。
*
* @param key 键
* @param value 要检查并添加的值
* @return 当前实例
*/
public RequestBody putIfNotEmpty(String key, Object value) {
if (!isNullOrEmpty(value)) {
this.data.put(key, value);
}
return this;
}
/**
* 当给定条件为 true 时,向请求体中添加一个键值对。
*
* @param condition 控制是否添加的布尔条件
* @param key 键
* @param value 值
* @return 当前实例
*/
public RequestBody putIf(boolean condition, String key, Object value) {
if (condition) {
this.data.put(key, value);
}
return this;
}
/**
* 检查给定的对象是否为 null 或 "空"。
*
* @param value 要检查的对象
* @return 如果对象为 null 或空,则返回 true否则返回 false
*/
private static boolean isNullOrEmpty(Object value) {
if (Objects.isNull(value)) {
return true;
} else if (value instanceof Collection) {
return ((Collection<?>) value).isEmpty();
} else if (value instanceof Map) {
return ((Map<?, ?>) value).isEmpty();
} else if (value instanceof String) {
return ((String) value).trim().isEmpty();
} else if (value.getClass().isArray()) {
return Array.getLength(value) == 0;
}
return false;
}
@Override
public String toString() {
return toJsonString();
}
}

View File

@@ -0,0 +1,18 @@
package com.yomahub.liteflow.ai.model;
/**
* 实现该接口的类可以转换为请求体。
*
* @author 苍镜月
* @since TODO
*/
public interface RequestBodyConvertible {
/**
* 将当前对象转换为 {@link RequestBody} 实例。
*
* @return 转换后的 {@link RequestBody} 实例
*/
RequestBody toRequestBody();
}

View File

@@ -1,6 +1,8 @@
package com.yomahub.liteflow.ai.model.chat.entity;
import com.yomahub.liteflow.ai.interact.transport.TransportType;
import com.yomahub.liteflow.ai.model.ModelConfig;
import com.yomahub.liteflow.ai.model.RequestBody;
import java.time.Duration;
import java.util.Map;
@@ -16,6 +18,12 @@ public class ChatConfig extends ModelConfig {
private boolean autoToolCallEnabled = true;
private boolean streaming = true;
private TransportType transportType = TransportType.SSE;
protected static final String STREAM_KEY = "stream";
public ChatConfig() {}
public ChatConfig(
@@ -26,15 +34,43 @@ public class ChatConfig extends ModelConfig {
String model,
Duration timeout,
Map<String, String> headersConfig,
boolean autoToolCallEnabled
boolean autoToolCallEnabled,
boolean streaming,
TransportType transportType
) {
super(apiUrl, endPoint, apiKey, provider, model, timeout, headersConfig);
this.autoToolCallEnabled = autoToolCallEnabled;
this.streaming = streaming;
this.transportType = transportType;
checkTransportConsistency();
}
private ChatConfig(Builder builder) {
protected ChatConfig(Builder<?> builder) {
super(builder);
this.autoToolCallEnabled = builder.autoToolCallEnabled;
this.streaming = builder.streaming;
this.transportType = builder.transportType;
checkTransportConsistency();
}
@Override
public RequestBody toRequestBody() {
return super.toRequestBody()
// 默认流式如果不需要流式输出则设置为false
.putIf(!streaming, STREAM_KEY, streaming);
}
/**
* 检查传输类型与流式输出模式的一致性。
* 如果流式输出模式启用但传输类型为HTTP则抛出异常。
* 如果阻塞式输出模式启用但传输类型不是HTTP则抛出异常。
*/
private void checkTransportConsistency() {
if (this.streaming && this.transportType == TransportType.HTTP) {
throw new IllegalArgumentException("流式输出模式启用但不支持HTTP传输。请使用SSE或WebSocket传输。");
} else if (!this.streaming && this.transportType != TransportType.HTTP) {
throw new IllegalArgumentException("阻塞式输出模式启用但传输类型不支持HTTP。请使用HTTP传输。");
}
}
public boolean isAutoToolCallEnabled() {
@@ -45,17 +81,33 @@ public class ChatConfig extends ModelConfig {
this.autoToolCallEnabled = autoToolCallEnabled;
}
public static class Builder extends ModelConfig.Builder {
private boolean autoToolCallEnabled;
public boolean isStreaming() {
return streaming;
}
public Builder autoToolCallEnabled(boolean autoToolCallEnabled) {
public TransportType getTransportType() {
return transportType;
}
public static abstract class Builder<B extends Builder<B>>
extends ModelConfig.Builder<B> {
protected boolean autoToolCallEnabled = true;
protected boolean streaming = true;
protected TransportType transportType = TransportType.SSE;
public B autoToolCallEnabled(boolean autoToolCallEnabled) {
this.autoToolCallEnabled = autoToolCallEnabled;
return this;
return self();
}
@Override
public ChatConfig build() {
return new ChatConfig(this);
public B streaming(boolean streaming) {
this.streaming = streaming;
return self();
}
public B transportType(TransportType transportType) {
this.transportType = transportType;
return self();
}
}
}

View File

@@ -1,6 +1,7 @@
package com.yomahub.liteflow.ai.model.chat.entity;
import com.yomahub.liteflow.ai.model.ModelOptions;
import com.yomahub.liteflow.ai.model.RequestBody;
/**
* 对话选项配置
@@ -11,16 +12,14 @@ import com.yomahub.liteflow.ai.model.ModelOptions;
public class ChatOptions implements ModelOptions {
public static final ChatOptions DEFAULT = new ChatOptions() {
{
DEFAULT.setTemperature(0.8f);
DEFAULT.setTopP(0.9f);
DEFAULT.setTopK(50f);
DEFAULT.setMaxTokens(512);
DEFAULT.setSeed(null);
DEFAULT.setEnableThinking(false);
}
};
public static final ChatOptions DEFAULT = ChatOptions.builder()
.temperature(0.8f)
.topP(0.9f)
.topK(50f)
.maxTokens(512)
.seed(null)
.enableThinking(false)
.build();
private Float temperature;
@@ -34,6 +33,50 @@ public class ChatOptions implements ModelOptions {
private Boolean enableThinking;
protected static final String TEMPERATURE_KEY = "options.temperature";
protected static final String TOP_P_KEY = "options.top_p";
protected static final String TOP_K_KEY = "options.top_k";
protected static final String SEED_KEY = "options.seed";
protected static final String THINK_KEY = "think";
public ChatOptions() {
}
public ChatOptions(
Float temperature,
Float topP,
Float topK,
Integer maxTokens,
String seed,
Boolean enableThinking
) {
this.temperature = temperature;
this.topP = topP;
this.topK = topK;
this.maxTokens = maxTokens;
this.seed = seed;
this.enableThinking = enableThinking;
}
public ChatOptions(Builder builder) {
this.temperature = builder.temperature;
this.topP = builder.topP;
this.topK = builder.topK;
this.maxTokens = builder.maxTokens;
this.seed = builder.seed;
this.enableThinking = builder.enableThinking;
}
@Override
public RequestBody toRequestBody() {
return RequestBody.of()
.putIfNotEmpty(TEMPERATURE_KEY, temperature)
.putIfNotEmpty(TOP_P_KEY, topP)
.putIfNotEmpty(TOP_K_KEY, topK)
.putIfNotEmpty(SEED_KEY, seed)
.putIfNotEmpty(THINK_KEY, enableThinking);
}
public Float getTemperature() {
return temperature;
}
@@ -81,4 +124,51 @@ public class ChatOptions implements ModelOptions {
public void setEnableThinking(Boolean enableThinking) {
this.enableThinking = enableThinking;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Float temperature;
private Float topP;
private Float topK;
private Integer maxTokens;
private String seed;
private Boolean enableThinking;
public Builder temperature(Float temperature) {
this.temperature = temperature;
return this;
}
public Builder topP(Float topP) {
this.topP = topP;
return this;
}
public Builder topK(Float topK) {
this.topK = topK;
return this;
}
public Builder maxTokens(Integer maxTokens) {
this.maxTokens = maxTokens;
return this;
}
public Builder seed(String seed) {
this.seed = seed;
return this;
}
public Builder enableThinking(Boolean enableThinking) {
this.enableThinking = enableThinking;
return this;
}
public ChatOptions build() {
return new ChatOptions(temperature, topP, topK, maxTokens, seed, enableThinking);
}
}
}

View File

@@ -5,6 +5,7 @@ import com.yomahub.liteflow.ai.interact.callbacks.ResultHandler;
import com.yomahub.liteflow.ai.interact.pipeline.ChatContext;
import com.yomahub.liteflow.ai.interact.transport.TransportListener;
import com.yomahub.liteflow.ai.model.ModelRequest;
import com.yomahub.liteflow.ai.model.RequestBody;
import com.yomahub.liteflow.ai.model.chat.message.Message;
import java.util.List;
@@ -45,6 +46,8 @@ public class ChatRequest implements ModelRequest {
*/
private final ChunkCallbackTransformer chunkCallbackTransformer;
private static final String MESSAGES_KEY = "messages";
/**
* 私有构造函数,使用 Builder 模式创建 ChatRequest 实例
*
@@ -58,6 +61,13 @@ public class ChatRequest implements ModelRequest {
this.chunkCallbackTransformer = builder.chunkCallbackTransformer;
}
@Override
public RequestBody toRequestBody() {
return RequestBody.of()
.putIfNotEmpty(MESSAGES_KEY, messages)
.merge(options.toRequestBody());
}
public List<Message> getMessages() {
return messages;
}
@@ -74,7 +84,7 @@ public class ChatRequest implements ModelRequest {
return resultHandler;
}
public ChunkCallbackTransformer getChunkCallback() {
public ChunkCallbackTransformer getChunkCallbackTransformer() {
return chunkCallbackTransformer;
}
@@ -236,7 +246,7 @@ public class ChatRequest implements ModelRequest {
}
this.transportListener = listenerAggregator.toTransportListener();
this.resultHandler = listenerAggregator.toResultHandler();
this.chunkCallbackTransformer = listenerAggregator.toChunkCallback();
this.chunkCallbackTransformer = listenerAggregator.toChunkCallbackTransformer();
return new ChatRequest(this);
}
@@ -293,7 +303,7 @@ public class ChatRequest implements ModelRequest {
};
}
ChunkCallbackTransformer toChunkCallback() {
ChunkCallbackTransformer toChunkCallbackTransformer() {
Objects.requireNonNull(onText, "onText cannot be null");
Objects.requireNonNull(onThinking, "onThinking cannot be null");
Objects.requireNonNull(onToolsCalling, "onToolsCalling cannot be null");