summaryrefslogtreecommitdiff
path: root/internal/executor/classifier.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-05-13 04:02:20 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-05-13 04:02:20 +0000
commit68399a598924775a3ec22a39c2336ae497fb07f3 (patch)
tree29ade8224eb51eca47a1d9d03bb4d0d3653a72aa /internal/executor/classifier.go
parentf01231cc45f41ce2dc37072e77428e467ef3fc15 (diff)
parentd970c0730ff0dc7d714d3261197d8ba52b5d21f4 (diff)
merge: integrate github/main — LocalRunner, real GeminiRunner, llm clientHEADmain
Merges 12 commits from github/main (formerly master) that were developed independently. Key additions: - LocalRunner: OpenAI-compatible local LLM execution (Ollama, LM Studio) - Real GeminiRunner with full sandbox parity to ClaudeRunner - llm.Client for enriching CI failures and elaboration via local model - retry.ParseRetryAfter moved to shared package - tokens_in/tokens_out columns in executions table Conflict resolutions: - Kept local main's VAPID/push, stories, projects, agent events schema - Merged both sets of Config fields (local + LocalModel from github/main) - Unified activePerAgent accounting (decActiveAgent helper) - Removed duplicate helpers from claude.go (now in helpers.go) - Fixed double-decrement bug in handleRunResult vs decActiveAgent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/executor/classifier.go')
-rw-r--r--internal/executor/classifier.go33
1 files changed, 33 insertions, 0 deletions
diff --git a/internal/executor/classifier.go b/internal/executor/classifier.go
index 7a474b6..049dc4f 100644
--- a/internal/executor/classifier.go
+++ b/internal/executor/classifier.go
@@ -6,6 +6,8 @@ import (
"fmt"
"os/exec"
"strings"
+
+ "github.com/thepeterstone/claudomator/internal/llm"
)
type Classification struct {
@@ -19,7 +21,12 @@ type SystemStatus struct {
RateLimited map[string]bool
}
+// Classifier picks a model for an incoming task. When LLM is non-nil the
+// classifier routes through the local OpenAI-compatible client (cheap,
+// private, fast). Otherwise it falls back to invoking the Gemini CLI
+// at GeminiBinaryPath.
type Classifier struct {
+ LLM *llm.Client
GeminiBinaryPath string
}
@@ -62,6 +69,10 @@ func (c *Classifier) Classify(ctx context.Context, taskName, instructions string
agentType, taskName, instructions, agentType,
)
+ if c.LLM != nil {
+ return c.classifyViaLLM(ctx, prompt, agentType)
+ }
+
binary := c.GeminiBinaryPath
if binary == "" {
binary = "gemini"
@@ -123,3 +134,25 @@ func (c *Classifier) Classify(ctx context.Context, taskName, instructions string
return &cls, nil
}
+
+// classifyViaLLM routes classification through the local OpenAI-compatible
+// client with response_format=json_object, so we get clean JSON without the
+// markdown-fence cleanup needed for the Gemini CLI fallback.
+func (c *Classifier) classifyViaLLM(ctx context.Context, prompt, agentType string) (*Classification, error) {
+ resp, err := c.LLM.Chat(ctx, llm.ChatRequest{
+ Messages: []llm.Message{{Role: "user", Content: prompt}},
+ ResponseJSON: true,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("classifier (local llm): %w", err)
+ }
+ body := strings.TrimSpace(resp.Content)
+ var cls Classification
+ if err := json.Unmarshal([]byte(body), &cls); err != nil {
+ return nil, fmt.Errorf("classifier (local llm): parse JSON: %w\nbody: %s", err, body)
+ }
+ if cls.AgentType == "" {
+ cls.AgentType = agentType
+ }
+ return &cls, nil
+}