mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-10 03:07:32 +08:00
feat(react-agent-openai): add OpenAI entry and OpenAISpec
OpenAI.of(modelName) returns an OpenAISpec exposing reasoningEffort, frequencyPenalty, and presencePenalty in addition to the common ModelSpec setters; resolve() reads liteflow.agent.openai credential. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.yomahub.liteflow.agent.openai;
|
||||
|
||||
/**
|
||||
* OpenAI 官方 API 入口。credential 来源:{@code liteflow.agent.openai}。
|
||||
*/
|
||||
public final class OpenAI {
|
||||
|
||||
private OpenAI() {}
|
||||
|
||||
public static OpenAISpec of(String modelName) {
|
||||
return new OpenAISpec(modelName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.yomahub.liteflow.agent.openai;
|
||||
|
||||
import com.yomahub.liteflow.agent.model.CredentialResolver;
|
||||
import com.yomahub.liteflow.agent.model.ModelSpec;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.PlatformCredential;
|
||||
import io.agentscope.core.model.GenerateOptions;
|
||||
import io.agentscope.core.model.Model;
|
||||
import io.agentscope.core.model.OpenAIChatModel;
|
||||
|
||||
/**
|
||||
* OpenAI 系(含 OpenAI 兼容族)通用 spec。
|
||||
* 暴露 OpenAI 平台特有的 reasoningEffort / frequencyPenalty / presencePenalty 等参数。
|
||||
*/
|
||||
public class OpenAISpec extends ModelSpec<OpenAISpec> {
|
||||
|
||||
private final String modelName;
|
||||
private String reasoningEffort;
|
||||
private Double frequencyPenalty;
|
||||
private Double presencePenalty;
|
||||
|
||||
public OpenAISpec(String modelName) {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
public OpenAISpec reasoningEffort(String level) { this.reasoningEffort = level; return this; }
|
||||
public OpenAISpec frequencyPenalty(double v) { this.frequencyPenalty = v; return this; }
|
||||
public OpenAISpec presencePenalty(double v) { this.presencePenalty = v; return this; }
|
||||
|
||||
public String getModelName() { return modelName; }
|
||||
public String getReasoningEffort() { return reasoningEffort; }
|
||||
public Double getFrequencyPenalty() { return frequencyPenalty; }
|
||||
public Double getPresencePenalty() { return presencePenalty; }
|
||||
|
||||
@Override
|
||||
public Model resolve(AgentConfig cfg) {
|
||||
PlatformCredential cred = CredentialResolver.requireFirstClass(
|
||||
cfg.getOpenai(), "liteflow.agent.openai");
|
||||
return buildModel(cred.getApiKey(), cred.getBaseUrl());
|
||||
}
|
||||
|
||||
/** 子类(OpenAICompatibleSpec)可覆盖以提供不同 baseUrl / apiKey 来源。 */
|
||||
protected Model buildModel(String apiKey, String baseUrl) {
|
||||
OpenAIChatModel.Builder builder = OpenAIChatModel.builder()
|
||||
.apiKey(apiKey)
|
||||
.modelName(modelName);
|
||||
if (baseUrl != null && !baseUrl.isBlank()) {
|
||||
builder.baseUrl(baseUrl);
|
||||
}
|
||||
GenerateOptions options = buildGenerateOptions();
|
||||
if (options != null) {
|
||||
builder.generateOptions(options);
|
||||
}
|
||||
if (getStream() != null) {
|
||||
builder.stream(getStream());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** 把共性 + 个性参数装配成 GenerateOptions;全部为 null 时返回 null。 */
|
||||
protected GenerateOptions buildGenerateOptions() {
|
||||
if (getTemperature() == null && getTopP() == null && getTopK() == null
|
||||
&& getMaxTokens() == null && getSeed() == null
|
||||
&& getCacheControl() == null
|
||||
&& reasoningEffort == null
|
||||
&& frequencyPenalty == null && presencePenalty == null) {
|
||||
return null;
|
||||
}
|
||||
GenerateOptions.Builder b = GenerateOptions.builder();
|
||||
if (getTemperature() != null) b.temperature(getTemperature());
|
||||
if (getTopP() != null) b.topP(getTopP());
|
||||
if (getTopK() != null) b.topK(getTopK());
|
||||
if (getMaxTokens() != null) b.maxTokens(getMaxTokens());
|
||||
if (getSeed() != null) b.seed(getSeed());
|
||||
if (getCacheControl() != null) b.cacheControl(getCacheControl());
|
||||
if (reasoningEffort != null) b.reasoningEffort(reasoningEffort);
|
||||
if (frequencyPenalty != null) b.frequencyPenalty(frequencyPenalty);
|
||||
if (presencePenalty != null) b.presencePenalty(presencePenalty);
|
||||
return b.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.yomahub.liteflow.agent.openai;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import io.agentscope.core.model.Model;
|
||||
import io.agentscope.core.model.OpenAIChatModel;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class OpenAIEntryTest {
|
||||
|
||||
@Test
|
||||
void buildsOpenAIChatModelWithGivenModelName() {
|
||||
AgentConfig cfg = new AgentConfig();
|
||||
cfg.getOpenai().setApiKey("sk-test");
|
||||
|
||||
OpenAISpec spec = OpenAI.of("gpt-4o").temperature(0.7);
|
||||
Model model = spec.resolve(cfg);
|
||||
|
||||
assertTrue(model instanceof OpenAIChatModel);
|
||||
assertEquals("gpt-4o", ((OpenAIChatModel) model).getModelName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwsWhenApiKeyMissing() {
|
||||
AgentConfig cfg = new AgentConfig(); // openai credential not set
|
||||
OpenAISpec spec = OpenAI.of("gpt-4o");
|
||||
AgentConfigException ex = assertThrows(AgentConfigException.class,
|
||||
() -> spec.resolve(cfg));
|
||||
assertTrue(ex.getMessage().contains("liteflow.agent.openai.api-key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void specSettersReturnSubclassType() {
|
||||
// 编译期断言:fluent 链返回 OpenAISpec,能链式调用 OpenAI 特有方法
|
||||
OpenAISpec spec = OpenAI.of("gpt-4o")
|
||||
.temperature(0.7)
|
||||
.topP(0.9)
|
||||
.reasoningEffort("high")
|
||||
.frequencyPenalty(0.1)
|
||||
.presencePenalty(0.2);
|
||||
assertNotNull(spec);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user