From bcb07b908a3afb6307113d2d6196b71c1a858362 Mon Sep 17 00:00:00 2001 From: LuanY77 <2307984361@qq.com> Date: Thu, 17 Jul 2025 18:48:40 +0800 Subject: [PATCH] =?UTF-8?q?Refactor:=20=E9=87=8D=E6=9E=84=20config?= =?UTF-8?q?=EF=BC=8Crequest=EF=BC=8Coptions=20=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E5=BC=95=E5=85=A5=20RequestBody?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../liteflow/ai/model/ModelConfig.java | 65 ++++--- .../liteflow/ai/model/ModelOptions.java | 2 +- .../liteflow/ai/model/ModelRequest.java | 3 +- .../liteflow/ai/model/RequestBody.java | 168 ++++++++++++++++++ .../ai/model/RequestBodyConvertible.java | 18 ++ .../ai/model/chat/entity/ChatConfig.java | 70 +++++++- .../ai/model/chat/entity/ChatOptions.java | 110 ++++++++++-- .../ai/model/chat/entity/ChatRequest.java | 16 +- 8 files changed, 405 insertions(+), 47 deletions(-) create mode 100644 liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBody.java create mode 100644 liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBodyConvertible.java diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelConfig.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelConfig.java index 0da988f19..b64b86e06 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelConfig.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelConfig.java @@ -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 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> { protected String apiUrl; protected String endPoint; @@ -138,48 +154,51 @@ public class ModelConfig { protected Map 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 headersConfig) { + public B headersConfig(Map 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(); } } diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelOptions.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelOptions.java index 5be3bcac5..c92a50944 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelOptions.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelOptions.java @@ -7,5 +7,5 @@ package com.yomahub.liteflow.ai.model; * @since TODO */ -public interface ModelOptions { +public interface ModelOptions extends RequestBodyConvertible { } diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelRequest.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelRequest.java index fd834aa1e..fb984d6e5 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelRequest.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/ModelRequest.java @@ -7,5 +7,6 @@ package com.yomahub.liteflow.ai.model; * @since TODO */ -public interface ModelRequest { +public interface ModelRequest extends RequestBodyConvertible { + } diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBody.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBody.java new file mode 100644 index 000000000..526b984e5 --- /dev/null +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBody.java @@ -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 data; + + public RequestBody() { + this.data = new LinkedHashMap<>(); + } + + public RequestBody(Map 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 data) { + return new RequestBody(data); + } + + /** + * 创建一个包含单个键值对的 RequestBody 实例的静态工厂方法。 + *

+ * 这是最便捷的创建并初始化实例的方式。 + * + * @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(); + } +} diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBodyConvertible.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBodyConvertible.java new file mode 100644 index 000000000..2cbf97be1 --- /dev/null +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/RequestBodyConvertible.java @@ -0,0 +1,18 @@ +package com.yomahub.liteflow.ai.model; + +/** + * 实现该接口的类可以转换为请求体。 + * + * @author 苍镜月 + * @since TODO + */ + +public interface RequestBodyConvertible { + + /** + * 将当前对象转换为 {@link RequestBody} 实例。 + * + * @return 转换后的 {@link RequestBody} 实例 + */ + RequestBody toRequestBody(); +} diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatConfig.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatConfig.java index cee80fe05..f3f2f8d4d 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatConfig.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatConfig.java @@ -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 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> + extends ModelConfig.Builder { + 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(); } } } diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatOptions.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatOptions.java index b27ef1b85..18aea96b7 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatOptions.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatOptions.java @@ -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); + } + } } diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatRequest.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatRequest.java index 89fb59a0f..0115b7704 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatRequest.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/model/chat/entity/ChatRequest.java @@ -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 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");