diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-08 23:44:14 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-08 23:44:14 +0000 |
| commit | e5255dcc85c7c4bb0e8838c0064dd545ed0bd830 (patch) | |
| tree | 4ab9eb814df90ebf6ac8db8a83a3387256e4bff4 /internal/executor | |
| parent | cfbcc7b921c48fd2eaebcd814b49f3b8a02d7823 (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/executor')
| -rw-r--r-- | internal/executor/classifier.go | 43 | ||||
| -rw-r--r-- | internal/executor/classifier_test.go | 6 | ||||
| -rw-r--r-- | internal/executor/gemini_test.go | 4 |
3 files changed, 40 insertions, 13 deletions
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) } |
