Merge pull request #87 from svc-design/codex/update-config-code-for-new-format

refactor: update server config structure
This commit is contained in:
shenlan 2025-08-09 12:19:44 +08:00 committed by GitHub
commit f46c58efd6
6 changed files with 127 additions and 47 deletions

View File

@ -22,7 +22,7 @@ func main() {
cfg, err := config.Load()
if err != nil {
slog.Warn("load config", "err", err)
cfg = &config.Server{}
cfg = &config.Config{}
}
level := slog.LevelInfo
@ -38,7 +38,7 @@ func main() {
slog.SetDefault(logger)
var conn *pgx.Conn
if dsn := cfg.Postgres.DSN; dsn != "" {
if dsn := cfg.Global.VectorDB.DSN(); dsn != "" {
logger.Debug("connecting to postgres", "dsn", dsn)
conn, err = pgx.Connect(context.Background(), dsn)
if err != nil {
@ -50,11 +50,11 @@ func main() {
logger.Warn("postgres dsn not provided")
}
if addr := cfg.Redis.Addr; addr != "" {
if addr := cfg.Global.Redis.Addr; addr != "" {
logger.Debug("connecting to redis", "addr", addr)
rdb := redis.NewClient(&redis.Options{
Addr: addr,
Password: cfg.Redis.Password,
Password: cfg.Global.Redis.Password,
})
if err := rdb.Ping(context.Background()).Err(); err != nil {
logger.Error("redis connect error", "err", err)

View File

@ -45,19 +45,25 @@ func registerAskAIRoutes(r *gin.RouterGroup) {
const chutesURL = "https://llm.chutes.ai/v1/chat/completions"
type serverConfig struct {
Env map[string]string `yaml:"env"`
Model []string `yaml:"model"`
AskAI struct {
Timeout int `yaml:"timeout"` // seconds
Retries int `yaml:"retries"`
} `yaml:"askai"`
LLM struct {
URL string `yaml:"url"`
Token string `yaml:"token"`
Models []string `yaml:"models"`
} `yaml:"llm"`
API struct {
AskAI struct {
Timeout int `yaml:"timeout"` // seconds
Retries int `yaml:"retries"`
} `yaml:"askai"`
} `yaml:"api"`
}
// loadConfig attempts to read CHUTES_API_TOKEN, model, timeout and retries from
// loadConfig attempts to read CHUTES_API_TOKEN, model, URL, timeout and retries from
// environment variables, falling back to config/server.yaml.
func loadConfig() (string, string, time.Duration, int) {
func loadConfig() (string, string, string, time.Duration, int) {
token := os.Getenv("CHUTES_API_TOKEN")
model := os.Getenv("CHUTES_API_MODEL")
url := os.Getenv("CHUTES_API_URL")
timeout := 30 * time.Second
retries := 3
path := filepath.Join("server", "config", "server.yaml")
@ -66,16 +72,19 @@ func loadConfig() (string, string, time.Duration, int) {
var cfg serverConfig
if err := yaml.Unmarshal(data, &cfg); err == nil {
if token == "" {
token = cfg.Env["CHUTES_API_TOKEN"]
token = cfg.LLM.Token
}
if model == "" && len(cfg.Model) > 0 {
model = cfg.Model[0]
if model == "" && len(cfg.LLM.Models) > 0 {
model = cfg.LLM.Models[0]
}
if cfg.AskAI.Timeout > 0 {
timeout = time.Duration(cfg.AskAI.Timeout) * time.Second
if url == "" {
url = cfg.LLM.URL
}
if cfg.AskAI.Retries > 0 {
retries = cfg.AskAI.Retries
if cfg.API.AskAI.Timeout > 0 {
timeout = time.Duration(cfg.API.AskAI.Timeout) * time.Second
}
if cfg.API.AskAI.Retries > 0 {
retries = cfg.API.AskAI.Retries
}
}
}
@ -88,19 +97,18 @@ func loadConfig() (string, string, time.Duration, int) {
if model == "" {
model = "deepseek-ai/DeepSeek-R1"
}
return token, model, timeout, retries
if url == "" {
url = chutesURL
}
return token, model, url, timeout, retries
}
// callChutes sends the question to the hosted LLM service and returns the reply.
func callChutes(question string) (string, error) {
token, model, timeout, retries := loadConfig()
token, model, url, timeout, retries := loadConfig()
if token == "" {
return "", errors.New("CHUTES_API_TOKEN not set")
}
url := os.Getenv("CHUTES_API_URL")
if url == "" {
url = chutesURL
}
reqBody := map[string]interface{}{
"model": model,

View File

@ -1,6 +1,7 @@
package config
import (
"fmt"
"os"
"path/filepath"
@ -16,24 +17,74 @@ type Redis struct {
Password string `yaml:"password"`
}
type Postgres struct {
DSN string `yaml:"dsn"`
type VectorDB struct {
PGURL string `yaml:"pgurl"`
PGHost string `yaml:"pg_host"`
PGPort int `yaml:"pg_port"`
PGUser string `yaml:"pg_user"`
PGPassword string `yaml:"pg_password"`
PGDBName string `yaml:"pg_db_name"`
PGSSLMode string `yaml:"pg_sslmode"`
}
type Server struct {
Log Log `yaml:"log"`
Redis Redis `yaml:"redis"`
Postgres Postgres `yaml:"postgres"`
func (v VectorDB) DSN() string {
if v.PGURL != "" {
return v.PGURL
}
if v.PGHost == "" || v.PGUser == "" || v.PGDBName == "" {
return ""
}
port := v.PGPort
if port == 0 {
port = 5432
}
ssl := v.PGSSLMode
if ssl == "" {
ssl = "require"
}
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s", v.PGUser, v.PGPassword, v.PGHost, port, v.PGDBName, ssl)
}
// Load reads server/config/server.yaml and unmarshals into Server struct.
func Load() (*Server, error) {
type Datasource struct {
Name string `yaml:"name"`
Repo string `yaml:"repo"`
Path string `yaml:"path"`
}
type Global struct {
Redis Redis `yaml:"redis"`
VectorDB VectorDB `yaml:"vectordb"`
Datasources []Datasource `yaml:"datasources"`
}
type LLM struct {
URL string `yaml:"url"`
Token string `yaml:"token"`
Models []string `yaml:"models"`
}
type API struct {
AskAI struct {
Timeout int `yaml:"timeout"`
Retries int `yaml:"retries"`
} `yaml:"askai"`
}
type Config struct {
Log Log `yaml:"log"`
Global Global `yaml:"global"`
LLM LLM `yaml:"llm"`
API API `yaml:"api"`
}
// Load reads server/config/server.yaml and unmarshals into Config struct.
func Load() (*Config, error) {
path := filepath.Join("server", "config", "server.yaml")
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg Server
var cfg Config
if err := yaml.Unmarshal(b, &cfg); err != nil {
return nil, err
}

View File

@ -21,7 +21,10 @@ func TestLoad(t *testing.T) {
if err != nil {
t.Fatalf("Load returned error: %v", err)
}
if cfg.Log.Level != "info" {
t.Fatalf("unexpected log level %q", cfg.Log.Level)
if cfg.Global.Redis.Addr != "127.0.0.1:6479" {
t.Fatalf("unexpected redis addr %q", cfg.Global.Redis.Addr)
}
if cfg.API.AskAI.Timeout != 100 {
t.Fatalf("unexpected askai timeout %d", cfg.API.AskAI.Timeout)
}
}

View File

@ -50,17 +50,26 @@ func (c *Config) ResolveEmbedding() RuntimeEmbedding {
return rt
}
// ResolveChunking returns chunking configuration with defaults applied.
func (c *Config) ResolveChunking() ChunkingCfg {
ch := c.Chunking
if ch.MaxTokens == 0 {
ch.MaxTokens = 800
// LoadServer loads global configuration from server/config/server.yaml.
func LoadServer() (*Runtime, error) {
path := filepath.Join("server", "config", "server.yaml")
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg struct {
Global Runtime `yaml:"global"`
}
if ch.OverlapTokens == 0 {
ch.OverlapTokens = 80
}
if len(ch.IncludeExts) == 0 {
ch.IncludeExts = []string{".md", ".mdx"}
return &cfg.Global, nil
}
// ToConfig converts runtime configuration into service configuration.
func (rt *Runtime) ToConfig() *Config {
if rt == nil {
return nil
}
if len(ch.IgnoreDirs) == 0 {
ch.IgnoreDirs = []string{".git", "node_modules", "dist", "build"}

View File

@ -12,8 +12,11 @@ import (
// Config represents server configuration loaded from YAML.
type Config struct {
Env map[string]string `yaml:"env"`
Model []string `yaml:"model"`
LLM struct {
URL string `yaml:"url"`
Token string `yaml:"token"`
Models []string `yaml:"models"`
} `yaml:"llm"`
}
// cfg holds the loaded configuration.
@ -31,8 +34,14 @@ func loadConfig() {
log.Printf("server config parse: %v", err)
return
}
for k, v := range cfg.Env {
os.Setenv(k, v)
if cfg.LLM.Token != "" {
os.Setenv("CHUTES_API_TOKEN", cfg.LLM.Token)
}
if cfg.LLM.URL != "" {
os.Setenv("CHUTES_API_URL", cfg.LLM.URL)
}
if len(cfg.LLM.Models) > 0 {
os.Setenv("CHUTES_API_MODEL", cfg.LLM.Models[0])
}
}