feat(kol): store KOL prompt content in database with object storage fallback
Add prompt_content columns to kol_prompts and kol_prompt_revisions so prompt bodies live alongside their metadata, eliminating an extra round trip to object storage for the common read path while keeping the old asset key as a fallback for legacy rows. Reads go through a singleflight-deduped, cache-backed loader that prefers the database column, falls back to the asset key, and tolerates missing objects via a new objectstorage.ErrObjectNotFound returned by both the Aliyun OSS and MinIO clients on 404/NoSuchKey responses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/shared/auth"
|
||||
sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache"
|
||||
@@ -95,6 +96,7 @@ type KolGenerationService struct {
|
||||
asset *KolPromptAsset
|
||||
rabbitMQ *rabbitmq.Client
|
||||
cache sharedcache.Cache
|
||||
cacheGroup singleflight.Group
|
||||
}
|
||||
|
||||
func NewKolGenerationService(
|
||||
@@ -223,14 +225,13 @@ func (s *KolGenerationService) enqueue(ctx context.Context, input kolGenerationE
|
||||
return nil, response.ErrInternal(50097, "kol_generation_card_config_decode_failed", err.Error())
|
||||
}
|
||||
|
||||
if prompt.PromptAssetKey == nil || *prompt.PromptAssetKey == "" {
|
||||
return nil, response.ErrInternal(50098, "kol_generation_prompt_asset_missing", "published prompt content not found")
|
||||
}
|
||||
|
||||
content, err := s.asset.Get(ctx, *prompt.PromptAssetKey)
|
||||
content, err := s.loadPromptContent(ctx, prompt)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50098, "kol_generation_prompt_asset_load_failed", err.Error())
|
||||
}
|
||||
if strings.TrimSpace(content) == "" {
|
||||
return nil, response.ErrInternal(50098, "kol_generation_prompt_content_missing", "published prompt content not found")
|
||||
}
|
||||
|
||||
renderedPrompt, err := RenderPrompt(content, *schema, variables)
|
||||
if err != nil {
|
||||
@@ -512,13 +513,22 @@ func (s *KolGenerationService) loadAuthorizedPrompt(ctx context.Context, actor a
|
||||
if err != nil {
|
||||
return nil, nil, response.ErrInternal(50110, "kol_prompt_query_failed", err.Error())
|
||||
}
|
||||
if prompt == nil || prompt.PromptAssetKey == nil || *prompt.PromptAssetKey == "" {
|
||||
if prompt == nil || !s.promptHasContent(ctx, prompt) {
|
||||
return nil, nil, response.ErrInternal(50111, "kol_prompt_content_missing", "published prompt content not found")
|
||||
}
|
||||
|
||||
return row, prompt, nil
|
||||
}
|
||||
|
||||
func (s *KolGenerationService) loadPromptContent(ctx context.Context, prompt *repository.KolPrompt) (string, error) {
|
||||
return loadKolPromptContent(ctx, s.cache, &s.cacheGroup, s.promptRepo, s.asset, prompt)
|
||||
}
|
||||
|
||||
func (s *KolGenerationService) promptHasContent(ctx context.Context, prompt *repository.KolPrompt) bool {
|
||||
content, err := s.loadPromptContent(ctx, prompt)
|
||||
return err == nil && strings.TrimSpace(content) != ""
|
||||
}
|
||||
|
||||
func HandleKolGenerationTaskFailure(
|
||||
ctx context.Context,
|
||||
pool *pgxpool.Pool,
|
||||
|
||||
Reference in New Issue
Block a user