mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-10 03:07:32 +08:00
feat(react-agent-anthropic): add Anthropic entry and AnthropicSpec
Anthropic.of(modelName) and AnthropicCompatible.custom(configKey, modelName) return AnthropicSpec, with a thinking sub-builder using Anthropic's native budget/enabled terms. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package com.yomahub.liteflow.agent.anthropic;
|
||||
|
||||
public final class Anthropic {
|
||||
private Anthropic() {}
|
||||
public static AnthropicSpec of(String modelName) {
|
||||
return new AnthropicSpec(modelName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.yomahub.liteflow.agent.anthropic;
|
||||
|
||||
public final class AnthropicCompatible {
|
||||
private AnthropicCompatible() {}
|
||||
public static AnthropicSpec custom(String configKey, String modelName) {
|
||||
return new AnthropicSpec(modelName, configKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.yomahub.liteflow.agent.anthropic;
|
||||
|
||||
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.AnthropicChatModel;
|
||||
import io.agentscope.core.model.GenerateOptions;
|
||||
import io.agentscope.core.model.Model;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AnthropicSpec extends ModelSpec<AnthropicSpec> {
|
||||
|
||||
private final String modelName;
|
||||
private Integer thinkingBudget;
|
||||
private Boolean thinkingEnabled;
|
||||
|
||||
/** null 表示走头等平台 (cfg.getAnthropic());非 null 表示走 anthropic-compatible map。 */
|
||||
private final String compatibleConfigKey;
|
||||
|
||||
public AnthropicSpec(String modelName) {
|
||||
this(modelName, null);
|
||||
}
|
||||
|
||||
public AnthropicSpec(String modelName, String compatibleConfigKey) {
|
||||
this.modelName = modelName;
|
||||
this.compatibleConfigKey = compatibleConfigKey;
|
||||
}
|
||||
|
||||
public AnthropicSpec thinking(Consumer<AnthropicThinking> c) {
|
||||
AnthropicThinking t = new AnthropicThinking();
|
||||
c.accept(t);
|
||||
this.thinkingBudget = t.getBudget();
|
||||
this.thinkingEnabled = t.getEnabled();
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getModelName() { return modelName; }
|
||||
public Integer getThinkingBudget() { return thinkingBudget; }
|
||||
public Boolean getThinkingEnabled() { return thinkingEnabled; }
|
||||
|
||||
@Override
|
||||
public Model resolve(AgentConfig cfg) {
|
||||
PlatformCredential cred;
|
||||
if (compatibleConfigKey == null) {
|
||||
cred = CredentialResolver.requireFirstClass(
|
||||
cfg.getAnthropic(), "liteflow.agent.anthropic");
|
||||
} else {
|
||||
cred = CredentialResolver.requireCompatible(
|
||||
cfg.getAnthropicCompatible(), compatibleConfigKey,
|
||||
"liteflow.agent.anthropic-compatible");
|
||||
}
|
||||
|
||||
AnthropicChatModel.Builder builder = AnthropicChatModel.builder()
|
||||
.apiKey(cred.getApiKey())
|
||||
.modelName(modelName);
|
||||
if (cred.getBaseUrl() != null && !cred.getBaseUrl().isBlank()) {
|
||||
builder.baseUrl(cred.getBaseUrl());
|
||||
}
|
||||
GenerateOptions options = buildGenerateOptions();
|
||||
if (options != null) {
|
||||
builder.defaultOptions(options);
|
||||
}
|
||||
if (getStream() != null) {
|
||||
builder.stream(getStream());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private GenerateOptions buildGenerateOptions() {
|
||||
if (getTemperature() == null && getTopP() == null && getTopK() == null
|
||||
&& getMaxTokens() == null && getSeed() == null
|
||||
&& thinkingBudget == 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 (thinkingBudget != null) b.thinkingBudget(thinkingBudget);
|
||||
return b.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.yomahub.liteflow.agent.anthropic;
|
||||
|
||||
/** Anthropic 平台 thinking 子构建器。沿用 Anthropic 原生术语。 */
|
||||
public final class AnthropicThinking {
|
||||
private Integer budget;
|
||||
private Boolean enabled;
|
||||
|
||||
public AnthropicThinking budget(int tokens) { this.budget = tokens; return this; }
|
||||
public AnthropicThinking enabled(boolean v) { this.enabled = v; return this; }
|
||||
|
||||
public Integer getBudget() { return budget; }
|
||||
public Boolean getEnabled() { return enabled; }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.yomahub.liteflow.agent.anthropic;
|
||||
|
||||
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.AnthropicChatModel;
|
||||
import io.agentscope.core.model.Model;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class AnthropicEntryTest {
|
||||
|
||||
@Test
|
||||
void buildsAnthropicChatModel() {
|
||||
AgentConfig cfg = new AgentConfig();
|
||||
cfg.getAnthropic().setApiKey("ak-test");
|
||||
|
||||
Model model = Anthropic.of("claude-sonnet-4-6")
|
||||
.temperature(0.5)
|
||||
.thinking(t -> t.budget(2000).enabled(true))
|
||||
.resolve(cfg);
|
||||
|
||||
assertTrue(model instanceof AnthropicChatModel);
|
||||
assertEquals("claude-sonnet-4-6", ((AnthropicChatModel) model).getModelName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwsWhenApiKeyMissing() {
|
||||
AgentConfig cfg = new AgentConfig();
|
||||
AgentConfigException ex = assertThrows(AgentConfigException.class,
|
||||
() -> Anthropic.of("claude-sonnet-4-6").resolve(cfg));
|
||||
assertTrue(ex.getMessage().contains("liteflow.agent.anthropic.api-key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void compatibleResolvesFromAnthropicCompatibleMap() {
|
||||
AgentConfig cfg = new AgentConfig();
|
||||
PlatformCredential c = new PlatformCredential();
|
||||
c.setApiKey("anc-key");
|
||||
c.setBaseUrl("https://my.anthropic-mirror/v1");
|
||||
cfg.getAnthropicCompatible().put("mirror", c);
|
||||
|
||||
Model model = AnthropicCompatible.custom("mirror", "claude-haiku")
|
||||
.resolve(cfg);
|
||||
assertEquals("claude-haiku", ((AnthropicChatModel) model).getModelName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void compatibleThrowsWhenKeyMissing() {
|
||||
AgentConfig cfg = new AgentConfig();
|
||||
AgentConfigException ex = assertThrows(AgentConfigException.class,
|
||||
() -> AnthropicCompatible.custom("mirror", "x").resolve(cfg));
|
||||
assertTrue(ex.getMessage().contains("anthropic-compatible.mirror"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void thinkingBuilderStoresBudgetAndEnabled() {
|
||||
AnthropicSpec spec = Anthropic.of("claude-sonnet-4-6")
|
||||
.thinking(t -> t.budget(1500).enabled(true));
|
||||
assertEquals(1500, spec.getThinkingBudget());
|
||||
assertEquals(Boolean.TRUE, spec.getThinkingEnabled());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user