2026-05-12 00:09:01 +08:00

356 lines
9.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}