summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-08 23:44:14 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-08 23:44:14 +0000
commite5255dcc85c7c4bb0e8838c0064dd545ed0bd830 (patch)
tree4ab9eb814df90ebf6ac8db8a83a3387256e4bff4 /internal
parentcfbcc7b921c48fd2eaebcd814b49f3b8a02d7823 (diff)
executor: update gemini model to 2.5-flash-lite and fix classifier parsing
Update the default Gemini model and classification prompt to use gemini-2.5-flash-lite, which is the current available model. Improved the classifier's parsing logic to correctly handle the JSON envelope returned by the gemini CLI (stripping 'response' wrapper and 'Loaded cached credentials' noise).
Diffstat (limited to 'internal')
-rw-r--r--internal/api/elaborate.go2
-rw-r--r--internal/executor/classifier.go43
-rw-r--r--internal/executor/classifier_test.go6
-rw-r--r--internal/executor/gemini_test.go4
4 files changed, 41 insertions, 14 deletions
diff --git a/internal/api/elaborate.go b/internal/api/elaborate.go
index 907cb98..2f6c707 100644
--- a/internal/api/elaborate.go
+++ b/internal/api/elaborate.go
@@ -29,7 +29,7 @@ Output ONLY a valid JSON object matching this schema (no markdown fences, no pro
"description": string — 1-2 sentence summary,
"agent": {
"type": "claude" | "gemini",
- "model": string — "sonnet" for claude, "gemini-2.0-flash" for gemini,
+ "model": string — "sonnet" for claude, "gemini-2.5-flash-lite" for gemini,
"instructions": string — detailed, step-by-step instructions for the agent,
` + workDirLine + `
"max_budget_usd": number — conservative estimate (0.25–5.00),
diff --git a/internal/executor/classifier.go b/internal/executor/classifier.go
index 79ebc27..28581ea 100644
--- a/internal/executor/classifier.go
+++ b/internal/executor/classifier.go
@@ -39,14 +39,14 @@ Claude:
- claude-3-5-haiku-20241022 (fast, cheap)
Gemini:
-- gemini-2.0-flash-lite (fastest, most efficient, best for simple tasks)
-- gemini-2.0-flash (fast, multimodal)
+- gemini-2.5-flash-lite (fastest, most efficient, best for simple tasks)
+- gemini-3-flash-preview (fast, multimodal)
- gemini-1.5-flash (fast, balanced)
- gemini-1.5-pro (more powerful, larger context)
Selection Criteria:
- Agent: Prefer the one with least running tasks and no active rate limit.
-- Model: Select based on task complexity. Use powerful models (opus, pro) for complex reasoning/coding, flash-lite/flash/haiku for simple tasks.
+- Model: Select based on task complexity. Use powerful models (opus, pro, pro-preview) for complex reasoning/coding, flash-lite/flash/haiku for simple tasks.
Task:
Name: %s
@@ -81,7 +81,7 @@ func (c *Classifier) Classify(ctx context.Context, taskName, instructions string
// Use a minimal model for classification to be fast and cheap.
args := []string{
"--prompt", prompt,
- "--model", "gemini-2.0-flash-lite",
+ "--model", "gemini-2.5-flash-lite",
"--output-format", "json",
}
@@ -94,15 +94,42 @@ func (c *Classifier) Classify(ctx context.Context, taskName, instructions string
return nil, fmt.Errorf("classifier failed: %w", err)
}
- var cls Classification
- // Gemini might wrap the JSON in markdown code blocks.
- cleanOut := strings.TrimSpace(string(out))
+ // 1. Parse the JSON envelope from the gemini CLI.
+ var cliOut struct {
+ Response string `json:"response"`
+ }
+ if err := json.Unmarshal(out, &cliOut); err != nil {
+ // If it's not JSON, it might be raw text (though we requested JSON).
+ // This can happen if the CLI prints "Loaded cached credentials" or other info.
+ cliOut.Response = string(out)
+ }
+
+ // 2. Extract the model response from the "response" field if present.
+ // If it was already raw text, cliOut.Response will have it.
+ cleanOut := strings.TrimSpace(cliOut.Response)
+
+ // 3. Clean up "Loaded cached credentials" or other noise that might be in the string
+ // if we fell back to string(out).
+ if strings.Contains(cleanOut, "Loaded cached credentials.") {
+ lines := strings.Split(cleanOut, "\n")
+ var modelLines []string
+ for _, line := range lines {
+ if !strings.Contains(line, "Loaded cached credentials.") {
+ modelLines = append(modelLines, line)
+ }
+ }
+ cleanOut = strings.TrimSpace(strings.Join(modelLines, "\n"))
+ }
+
+ // 4. Gemini might wrap the JSON in markdown code blocks.
cleanOut = strings.TrimPrefix(cleanOut, "```json")
+ cleanOut = strings.TrimPrefix(cleanOut, "```") // fallback
cleanOut = strings.TrimSuffix(cleanOut, "```")
cleanOut = strings.TrimSpace(cleanOut)
+ var cls Classification
if err := json.Unmarshal([]byte(cleanOut), &cls); err != nil {
- return nil, fmt.Errorf("failed to parse classification JSON: %w\nOutput: %s", err, cleanOut)
+ return nil, fmt.Errorf("failed to parse classification JSON: %w\nOriginal Output: %s\nCleaned Output: %s", err, string(out), cleanOut)
}
return &cls, nil
diff --git a/internal/executor/classifier_test.go b/internal/executor/classifier_test.go
index 4de44ca..631952f 100644
--- a/internal/executor/classifier_test.go
+++ b/internal/executor/classifier_test.go
@@ -11,7 +11,7 @@ func TestClassifier_Classify_Mock(t *testing.T) {
// Create a temporary mock binary.
mockBinary := filepathJoin(t.TempDir(), "mock-gemini")
mockContent := `#!/bin/sh
-echo '{"agent_type": "gemini", "model": "gemini-2.0-flash", "reason": "test reason"}'
+echo '{"response": "{\"agent_type\": \"gemini\", \"model\": \"gemini-2.5-flash-lite\", \"reason\": \"test reason\"}"}'
`
if err := os.WriteFile(mockBinary, []byte(mockContent), 0755); err != nil {
t.Fatal(err)
@@ -31,8 +31,8 @@ echo '{"agent_type": "gemini", "model": "gemini-2.0-flash", "reason": "test reas
if cls.AgentType != "gemini" {
t.Errorf("expected gemini, got %s", cls.AgentType)
}
- if cls.Model != "gemini-2.0-flash" {
- t.Errorf("expected gemini-2.0-flash, got %s", cls.Model)
+ if cls.Model != "gemini-2.5-flash-lite" {
+ t.Errorf("expected gemini-2.5-flash-lite, got %s", cls.Model)
}
}
diff --git a/internal/executor/gemini_test.go b/internal/executor/gemini_test.go
index 42253da..363f0e9 100644
--- a/internal/executor/gemini_test.go
+++ b/internal/executor/gemini_test.go
@@ -17,7 +17,7 @@ func TestGeminiRunner_BuildArgs_BasicTask(t *testing.T) {
Agent: task.AgentConfig{
Type: "gemini",
Instructions: "fix the bug",
- Model: "gemini-2.0-flash",
+ Model: "gemini-2.5-flash-lite",
SkipPlanning: true,
},
}
@@ -33,7 +33,7 @@ func TestGeminiRunner_BuildArgs_BasicTask(t *testing.T) {
for _, a := range args {
argMap[a] = true
}
- for _, want := range []string{"--output-format", "stream-json", "--model", "gemini-2.0-flash"} {
+ for _, want := range []string{"--output-format", "stream-json", "--model", "gemini-2.5-flash-lite"} {
if !argMap[want] {
t.Errorf("missing arg %q in %v", want, args)
}