feat: introduce storage provider configuration and enhance backward compatibility
- Added a new `StorageProviderConfig` struct to the `KnowledgeBase` for improved storage provider management, separating provider selection from storage credentials. - Updated the `UnmarshalJSON` method to auto-populate `StorageProviderConfig` from the legacy `StorageConfig` for backward compatibility. - Refactored various service methods to utilize the new storage provider configuration, ensuring a smooth transition from the legacy system. - Implemented database migrations to add the `storage_provider_config` column and migrate existing data, maintaining compatibility with historical knowledge bases. This update enhances the clarity and flexibility of storage configurations while ensuring compatibility with existing data structures.
This commit is contained in:
+11
-4
@@ -25,9 +25,10 @@ type KnowledgeBase struct {
|
||||
FAQConfig *FAQConfig `json:"faq_config"`
|
||||
EmbeddingModelID string `json:"embedding_model_id"`
|
||||
SummaryModelID string `json:"summary_model_id"`
|
||||
VLMConfig VLMConfig `json:"vlm_config"`
|
||||
StorageConfig StorageConfig `json:"storage_config"`
|
||||
ExtractConfig *ExtractConfig `json:"extract_config"`
|
||||
VLMConfig VLMConfig `json:"vlm_config"`
|
||||
StorageProviderConfig *StorageProviderConfig `json:"storage_provider_config"`
|
||||
StorageConfig StorageConfig `json:"storage_config"`
|
||||
ExtractConfig *ExtractConfig `json:"extract_config"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
// Computed fields (not stored in database)
|
||||
@@ -68,7 +69,13 @@ type VLMConfig struct {
|
||||
ModelID string `json:"model_id"`
|
||||
}
|
||||
|
||||
// StorageConfig represents the storage configuration
|
||||
// StorageProviderConfig stores the KB-level storage provider selection.
|
||||
type StorageProviderConfig struct {
|
||||
Provider string `json:"provider"`
|
||||
}
|
||||
|
||||
// StorageConfig represents the legacy storage configuration (cos_config).
|
||||
// Deprecated: use StorageProviderConfig for provider selection.
|
||||
type StorageConfig struct {
|
||||
SecretID string `json:"secret_id"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
|
||||
@@ -190,8 +190,8 @@ func (s *knowledgeService) CreateKnowledgeFromFile(ctx context.Context,
|
||||
if !IsImageType(getFileType(fileName)) {
|
||||
logger.Info(ctx, "Non-image file with multimodal enabled, skipping COS/VLM validation")
|
||||
} else {
|
||||
// 解析有效 provider:优先 KB 级别,其次租户默认
|
||||
provider := strings.ToLower(strings.TrimSpace(kb.StorageConfig.Provider))
|
||||
// 解析有效 provider:优先 KB 级别(新字段 > 旧字段),其次租户默认
|
||||
provider := kb.GetStorageProvider()
|
||||
tenant, _ := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
if provider == "" && tenant != nil && tenant.StorageEngineConfig != nil {
|
||||
provider = strings.ToLower(strings.TrimSpace(tenant.StorageEngineConfig.DefaultProvider))
|
||||
@@ -2286,9 +2286,9 @@ func (s *knowledgeService) GetKnowledgeFile(ctx context.Context, id string) (io.
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Resolve KB-level file service
|
||||
// Resolve KB-level file service with FilePath fallback protection
|
||||
kb, _ := s.kbService.GetKnowledgeBaseByID(ctx, knowledge.KnowledgeBaseID)
|
||||
file, err := s.resolveFileService(ctx, kb).GetFile(ctx, knowledge.FilePath)
|
||||
file, err := s.resolveFileServiceForPath(ctx, kb, knowledge.FilePath).GetFile(ctx, knowledge.FilePath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -6737,13 +6737,13 @@ func (s *knowledgeService) getVLMConfig(ctx context.Context, kb *types.Knowledge
|
||||
}
|
||||
|
||||
func (s *knowledgeService) buildStorageConfig(ctx context.Context, kb *types.KnowledgeBase) *types.DocParserStorageConfig {
|
||||
sc := &kb.StorageConfig
|
||||
provider := strings.ToLower(strings.TrimSpace(sc.Provider))
|
||||
provider := kb.GetStorageProvider()
|
||||
if provider == "" {
|
||||
provider = "local"
|
||||
}
|
||||
|
||||
// Backward compatibility: if KB has full params for the chosen provider, use them.
|
||||
// Backward compatibility: if legacy cos_config has full params for the chosen provider, use them.
|
||||
sc := &kb.StorageConfig
|
||||
hasKBFull := false
|
||||
switch provider {
|
||||
case "cos":
|
||||
@@ -6751,12 +6751,11 @@ func (s *knowledgeService) buildStorageConfig(ctx context.Context, kb *types.Kno
|
||||
case "minio":
|
||||
hasKBFull = sc.BucketName != ""
|
||||
case "local":
|
||||
// Local has no credentials; always merge from tenant if available
|
||||
hasKBFull = false
|
||||
}
|
||||
|
||||
if hasKBFull {
|
||||
logger.Infof(ctx, "[storage] buildStorageConfig use kb config: kb=%s provider=%s bucket=%s path_prefix=%s",
|
||||
logger.Infof(ctx, "[storage] buildStorageConfig use legacy kb config: kb=%s provider=%s bucket=%s path_prefix=%s",
|
||||
kb.ID, provider, sc.BucketName, sc.PathPrefix)
|
||||
return &types.DocParserStorageConfig{
|
||||
Provider: strings.ToUpper(provider),
|
||||
@@ -6769,7 +6768,7 @@ func (s *knowledgeService) buildStorageConfig(ctx context.Context, kb *types.Kno
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise merge from tenant's StorageEngineConfig.
|
||||
// Merge from tenant's StorageEngineConfig.
|
||||
var out types.DocParserStorageConfig
|
||||
out.Provider = strings.ToUpper(provider)
|
||||
|
||||
@@ -6817,7 +6816,7 @@ func (s *knowledgeService) buildStorageConfig(ctx context.Context, kb *types.Kno
|
||||
}
|
||||
|
||||
// resolveFileService returns the FileService for the given knowledge base,
|
||||
// based on the KB's StorageConfig.Provider and the tenant's StorageEngineConfig.
|
||||
// based on the KB's StorageProviderConfig (or legacy StorageConfig.Provider) and the tenant's StorageEngineConfig.
|
||||
// Falls back to the global fileSvc when no tenant-level storage config is found.
|
||||
func (s *knowledgeService) resolveFileService(ctx context.Context, kb *types.KnowledgeBase) interfaces.FileService {
|
||||
if kb == nil {
|
||||
@@ -6825,7 +6824,7 @@ func (s *knowledgeService) resolveFileService(ctx context.Context, kb *types.Kno
|
||||
return s.fileSvc
|
||||
}
|
||||
|
||||
provider := strings.ToLower(strings.TrimSpace(kb.StorageConfig.Provider))
|
||||
provider := kb.GetStorageProvider()
|
||||
|
||||
tenant, _ := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
if provider == "" && tenant != nil && tenant.StorageEngineConfig != nil {
|
||||
@@ -6915,6 +6914,40 @@ func (s *knowledgeService) resolveFileService(ctx context.Context, kb *types.Kno
|
||||
return svc
|
||||
}
|
||||
|
||||
// resolveFileServiceForPath is like resolveFileService but adds a safety check:
|
||||
// if the resolved provider doesn't match what the filePath implies, fall back to
|
||||
// the provider inferred from the file path. This protects historical data when
|
||||
// tenant/KB config changes but files were stored under the old provider.
|
||||
func (s *knowledgeService) resolveFileServiceForPath(ctx context.Context, kb *types.KnowledgeBase, filePath string) interfaces.FileService {
|
||||
svc := s.resolveFileService(ctx, kb)
|
||||
if filePath == "" {
|
||||
return svc
|
||||
}
|
||||
|
||||
inferred := types.InferStorageFromFilePath(filePath)
|
||||
if inferred == "" {
|
||||
return svc
|
||||
}
|
||||
|
||||
configured := kb.GetStorageProvider()
|
||||
if configured == "" {
|
||||
tenant, _ := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
if tenant != nil && tenant.StorageEngineConfig != nil {
|
||||
configured = strings.ToLower(strings.TrimSpace(tenant.StorageEngineConfig.DefaultProvider))
|
||||
}
|
||||
}
|
||||
if configured == "" {
|
||||
configured = strings.ToLower(strings.TrimSpace(os.Getenv("STORAGE_TYPE")))
|
||||
}
|
||||
|
||||
if configured != "" && configured != inferred {
|
||||
logger.Warnf(ctx, "[storage] FilePath format mismatch: configured=%s inferred=%s filePath=%s, using global fallback",
|
||||
configured, inferred, filePath)
|
||||
return s.fileSvc
|
||||
}
|
||||
return svc
|
||||
}
|
||||
|
||||
func IsImageType(fileType string) bool {
|
||||
switch fileType {
|
||||
case "jpg", "jpeg", "png", "gif", "webp", "bmp", "svg", "tiff":
|
||||
@@ -7304,7 +7337,7 @@ func (s *knowledgeService) convert(
|
||||
imageStorage := s.buildImageStorageConfig(ctx, kb)
|
||||
if imageStorage == nil {
|
||||
logger.Infof(ctx, "[storage] chunk image storage: kb=%s provider=%s image_storage=nil(use local temp/files)",
|
||||
kb.ID, strings.ToLower(strings.TrimSpace(kb.StorageConfig.Provider)))
|
||||
kb.ID, kb.GetStorageProvider())
|
||||
} else {
|
||||
logger.Infof(ctx, "[storage] chunk image storage: kb=%s provider=%s bucket=%s endpoint=%s path_prefix=%s",
|
||||
kb.ID,
|
||||
@@ -7325,7 +7358,7 @@ func (s *knowledgeService) convert(
|
||||
}
|
||||
|
||||
if !isURL {
|
||||
fileReader, err := s.resolveFileService(ctx, kb).GetFile(ctx, payload.FilePath)
|
||||
fileReader, err := s.resolveFileServiceForPath(ctx, kb, payload.FilePath).GetFile(ctx, payload.FilePath)
|
||||
if err != nil {
|
||||
return s.failKnowledge(ctx, knowledge, isLastRetry, "failed to get file: %v", err)
|
||||
}
|
||||
@@ -7402,7 +7435,7 @@ func (s *knowledgeService) reuploadImagesToStorage(
|
||||
images []docparser.StoredImage,
|
||||
markdown string,
|
||||
) (string, []docparser.StoredImage) {
|
||||
provider := strings.ToLower(strings.TrimSpace(kb.StorageConfig.Provider))
|
||||
provider := kb.GetStorageProvider()
|
||||
tenant, _ := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
if provider == "" && tenant != nil && tenant.StorageEngineConfig != nil {
|
||||
provider = strings.ToLower(strings.TrimSpace(tenant.StorageEngineConfig.DefaultProvider))
|
||||
@@ -7456,7 +7489,7 @@ func (s *knowledgeService) reuploadImagesToStorage(
|
||||
func (s *knowledgeService) buildImageStorageConfig(ctx context.Context, kb *types.KnowledgeBase) map[string]string {
|
||||
provider := ""
|
||||
if kb != nil {
|
||||
provider = strings.ToLower(strings.TrimSpace(kb.StorageConfig.Provider))
|
||||
provider = kb.GetStorageProvider()
|
||||
}
|
||||
tenant, _ := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
if provider == "" && tenant != nil && tenant.StorageEngineConfig != nil {
|
||||
|
||||
@@ -575,6 +575,7 @@ func (s *knowledgeBaseService) CopyKnowledgeBase(ctx context.Context,
|
||||
EmbeddingModelID: sourceKB.EmbeddingModelID,
|
||||
SummaryModelID: sourceKB.SummaryModelID,
|
||||
VLMConfig: sourceKB.VLMConfig,
|
||||
StorageProviderConfig: sourceKB.StorageProviderConfig,
|
||||
StorageConfig: sourceKB.StorageConfig,
|
||||
FAQConfig: faqConfig,
|
||||
}
|
||||
|
||||
@@ -413,6 +413,11 @@ func initDatabase(cfg *config.Config) (*gorm.DB, error) {
|
||||
"Continuing with application startup. Please run migrations manually if needed.",
|
||||
)
|
||||
}
|
||||
|
||||
// Post-migration: resolve __pending_env__ storage provider markers for historical KBs.
|
||||
// The SQL migration marks KBs that have documents but no provider with "__pending_env__";
|
||||
// we replace that with the actual STORAGE_TYPE from the environment.
|
||||
resolveStorageProviderPending(db)
|
||||
} else {
|
||||
logger.Infof(context.Background(), "Auto-migration is disabled (AUTO_MIGRATE=false)")
|
||||
}
|
||||
@@ -437,6 +442,27 @@ func initDatabase(cfg *config.Config) (*gorm.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// resolveStorageProviderPending replaces the "__pending_env__" sentinel in
|
||||
// knowledge_bases.storage_provider_config with the actual STORAGE_TYPE from the environment.
|
||||
// This runs once after SQL migrations to bind historical KBs to their real storage provider.
|
||||
func resolveStorageProviderPending(db *gorm.DB) {
|
||||
storageType := strings.TrimSpace(os.Getenv("STORAGE_TYPE"))
|
||||
if storageType == "" {
|
||||
storageType = "local"
|
||||
}
|
||||
storageType = strings.ToLower(storageType)
|
||||
|
||||
result := db.Exec(
|
||||
`UPDATE knowledge_bases SET storage_provider_config = ? WHERE storage_provider_config IS NOT NULL AND storage_provider_config->>'provider' = '__pending_env__'`,
|
||||
fmt.Sprintf(`{"provider":"%s"}`, storageType),
|
||||
)
|
||||
if result.Error != nil {
|
||||
logger.Warnf(context.Background(), "Failed to resolve __pending_env__ storage providers: %v", result.Error)
|
||||
} else if result.RowsAffected > 0 {
|
||||
logger.Infof(context.Background(), "Resolved %d knowledge bases with __pending_env__ storage provider → %s", result.RowsAffected, storageType)
|
||||
}
|
||||
}
|
||||
|
||||
// initFileService initializes file storage service
|
||||
// Creates the appropriate file storage service based on configuration
|
||||
// Supports multiple storage backends (MinIO, COS, local filesystem)
|
||||
|
||||
@@ -297,12 +297,12 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) {
|
||||
kb.VLMConfig.ModelID = ""
|
||||
}
|
||||
|
||||
// 存储引擎:仅写入 provider,参数从租户全局 StorageEngineConfig 读取
|
||||
// 存储引擎:仅写入 provider 到新字段,参数从租户全局 StorageEngineConfig 读取
|
||||
provider := strings.ToLower(strings.TrimSpace(req.StorageProvider))
|
||||
if provider == "" {
|
||||
provider = "local"
|
||||
}
|
||||
oldProvider := strings.ToLower(strings.TrimSpace(kb.StorageConfig.Provider))
|
||||
oldProvider := kb.GetStorageProvider()
|
||||
if oldProvider == "" {
|
||||
oldProvider = "local"
|
||||
}
|
||||
@@ -315,7 +315,7 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
kb.StorageConfig = types.StorageConfig{Provider: provider}
|
||||
kb.SetStorageProvider(provider)
|
||||
|
||||
// 更新知识图谱配置
|
||||
if req.NodeExtract.Enabled {
|
||||
@@ -711,6 +711,8 @@ func (h *InitializationHandler) applyKnowledgeBaseInitialization(
|
||||
switch req.Multimodal.StorageType {
|
||||
case "cos":
|
||||
if req.Multimodal.COS != nil {
|
||||
kb.SetStorageProvider("cos")
|
||||
// Legacy: also write to cos_config for backward compat with old code paths
|
||||
kb.StorageConfig = types.StorageConfig{
|
||||
Provider: req.Multimodal.StorageType,
|
||||
BucketName: req.Multimodal.COS.BucketName,
|
||||
@@ -723,6 +725,8 @@ func (h *InitializationHandler) applyKnowledgeBaseInitialization(
|
||||
}
|
||||
case "minio":
|
||||
if req.Multimodal.Minio != nil {
|
||||
kb.SetStorageProvider("minio")
|
||||
// Legacy: also write to cos_config for backward compat with old code paths
|
||||
kb.StorageConfig = types.StorageConfig{
|
||||
Provider: req.Multimodal.StorageType,
|
||||
BucketName: req.Multimodal.Minio.BucketName,
|
||||
@@ -734,6 +738,7 @@ func (h *InitializationHandler) applyKnowledgeBaseInitialization(
|
||||
}
|
||||
} else {
|
||||
kb.VLMConfig = types.VLMConfig{}
|
||||
kb.SetStorageProvider("")
|
||||
kb.StorageConfig = types.StorageConfig{}
|
||||
}
|
||||
|
||||
@@ -1333,15 +1338,16 @@ func (h *InitializationHandler) buildConfigResponse(ctx context.Context, models
|
||||
}
|
||||
}
|
||||
|
||||
// 判断多模态是否启用:有VLM模型ID或有存储配置
|
||||
// 判断多模态是否启用:有VLM模型ID或有存储配置(兼容新旧字段)
|
||||
storageProvider := kb.GetStorageProvider()
|
||||
hasMultimodal := (kb.VLMConfig.IsEnabled() ||
|
||||
kb.StorageConfig.SecretID != "" || kb.StorageConfig.BucketName != "")
|
||||
kb.StorageConfig.SecretID != "" || kb.StorageConfig.BucketName != "" ||
|
||||
(storageProvider != "" && storageProvider != "local"))
|
||||
if config["multimodal"] == nil {
|
||||
config["multimodal"] = map[string]interface{}{
|
||||
"enabled": hasMultimodal,
|
||||
}
|
||||
} else {
|
||||
// 如果已经设置过 multimodal,更新 enabled 状态
|
||||
config["multimodal"].(map[string]interface{})["enabled"] = hasMultimodal
|
||||
}
|
||||
|
||||
@@ -1363,16 +1369,17 @@ func (h *InitializationHandler) buildConfigResponse(ctx context.Context, models
|
||||
"separators": kb.ChunkingConfig.Separators,
|
||||
}
|
||||
|
||||
// 添加多模态的COS配置信息
|
||||
if kb.StorageConfig.SecretID != "" {
|
||||
// 添加多模态的存储配置信息(优先读新字段,兼容旧 cos_config)
|
||||
effectiveProvider := kb.GetStorageProvider()
|
||||
if kb.StorageConfig.SecretID != "" || (effectiveProvider != "" && effectiveProvider != "local") {
|
||||
if config["multimodal"] == nil {
|
||||
config["multimodal"] = map[string]interface{}{
|
||||
"enabled": true,
|
||||
}
|
||||
}
|
||||
multimodal := config["multimodal"].(map[string]interface{})
|
||||
multimodal["storageType"] = kb.StorageConfig.Provider
|
||||
switch kb.StorageConfig.Provider {
|
||||
multimodal["storageType"] = effectiveProvider
|
||||
switch effectiveProvider {
|
||||
case "cos":
|
||||
multimodal["cos"] = map[string]interface{}{
|
||||
"secretId": kb.StorageConfig.SecretID,
|
||||
|
||||
@@ -3,6 +3,7 @@ package types
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -59,8 +60,10 @@ type KnowledgeBase struct {
|
||||
SummaryModelID string `yaml:"summary_model_id" json:"summary_model_id"`
|
||||
// VLM config
|
||||
VLMConfig VLMConfig `yaml:"vlm_config" json:"vlm_config" gorm:"type:json"`
|
||||
// Storage config
|
||||
StorageConfig StorageConfig `yaml:"storage_config" json:"storage_config" gorm:"column:cos_config;type:json"`
|
||||
// Storage provider config (new): only stores provider selection; credentials from tenant StorageEngineConfig
|
||||
StorageProviderConfig *StorageProviderConfig `yaml:"storage_provider_config" json:"storage_provider_config" gorm:"column:storage_provider_config;type:jsonb"`
|
||||
// Deprecated: legacy COS config column. Kept for backward compatibility with old data.
|
||||
StorageConfig StorageConfig `yaml:"-" json:"storage_config" gorm:"column:cos_config;type:json"`
|
||||
// Extract config
|
||||
ExtractConfig *ExtractConfig `yaml:"extract_config" json:"extract_config" gorm:"column:extract_config;type:json"`
|
||||
// FAQConfig stores FAQ specific configuration such as indexing strategy
|
||||
@@ -130,22 +133,37 @@ func (c ChunkingConfig) ResolveParserEngine(fileType string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// COSConfig represents the COS configuration
|
||||
// StorageProviderConfig stores the KB-level storage provider selection.
|
||||
// Credentials are managed at the tenant level (StorageEngineConfig).
|
||||
type StorageProviderConfig struct {
|
||||
Provider string `yaml:"provider" json:"provider"` // "local", "minio", "cos", "tos"
|
||||
}
|
||||
|
||||
func (c StorageProviderConfig) Value() (driver.Value, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
func (c *StorageProviderConfig) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
b, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(b, c)
|
||||
}
|
||||
|
||||
// Deprecated: StorageConfig is the legacy COS configuration stored in the cos_config column.
|
||||
// New code should use StorageProviderConfig. Kept for backward compatibility with old data.
|
||||
type StorageConfig struct {
|
||||
// Secret ID
|
||||
SecretID string `yaml:"secret_id" json:"secret_id"`
|
||||
// Secret Key
|
||||
SecretKey string `yaml:"secret_key" json:"secret_key"`
|
||||
// Region
|
||||
Region string `yaml:"region" json:"region"`
|
||||
// Bucket Name
|
||||
SecretID string `yaml:"secret_id" json:"secret_id"`
|
||||
SecretKey string `yaml:"secret_key" json:"secret_key"`
|
||||
Region string `yaml:"region" json:"region"`
|
||||
BucketName string `yaml:"bucket_name" json:"bucket_name"`
|
||||
// App ID
|
||||
AppID string `yaml:"app_id" json:"app_id"`
|
||||
// Path Prefix
|
||||
AppID string `yaml:"app_id" json:"app_id"`
|
||||
PathPrefix string `yaml:"path_prefix" json:"path_prefix"`
|
||||
// Provider
|
||||
Provider string `yaml:"provider" json:"provider"`
|
||||
Provider string `yaml:"provider" json:"provider"`
|
||||
}
|
||||
|
||||
func (c StorageConfig) Value() (driver.Value, error) {
|
||||
@@ -164,7 +182,7 @@ func (c *StorageConfig) Scan(value interface{}) error {
|
||||
}
|
||||
|
||||
// UnmarshalJSON keeps backward compatibility for legacy clients that still send
|
||||
// `cos_config`, while standardizing the API field as `storage_config`.
|
||||
// `cos_config` or `storage_config`, while migrating to `storage_provider_config`.
|
||||
func (kb *KnowledgeBase) UnmarshalJSON(data []byte) error {
|
||||
type alias KnowledgeBase
|
||||
aux := struct {
|
||||
@@ -176,12 +194,57 @@ func (kb *KnowledgeBase) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
// Backward compat: populate legacy StorageConfig from cos_config
|
||||
if aux.LegacyStorageConfig != nil && kb.StorageConfig == (StorageConfig{}) {
|
||||
kb.StorageConfig = *aux.LegacyStorageConfig
|
||||
}
|
||||
// Auto-populate StorageProviderConfig from legacy StorageConfig if not set
|
||||
if kb.StorageProviderConfig == nil && kb.StorageConfig.Provider != "" {
|
||||
kb.StorageProviderConfig = &StorageProviderConfig{Provider: kb.StorageConfig.Provider}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStorageProvider returns the effective storage provider for this KB.
|
||||
// Priority: StorageProviderConfig (new) > StorageConfig.Provider (legacy cos_config).
|
||||
func (kb *KnowledgeBase) GetStorageProvider() string {
|
||||
if kb == nil {
|
||||
return ""
|
||||
}
|
||||
if kb.StorageProviderConfig != nil {
|
||||
p := strings.ToLower(strings.TrimSpace(kb.StorageProviderConfig.Provider))
|
||||
if p != "" && p != "__pending_env__" {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(kb.StorageConfig.Provider))
|
||||
}
|
||||
|
||||
// SetStorageProvider writes the provider to the new StorageProviderConfig field.
|
||||
func (kb *KnowledgeBase) SetStorageProvider(provider string) {
|
||||
if kb == nil {
|
||||
return
|
||||
}
|
||||
kb.StorageProviderConfig = &StorageProviderConfig{Provider: provider}
|
||||
}
|
||||
|
||||
// InferStorageFromFilePath deduces the storage provider from a file path format.
|
||||
// Used as a safety fallback when the KB's configured provider doesn't match the data.
|
||||
func InferStorageFromFilePath(filePath string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(filePath, "minio://"):
|
||||
return "minio"
|
||||
case strings.HasPrefix(filePath, "tos://"):
|
||||
return "tos"
|
||||
case strings.HasPrefix(filePath, "https://") && strings.Contains(filePath, ".cos."):
|
||||
return "cos"
|
||||
case strings.HasPrefix(filePath, "/"):
|
||||
return "local"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// ImageProcessingConfig represents the image processing configuration
|
||||
type ImageProcessingConfig struct {
|
||||
// Model ID
|
||||
|
||||
@@ -55,6 +55,7 @@ CREATE TABLE IF NOT EXISTS knowledge_bases (
|
||||
embedding_model_id VARCHAR(64) NOT NULL,
|
||||
summary_model_id VARCHAR(64) NOT NULL,
|
||||
cos_config TEXT NOT NULL DEFAULT '{}',
|
||||
storage_provider_config TEXT DEFAULT NULL,
|
||||
vlm_config TEXT NOT NULL DEFAULT '{}',
|
||||
extract_config TEXT NULL DEFAULT NULL,
|
||||
faq_config TEXT,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Rollback: copy provider back to cos_config if needed, then drop new column
|
||||
UPDATE knowledge_bases
|
||||
SET cos_config = jsonb_set(
|
||||
COALESCE(cos_config, '{}'::jsonb),
|
||||
'{provider}',
|
||||
to_jsonb(storage_provider_config->>'provider')
|
||||
)
|
||||
WHERE storage_provider_config IS NOT NULL
|
||||
AND storage_provider_config->>'provider' IS NOT NULL
|
||||
AND storage_provider_config->>'provider' != ''
|
||||
AND storage_provider_config->>'provider' != '__pending_env__'
|
||||
AND (cos_config IS NULL
|
||||
OR cos_config->>'provider' IS NULL
|
||||
OR cos_config->>'provider' = '');
|
||||
|
||||
ALTER TABLE knowledge_bases DROP COLUMN IF EXISTS storage_provider_config;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- Description: Add storage_provider_config column to knowledge_bases, migrating from legacy cos_config.
|
||||
-- This separates the storage provider selection (KB-level) from storage credentials (tenant-level).
|
||||
DO $$ BEGIN RAISE NOTICE '[Migration 000014] Adding storage_provider_config to knowledge_bases'; END $$;
|
||||
|
||||
-- Step 1: Add new column
|
||||
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS storage_provider_config JSONB DEFAULT NULL;
|
||||
COMMENT ON COLUMN knowledge_bases.storage_provider_config IS 'Storage provider config for this KB. Only stores provider name; credentials come from tenant StorageEngineConfig.';
|
||||
|
||||
-- Step 2: Migrate existing provider from legacy cos_config
|
||||
UPDATE knowledge_bases
|
||||
SET storage_provider_config = jsonb_build_object('provider', cos_config->>'provider')
|
||||
WHERE cos_config IS NOT NULL
|
||||
AND cos_config->>'provider' IS NOT NULL
|
||||
AND cos_config->>'provider' != ''
|
||||
AND (storage_provider_config IS NULL
|
||||
OR storage_provider_config->>'provider' IS NULL
|
||||
OR storage_provider_config->>'provider' = '');
|
||||
|
||||
-- Step 3: For KBs that have documents but no provider set, mark them with the
|
||||
-- sentinel value so the application can fill in the actual STORAGE_TYPE on startup.
|
||||
-- We use 'pending_migration' as a marker; the app replaces it with the real env value.
|
||||
UPDATE knowledge_bases kb
|
||||
SET storage_provider_config = '{"provider": "__pending_env__"}'
|
||||
WHERE (kb.storage_provider_config IS NULL
|
||||
OR kb.storage_provider_config->>'provider' IS NULL
|
||||
OR kb.storage_provider_config->>'provider' = '')
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM knowledges k
|
||||
WHERE k.knowledge_base_id = kb.id
|
||||
AND k.deleted_at IS NULL
|
||||
);
|
||||
Reference in New Issue
Block a user