mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-12 12:31:05 +08:00
Refactor: 重构 config,request,options 相关代码,引入 RequestBody
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ package com.yomahub.liteflow.ai.model;
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public interface ModelOptions {
|
||||
public interface ModelOptions extends RequestBodyConvertible {
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@ package com.yomahub.liteflow.ai.model;
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public interface ModelRequest {
|
||||
public interface ModelRequest extends RequestBodyConvertible {
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.yomahub.liteflow.ai.model;
|
||||
|
||||
/**
|
||||
* 实现该接口的类可以转换为请求体。
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public interface RequestBodyConvertible {
|
||||
|
||||
/**
|
||||
* 将当前对象转换为 {@link RequestBody} 实例。
|
||||
*
|
||||
* @return 转换后的 {@link RequestBody} 实例
|
||||
*/
|
||||
RequestBody toRequestBody();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user