356 lines
9.5 KiB
Go
356 lines
9.5 KiB
Go
// 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
|
||
}
|