mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-10 11:17:00 +08:00
Feat: 打通 core 与 engine 模块沟通桥梁。大幅重构注解解析模块代码。补充注解注释信息,完善注解参数配置。
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.yomahub.liteflow.ai.annotation;
|
||||
|
||||
import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatOptions;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
|
||||
@@ -22,8 +23,9 @@ public @interface AIChat {
|
||||
/**
|
||||
* 从上下文中获取的请求参数的上下文路径表达式。
|
||||
* <p>
|
||||
* 如果你不希望在注解中进行静态的模型配置,请使用这个属性,
|
||||
* 并在上下文中提供对应的 {@link ChatRequest} 值
|
||||
* 如果你不希望在注解中进行静态的模型配置,或者你希望使用特定厂商实现的 ChatRequest(其中可能存在一些独有的参数),
|
||||
* 请使用这个属性,并在上下文中提供对应的 {@link ChatRequest} 值。
|
||||
* 如果不提供,框架将统一使用 {@link ChatRequest} 发起请求。
|
||||
* <p>
|
||||
* {@link ChatRequest} 可以是对应提供商的具体实现类。
|
||||
* <p>
|
||||
@@ -33,9 +35,10 @@ public @interface AIChat {
|
||||
* 以及,你会发现 {@link ChatRequest#getOptions()} 的 {@link ChatOptions} 和 {@link AIComponent} 中的配置有重合。
|
||||
* 同样的,即使你进行了 {@link AIComponent} 的配置,会优先使用 {@link ChatRequest} 的配置。
|
||||
* <p>
|
||||
* 但是如果相关的配置为空或默认值,则会使用 {@link AIComponent} 和 {@link AIChat} 中的配置。
|
||||
* <b>请注意:如果相关的配置为空或默认值,则会使用 {@link AIComponent} 和 {@link AIChat} 中的配置!!!</b>
|
||||
* <p>
|
||||
* 该方法用于从一个必须提供 {@code get} 方法的上下文中检索数据。
|
||||
* <p>
|
||||
* 用于从一个必须提供 {@code get} 方法的上下文中检索数据。
|
||||
* 表达式支持以下两种形式:
|
||||
* <ul>
|
||||
* <li>
|
||||
@@ -63,8 +66,15 @@ public @interface AIChat {
|
||||
String userPrompt() default "";
|
||||
|
||||
/**
|
||||
* 是否开启 stream,默认 false
|
||||
* 是否开启 stream,默认 true
|
||||
*/
|
||||
boolean streaming() default false;
|
||||
boolean streaming() default true;
|
||||
|
||||
/**
|
||||
* 传输类型,默认 SSE
|
||||
* <p>
|
||||
* 请在此查看传输类型 -> {@link TransportType}
|
||||
*/
|
||||
TransportType transportType() default TransportType.SSE;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.yomahub.liteflow.ai.annotation;
|
||||
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatOptions;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -16,6 +19,41 @@ import java.lang.annotation.Target;
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface AIClassify {
|
||||
|
||||
/**
|
||||
* 从上下文中获取的请求参数的上下文路径表达式。
|
||||
* <p>
|
||||
* 如果你不希望在注解中进行静态的模型配置,或者你希望使用特定厂商实现的 ChatRequest(其中可能存在一些独有的参数),
|
||||
* 请使用这个属性,并在上下文中提供对应的 {@link ChatRequest} 值。
|
||||
* 如果不提供,框架将统一使用 {@link ChatRequest} 发起请求。
|
||||
* <p>
|
||||
* {@link ChatRequest} 可以是对应提供商的具体实现类。
|
||||
* <p>
|
||||
* 如果你使用了这个属性可以不进行 {@link AIChat#systemPrompt()} 和 {@link AIChat#userPrompt()} 和 {@link AIChat#streaming()} 的配置,
|
||||
* 因为这些配置会从 {@link ChatRequest} 中获取。如果进行了配置,优先使用 {@link ChatRequest} 中的配置。
|
||||
* <p>
|
||||
* 以及,你会发现 {@link ChatRequest#getOptions()} 的 {@link ChatOptions} 和 {@link AIComponent} 中的配置有重合。
|
||||
* 同样的,即使你进行了 {@link AIComponent} 的配置,会优先使用 {@link ChatRequest} 的配置。
|
||||
* <p>
|
||||
* <b>请注意:如果相关的配置为空或默认值,则会使用 {@link AIComponent} 和 {@link AIChat} 中的配置!!!</b>
|
||||
* <p>
|
||||
* 该方法用于从一个必须提供 {@code get} 方法的上下文中检索数据。
|
||||
* <p>
|
||||
* 表达式支持以下两种形式:
|
||||
* <ul>
|
||||
* <li>
|
||||
* <b>直接属性检索:</b><br>
|
||||
* 例如,从上下文中获取 OpenAI 的 ChatRequest,他的名字为 {@code openAIChatRequest},
|
||||
* 表达式应为:{@code "openAIChatRequest"}。
|
||||
* </li>
|
||||
* <li>
|
||||
* <b>嵌套属性检索 (例如 Map):</b><br>
|
||||
* 例如,从上下文的一个名为 {@code requestMap} 的 Map 对象中,获取键为 {@code openAI} 的 ChatRequest 对象,
|
||||
* 表达式应为:{@code "requestMap.openAI"}。
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
String getChatRequest() default "";
|
||||
|
||||
/**
|
||||
* 系统提示词
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* AI 组件注解,提供配置和标识功能
|
||||
@@ -134,12 +135,90 @@ public @interface AIComponent {
|
||||
* 是否并行 ToolCall
|
||||
*/
|
||||
TriState parallelToolCalls() default TriState.UNSET;
|
||||
/**
|
||||
* 是否自动进行 ToolCall
|
||||
*/
|
||||
TriState autoToolCallEnabled() default TriState.UNSET;
|
||||
|
||||
// --- 网络和日志参数 ---
|
||||
/**
|
||||
* 超时时间
|
||||
* <p>连接超时时间</p>
|
||||
*
|
||||
* <p>该值最终会被解析为一个 {@link Duration} 对象。为了提供灵活性,
|
||||
* 支持以下两种字符串格式:</p>
|
||||
*
|
||||
* <ol>
|
||||
* <li><b>标准 ISO-8601 格式</b>:
|
||||
* 这是由 {@link Duration#parse(CharSequence)} 支持的标准格式。
|
||||
* <p><b>示例:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code "PT30S"} 代表 30 秒。</li>
|
||||
* <li>{@code "PT10M"} 代表 10 分钟。</li>
|
||||
* <li>{@code "PT2H"} 代表 2 小时。</li>
|
||||
* <li>{@code "P1D"} 代表 1 天。</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li><b>自定义简化格式</b>:
|
||||
* 为了方便配置,也支持由数字和单位后缀组成的简化格式 (单位不区分大小写)。
|
||||
* <p><b>支持的单位:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code s} - 秒</li>
|
||||
* <li>{@code m} - 分钟</li>
|
||||
* <li>{@code h} - 小时</li>
|
||||
* </ul>
|
||||
* <p><b>示例:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code "60s"} 代表 60 秒。</li>
|
||||
* <li>{@code "5m"} 代表 5 分钟。</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>默认值行为:</b><br>
|
||||
* 如果该值保持默认的空字符串 ({@code ""}),处理该注解的系统将会应用一个预设的、
|
||||
* 全局的默认超时时间(60s)。</p>
|
||||
* @see Duration
|
||||
*/
|
||||
String timeout() default "";
|
||||
String connectTimeout() default "";
|
||||
/**
|
||||
* <p>读取超时时间</p>
|
||||
*
|
||||
* <p>该值最终会被解析为一个 {@link Duration} 对象。为了提供灵活性,
|
||||
* 支持以下两种字符串格式:</p>
|
||||
*
|
||||
* <ol>
|
||||
* <li><b>标准 ISO-8601 格式</b>:
|
||||
* 这是由 {@link Duration#parse(CharSequence)} 支持的标准格式。
|
||||
* <p><b>示例:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code "PT30S"} 代表 30 秒。</li>
|
||||
* <li>{@code "PT10M"} 代表 10 分钟。</li>
|
||||
* <li>{@code "PT2H"} 代表 2 小时。</li>
|
||||
* <li>{@code "P1D"} 代表 1 天。</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li><b>自定义简化格式</b>:
|
||||
* 为了方便配置,也支持由数字和单位后缀组成的简化格式 (单位不区分大小写)。
|
||||
* <p><b>支持的单位:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code s} - 秒</li>
|
||||
* <li>{@code m} - 分钟</li>
|
||||
* <li>{@code h} - 小时</li>
|
||||
* </ul>
|
||||
* <p><b>示例:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code "60s"} 代表 60 秒。</li>
|
||||
* <li>{@code "5m"} 代表 5 分钟。</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>默认值行为:</b><br>
|
||||
* 如果该值保持默认的空字符串 ({@code ""}),处理该注解的系统将会应用一个预设的、
|
||||
* 全局的默认超时时间(60s)。</p>
|
||||
* @see Duration
|
||||
*/
|
||||
String readTimeout() default "";
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
@@ -158,5 +237,9 @@ public @interface AIComponent {
|
||||
* 自定义请求头
|
||||
*/
|
||||
KeyValue[] customHeaders() default {};
|
||||
/**
|
||||
* 是否开启思考模式
|
||||
*/
|
||||
TriState enableThinking() default TriState.UNSET;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.yomahub.liteflow.ai.annotation;
|
||||
|
||||
import com.yomahub.liteflow.ai.domain.enums.ResponseType;
|
||||
import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -95,7 +95,7 @@ public @interface AIOutput {
|
||||
/**
|
||||
* 如需启用,请设置 {@link AIOutput#responseType()} 为 {@link ResponseType#JSON}
|
||||
* <p>
|
||||
* 表示输出的 JSON Schema 定义。如果需要添加描述信息,请使用 LangChain4j 的相关注解
|
||||
* 表示输出的 JSON Schema 定义。如果需要添加描述信息,请使用{@link com.yomahub.liteflow.ai.engine.model.output.structure.Description}
|
||||
*/
|
||||
Class<?> entityClass() default String.class;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.yomahub.liteflow.ai.context;
|
||||
|
||||
import com.yomahub.liteflow.ai.engine.interact.pipeline.InteractContext;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatResponse;
|
||||
import com.yomahub.liteflow.ai.engine.model.output.TokenUsage;
|
||||
|
||||
/**
|
||||
* 流式输出处理器
|
||||
@@ -11,6 +12,29 @@ import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatResponse;
|
||||
*/
|
||||
|
||||
public interface StreamHandler {
|
||||
|
||||
/**
|
||||
* 请求开始时的回调方法
|
||||
*
|
||||
* @param context 聊天上下文,包含处理过程中的状态和信息
|
||||
*/
|
||||
void onStart(InteractContext context);
|
||||
|
||||
/**
|
||||
* 请求关闭时的回调方法
|
||||
*
|
||||
* @param context 聊天上下文,包含处理过程中的状态和信息
|
||||
*/
|
||||
void onClose(InteractContext context);
|
||||
|
||||
/**
|
||||
* 处理过程中发生错误的回调方法。
|
||||
*
|
||||
* @param context 聊天上下文,包含处理过程中的状态和信息
|
||||
* @param t
|
||||
*/
|
||||
void onError(InteractContext context, Throwable t);
|
||||
|
||||
/**
|
||||
* 处理文本消息的回调方法。需要启用流式调用
|
||||
*
|
||||
@@ -42,8 +66,7 @@ public interface StreamHandler {
|
||||
* @param content Token 统计信息内容
|
||||
* @param context 聊天上下文,包含处理过程中的状态和信息
|
||||
*/
|
||||
// TODO args
|
||||
Object onUsage(Object content, InteractContext context);
|
||||
TokenUsage onUsage(Object content, InteractContext context);
|
||||
|
||||
/**
|
||||
* 处理基础信息/搜索结果的回调方法。需要启用流式调用
|
||||
@@ -61,20 +84,8 @@ public interface StreamHandler {
|
||||
* @param context 聊天上下文,包含处理过程中的状态和信息
|
||||
* @return 处理后的结果
|
||||
*/
|
||||
// TODO args
|
||||
ChatResponse onCompletion(ChatResponse response, InteractContext context);
|
||||
|
||||
/**
|
||||
* 处理过程中发生错误的回调方法。
|
||||
*
|
||||
* @param response 处理后的聊天响应结果,可能包含错误信息
|
||||
* @param context 聊天上下文,包含处理过程中的状态和信息
|
||||
* @param e
|
||||
* @return 处理后的结果
|
||||
*/
|
||||
// TODO args
|
||||
ChatResponse onError(ChatResponse response, InteractContext context, Exception e);
|
||||
|
||||
/**
|
||||
* 最终结果处理的回调方法。无论是否发生错误均会调用此方法。
|
||||
*
|
||||
@@ -82,6 +93,5 @@ public interface StreamHandler {
|
||||
* @param context 聊天上下文,包含处理过程中的状态和信息
|
||||
* @return 处理后的结果
|
||||
*/
|
||||
// TODO args
|
||||
ChatResponse onFinal(ChatResponse response, InteractContext context);
|
||||
}
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
package com.yomahub.liteflow.ai.domain;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.AIComponent;
|
||||
import com.yomahub.liteflow.ai.util.KeyValue;
|
||||
import com.yomahub.liteflow.ai.util.TriState;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 模型配置
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public final class ModelConfig {
|
||||
|
||||
private final String provider;
|
||||
private final String apiUrl;
|
||||
private final String endPoint;
|
||||
private final String model;
|
||||
private final String apiKey;
|
||||
private final String version;
|
||||
private final String timeout;
|
||||
private final int maxRetries;
|
||||
private final double temperature;
|
||||
private final double topP;
|
||||
private final int topK;
|
||||
private final int maxTokens;
|
||||
private final List<String> stop;
|
||||
private final int seed;
|
||||
private final double repeatPenalty;
|
||||
private final double presencePenalty;
|
||||
private final double frequencyPenalty;
|
||||
private final TriState parallelToolCalls;
|
||||
private final TriState logRequests;
|
||||
private final TriState logResponses;
|
||||
private final List<KeyValue> customHeaders;
|
||||
|
||||
public ModelConfig(String provider, String apiUrl, String endPoint, String model, String apiKey,
|
||||
String version, String timeout, int maxRetries, double temperature, double topP,
|
||||
int topK, int maxTokens, List<String> stop, int seed, double repeatPenalty,
|
||||
double presencePenalty, double frequencyPenalty,
|
||||
TriState parallelToolCalls, TriState logRequests,
|
||||
TriState logResponses, List<KeyValue> customHeaders) {
|
||||
this.provider = provider;
|
||||
this.apiUrl = apiUrl;
|
||||
this.endPoint = endPoint;
|
||||
this.model = model;
|
||||
this.apiKey = apiKey;
|
||||
this.version = version;
|
||||
this.timeout = timeout;
|
||||
this.maxRetries = maxRetries;
|
||||
this.temperature = temperature;
|
||||
this.topP = topP;
|
||||
this.topK = topK;
|
||||
this.maxTokens = maxTokens;
|
||||
this.stop = stop;
|
||||
this.seed = seed;
|
||||
this.repeatPenalty = repeatPenalty;
|
||||
this.presencePenalty = presencePenalty;
|
||||
this.frequencyPenalty = frequencyPenalty;
|
||||
this.parallelToolCalls = parallelToolCalls;
|
||||
this.logRequests = logRequests;
|
||||
this.logResponses = logResponses;
|
||||
this.customHeaders = customHeaders;
|
||||
}
|
||||
|
||||
public static ModelConfig fromAnnotation(AIComponent anno) {
|
||||
return new ModelConfig(
|
||||
anno.provider(), anno.apiUrl(), anno.endPoint(), anno.model(), anno.apiKey(), anno.version(),
|
||||
anno.timeout(), anno.maxRetries(), anno.temperature(), anno.topP(), anno.topK(),
|
||||
anno.maxTokens(), Arrays.asList(anno.stop()), anno.seed(), anno.repeatPenalty(),
|
||||
anno.presencePenalty(), anno.frequencyPenalty(), anno.parallelToolCalls(), anno.logRequests(),
|
||||
anno.logResponses(), Arrays.asList(anno.customHeaders())
|
||||
);
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public String getApiUrl() {
|
||||
return apiUrl;
|
||||
}
|
||||
|
||||
public String getEndPoint() {
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public int getMaxRetries() {
|
||||
return maxRetries;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public double getTopP() {
|
||||
return topP;
|
||||
}
|
||||
|
||||
public int getTopK() {
|
||||
return topK;
|
||||
}
|
||||
|
||||
public int getMaxTokens() {
|
||||
return maxTokens;
|
||||
}
|
||||
|
||||
public List<String> getStop() {
|
||||
return stop;
|
||||
}
|
||||
|
||||
public int getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
public double getRepeatPenalty() {
|
||||
return repeatPenalty;
|
||||
}
|
||||
|
||||
public double getPresencePenalty() {
|
||||
return presencePenalty;
|
||||
}
|
||||
|
||||
public double getFrequencyPenalty() {
|
||||
return frequencyPenalty;
|
||||
}
|
||||
|
||||
public TriState getParallelToolCalls() {
|
||||
return parallelToolCalls;
|
||||
}
|
||||
|
||||
public TriState getLogRequests() {
|
||||
return logRequests;
|
||||
}
|
||||
|
||||
public TriState getLogResponses() {
|
||||
return logResponses;
|
||||
}
|
||||
|
||||
public List<KeyValue> getCustomHeaders() {
|
||||
return customHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ModelConfig that = (ModelConfig) o;
|
||||
return maxRetries == that.maxRetries &&
|
||||
Double.compare(that.temperature, temperature) == 0 &&
|
||||
Double.compare(that.topP, topP) == 0 &&
|
||||
topK == that.topK &&
|
||||
maxTokens == that.maxTokens &&
|
||||
seed == that.seed &&
|
||||
Double.compare(that.repeatPenalty, repeatPenalty) == 0 &&
|
||||
Double.compare(that.presencePenalty, presencePenalty) == 0 &&
|
||||
Double.compare(that.frequencyPenalty, frequencyPenalty) == 0 &&
|
||||
Objects.equals(provider, that.provider) &&
|
||||
Objects.equals(apiUrl, that.apiUrl) &&
|
||||
Objects.equals(endPoint, that.endPoint) &&
|
||||
Objects.equals(model, that.model) &&
|
||||
Objects.equals(apiKey, that.apiKey) &&
|
||||
Objects.equals(version, that.version) &&
|
||||
Objects.equals(timeout, that.timeout) &&
|
||||
Objects.equals(stop, that.stop) &&
|
||||
parallelToolCalls == that.parallelToolCalls &&
|
||||
logRequests == that.logRequests &&
|
||||
logResponses == that.logResponses &&
|
||||
Objects.equals(customHeaders, that.customHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(provider, apiUrl, endPoint, model, apiKey, version, timeout, maxRetries, temperature, topP, topK, maxTokens, stop, seed, repeatPenalty, presencePenalty, frequencyPenalty, parallelToolCalls, logRequests, logResponses, customHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModelConfig{" +
|
||||
"provider='" + provider + '\'' +
|
||||
", apiUrl='" + apiUrl + '\'' +
|
||||
", endPoint='" + endPoint + '\'' +
|
||||
", model='" + model + '\'' +
|
||||
", apiKey='" + apiKey + '\'' +
|
||||
", version='" + version + '\'' +
|
||||
", timeout='" + timeout + '\'' +
|
||||
", maxRetries=" + maxRetries +
|
||||
", temperature=" + temperature +
|
||||
", topP=" + topP +
|
||||
", topK=" + topK +
|
||||
", maxTokens=" + maxTokens +
|
||||
", stop=" + stop +
|
||||
", seed=" + seed +
|
||||
", repeatPenalty=" + repeatPenalty +
|
||||
", presencePenalty=" + presencePenalty +
|
||||
", frequencyPenalty=" + frequencyPenalty +
|
||||
", parallelToolCalls=" + parallelToolCalls +
|
||||
", logRequests=" + logRequests +
|
||||
", logResponses=" + logResponses +
|
||||
", customHeaders=" + customHeaders +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package com.yomahub.liteflow.ai.domain.dto;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.AIComponent;
|
||||
import com.yomahub.liteflow.ai.util.DurationUtil;
|
||||
import com.yomahub.liteflow.ai.util.KeyValue;
|
||||
import com.yomahub.liteflow.ai.util.TriState;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 模型配置聚合(与 {@link AIComponent} 相互映射)
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public final class ModelConfigAggregator {
|
||||
|
||||
private final String provider;
|
||||
// --- 连接、鉴权参数 ---
|
||||
private final String apiUrl;
|
||||
private final String endPoint;
|
||||
private final String model;
|
||||
private final String apiKey;
|
||||
private final String version;
|
||||
// --- 核心参数 ---
|
||||
private final double temperature;
|
||||
private final double topP;
|
||||
private final int topK;
|
||||
private final int maxTokens;
|
||||
private final List<String> stop;
|
||||
private final int seed;
|
||||
// --- 惩罚参数 ---
|
||||
private final double repeatPenalty;
|
||||
private final double presencePenalty;
|
||||
private final double frequencyPenalty;
|
||||
// --- Tool Calling ----
|
||||
private final TriState parallelToolCalls;
|
||||
private final TriState autoToolCallEnabled;
|
||||
// --- 网络和日志参数 ---
|
||||
private final Duration connectTimeout;
|
||||
private final Duration readTimeout;
|
||||
private final int maxRetries;
|
||||
private final TriState logRequests;
|
||||
private final TriState logResponses;
|
||||
// --- 其他参数 ---
|
||||
private final List<KeyValue> customHeaders;
|
||||
private final TriState enableThinking;
|
||||
|
||||
/**
|
||||
* 从 {@link AIComponent} 注解中解析模型配置
|
||||
*
|
||||
* @param aiComponent AIComponent 注解实例
|
||||
* @return ModelConfigAggregator 实例
|
||||
*/
|
||||
public static ModelConfigAggregator parseFromAnnotation(AIComponent aiComponent) {
|
||||
return new ModelConfigAggregator(
|
||||
aiComponent.provider(),
|
||||
aiComponent.apiUrl(),
|
||||
aiComponent.endPoint(),
|
||||
aiComponent.model(),
|
||||
aiComponent.apiKey(),
|
||||
aiComponent.version(),
|
||||
aiComponent.temperature(),
|
||||
aiComponent.topP(),
|
||||
aiComponent.topK(),
|
||||
aiComponent.maxTokens(),
|
||||
Objects.nonNull(aiComponent.stop()) ? Arrays.asList(aiComponent.stop()) : Collections.emptyList(),
|
||||
aiComponent.seed(),
|
||||
aiComponent.repeatPenalty(),
|
||||
aiComponent.presencePenalty(),
|
||||
aiComponent.frequencyPenalty(),
|
||||
aiComponent.parallelToolCalls(),
|
||||
aiComponent.autoToolCallEnabled(),
|
||||
DurationUtil.toDuration(aiComponent.connectTimeout(), Duration.ofSeconds(60)),
|
||||
DurationUtil.toDuration(aiComponent.readTimeout(), Duration.ofSeconds(60)),
|
||||
aiComponent.maxRetries() <= 0 ? 3 : aiComponent.maxRetries(),
|
||||
aiComponent.logRequests(),
|
||||
aiComponent.logResponses(),
|
||||
Objects.nonNull(aiComponent.customHeaders()) ? Arrays.asList(aiComponent.customHeaders()) : Collections.emptyList(),
|
||||
aiComponent.enableThinking()
|
||||
);
|
||||
}
|
||||
|
||||
private ModelConfigAggregator(String provider, String apiUrl, String endPoint,
|
||||
String model, String apiKey, String version,
|
||||
double temperature, double topP, int topK,
|
||||
int maxTokens, List<String> stop, int seed,
|
||||
double repeatPenalty, double presencePenalty,
|
||||
double frequencyPenalty, TriState parallelToolCalls,
|
||||
TriState autoToolCallEnabled, Duration connectTimeout,
|
||||
Duration readTimeout, int maxRetries, TriState logRequests,
|
||||
TriState logResponses, List<KeyValue> customHeaders, TriState enableThinking) {
|
||||
this.provider = provider;
|
||||
this.apiUrl = apiUrl;
|
||||
this.endPoint = endPoint;
|
||||
this.model = model;
|
||||
this.apiKey = apiKey;
|
||||
this.version = version;
|
||||
this.temperature = temperature;
|
||||
this.topP = topP;
|
||||
this.topK = topK;
|
||||
this.maxTokens = maxTokens;
|
||||
this.stop = stop;
|
||||
this.seed = seed;
|
||||
this.repeatPenalty = repeatPenalty;
|
||||
this.presencePenalty = presencePenalty;
|
||||
this.frequencyPenalty = frequencyPenalty;
|
||||
this.parallelToolCalls = parallelToolCalls;
|
||||
this.autoToolCallEnabled = autoToolCallEnabled;
|
||||
this.connectTimeout = connectTimeout;
|
||||
this.readTimeout = readTimeout;
|
||||
this.maxRetries = maxRetries;
|
||||
this.logRequests = logRequests;
|
||||
this.logResponses = logResponses;
|
||||
this.customHeaders = customHeaders;
|
||||
this.enableThinking = enableThinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认的模型配置聚合实例
|
||||
*/
|
||||
public static ModelConfigAggregator getDefault() {
|
||||
return new ModelConfigAggregator(
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1,
|
||||
-1,
|
||||
Collections.emptyList(),
|
||||
-1,
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0,
|
||||
TriState.UNSET,
|
||||
TriState.UNSET,
|
||||
Duration.ofSeconds(60),
|
||||
Duration.ofSeconds(60),
|
||||
-1,
|
||||
TriState.UNSET,
|
||||
TriState.UNSET,
|
||||
Collections.emptyList(),
|
||||
TriState.UNSET
|
||||
);
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public String getApiUrl() {
|
||||
return apiUrl;
|
||||
}
|
||||
|
||||
public String getEndPoint() {
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public double getTopP() {
|
||||
return topP;
|
||||
}
|
||||
|
||||
public int getTopK() {
|
||||
return topK;
|
||||
}
|
||||
|
||||
public int getMaxTokens() {
|
||||
return maxTokens;
|
||||
}
|
||||
|
||||
public List<String> getStop() {
|
||||
return stop;
|
||||
}
|
||||
|
||||
public int getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
public double getRepeatPenalty() {
|
||||
return repeatPenalty;
|
||||
}
|
||||
|
||||
public double getPresencePenalty() {
|
||||
return presencePenalty;
|
||||
}
|
||||
|
||||
public double getFrequencyPenalty() {
|
||||
return frequencyPenalty;
|
||||
}
|
||||
|
||||
public TriState getParallelToolCalls() {
|
||||
return parallelToolCalls;
|
||||
}
|
||||
|
||||
public TriState getAutoToolCallEnabled() {
|
||||
return autoToolCallEnabled;
|
||||
}
|
||||
|
||||
public Duration getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
public Duration getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
public int getMaxRetries() {
|
||||
return maxRetries;
|
||||
}
|
||||
|
||||
public TriState getLogRequests() {
|
||||
return logRequests;
|
||||
}
|
||||
|
||||
public TriState getLogResponses() {
|
||||
return logResponses;
|
||||
}
|
||||
|
||||
public List<KeyValue> getCustomHeaders() {
|
||||
return customHeaders;
|
||||
}
|
||||
|
||||
public TriState getEnableThinking() {
|
||||
return enableThinking;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.yomahub.liteflow.ai.domain.dto;
|
||||
|
||||
import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
|
||||
|
||||
/**
|
||||
* 解析后的注解配置类
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public class ParsedAnnotationConfig {
|
||||
|
||||
private String systemPrompt;
|
||||
private String userPrompt;
|
||||
private ResponseType responseType = ResponseType.TEXT;
|
||||
private Class<?> entityClass = String.class;
|
||||
|
||||
public String getSystemPrompt() {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public String getUserPrompt() {
|
||||
return userPrompt;
|
||||
}
|
||||
|
||||
public ResponseType getResponseType() {
|
||||
return responseType;
|
||||
}
|
||||
|
||||
public Class<?> getEntityClass() {
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
public void setSystemPrompt(String systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
public void setUserPrompt(String userPrompt) {
|
||||
this.userPrompt = userPrompt;
|
||||
}
|
||||
|
||||
public void setResponseType(ResponseType responseType) {
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
public void setEntityClass(Class<?> entityClass) {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package com.yomahub.liteflow.ai.parse;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.yomahub.liteflow.ai.annotation.AIComponent;
|
||||
import com.yomahub.liteflow.ai.annotation.AIInput;
|
||||
import com.yomahub.liteflow.ai.annotation.AIOutput;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ModelConfigAggregator;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ParsedAnnotationConfig;
|
||||
import com.yomahub.liteflow.ai.domain.enums.AITypeEnum;
|
||||
import com.yomahub.liteflow.ai.domain.enums.ResponseType;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
|
||||
import com.yomahub.liteflow.ai.parse.assemble.ChatRequestAssembler;
|
||||
import com.yomahub.liteflow.ai.parse.assemble.RequestAssembler;
|
||||
import com.yomahub.liteflow.ai.parse.context.ContextAccessor;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
import com.yomahub.liteflow.ai.parse.prompt.PromptTemplateParser;
|
||||
@@ -31,6 +37,8 @@ public abstract class AbstractAnnotationProcessor<A extends Annotation, T extend
|
||||
|
||||
protected final LFLog LOG = LFLoggerManager.getLogger(this.getClass());
|
||||
|
||||
protected static final RequestAssembler<ChatRequest> CHAT_REQUEST_ASSEMBLER = new ChatRequestAssembler();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
AnnotationParser.register(getAIType().getCode(), this);
|
||||
@@ -43,6 +51,23 @@ public abstract class AbstractAnnotationProcessor<A extends Annotation, T extend
|
||||
*/
|
||||
protected abstract AITypeEnum getAIType();
|
||||
|
||||
/**
|
||||
* 解析 {@link AIComponent} 模型配置
|
||||
*
|
||||
* @param context 处理器上下文
|
||||
*/
|
||||
protected void parseModelConfig(ProcessorContext<T> context) {
|
||||
T wrapBean = context.getWrapBean();
|
||||
// 获取模型配置聚合器
|
||||
AIComponent aiComponent = wrapBean.getAiComponent();
|
||||
if (Objects.isNull(aiComponent)) {
|
||||
LOG.warn("AIComponent annotation is null, using default model configuration.");
|
||||
wrapBean.setConfig(ModelConfigAggregator.getDefault());
|
||||
return;
|
||||
}
|
||||
wrapBean.setConfig(ModelConfigAggregator.parseFromAnnotation(aiComponent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析提示词
|
||||
*
|
||||
@@ -85,15 +110,16 @@ public abstract class AbstractAnnotationProcessor<A extends Annotation, T extend
|
||||
if (Objects.isNull(outputAnno)) return;
|
||||
|
||||
// 这里仅处理结构化输出相关的参数,其他参数交给 after 逻辑进行处理
|
||||
T wrapBean = context.getWrapBean();
|
||||
ParsedAnnotationConfig annotationConfig = context.getParsedAnnotationConfig();
|
||||
// 是否需要结构化输出
|
||||
if (Objects.equals(ResponseType.JSON, outputAnno.responseType())) {
|
||||
wrapBean.setResponseType(ResponseType.JSON);
|
||||
annotationConfig.setResponseType(ResponseType.JSON);
|
||||
// 设置输出实体类
|
||||
wrapBean.setEntityClass(outputAnno.entityClass());
|
||||
annotationConfig.setEntityClass(outputAnno.entityClass());
|
||||
} else {
|
||||
annotationConfig.setResponseType(ResponseType.TEXT);
|
||||
// 文本输出,设置 entityClass 为 String
|
||||
wrapBean.setEntityClass(String.class);
|
||||
annotationConfig.setEntityClass(String.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.yomahub.liteflow.ai.parse.anno;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.AIChat;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ParsedAnnotationConfig;
|
||||
import com.yomahub.liteflow.ai.domain.enums.AITypeEnum;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
import com.yomahub.liteflow.ai.parse.AbstractAnnotationProcessor;
|
||||
import com.yomahub.liteflow.ai.parse.context.ContextAccessor;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.ChatProxyWrapBean;
|
||||
import com.yomahub.liteflow.ai.util.SetUtil;
|
||||
@@ -17,21 +20,29 @@ public class ChatAnnotationProcessor extends AbstractAnnotationProcessor<AIChat,
|
||||
|
||||
@Override
|
||||
public void postProcessBeforeTrigger(AIChat annotation, ProcessorContext<ChatProxyWrapBean> context) {
|
||||
// 解析模型配置
|
||||
parseModelConfig(context);
|
||||
|
||||
ChatProxyWrapBean wrapBean = context.getWrapBean();
|
||||
ParsedAnnotationConfig annotationConfig = context.getParsedAnnotationConfig();
|
||||
|
||||
// 设置基本属性
|
||||
SetUtil.setIfPresent(wrapBean::setStreaming, annotation.streaming());
|
||||
|
||||
// 处理系统提示词
|
||||
parsePrompt(annotation.systemPrompt(), context, wrapBean::setSystemPrompt);
|
||||
parsePrompt(annotation.systemPrompt(), context, annotationConfig::setSystemPrompt);
|
||||
|
||||
// 处理用户提示词
|
||||
parsePrompt(annotation.userPrompt(), context, wrapBean::setUserPrompt);
|
||||
parsePrompt(annotation.userPrompt(), context, annotationConfig::setUserPrompt);
|
||||
|
||||
// 处理结构化输出参数绑定
|
||||
parseOutput(context);
|
||||
|
||||
// TODO 组装 Request
|
||||
// 从上下文获取动态 ChatRequest
|
||||
ChatRequest contextChatRequest = ContextAccessor.searchContextByExpression(annotation.getChatRequest(), context);
|
||||
|
||||
// 组装 ChatRequest
|
||||
CHAT_REQUEST_ASSEMBLER.assemble(contextChatRequest, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.yomahub.liteflow.ai.parse.anno;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.AIClassify;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ParsedAnnotationConfig;
|
||||
import com.yomahub.liteflow.ai.domain.enums.AITypeEnum;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
import com.yomahub.liteflow.ai.parse.AbstractAnnotationProcessor;
|
||||
import com.yomahub.liteflow.ai.parse.context.ContextAccessor;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.ClassifyProxyWrapBean;
|
||||
import com.yomahub.liteflow.ai.util.SetUtil;
|
||||
@@ -21,20 +24,30 @@ public class ClassifyAnnotationProcessor extends AbstractAnnotationProcessor<AIC
|
||||
|
||||
@Override
|
||||
public void postProcessBeforeTrigger(AIClassify annotation, ProcessorContext<ClassifyProxyWrapBean> context) {
|
||||
// 解析模型配置
|
||||
parseModelConfig(context);
|
||||
|
||||
ClassifyProxyWrapBean wrapBean = context.getWrapBean();
|
||||
ParsedAnnotationConfig annotationConfig = context.getParsedAnnotationConfig();
|
||||
|
||||
SetUtil.setIfPresent(wrapBean::setCategories, Arrays.stream(annotation.categories()).collect(Collectors.toList()));
|
||||
|
||||
SetUtil.setIfPresent(wrapBean::setMultiLabel, annotation.multiLabel());
|
||||
|
||||
// 处理系统提示词
|
||||
parsePrompt(annotation.systemPrompt(), context, wrapBean::setSystemPrompt);
|
||||
parsePrompt(annotation.systemPrompt(), context, annotationConfig::setSystemPrompt);
|
||||
|
||||
// 处理用户提示词
|
||||
parsePrompt(annotation.userPrompt(), context, wrapBean::setUserPrompt);
|
||||
parsePrompt(annotation.userPrompt(), context, annotationConfig::setUserPrompt);
|
||||
|
||||
// 处理结构化输出参数绑定
|
||||
parseOutput(context);
|
||||
|
||||
// 从上下文获取动态 ChatRequest
|
||||
ChatRequest contextChatRequest = ContextAccessor.searchContextByExpression(annotation.getChatRequest(), context);
|
||||
|
||||
// 组装 ChatRequest
|
||||
CHAT_REQUEST_ASSEMBLER.assemble(contextChatRequest, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.yomahub.liteflow.ai.parse.assemble;
|
||||
|
||||
import com.yomahub.liteflow.ai.context.ChatContext;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ModelConfigAggregator;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ParsedAnnotationConfig;
|
||||
import com.yomahub.liteflow.ai.engine.model.ModelRequest;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
import com.yomahub.liteflow.ai.util.SetUtil;
|
||||
import com.yomahub.liteflow.log.LFLog;
|
||||
import com.yomahub.liteflow.log.LFLoggerManager;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 抽象请求组装器
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public abstract class AbstractRequestAssembler<R extends ModelRequest> implements RequestAssembler<R> {
|
||||
|
||||
protected final LFLog LOG = LFLoggerManager.getLogger(this.getClass());
|
||||
|
||||
/**
|
||||
* 组装请求对象(最终请求对象存储于 {@link ProcessorContext} 中的 {@link ModelRequest} 属性中)
|
||||
*
|
||||
* @param contextRequest 上下文中的请求示例(可以为 null)
|
||||
* @param context 处理器上下文
|
||||
*/
|
||||
public final void assemble(R contextRequest, ProcessorContext<?> context) {
|
||||
ParsedAnnotationConfig annotationConfig = context.getParsedAnnotationConfig();
|
||||
ModelConfigAggregator config = context.getWrapBean().getConfig();
|
||||
|
||||
// 组装请求
|
||||
R req = doAssemble(contextRequest, annotationConfig, config, context.getChatContext());
|
||||
// 设置到处理器上下文
|
||||
context.setModelRequest(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际的请求组装逻辑
|
||||
*
|
||||
* @param contextRequest 上下文中的请求示例(可以为 null)
|
||||
* @param annotationConfig 注解解析配置
|
||||
* @param config 模型配置聚合
|
||||
* @param context chat上下文
|
||||
* @return 最终请求对象
|
||||
*/
|
||||
protected abstract R doAssemble(
|
||||
R contextRequest,
|
||||
ParsedAnnotationConfig annotationConfig,
|
||||
ModelConfigAggregator config,
|
||||
ChatContext context
|
||||
);
|
||||
|
||||
/**
|
||||
* 合并多个 Supplier 的值,返回第一个非空的值
|
||||
*
|
||||
* @param suppliers 多个 Supplier
|
||||
* @param <T> 值的类型
|
||||
* @return 第一个非空的值,如果都为空则返回 null
|
||||
*/
|
||||
@SafeVarargs
|
||||
protected static <T> T merge(Supplier<T>... suppliers) {
|
||||
for (Supplier<T> supplier : suppliers) {
|
||||
T value = supplier.get();
|
||||
if (SetUtil.isPresent(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.yomahub.liteflow.ai.parse.assemble;
|
||||
|
||||
import com.yomahub.liteflow.ai.context.ChatContext;
|
||||
import com.yomahub.liteflow.ai.context.StreamHandler;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ModelConfigAggregator;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ParsedAnnotationConfig;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatOptions;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.message.Message;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.message.SystemMessage;
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.message.UserMessage;
|
||||
import com.yomahub.liteflow.ai.util.SetUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* ChatRequest 组装器
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public class ChatRequestAssembler extends AbstractRequestAssembler<ChatRequest> {
|
||||
|
||||
@Override
|
||||
protected ChatRequest doAssemble(ChatRequest contextRequest, ParsedAnnotationConfig annotationConfig, ModelConfigAggregator config, ChatContext context) {
|
||||
ChatOptions contextOptions = Optional.ofNullable(contextRequest)
|
||||
.map(ChatRequest::getOptions)
|
||||
.orElse(ChatOptions.builder().build());
|
||||
|
||||
ChatRequest.Builder<?> builder = ChatRequest.builder();
|
||||
|
||||
// 1. 连接 StreamHandler 回调
|
||||
StreamHandler streamHandler = context.getStreamHandler();
|
||||
if (Objects.nonNull(streamHandler)) {
|
||||
LOG.info("Connecting StreamHandler to ChatRequest");
|
||||
builder.onStart(streamHandler::onStart)
|
||||
.onClose(streamHandler::onClose)
|
||||
.onError(streamHandler::onError)
|
||||
.onText(streamHandler::onText)
|
||||
.onThinking(streamHandler::onThinking)
|
||||
.onToolsCalling(streamHandler::onToolsCalling)
|
||||
.onUsage(streamHandler::onUsage)
|
||||
.onGrounding(streamHandler::onGrounding)
|
||||
.onCompletion(streamHandler::onCompletion)
|
||||
.onFinal(streamHandler::onFinal);
|
||||
}
|
||||
|
||||
// 2. 合并 ChatOptions
|
||||
ChatOptions.Builder<?> optionsBuilder = ChatOptions.builder();
|
||||
optionsBuilder.temperature(merge(contextOptions::getTemperature, config::getTemperature));
|
||||
optionsBuilder.topP(merge(contextOptions::getTopP, config::getTopP));
|
||||
optionsBuilder.topK(merge(contextOptions::getTopK, config::getTopK));
|
||||
optionsBuilder.maxTokens(merge(contextOptions::getMaxTokens, config::getMaxTokens));
|
||||
optionsBuilder.seed(merge(contextOptions::getSeed, config::getSeed));
|
||||
optionsBuilder.enableThinking(merge(contextOptions::getEnableThinking, () -> config.getEnableThinking().toBool()));
|
||||
builder.options(optionsBuilder.build());
|
||||
|
||||
// 3. 合并 Message
|
||||
List<Message> messages = new ArrayList<>();
|
||||
SetUtil.setIfPresent(t -> messages.add(new SystemMessage(t)), annotationConfig.getSystemPrompt());
|
||||
SetUtil.setIfPresent(t -> messages.add(new UserMessage(t)), annotationConfig.getUserPrompt());
|
||||
|
||||
builder.messages(
|
||||
merge(() -> Objects.nonNull(contextRequest) ? contextRequest.getMessages() : null, () -> messages)
|
||||
);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.yomahub.liteflow.ai.parse.assemble;
|
||||
|
||||
import com.yomahub.liteflow.ai.engine.model.ModelRequest;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
|
||||
/**
|
||||
* 请求组装器接口
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public interface RequestAssembler<R extends ModelRequest> {
|
||||
|
||||
/**
|
||||
* 组装请求对象(最终请求对象存储于 {@link ProcessorContext} 中的 {@link ModelRequest} 属性中)
|
||||
*
|
||||
* @param contextRequest 上下文中的请求示例(可以为 null)
|
||||
* @param context 处理器上下文
|
||||
*/
|
||||
void assemble(R contextRequest, ProcessorContext<?> context);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class ContextAccessor {
|
||||
* @param context 处理器上下文
|
||||
* @return 查找到的值
|
||||
*/
|
||||
public static String searchContextByExpression(String expression, ProcessorContext<?> context) {
|
||||
public static <T> T searchContextByExpression(String expression, ProcessorContext<?> context) {
|
||||
if (StrUtil.isBlank(expression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.yomahub.liteflow.ai.parse.context;
|
||||
import com.yomahub.liteflow.ai.annotation.AIInput;
|
||||
import com.yomahub.liteflow.ai.annotation.AIOutput;
|
||||
import com.yomahub.liteflow.ai.context.ChatContext;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ParsedAnnotationConfig;
|
||||
import com.yomahub.liteflow.ai.engine.model.ModelRequest;
|
||||
import com.yomahub.liteflow.ai.parse.prompt.loader.DefaultPromptResourceLoader;
|
||||
import com.yomahub.liteflow.ai.parse.prompt.loader.PromptResourceLoader;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.AIProxyWrapBean;
|
||||
@@ -18,14 +20,18 @@ import com.yomahub.liteflow.core.NodeComponent;
|
||||
public class ProcessorContext<T extends AIProxyWrapBean<?>> {
|
||||
|
||||
private final T wrapBean;
|
||||
private final ParsedAnnotationConfig parsedAnnotationConfig;
|
||||
private final ChatContext chatContext;
|
||||
private final NodeComponent nodeComponent;
|
||||
private final AIInput aiInputAnno;
|
||||
private final AIOutput aiOutputAnno;
|
||||
private final PromptResourceLoader resourceLoader;
|
||||
|
||||
private ModelRequest modelRequest;
|
||||
|
||||
public ProcessorContext(T wrapBean, ChatContext chatContext, NodeComponent nodeComponent) {
|
||||
this.wrapBean = wrapBean;
|
||||
parsedAnnotationConfig = new ParsedAnnotationConfig();
|
||||
this.chatContext = chatContext;
|
||||
this.nodeComponent = nodeComponent;
|
||||
this.aiInputAnno = wrapBean.getInterfaceClass().getAnnotation(AIInput.class);
|
||||
@@ -56,4 +62,16 @@ public class ProcessorContext<T extends AIProxyWrapBean<?>> {
|
||||
public PromptResourceLoader getResourceLoader() {
|
||||
return resourceLoader;
|
||||
}
|
||||
|
||||
public ParsedAnnotationConfig getParsedAnnotationConfig() {
|
||||
return parsedAnnotationConfig;
|
||||
}
|
||||
|
||||
public ModelRequest getModelRequest() {
|
||||
return modelRequest;
|
||||
}
|
||||
|
||||
public void setModelRequest(ModelRequest modelRequest) {
|
||||
this.modelRequest = modelRequest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.yomahub.liteflow.ai.proxy.invocation;
|
||||
|
||||
import com.yomahub.liteflow.ai.context.ChatContext;
|
||||
import com.yomahub.liteflow.ai.domain.ModelConfig;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ModelConfigAggregator;
|
||||
import com.yomahub.liteflow.ai.exception.LiteFlowAIException;
|
||||
import com.yomahub.liteflow.ai.parse.AnnotationParser;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
@@ -77,19 +77,15 @@ public abstract class AbstractAIInvocationHandler<T extends AIProxyWrapBean<?>>
|
||||
* @param processorContext 处理器上下文
|
||||
*/
|
||||
protected void checkValidation(ProcessorContext<T> processorContext) {
|
||||
// 校验Prompt
|
||||
if (SetUtil.isNotPresent(wrapBean.getUserPrompt()) && SetUtil.isNotPresent(wrapBean.getSystemPrompt())) {
|
||||
throw new LiteFlowAIException("User prompt and system prompt cannot both be empty");
|
||||
}
|
||||
// 校验必需参数
|
||||
ModelConfig modelConfig = wrapBean.getConfig();
|
||||
if (SetUtil.isNotPresent(modelConfig.getProvider())) {
|
||||
ModelConfigAggregator modelConfigAggregator = wrapBean.getConfig();
|
||||
if (SetUtil.isNotPresent(modelConfigAggregator.getProvider())) {
|
||||
throw new LiteFlowAIException("Provider cannot be empty for AI node: " + wrapBean.getNodeId());
|
||||
}
|
||||
if (SetUtil.isNotPresent(modelConfig.getApiUrl())) {
|
||||
if (SetUtil.isNotPresent(modelConfigAggregator.getApiUrl())) {
|
||||
throw new LiteFlowAIException("API URL cannot be empty for AI node: " + wrapBean.getNodeId());
|
||||
}
|
||||
if (SetUtil.isNotPresent(modelConfig.getModel())) {
|
||||
if (SetUtil.isNotPresent(modelConfigAggregator.getModel())) {
|
||||
throw new LiteFlowAIException("Model cannot be empty for AI node: " + wrapBean.getNodeId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
import com.yomahub.liteflow.ai.model.ModelFactory;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.ChatProxyWrapBean;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
|
||||
/**
|
||||
* 聊天组件的调用处理器
|
||||
@@ -28,50 +27,14 @@ public class ChatAIInvocationHandler extends AbstractAIInvocationHandler<ChatPro
|
||||
|
||||
@Override
|
||||
protected Object doExecuteAIProcess(ProcessorContext<ChatProxyWrapBean> processorContext, Object[] args) {
|
||||
NodeComponent nodeComponent = processorContext.getNodeComponent();
|
||||
ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
|
||||
ChatRequest chatRequest = processorContext.getModelRequest().toChatRequest();
|
||||
|
||||
if (wrapBean.isStreaming()) {
|
||||
return processStreaming(nodeComponent);
|
||||
chatModel.stream(chatRequest);
|
||||
return null;
|
||||
} else {
|
||||
return processBlocking(nodeComponent);
|
||||
return chatModel.chat(chatRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private Void processStreaming(NodeComponent nodeComponent) {
|
||||
ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
|
||||
// StreamingChatModel streamingChatModel = ModelFactory.getStreamingChatModel(wrapBean);
|
||||
// // 创建AI服务实例
|
||||
// Object aiService = AiServiceFactory.createAiService(wrapBean.getEntityClass(), streamingChatModel);
|
||||
//
|
||||
// try {
|
||||
// TokenStream tokenStream = AiServiceFactory.chatStream(aiService, wrapBean.getUserPrompt(), wrapBean.getSystemPrompt());
|
||||
// // 处理流式响应
|
||||
// ChatContext chatContext = nodeComponent.getContextBean(ChatContext.class);
|
||||
// // 如果存在流处理器,则将TokenStream传递给它
|
||||
// if (Objects.nonNull(chatContext)) {
|
||||
// Optional.of(chatContext.getStreamHandler())
|
||||
// .ifPresent(streamHandler -> streamHandler.acceptTokenStream(tokenStream));
|
||||
// }
|
||||
// } catch (Throwable e) {
|
||||
// throw new LiteFlowAIException("Error during streaming chat processing", e);
|
||||
// }
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object processBlocking(NodeComponent nodeComponent) {
|
||||
ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
|
||||
chatModel.chat(ChatRequest.builder().build());
|
||||
// ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
|
||||
//
|
||||
// // 创建AI服务实例
|
||||
// Object aiService = AiServiceFactory.createAiService(wrapBean.getEntityClass(), chatModel);
|
||||
//
|
||||
// try {
|
||||
// Object result = AiServiceFactory.chat(aiService, wrapBean.getUserPrompt(), wrapBean.getSystemPrompt());
|
||||
// LOG.info("Chat response: {}", result);
|
||||
// return result;
|
||||
// } catch (Throwable e) {
|
||||
// throw new LiteFlowAIException("Error during blocking chat processing", e);
|
||||
// }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.yomahub.liteflow.ai.proxy.invocation;
|
||||
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.ChatModel;
|
||||
import com.yomahub.liteflow.ai.exception.LiteFlowAIException;
|
||||
import com.yomahub.liteflow.ai.model.ModelFactory;
|
||||
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.ClassifyProxyWrapBean;
|
||||
import com.yomahub.liteflow.ai.util.SetUtil;
|
||||
@@ -37,9 +39,7 @@ public class ClassifyAIInvocationHandler extends AbstractAIInvocationHandler<Cla
|
||||
|
||||
@Override
|
||||
protected Object doExecuteAIProcess(ProcessorContext<ClassifyProxyWrapBean> processorContext, Object[] args) {
|
||||
|
||||
// ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
|
||||
|
||||
return null;
|
||||
ChatModel chatModel = ModelFactory.getChatModel(processorContext.getWrapBean());
|
||||
return chatModel.chat(processorContext.getModelRequest().toChatRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.yomahub.liteflow.ai.proxy.wrap;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.AIComponent;
|
||||
import com.yomahub.liteflow.ai.domain.ModelConfig;
|
||||
import com.yomahub.liteflow.ai.domain.enums.ResponseType;
|
||||
import com.yomahub.liteflow.ai.domain.dto.ModelConfigAggregator;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
@@ -15,31 +14,24 @@ import java.lang.annotation.Annotation;
|
||||
|
||||
public abstract class AIProxyWrapBean<T extends Annotation> {
|
||||
|
||||
protected T annotation;
|
||||
// AI组件注解
|
||||
protected final T annotation;
|
||||
// AI 组件配置注解
|
||||
protected final AIComponent aiComponent;
|
||||
// LiteFlow Node ID
|
||||
protected final String nodeId;
|
||||
// LiteFlow Node 名称
|
||||
protected final String nodeName;
|
||||
// 代理接口名称
|
||||
protected final Class<?> interfaceClass;
|
||||
// Bean 名称
|
||||
protected final String beanName;
|
||||
// 模型配置聚合
|
||||
protected ModelConfigAggregator config;
|
||||
|
||||
protected ModelConfig config;
|
||||
|
||||
protected String nodeId;
|
||||
|
||||
protected String nodeName;
|
||||
|
||||
protected Class<?> interfaceClass;
|
||||
|
||||
protected String beanName;
|
||||
|
||||
protected String systemPrompt;
|
||||
|
||||
protected String userPrompt;
|
||||
|
||||
protected ResponseType responseType = ResponseType.TEXT;
|
||||
|
||||
protected Class<?> entityClass = String.class;
|
||||
|
||||
public AIProxyWrapBean() {
|
||||
}
|
||||
|
||||
public AIProxyWrapBean(AIComponent aiComponent, Class<?> interfaceClass, String beanName) {
|
||||
this.config = ModelConfig.fromAnnotation(aiComponent);
|
||||
public AIProxyWrapBean(AIComponent aiComponent, T annotation, Class<?> interfaceClass, String beanName) {
|
||||
this.aiComponent = aiComponent;
|
||||
this.annotation = annotation;
|
||||
this.nodeId = aiComponent.nodeId();
|
||||
this.nodeName = aiComponent.nodeName();
|
||||
this.interfaceClass = interfaceClass;
|
||||
@@ -58,7 +50,11 @@ public abstract class AIProxyWrapBean<T extends Annotation> {
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
public ModelConfig getConfig() {
|
||||
public AIComponent getAiComponent() {
|
||||
return aiComponent;
|
||||
}
|
||||
|
||||
public ModelConfigAggregator getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -70,55 +66,7 @@ public abstract class AIProxyWrapBean<T extends Annotation> {
|
||||
return beanName;
|
||||
}
|
||||
|
||||
public String getSystemPrompt() {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public String getUserPrompt() {
|
||||
return userPrompt;
|
||||
}
|
||||
|
||||
public ResponseType getResponseType() {
|
||||
return responseType;
|
||||
}
|
||||
|
||||
public Class<?> getEntityClass() {
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
public void setNodeId(String nodeId) {
|
||||
this.nodeId = nodeId;
|
||||
}
|
||||
|
||||
public void setNodeName(String nodeName) {
|
||||
this.nodeName = nodeName;
|
||||
}
|
||||
|
||||
public void setConfig(ModelConfig config) {
|
||||
public void setConfig(ModelConfigAggregator config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setInterfaceClass(Class<?> interfaceClass) {
|
||||
this.interfaceClass = interfaceClass;
|
||||
}
|
||||
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
public void setSystemPrompt(String systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
public void setUserPrompt(String userPrompt) {
|
||||
this.userPrompt = userPrompt;
|
||||
}
|
||||
|
||||
public void setResponseType(ResponseType responseType) {
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
public void setEntityClass(Class<?> entityClass) {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,8 @@ public class ChatProxyWrapBean extends AIProxyWrapBean<AIChat> {
|
||||
|
||||
private boolean streaming;
|
||||
|
||||
public ChatProxyWrapBean() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ChatProxyWrapBean(AIComponent aiComponent, AIChat annotation,
|
||||
Class<?> interfaceClass, String beanName) {
|
||||
super(aiComponent, interfaceClass, beanName);
|
||||
this.annotation = annotation;
|
||||
public ChatProxyWrapBean(AIComponent aiComponent, AIChat annotation, Class<?> interfaceClass, String beanName) {
|
||||
super(aiComponent, annotation, interfaceClass, beanName);
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
|
||||
@@ -18,14 +18,8 @@ public class ClassifyProxyWrapBean extends AIProxyWrapBean<AIClassify> {
|
||||
|
||||
private boolean multiLabel;
|
||||
|
||||
public ClassifyProxyWrapBean() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ClassifyProxyWrapBean(AIComponent aiComponent, AIClassify annotation,
|
||||
Class<?> interfaceClass, String beanName) {
|
||||
super(aiComponent, interfaceClass, beanName);
|
||||
this.annotation = annotation;
|
||||
public ClassifyProxyWrapBean(AIComponent aiComponent, AIClassify annotation, Class<?> interfaceClass, String beanName) {
|
||||
super(aiComponent, annotation, interfaceClass, beanName);
|
||||
}
|
||||
|
||||
public List<String> getCategories() {
|
||||
|
||||
@@ -12,13 +12,7 @@ import com.yomahub.liteflow.ai.annotation.AIRetrieval;
|
||||
|
||||
public class RetrievalProxyWrapBean extends AIProxyWrapBean<AIRetrieval> {
|
||||
|
||||
public RetrievalProxyWrapBean() {
|
||||
super();
|
||||
}
|
||||
|
||||
public RetrievalProxyWrapBean(AIComponent aiComponent, AIRetrieval annotation,
|
||||
Class<?> interfaceClass, String beanName) {
|
||||
super(aiComponent, interfaceClass, beanName);
|
||||
this.annotation = annotation;
|
||||
public RetrievalProxyWrapBean(AIComponent aiComponent, AIRetrieval annotation, Class<?> interfaceClass, String beanName) {
|
||||
super(aiComponent, annotation, interfaceClass, beanName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.yomahub.liteflow.ai.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 将时间字符串转换为 {@link java.time.Duration} 工具类
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public class DurationUtil {
|
||||
/**
|
||||
* 用于解析简化时间格式 (e.g., "10s", "5m", "1h") 的正则表达式.
|
||||
* 它捕获一个或多个数字作为第一组,以及单位 's', 'm', 或 'h' 作为第二组.
|
||||
*/
|
||||
private static final Pattern SIMPLE_FORMAT_PATTERN = Pattern.compile("(\\d+)([smh])", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* 私有构造函数
|
||||
*/
|
||||
private DurationUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>将一个字符串解析为 {@link Duration} 对象。</p>
|
||||
*
|
||||
* <p>该方法提供了两种格式的解析支持:</p>
|
||||
* <ol>
|
||||
* <li><b>标准 ISO 8601 持续时间格式:</b> 这是 Java 内置支持的标准格式,以 {@code "PT"} 开头。
|
||||
* 这是首选的解析方式。</li>
|
||||
* <li><b>自定义简化格式:</b> 为了方便用户配置,支持一种更简洁的格式,由数字和单位后缀组成。
|
||||
* 支持的单位包括:
|
||||
* <ul>
|
||||
* <li>{@code s} - 秒</li>
|
||||
* <li>{@code m} - 分钟</li>
|
||||
* <li>{@code h} - 小时</li>
|
||||
* </ul>
|
||||
* 单位不区分大小写 (例如, {@code "10S"} 和 {@code "10s"} 效果相同)。
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>解析逻辑:</b></p>
|
||||
* <p>方法会首先尝试使用标准的 ISO 8601 格式进行解析。如果解析失败,它会回退 (fallback) 到自定义的
|
||||
* 简化格式进行尝试。如果两种格式都无法解析,或者输入字符串为 {@code null} 或空,
|
||||
* 将返回指定的默认 {@code Duration} 对象。</p>
|
||||
*
|
||||
* @param durationStr 要解析的持续时间字符串。
|
||||
* <p><b>ISO 8601 格式示例:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code "PT20S"} - 20 秒</li>
|
||||
* <li>{@code "PT15M"} - 15 分钟</li>
|
||||
* <li>{@code "PT10H"} - 10 小时</li>
|
||||
* <li>{@code "P2D"} - 2 天</li>
|
||||
* </ul>
|
||||
* <p><b>自定义简化格式示例:</b></p>
|
||||
* <ul>
|
||||
* <li>{@code "30s"} - 30 秒</li>
|
||||
* <li>{@code "5m"} - 5 分钟</li>
|
||||
* <li>{@code "1h"} - 1 小时</li>
|
||||
* </ul>
|
||||
* @param defaultDuration 当输入字符串无法被解析、为 {@code null} 或为空时,返回的默认 {@code Duration} 对象。
|
||||
* 此参数不能为 {@code null}。
|
||||
* @return 解析后的 {@link Duration} 对象,或在无法解析时返回 {@code defaultDuration}。
|
||||
* @throws NullPointerException 如果 {@code defaultDuration} 为 {@code null}。
|
||||
* @see java.time.Duration#parse(CharSequence)
|
||||
*/
|
||||
public static Duration toDuration(String durationStr, Duration defaultDuration) {
|
||||
if (StrUtil.isBlank(durationStr)) {
|
||||
return defaultDuration;
|
||||
}
|
||||
|
||||
// 1. 尝试使用标准的 ISO 8601 格式进行解析
|
||||
try {
|
||||
return Duration.parse(durationStr);
|
||||
} catch (DateTimeParseException ignore) {
|
||||
}
|
||||
|
||||
// 2. 尝试使用自定义的简化格式进行解析
|
||||
Matcher matcher = SIMPLE_FORMAT_PATTERN.matcher(durationStr.trim());
|
||||
|
||||
if (matcher.matches()) {
|
||||
try {
|
||||
long value = Long.parseLong(matcher.group(1));
|
||||
String unit = matcher.group(2).toLowerCase();
|
||||
|
||||
switch (unit) {
|
||||
case "s":
|
||||
return Duration.ofSeconds(value);
|
||||
case "m":
|
||||
return Duration.ofMinutes(value);
|
||||
case "h":
|
||||
return Duration.ofHours(value);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
// 3. 如果两种格式都无法解析,返回默认的 Duration
|
||||
return defaultDuration;
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,17 @@ public enum TriState {
|
||||
TRUE,
|
||||
FALSE,
|
||||
UNSET,
|
||||
;
|
||||
|
||||
public Boolean toBool() {
|
||||
switch (this) {
|
||||
case TRUE:
|
||||
return true;
|
||||
case FALSE:
|
||||
return false;
|
||||
case UNSET:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ public class HttpTransport implements Transport {
|
||||
.builder()
|
||||
.connectTimeout(config.getTimeout())
|
||||
.readTimeout(config.getTimeout())
|
||||
.writeTimeout(config.getTimeout())
|
||||
.build()) {
|
||||
// 构建请求体
|
||||
String requestBody = buildRequestBody(config, request);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.yomahub.liteflow.ai.engine.model;
|
||||
|
||||
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
|
||||
import com.yomahub.liteflow.ai.engine.util.request.RequestBodyConvertible;
|
||||
|
||||
/**
|
||||
@@ -11,4 +12,7 @@ import com.yomahub.liteflow.ai.engine.util.request.RequestBodyConvertible;
|
||||
|
||||
public interface ModelRequest extends RequestBodyConvertible {
|
||||
|
||||
default ChatRequest toChatRequest() {
|
||||
return (ChatRequest) this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,23 +13,23 @@ import com.yomahub.liteflow.ai.engine.util.request.RequestBody;
|
||||
public class ChatOptions implements ModelOptions {
|
||||
|
||||
public static final ChatOptions DEFAULT = ChatOptions.builder()
|
||||
.temperature(0.8f)
|
||||
.topP(0.9f)
|
||||
.topK(50f)
|
||||
.temperature(0.8)
|
||||
.topP(0.9)
|
||||
.topK(50)
|
||||
.maxTokens(512)
|
||||
.seed(null)
|
||||
.enableThinking(false)
|
||||
.build();
|
||||
|
||||
protected Float temperature;
|
||||
protected Double temperature;
|
||||
|
||||
protected Float topP;
|
||||
protected Double topP;
|
||||
|
||||
protected Float topK;
|
||||
protected Integer topK;
|
||||
|
||||
protected Integer maxTokens;
|
||||
|
||||
protected String seed;
|
||||
protected Integer seed;
|
||||
|
||||
protected Boolean enableThinking;
|
||||
|
||||
@@ -49,11 +49,11 @@ public class ChatOptions implements ModelOptions {
|
||||
}
|
||||
|
||||
public ChatOptions(
|
||||
Float temperature,
|
||||
Float topP,
|
||||
Float topK,
|
||||
Double temperature,
|
||||
Double topP,
|
||||
Integer topK,
|
||||
Integer maxTokens,
|
||||
String seed,
|
||||
Integer seed,
|
||||
Boolean enableThinking
|
||||
) {
|
||||
this.temperature = temperature;
|
||||
@@ -83,15 +83,15 @@ public class ChatOptions implements ModelOptions {
|
||||
.putIfNotEmpty(THINK_KEY, enableThinking);
|
||||
}
|
||||
|
||||
public Float getTemperature() {
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public Float getTopP() {
|
||||
public Double getTopP() {
|
||||
return topP;
|
||||
}
|
||||
|
||||
public Float getTopK() {
|
||||
public Integer getTopK() {
|
||||
return topK;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class ChatOptions implements ModelOptions {
|
||||
return maxTokens;
|
||||
}
|
||||
|
||||
public String getSeed() {
|
||||
public Integer getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
@@ -107,15 +107,15 @@ public class ChatOptions implements ModelOptions {
|
||||
return enableThinking;
|
||||
}
|
||||
|
||||
public void setTemperature(Float temperature) {
|
||||
public void setTemperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public void setTopP(Float topP) {
|
||||
public void setTopP(Double topP) {
|
||||
this.topP = topP;
|
||||
}
|
||||
|
||||
public void setTopK(Float topK) {
|
||||
public void setTopK(Integer topK) {
|
||||
this.topK = topK;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class ChatOptions implements ModelOptions {
|
||||
this.maxTokens = maxTokens;
|
||||
}
|
||||
|
||||
public void setSeed(String seed) {
|
||||
public void setSeed(Integer seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
@@ -136,28 +136,28 @@ public class ChatOptions implements ModelOptions {
|
||||
}
|
||||
|
||||
public static abstract class Builder<B extends Builder<B>> {
|
||||
protected Float temperature;
|
||||
protected Float topP;
|
||||
protected Float topK;
|
||||
protected Double temperature;
|
||||
protected Double topP;
|
||||
protected Integer topK;
|
||||
protected Integer maxTokens;
|
||||
protected String seed;
|
||||
protected Integer seed;
|
||||
protected Boolean enableThinking;
|
||||
|
||||
protected abstract B self();
|
||||
|
||||
public abstract ChatOptions build();
|
||||
|
||||
public B temperature(Float temperature) {
|
||||
public B temperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B topP(Float topP) {
|
||||
public B topP(Double topP) {
|
||||
this.topP = topP;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B topK(Float topK) {
|
||||
public B topK(Integer topK) {
|
||||
this.topK = topK;
|
||||
return self();
|
||||
}
|
||||
@@ -167,7 +167,7 @@ public class ChatOptions implements ModelOptions {
|
||||
return self();
|
||||
}
|
||||
|
||||
public B seed(String seed) {
|
||||
public B seed(Integer seed) {
|
||||
this.seed = seed;
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,4 @@ public class UserMessage extends AbstractMessage {
|
||||
public UserMessage(String textContent) {
|
||||
this(textContent, new HashMap<>());
|
||||
}
|
||||
|
||||
// TODO 添加 Resource 支持
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.yomahub.liteflow.ai.domain.enums;
|
||||
package com.yomahub.liteflow.ai.engine.model.output;
|
||||
|
||||
/**
|
||||
* AI响应类型枚举
|
||||
@@ -37,7 +37,6 @@ public final class HttpUtil implements AutoCloseable {
|
||||
this.syncClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(builder.connectTimeout)
|
||||
.readTimeout(builder.readTimeout)
|
||||
.writeTimeout(builder.writeTimeout)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -305,9 +304,8 @@ public final class HttpUtil implements AutoCloseable {
|
||||
* 用于创建 {@link HttpUtil} 的构建器。
|
||||
*/
|
||||
public static class Builder {
|
||||
private Duration connectTimeout = Duration.ofSeconds(30);
|
||||
private Duration connectTimeout = Duration.ofSeconds(60);
|
||||
private Duration readTimeout = Duration.ofSeconds(60);
|
||||
private Duration writeTimeout = Duration.ofSeconds(30);
|
||||
|
||||
public Builder connectTimeout(Duration duration) {
|
||||
this.connectTimeout = Objects.requireNonNull(duration, "connectTimeout 不能为空");
|
||||
@@ -319,11 +317,6 @@ public final class HttpUtil implements AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder writeTimeout(Duration duration) {
|
||||
this.writeTimeout = Objects.requireNonNull(duration, "writeTimeout 不能为空");
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpUtil build() {
|
||||
return new HttpUtil(this);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.yomahub.liteflow.test.ai.core.proxy.cmp;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.*;
|
||||
import com.yomahub.liteflow.ai.domain.enums.ResponseType;
|
||||
import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
|
||||
Reference in New Issue
Block a user