// Package config 为 Orca 框架提供配置管理功能。 // // 配置从 TOML 文件加载,默认路径为 ~/.orca/config.toml。 // 所有模型参数和 Agent 身份描述均通过配置文件管理, // 不再从环境变量读取。 package config import ( "fmt" "os" "path/filepath" "time" "github.com/BurntSushi/toml" ) const ( // ProviderOllama 选择本地 Ollama LLM 后端。 ProviderOllama = "ollama" // ProviderDeepSeek 选择云端 DeepSeek LLM 后端。 ProviderDeepSeek = "deepseek" ) // Config 保存 Orca 框架的所有配置信息。 type Config struct { Provider string `toml:"provider"` Ollama OllamaConfig `toml:"ollama"` DeepSeek DeepSeekConfig `toml:"deepseek"` Sandbox SandboxConfig `toml:"sandbox"` Session SessionConfig `toml:"session"` Agent AgentConfig `toml:"agent"` Embedding EmbeddingConfig `toml:"embedding"` Memory MemoryConfig `toml:"memory"` MemoryAgent MemoryAgentConfig `toml:"memory_agent"` SiliconFlow SiliconConfig `toml:"siliconflow"` } // OllamaConfig 保存 Ollama LLM 后端的设置。 type OllamaConfig struct { BaseURL string `toml:"base_url"` Model string `toml:"model"` Timeout time.Duration `toml:"timeout"` } // DeepSeekConfig 保存 DeepSeek LLM 后端的设置。 type DeepSeekConfig struct { BaseURL string `toml:"base_url"` Model string `toml:"model"` APIKey string `toml:"api_key"` Timeout time.Duration `toml:"timeout"` } // SandboxConfig 保存命令执行沙箱的设置。 type SandboxConfig struct { Timeout time.Duration `toml:"timeout"` MaxMemory int64 `toml:"max_memory"` WorkingDir string `toml:"working_dir"` } // SessionConfig 保存对话会话存储的设置。 type SessionConfig struct { StorageDir string `toml:"storage_dir"` MaxHistory int `toml:"max_history"` } type EmbeddingConfig struct { Provider string `toml:"provider"` Model string `toml:"model"` Dim int `toml:"dimensions"` MaxCtx int `toml:"max_context"` } type MemoryConfig struct { Enabled bool `toml:"enabled"` MaxHistory int `toml:"max_history"` ShortTerm ShortTermConfig `toml:"short_term"` LongTerm LongTermConfig `toml:"long_term"` Inject InjectConfig `toml:"inject"` } type ShortTermConfig struct { MaxItems int `toml:"max_items"` CompressionThreshold int `toml:"compression_threshold"` } type LongTermConfig struct { Enabled bool `toml:"enabled"` VectorIndex bool `toml:"vector_index"` MaxReturn int `toml:"max_return"` ArchiveThreshold float64 `toml:"archive_threshold"` } type InjectConfig struct { FirstRoundEmpty bool `toml:"first_round_empty"` ShortTermCount int `toml:"short_term_count"` LongTermTrigger string `toml:"long_term_trigger"` MinQueryLength int `toml:"min_query_length"` } type MemoryAgentConfig struct { Enabled bool `toml:"enabled"` Provider string `toml:"provider"` Model string `toml:"model"` APIKey string `toml:"api_key"` BaseURL string `toml:"base_url"` Timeout time.Duration `toml:"timeout"` Extract ExtractConfig `toml:"extract"` Summarize SummarizeConfig `toml:"summarize"` } type ExtractConfig struct { BatchSize int `toml:"batch_size"` MaxFacts int `toml:"max_facts"` MinConfidence float64 `toml:"min_confidence"` AutoTag bool `toml:"auto_tag"` } type SummarizeConfig struct { Enabled bool `toml:"enabled"` TriggerTokens int `toml:"trigger_tokens"` } type SiliconConfig struct { APIKey string `toml:"api_key"` BaseURL string `toml:"base_url"` } // AgentConfig 保存 Agent 身份和行为的配置。 type AgentConfig struct { // Role 是 Agent 的角色标识,如 "assistant", "coder", "reviewer" 等。 Role string `toml:"role"` // SystemPrompt 是 Agent 的系统提示词(直接内联配置)。 // 如果 PromptFile 也配置了,PromptFile 优先级更高。 SystemPrompt string `toml:"system_prompt"` // PromptFile 是外部提示词文件的路径(相对于 ~/.orca/ 或绝对路径)。 // 如果文件存在,其内容会作为 system prompt 使用。 PromptFile string `toml:"prompt_file"` } // DefaultConfig 返回默认配置。 // 注意:模型相关默认值均为空字符串,要求用户必须在 config.toml 中配置。 func DefaultConfig() *Config { home, _ := os.UserHomeDir() return &Config{ Provider: ProviderDeepSeek, Ollama: OllamaConfig{ BaseURL: "http://localhost:11434", Model: "", Timeout: 120 * time.Second, }, DeepSeek: DeepSeekConfig{ BaseURL: "https://api.deepseek.com/v1", Model: "", APIKey: "", Timeout: 120 * time.Second, }, Sandbox: SandboxConfig{ Timeout: 30 * time.Second, MaxMemory: 512 * 1024 * 1024, WorkingDir: filepath.Join(home, ".orca", "sandbox"), }, Session: SessionConfig{ StorageDir: filepath.Join(home, ".orca", "sessions"), MaxHistory: 100, }, Agent: AgentConfig{ Role: "assistant", SystemPrompt: "", PromptFile: "", }, Embedding: EmbeddingConfig{ Provider: "siliconflow", Model: "Pro/BAAI/bge-m3", Dim: 1024, MaxCtx: 8192, }, Memory: MemoryConfig{ Enabled: true, MaxHistory: 100, ShortTerm: ShortTermConfig{ MaxItems: 10, CompressionThreshold: 200, }, LongTerm: LongTermConfig{ Enabled: true, VectorIndex: true, MaxReturn: 2, ArchiveThreshold: 0.3, }, Inject: InjectConfig{ FirstRoundEmpty: true, ShortTermCount: 3, LongTermTrigger: "technical", MinQueryLength: 10, }, }, MemoryAgent: MemoryAgentConfig{ Enabled: true, Provider: "deepseek", Model: "deepseek-v4-flash", Timeout: 60 * time.Second, Extract: ExtractConfig{ BatchSize: 5, MaxFacts: 10, MinConfidence: 0.6, AutoTag: true, }, Summarize: SummarizeConfig{ Enabled: true, TriggerTokens: 4000, }, }, SiliconFlow: SiliconConfig{ APIKey: "", BaseURL: "https://api.siliconflow.cn/v1", }, } } func (c *Config) expandPaths() { c.Session.StorageDir = expandHomeDir(c.Session.StorageDir) c.Sandbox.WorkingDir = expandHomeDir(c.Sandbox.WorkingDir) c.Agent.PromptFile = expandHomeDir(c.Agent.PromptFile) } func expandHomeDir(path string) string { if len(path) > 0 && path[0] == '~' { home, err := os.UserHomeDir() if err == nil { return home + path[1:] } } return path } // LoadConfigFromFile 从指定路径加载 TOML 配置文件。 // 如果文件不存在,返回默认配置。 func LoadConfigFromFile(path string) (*Config, error) { cfg := DefaultConfig() if _, err := os.Stat(path); os.IsNotExist(err) { return cfg, nil } if _, err := toml.DecodeFile(path, cfg); err != nil { return nil, fmt.Errorf("config: failed to load %q: %w", path, err) } cfg.expandPaths() return cfg, nil } // LoadConfig 从默认路径加载配置。 // 默认路径优先级: // 1. ./config.toml(当前工作目录) // 2. ~/.orca/config.toml func LoadConfig() (*Config, error) { // 尝试当前目录 if _, err := os.Stat("config.toml"); err == nil { return LoadConfigFromFile("config.toml") } // 尝试用户主目录 home, err := os.UserHomeDir() if err != nil { return DefaultConfig(), nil } defaultPath := filepath.Join(home, ".orca", "config.toml") return LoadConfigFromFile(defaultPath) } // GetSystemPrompt 返回 Agent 的系统提示词。 // 优先级:PromptFile > SystemPrompt > 空字符串。 func (c *Config) GetSystemPrompt() string { // 如果配置了外部文件,优先读取文件 if c.Agent.PromptFile != "" { path := c.Agent.PromptFile // 如果是相对路径,基于 ~/.orca/ 解析 if !filepath.IsAbs(path) { home, _ := os.UserHomeDir() path = filepath.Join(home, ".orca", path) } if data, err := os.ReadFile(path); err == nil { return string(data) } } // 返回内联配置的 prompt return c.Agent.SystemPrompt } // GetPromptsDir 返回提示词文件目录。 func GetPromptsDir() string { home, _ := os.UserHomeDir() return filepath.Join(home, ".orca", "prompts") } func (c *Config) IsValid() error { if c.Provider != ProviderOllama && c.Provider != ProviderDeepSeek { return errConfig("provider must be 'ollama' or 'deepseek'") } if c.Provider == ProviderOllama { if c.Ollama.BaseURL == "" { return errConfig("ollama.base_url must not be empty") } if c.Ollama.Model == "" { return errConfig("ollama.model must not be empty") } if c.Ollama.Timeout <= 0 { return errConfig("ollama.timeout must be positive") } } if c.Provider == ProviderDeepSeek { if c.DeepSeek.BaseURL == "" { return errConfig("deepseek.base_url must not be empty") } if c.DeepSeek.Model == "" { return errConfig("deepseek.model must not be empty") } if c.DeepSeek.APIKey == "" { return errConfig("deepseek.api_key must not be empty") } if c.DeepSeek.Timeout <= 0 { return errConfig("deepseek.timeout must be positive") } } if c.Sandbox.Timeout <= 0 { return errConfig("sandbox.timeout must be positive") } if c.Sandbox.MaxMemory <= 0 { return errConfig("sandbox.max_memory must be positive") } if c.Session.MaxHistory <= 0 { return errConfig("session.max_history must be positive") } return nil } func errConfig(msg string) error { return &ConfigError{Message: msg} } type ConfigError struct { Message string } func (e *ConfigError) Error() string { return "config: " + e.Message }