Files
LocalAI/core/services/agents/config.go
T
Ettore Di Giacinto 84e2fc5eac feat(agents): support tool lists and required tool in distributed mode
LocalAGI 7e0947d added allowed_tools/excluded_tools and the
required_tool_before_finish gate. Single-node agents get them through
LocalAGI's runtime, but the distributed executor drives cogito directly
and its static config meta did not list the fields, so the agent form
hid them and the worker ignored them.

The distributed config now parses the tool lists from a JSON array or a
comma/newline separated string, and the meta entries match LocalAGI's.
The executor filters the knowledge base, skill and MCP tools (MCP via
cogito.WithMCPToolFilter) before the model sees them, and re-prompts the
model when it answers before the required tool returned "ok": true, up
to the configured number of reminders.

LocalAGI keeps its filter and gate helpers unexported, so a minimal copy
lives in core/services/agents/toolpolicy.go. A spec compares the meta
entries with LocalAGI's to catch drift.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-5-5 [Claude Code]
2026-09-27 20:49:12 +00:00

225 lines
8.3 KiB
Go

package agents
import "encoding/json"
// KB mode constants for AgentConfig.KBMode.
const (
KBModeAutoSearch = "auto_search" // default: auto-search KB on each user message
KBModeTools = "tools" // expose search_memory / add_memory as agent tools
KBModeBoth = "both" // auto-search + tools
)
// Skills mode constants for AgentConfig.SkillsMode.
const (
SkillsModePrompt = "prompt" // default: inject skill descriptions into system prompt
SkillsModeTools = "tools" // expose skills as callable tools
SkillsModeBoth = "both" // both prompt + tools
)
// Conversation storage mode constants for AgentConfig.ConversationStorageMode.
const (
ConvStorageUserOnly = "user_only" // default: store only user messages
ConvStorageUserAndAssistant = "user_and_assistant" // store user and assistant messages separately
ConvStorageWholeConversation = "whole_conversation" // store entire conversation as single block
)
// AgentConfig defines the configuration for an agent.
// JSON field names match LocalAGI's state.AgentConfig for import/export compatibility.
type AgentConfig struct {
// Integrations
Connector []ConnectorConfig `json:"connectors"`
Actions []ActionsConfig `json:"actions"`
DynamicPrompts []DynamicPromptsConfig `json:"dynamic_prompts"`
MCPServers []MCPServer `json:"mcp_servers"`
MCPSTDIOServers MCPSTDIOServers `json:"mcp_stdio_servers"`
MCPPrepareScript string `json:"mcp_prepare_script"`
Filters []FiltersConfig `json:"filters"`
// Basic info
Description string `json:"description"`
Name string `json:"name"`
// Models
Model string `json:"model"`
MultimodalModel string `json:"multimodal_model"`
TranscriptionModel string `json:"transcription_model"`
TranscriptionLanguage string `json:"transcription_language"`
TTSModel string `json:"tts_model"`
// API
APIURL string `json:"api_url"`
APIKey string `json:"api_key"`
LocalRAGURL string `json:"local_rag_url"`
LocalRAGAPIKey string `json:"local_rag_api_key"`
// Timing
LastMessageDuration string `json:"last_message_duration"`
PeriodicRuns string `json:"periodic_runs"`
SchedulerPollInterval string `json:"scheduler_poll_interval"`
// Behavior
StandaloneJob bool `json:"standalone_job"`
InitiateConversations bool `json:"initiate_conversations"`
CanPlan bool `json:"enable_planning"`
PlanReviewerModel string `json:"plan_reviewer_model"`
DisableSinkState bool `json:"disable_sink_state"` // legacy, kept for JSON compat — sink state is always disabled
PermanentGoal string `json:"permanent_goal"`
SystemPrompt string `json:"system_prompt"`
SkillsPrompt string `json:"skills_prompt"`
InnerMonologueTemplate string `json:"inner_monologue_template"`
SchedulerTaskTemplate string `json:"scheduler_task_template"`
// Knowledge base
EnableKnowledgeBase bool `json:"enable_kb"`
KBMode string `json:"kb_mode,omitempty"` // "auto_search" (default), "tools", "both"
KnowledgeBaseResults int `json:"kb_results"`
EnableKBCompaction bool `json:"enable_kb_compaction"`
KBCompactionInterval string `json:"kb_compaction_interval"`
KBCompactionSummarize bool `json:"kb_compaction_summarize"`
KBAutoSearch bool `json:"kb_auto_search"` // legacy, kept for JSON compat
KBAsTools bool `json:"kb_as_tools"` // legacy, kept for JSON compat
// Reasoning & tools
EnableReasoning bool `json:"enable_reasoning"` // legacy, kept for JSON compat
EnableForceReasoningTool bool `json:"enable_reasoning_tool"` // legacy, kept for JSON compat
EnableReasoningForInstruct bool `json:"enable_reasoning_for_instruct"` // enables ForceReasoning + ForceReasoningTool
EnableGuidedTools bool `json:"enable_guided_tools"`
CanStopItself bool `json:"can_stop_itself"`
// Skills
EnableSkills bool `json:"enable_skills"`
SkillsMode string `json:"skills_mode,omitempty"` // "prompt" (default), "tools", or "both"
SelectedSkills []string `json:"selected_skills,omitempty"` // Per-agent skill selection
// Memory
LongTermMemory bool `json:"long_term_memory"`
SummaryLongTermMemory bool `json:"summary_long_term_memory"`
ConversationStorageMode string `json:"conversation_storage_mode"`
// Execution
ParallelJobs int `json:"parallel_jobs"`
CancelPreviousOnNewMessage *bool `json:"cancel_previous_on_new_message"`
StripThinkingTags bool `json:"strip_thinking_tags"`
EnableEvaluation bool `json:"enable_evaluation"`
MaxEvaluationLoops int `json:"max_evaluation_loops"`
MaxAttempts int `json:"max_attempts"`
MaxIterations int `json:"max_iterations"` // max tool loop iterations
LoopDetection int `json:"loop_detection"`
EnableAutoCompaction bool `json:"enable_auto_compaction"`
AutoCompactionThreshold int `json:"auto_compaction_threshold"`
// Tool policy (see toolpolicy.go)
RequiredToolBeforeFinish string `json:"required_tool_before_finish"`
RequiredToolBeforeFinishPrompt string `json:"required_tool_before_finish_prompt"`
RequiredToolBeforeFinishAttempts int `json:"required_tool_before_finish_attempts"`
AllowedTools ToolNames `json:"allowed_tools"`
ExcludedTools ToolNames `json:"excluded_tools"`
}
// ConnectorConfig defines a connector integration (Slack, Discord, etc.).
type ConnectorConfig struct {
Type string `json:"type"`
Config string `json:"config"`
}
// ActionsConfig defines an action (tool) available to the agent.
type ActionsConfig struct {
Type string `json:"type"`
Config string `json:"config"`
}
// DynamicPromptsConfig defines a dynamic prompt template.
type DynamicPromptsConfig struct {
Type string `json:"type"`
Config string `json:"config"`
}
// FiltersConfig defines a job filter/trigger.
type FiltersConfig struct {
Type string `json:"type"`
Config string `json:"config"`
}
// MCPServer defines an HTTP-based MCP server endpoint.
type MCPServer struct {
URL string `json:"url"`
Token string `json:"token"`
}
// MCPSTDIOServers is a list of stdio MCP servers that can be unmarshaled from
// either a JSON array of MCPSTDIOServer objects or a JSON string in Claude Desktop
// format ({"mcpServers": {"name": {"command": "...", "args": [...]}}}).
type MCPSTDIOServers []MCPSTDIOServer
// UnmarshalJSON handles both array and string (Claude Desktop JSON) formats.
func (m *MCPSTDIOServers) UnmarshalJSON(data []byte) error {
// Try array format first
var arr []MCPSTDIOServer
if err := json.Unmarshal(data, &arr); err == nil {
*m = arr
return nil
}
// Try string format (Claude Desktop JSON blob)
var raw string
if err := json.Unmarshal(data, &raw); err == nil {
parsed, err := parseClaudeDesktopMCP(raw)
if err != nil {
return nil // silently ignore unparseable strings
}
*m = parsed
return nil
}
return nil
}
// parseClaudeDesktopMCP parses the {"mcpServers": {...}} format into MCPSTDIOServer list.
func parseClaudeDesktopMCP(raw string) ([]MCPSTDIOServer, error) {
var wrapper struct {
MCPServers map[string]struct {
Command string `json:"command"`
Cmd string `json:"cmd"`
Args []string `json:"args"`
Env map[string]string `json:"env"`
} `json:"mcpServers"`
}
if err := json.Unmarshal([]byte(raw), &wrapper); err != nil {
return nil, err
}
var result []MCPSTDIOServer
for name, srv := range wrapper.MCPServers {
cmd := srv.Command
if cmd == "" {
cmd = srv.Cmd
}
var envList []string
for k, v := range srv.Env {
envList = append(envList, k+"="+v)
}
result = append(result, MCPSTDIOServer{
Name: name,
Cmd: cmd,
Args: srv.Args,
Env: envList,
})
}
return result, nil
}
// MCPSTDIOServer defines a local command-based MCP server.
type MCPSTDIOServer struct {
Name string `json:"name,omitempty"`
Args []string `json:"args"`
Env []string `json:"env"`
Cmd string `json:"cmd"`
}
// AgentKey returns the namespaced key for an agent: "{userID}:{name}" or just "{name}" if no userID.
func AgentKey(userID, name string) string {
if userID == "" {
return name
}
return userID + ":" + name
}