Files
geo/server/internal/tenant/app/runtime_config.go
T
root 618399f86d feat(server): hot-reload config store and reloadable infra clients
- Replace single Load() with a watching Store that re-reads config files
  (and config.local.yaml) and fans out a ReloadEvent with a per-field diff
  so consumers can decide whether the change is hot-applicable or requires
  a process restart.
- Wrap llm, retrieval, vector store, and object storage clients in
  Reloadable* shells so the bootstrap can swap their underlying impls when
  config changes without re-instantiating handlers.
- Make jwt.Manager and ops TokenIssuer mutable under a lock so secrets and
  TTLs can be rotated live; thread default plan code through a setter on
  the ops AdminUserService.
- Wire ConfigStore through bootstrap and every cmd/main.go, scheduler /
  worker / tenant-api / ops-api start the watcher; services and handlers
  take a config.Provider so they always read current values for things
  like generation.stream_enabled, scheduler dispatch, retrieval, etc.
- Switch shared/config decoding off viper to a Kratos-derived runtime
  package so env placeholders (\${VAR:default}) resolve consistently and
  the same source machinery powers both the loader and the watcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:01:23 +08:00

106 lines
2.5 KiB
Go

package app
import (
"strings"
"time"
"github.com/geo-platform/tenant-api/internal/shared/config"
)
const defaultArticleGenerationTimeout = 8 * time.Minute
type runtimeConfigProvider = config.Provider
type generationRuntimeConfig struct {
StreamEnabled bool
ArticleTimeout time.Duration
MaxOutputTokens int64
}
type knowledgeRuntimeConfig struct {
ChunkSize int
ChunkOverlap int
EmbeddingBatchSize int
RecallLimit int
RerankTopN int
ArkBaseURL string
ArkAPIKey string
URLMarkdownModel string
}
type generationStreamDisabledConfigProvider struct {
base config.Provider
}
func NewGenerationStreamDisabledConfigProvider(base config.Provider) config.Provider {
return generationStreamDisabledConfigProvider{base: base}
}
func (p generationStreamDisabledConfigProvider) Current() *config.Config {
if p.base == nil {
return nil
}
cfg := p.base.Current()
if cfg == nil {
return nil
}
next := *cfg
next.Generation.StreamEnabled = false
return &next
}
func generationRuntimeFromConfig(generation config.GenerationConfig, llmCfg config.LLMConfig) generationRuntimeConfig {
articleTimeout := generation.ArticleTimeout
if articleTimeout <= 0 {
articleTimeout = defaultArticleGenerationTimeout
}
return generationRuntimeConfig{
StreamEnabled: generation.StreamEnabled,
ArticleTimeout: articleTimeout,
MaxOutputTokens: llmCfg.MaxOutputTokens,
}
}
func knowledgeRuntimeFromConfig(retrievalCfg config.RetrievalConfig, llmCfg config.LLMConfig) knowledgeRuntimeConfig {
chunkSize := retrievalCfg.ChunkSize
if chunkSize <= 0 {
chunkSize = 900
}
chunkOverlap := retrievalCfg.ChunkOverlap
if chunkOverlap < 0 {
chunkOverlap = 120
}
batchSize := retrievalCfg.EmbeddingBatchSize
if batchSize <= 0 {
batchSize = 16
}
recallLimit := retrievalCfg.RecallLimit
if recallLimit <= 0 {
recallLimit = 12
}
rerankTopN := retrievalCfg.RerankTopN
if rerankTopN <= 0 {
rerankTopN = 6
}
model := strings.TrimSpace(llmCfg.KnowledgeURLModel)
if model == "" {
model = strings.TrimSpace(llmCfg.Model)
}
baseURL := strings.TrimSpace(llmCfg.BaseURL)
if baseURL == "" {
baseURL = defaultKnowledgeArkBaseURL
}
return knowledgeRuntimeConfig{
ChunkSize: chunkSize,
ChunkOverlap: chunkOverlap,
EmbeddingBatchSize: batchSize,
RecallLimit: recallLimit,
RerankTopN: rerankTopN,
ArkBaseURL: baseURL,
ArkAPIKey: strings.TrimSpace(llmCfg.APIKey),
URLMarkdownModel: model,
}
}