feat(react-agent-gemini): add Gemini entry and GeminiSpec

Gemini.of(modelName) returns GeminiSpec exposing a thinking sub-builder
that supports both Gemini 2.5's level() and the legacy budget() form.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
everywhere.z
2026-04-29 18:22:33 +08:00
parent 994cbd2a25
commit 64d5404b2c
4 changed files with 133 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
package com.yomahub.liteflow.agent.gemini;
public final class Gemini {
private Gemini() {}
public static GeminiSpec of(String modelName) {
return new GeminiSpec(modelName);
}
}

View File

@@ -0,0 +1,67 @@
package com.yomahub.liteflow.agent.gemini;
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.GeminiChatModel;
import io.agentscope.core.model.GenerateOptions;
import io.agentscope.core.model.Model;
import java.util.function.Consumer;
public class GeminiSpec extends ModelSpec<GeminiSpec> {
private final String modelName;
private String thinkingLevel;
private Integer thinkingBudget;
public GeminiSpec(String modelName) { this.modelName = modelName; }
public GeminiSpec thinking(Consumer<GeminiThinking> c) {
GeminiThinking t = new GeminiThinking();
c.accept(t);
this.thinkingLevel = t.getLevel();
this.thinkingBudget = t.getBudget();
return this;
}
public String getModelName() { return modelName; }
public String getThinkingLevel() { return thinkingLevel; }
public Integer getThinkingBudget() { return thinkingBudget; }
@Override
public Model resolve(AgentConfig cfg) {
PlatformCredential cred = CredentialResolver.requireFirstClass(
cfg.getGemini(), "liteflow.agent.gemini");
GeminiChatModel.Builder builder = GeminiChatModel.builder()
.apiKey(cred.getApiKey())
.modelName(modelName);
GenerateOptions options = buildGenerateOptions();
if (options != null) {
builder.defaultOptions(options);
}
if (getStream() != null) {
builder.streamEnabled(getStream());
}
return builder.build();
}
private GenerateOptions buildGenerateOptions() {
if (getTemperature() == null && getTopP() == null && getTopK() == null
&& getMaxTokens() == null && getSeed() == null
&& thinkingLevel == 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 (thinkingLevel != null) b.reasoningEffort(thinkingLevel);
if (thinkingBudget != null) b.thinkingBudget(thinkingBudget);
return b.build();
}
}

View File

@@ -0,0 +1,16 @@
package com.yomahub.liteflow.agent.gemini;
/**
* Gemini 平台 thinking 子构建器。
* Gemini 2.5 使用 "thinking_level"low/medium/high老接口用 "thinking_budget"token 数)。
*/
public final class GeminiThinking {
private String level;
private Integer budget;
public GeminiThinking level(String level) { this.level = level; return this; }
public GeminiThinking budget(int tokens) { this.budget = tokens; return this; }
public String getLevel() { return level; }
public Integer getBudget() { return budget; }
}

View File

@@ -0,0 +1,42 @@
package com.yomahub.liteflow.agent.gemini;
import com.yomahub.liteflow.agent.exception.AgentConfigException;
import com.yomahub.liteflow.property.agent.AgentConfig;
import io.agentscope.core.model.GeminiChatModel;
import io.agentscope.core.model.Model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GeminiEntryTest {
@Test
void buildsGeminiChatModel() {
AgentConfig cfg = new AgentConfig();
cfg.getGemini().setApiKey("g-key");
Model model = Gemini.of("gemini-2.5-pro")
.temperature(0.6)
.thinking(t -> t.level("high"))
.resolve(cfg);
assertTrue(model instanceof GeminiChatModel);
assertEquals("gemini-2.5-pro", ((GeminiChatModel) model).getModelName());
}
@Test
void thinkingLevelAndBudgetStored() {
GeminiSpec spec = Gemini.of("gemini-2.5-pro")
.thinking(t -> t.level("medium").budget(1024));
assertEquals("medium", spec.getThinkingLevel());
assertEquals(1024, spec.getThinkingBudget());
}
@Test
void throwsWhenApiKeyMissing() {
AgentConfig cfg = new AgentConfig();
AgentConfigException ex = assertThrows(AgentConfigException.class,
() -> Gemini.of("gemini-2.5-pro").resolve(cfg));
assertTrue(ex.getMessage().contains("liteflow.agent.gemini.api-key"));
}
}