feat(react-agent-openai): add OpenAI-compatible vendor entries

DeepSeek/Kimi/GLM/Minimax static entries with default baseUrls,
plus OpenAICompatible.custom() fallback for arbitrary vendors.
All read credentials from liteflow.agent.openai-compatible.<key>.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
everywhere.z
2026-04-29 18:15:36 +08:00
parent 5cc43acff2
commit 679dc33ca2
7 changed files with 167 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.agent.openai;
public final class DeepSeek {
private static final String CONFIG_KEY = "deepseek";
private static final String BASE_URL = "https://api.deepseek.com/v1";
private DeepSeek() {}
public static OpenAICompatibleSpec of(String modelName) {
return new OpenAICompatibleSpec(CONFIG_KEY, modelName, BASE_URL);
}
}

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.agent.openai;
public final class GLM {
private static final String CONFIG_KEY = "glm";
private static final String BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
private GLM() {}
public static OpenAICompatibleSpec of(String modelName) {
return new OpenAICompatibleSpec(CONFIG_KEY, modelName, BASE_URL);
}
}

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.agent.openai;
public final class Kimi {
private static final String CONFIG_KEY = "kimi";
private static final String BASE_URL = "https://api.moonshot.cn/v1";
private Kimi() {}
public static OpenAICompatibleSpec of(String modelName) {
return new OpenAICompatibleSpec(CONFIG_KEY, modelName, BASE_URL);
}
}

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.agent.openai;
public final class Minimax {
private static final String CONFIG_KEY = "minimax";
private static final String BASE_URL = "https://api.minimax.chat/v1";
private Minimax() {}
public static OpenAICompatibleSpec of(String modelName) {
return new OpenAICompatibleSpec(CONFIG_KEY, modelName, BASE_URL);
}
}

View File

@@ -0,0 +1,14 @@
package com.yomahub.liteflow.agent.openai;
/**
* 自定义 OpenAI 兼容厂商兜底入口。
* 用户在配置中挂 {@code liteflow.agent.openai-compatible.<configKey>}
* 至少要提供 api-keybase-url 也由用户配置决定(无默认值)。
*/
public final class OpenAICompatible {
private OpenAICompatible() {}
public static OpenAICompatibleSpec custom(String configKey, String modelName) {
return new OpenAICompatibleSpec(configKey, modelName, null);
}
}

View File

@@ -0,0 +1,33 @@
package com.yomahub.liteflow.agent.openai;
import com.yomahub.liteflow.agent.model.CredentialResolver;
import com.yomahub.liteflow.property.agent.AgentConfig;
import com.yomahub.liteflow.property.agent.PlatformCredential;
import io.agentscope.core.model.Model;
/**
* OpenAI 兼容族 spec。与 {@link OpenAISpec} 共享所有可调参数,
* 但 credential 来源换成 {@code liteflow.agent.openai-compatible.<configKey>}。
* 子类内置默认 baseUrl用户配置中的 baseUrl 优先生效。
*/
public class OpenAICompatibleSpec extends OpenAISpec {
private final String configKey;
private final String defaultBaseUrl;
public OpenAICompatibleSpec(String configKey, String modelName, String defaultBaseUrl) {
super(modelName);
this.configKey = configKey;
this.defaultBaseUrl = defaultBaseUrl;
}
@Override
public Model resolve(AgentConfig cfg) {
PlatformCredential cred = CredentialResolver.requireCompatible(
cfg.getOpenaiCompatible(), configKey, "liteflow.agent.openai-compatible");
String baseUrl = (cred.getBaseUrl() != null && !cred.getBaseUrl().isBlank())
? cred.getBaseUrl()
: defaultBaseUrl;
return buildModel(cred.getApiKey(), baseUrl);
}
}

View File

@@ -0,0 +1,76 @@
package com.yomahub.liteflow.agent.openai;
import com.yomahub.liteflow.agent.exception.AgentConfigException;
import com.yomahub.liteflow.property.agent.AgentConfig;
import com.yomahub.liteflow.property.agent.PlatformCredential;
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 OpenAICompatibleEntryTest {
private static AgentConfig cfgWith(String key, String apiKey) {
AgentConfig cfg = new AgentConfig();
PlatformCredential c = new PlatformCredential();
c.setApiKey(apiKey);
cfg.getOpenaiCompatible().put(key, c);
return cfg;
}
@Test
void deepseekResolvesFromOpenaiCompatibleDeepseek() {
AgentConfig cfg = cfgWith("deepseek", "ds-key");
Model model = DeepSeek.of("deepseek-chat").temperature(0.7).resolve(cfg);
assertTrue(model instanceof OpenAIChatModel);
assertEquals("deepseek-chat", ((OpenAIChatModel) model).getModelName());
}
@Test
void kimiResolvesFromOpenaiCompatibleKimi() {
AgentConfig cfg = cfgWith("kimi", "kimi-key");
Model model = Kimi.of("kimi-k2").resolve(cfg);
assertEquals("kimi-k2", ((OpenAIChatModel) model).getModelName());
}
@Test
void glmResolvesFromOpenaiCompatibleGlm() {
AgentConfig cfg = cfgWith("glm", "glm-key");
Model model = GLM.of("glm-4").resolve(cfg);
assertEquals("glm-4", ((OpenAIChatModel) model).getModelName());
}
@Test
void minimaxResolvesFromOpenaiCompatibleMinimax() {
AgentConfig cfg = cfgWith("minimax", "mm-key");
Model model = Minimax.of("abab6.5").resolve(cfg);
assertEquals("abab6.5", ((OpenAIChatModel) model).getModelName());
}
@Test
void customResolvesFromGivenConfigKey() {
AgentConfig cfg = cfgWith("myvendor", "my-key");
PlatformCredential c = cfg.getOpenaiCompatible().get("myvendor");
c.setBaseUrl("https://my.vendor/v1");
Model model = OpenAICompatible.custom("myvendor", "my-model").resolve(cfg);
assertEquals("my-model", ((OpenAIChatModel) model).getModelName());
}
@Test
void customThrowsWhenKeyMissing() {
AgentConfig cfg = new AgentConfig();
AgentConfigException ex = assertThrows(AgentConfigException.class,
() -> OpenAICompatible.custom("myvendor", "x").resolve(cfg));
assertTrue(ex.getMessage().contains("openai-compatible.myvendor"));
}
@Test
void deepseekThrowsWhenCredentialMissing() {
AgentConfig cfg = new AgentConfig();
AgentConfigException ex = assertThrows(AgentConfigException.class,
() -> DeepSeek.of("deepseek-chat").resolve(cfg));
assertTrue(ex.getMessage().contains("openai-compatible.deepseek"));
}
}