mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
394d1d1197
* fix: add MaxTokens and Temperature fields to AgentInstance and update related logic * feat: add MaxTokens and Temperature options to SubagentManager and update tool loop logic * feat: add default temperature handling and update related tests * feat: allow temperature 0 and distinguish unset * fix: format MockLLMProvider struct in subagent_tool_test.go
160 lines
4.6 KiB
Go
160 lines
4.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/config"
|
|
"github.com/sipeed/picoclaw/pkg/providers"
|
|
"github.com/sipeed/picoclaw/pkg/routing"
|
|
"github.com/sipeed/picoclaw/pkg/session"
|
|
"github.com/sipeed/picoclaw/pkg/tools"
|
|
)
|
|
|
|
// AgentInstance represents a fully configured agent with its own workspace,
|
|
// session manager, context builder, and tool registry.
|
|
type AgentInstance struct {
|
|
ID string
|
|
Name string
|
|
Model string
|
|
Fallbacks []string
|
|
Workspace string
|
|
MaxIterations int
|
|
MaxTokens int
|
|
Temperature float64
|
|
ContextWindow int
|
|
Provider providers.LLMProvider
|
|
Sessions *session.SessionManager
|
|
ContextBuilder *ContextBuilder
|
|
Tools *tools.ToolRegistry
|
|
Subagents *config.SubagentsConfig
|
|
SkillsFilter []string
|
|
Candidates []providers.FallbackCandidate
|
|
}
|
|
|
|
// NewAgentInstance creates an agent instance from config.
|
|
func NewAgentInstance(
|
|
agentCfg *config.AgentConfig,
|
|
defaults *config.AgentDefaults,
|
|
cfg *config.Config,
|
|
provider providers.LLMProvider,
|
|
) *AgentInstance {
|
|
workspace := resolveAgentWorkspace(agentCfg, defaults)
|
|
os.MkdirAll(workspace, 0755)
|
|
|
|
model := resolveAgentModel(agentCfg, defaults)
|
|
fallbacks := resolveAgentFallbacks(agentCfg, defaults)
|
|
|
|
restrict := defaults.RestrictToWorkspace
|
|
toolsRegistry := tools.NewToolRegistry()
|
|
toolsRegistry.Register(tools.NewReadFileTool(workspace, restrict))
|
|
toolsRegistry.Register(tools.NewWriteFileTool(workspace, restrict))
|
|
toolsRegistry.Register(tools.NewListDirTool(workspace, restrict))
|
|
toolsRegistry.Register(tools.NewExecToolWithConfig(workspace, restrict, cfg))
|
|
toolsRegistry.Register(tools.NewEditFileTool(workspace, restrict))
|
|
toolsRegistry.Register(tools.NewAppendFileTool(workspace, restrict))
|
|
|
|
sessionsDir := filepath.Join(workspace, "sessions")
|
|
sessionsManager := session.NewSessionManager(sessionsDir)
|
|
|
|
contextBuilder := NewContextBuilder(workspace)
|
|
contextBuilder.SetToolsRegistry(toolsRegistry)
|
|
|
|
agentID := routing.DefaultAgentID
|
|
agentName := ""
|
|
var subagents *config.SubagentsConfig
|
|
var skillsFilter []string
|
|
|
|
if agentCfg != nil {
|
|
agentID = routing.NormalizeAgentID(agentCfg.ID)
|
|
agentName = agentCfg.Name
|
|
subagents = agentCfg.Subagents
|
|
skillsFilter = agentCfg.Skills
|
|
}
|
|
|
|
maxIter := defaults.MaxToolIterations
|
|
if maxIter == 0 {
|
|
maxIter = 20
|
|
}
|
|
|
|
maxTokens := defaults.MaxTokens
|
|
if maxTokens == 0 {
|
|
maxTokens = 8192
|
|
}
|
|
|
|
temperature := 0.7
|
|
if defaults.Temperature != nil {
|
|
temperature = *defaults.Temperature
|
|
}
|
|
|
|
// Resolve fallback candidates
|
|
modelCfg := providers.ModelConfig{
|
|
Primary: model,
|
|
Fallbacks: fallbacks,
|
|
}
|
|
candidates := providers.ResolveCandidates(modelCfg, defaults.Provider)
|
|
|
|
return &AgentInstance{
|
|
ID: agentID,
|
|
Name: agentName,
|
|
Model: model,
|
|
Fallbacks: fallbacks,
|
|
Workspace: workspace,
|
|
MaxIterations: maxIter,
|
|
MaxTokens: maxTokens,
|
|
Temperature: temperature,
|
|
ContextWindow: maxTokens,
|
|
Provider: provider,
|
|
Sessions: sessionsManager,
|
|
ContextBuilder: contextBuilder,
|
|
Tools: toolsRegistry,
|
|
Subagents: subagents,
|
|
SkillsFilter: skillsFilter,
|
|
Candidates: candidates,
|
|
}
|
|
}
|
|
|
|
// resolveAgentWorkspace determines the workspace directory for an agent.
|
|
func resolveAgentWorkspace(agentCfg *config.AgentConfig, defaults *config.AgentDefaults) string {
|
|
if agentCfg != nil && strings.TrimSpace(agentCfg.Workspace) != "" {
|
|
return expandHome(strings.TrimSpace(agentCfg.Workspace))
|
|
}
|
|
if agentCfg == nil || agentCfg.Default || agentCfg.ID == "" || routing.NormalizeAgentID(agentCfg.ID) == "main" {
|
|
return expandHome(defaults.Workspace)
|
|
}
|
|
home, _ := os.UserHomeDir()
|
|
id := routing.NormalizeAgentID(agentCfg.ID)
|
|
return filepath.Join(home, ".picoclaw", "workspace-"+id)
|
|
}
|
|
|
|
// resolveAgentModel resolves the primary model for an agent.
|
|
func resolveAgentModel(agentCfg *config.AgentConfig, defaults *config.AgentDefaults) string {
|
|
if agentCfg != nil && agentCfg.Model != nil && strings.TrimSpace(agentCfg.Model.Primary) != "" {
|
|
return strings.TrimSpace(agentCfg.Model.Primary)
|
|
}
|
|
return defaults.Model
|
|
}
|
|
|
|
// resolveAgentFallbacks resolves the fallback models for an agent.
|
|
func resolveAgentFallbacks(agentCfg *config.AgentConfig, defaults *config.AgentDefaults) []string {
|
|
if agentCfg != nil && agentCfg.Model != nil && agentCfg.Model.Fallbacks != nil {
|
|
return agentCfg.Model.Fallbacks
|
|
}
|
|
return defaults.ModelFallbacks
|
|
}
|
|
|
|
func expandHome(path string) string {
|
|
if path == "" {
|
|
return path
|
|
}
|
|
if path[0] == '~' {
|
|
home, _ := os.UserHomeDir()
|
|
if len(path) > 1 && path[1] == '/' {
|
|
return home + path[1:]
|
|
}
|
|
return home
|
|
}
|
|
return path
|
|
}
|