feat: Enhance Kol Generation Service with web search and knowledge group support

- Added `EnableWebSearch` and `KnowledgeGroupIDs` fields to `KolGenerationSubmitRequest`.
- Updated `Submit` method in `KolGenerationService` to handle new request fields.
- Integrated web search and knowledge group validation based on card configuration.
- Introduced caching mechanisms in `KolPackageService`, `KolPromptService`, and `KolSubscriptionAdminService` to improve performance.
- Implemented knowledge context resolution in `KolGenerationWorker` to enrich prompts with relevant knowledge snippets.
- Added utility functions for handling card configuration in both backend and frontend.
- Created tests for knowledge extraction and rendering to ensure accuracy and reliability.
This commit is contained in:
2026-04-18 13:47:32 +08:00
parent 3ef0807456
commit b2605abd6a
27 changed files with 2051 additions and 171 deletions
@@ -10,6 +10,7 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"github.com/geo-platform/tenant-api/internal/shared/auth"
sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache"
"github.com/geo-platform/tenant-api/internal/shared/response"
"github.com/geo-platform/tenant-api/internal/tenant/repository"
)
@@ -66,6 +67,7 @@ type KolPromptService struct {
promptRepo repository.KolPromptRepository
subRepo repository.KolSubscriptionRepository
asset *KolPromptAsset
cache sharedcache.Cache
}
func NewKolPromptService(
@@ -86,6 +88,11 @@ func NewKolPromptService(
}
}
func (s *KolPromptService) WithCache(c sharedcache.Cache) *KolPromptService {
s.cache = c
return s
}
func (s *KolPromptService) CreatePrompt(ctx context.Context, actor auth.Actor, input CreatePromptInput) (*KolPromptDetailResponse, error) {
if strings.TrimSpace(input.Name) == "" {
return nil, response.ErrBadRequest(40063, "kol_prompt_name_required", "prompt name is required")
@@ -107,6 +114,7 @@ func (s *KolPromptService) CreatePrompt(ctx context.Context, actor auth.Actor, i
return nil, response.ErrInternal(50070, "kol_prompt_create_failed", err.Error())
}
InvalidateKolPromptCaches(ctx, s.cache)
return s.buildPromptDetail(ctx, prompt, false)
}
@@ -176,6 +184,7 @@ func (s *KolPromptService) UpdateMetadata(ctx context.Context, actor auth.Actor,
return nil, ErrPromptNotFound
}
InvalidateKolPromptCaches(ctx, s.cache)
return s.buildPromptDetail(ctx, updated, false)
}
@@ -248,6 +257,7 @@ func (s *KolPromptService) Save(ctx context.Context, actor auth.Actor, promptID
return nil, ErrPromptNotFound
}
InvalidateKolPromptCaches(ctx, s.cache)
return s.buildPromptDetail(ctx, updated, true)
}
@@ -330,6 +340,7 @@ func (s *KolPromptService) Publish(ctx context.Context, actor auth.Actor, prompt
return nil, ErrPromptNotFound
}
InvalidateKolPromptCaches(ctx, s.cache)
return s.buildPromptDetail(ctx, updated, true)
}
@@ -373,6 +384,7 @@ func (s *KolPromptService) Activate(ctx context.Context, actor auth.Actor, promp
return nil, ErrPromptNotFound
}
InvalidateKolPromptCaches(ctx, s.cache)
return s.buildPromptDetail(ctx, updated, false)
}
@@ -393,6 +405,7 @@ func (s *KolPromptService) Archive(ctx context.Context, actor auth.Actor, prompt
return nil, ErrPromptNotFound
}
InvalidateKolPromptCaches(ctx, s.cache)
return s.buildPromptDetail(ctx, updated, false)
}
@@ -405,6 +418,7 @@ func (s *KolPromptService) Delete(ctx context.Context, actor auth.Actor, promptI
return response.ErrInternal(50082, "kol_prompt_delete_failed", err.Error())
}
InvalidateKolPromptCaches(ctx, s.cache)
return nil
}
@@ -520,10 +534,36 @@ func (s *KolPromptService) grantPromptToActiveSubscribers(
}
func normalizeKolCardConfig(cardConfig map[string]interface{}) map[string]interface{} {
if cardConfig == nil {
return map[string]interface{}{}
normalized := map[string]interface{}{
"allow_web_search": false,
"allow_user_knowledge": true,
}
return cardConfig
for key, value := range cardConfig {
normalized[key] = value
}
normalized["allow_web_search"] = kolCardConfigBoolValue(cardConfig["allow_web_search"], false)
normalized["allow_user_knowledge"] = kolCardConfigBoolValue(cardConfig["allow_user_knowledge"], true)
return normalized
}
func kolCardConfigBoolValue(value interface{}, defaultValue bool) bool {
switch typed := value.(type) {
case bool:
return typed
case *bool:
if typed != nil {
return *typed
}
}
return defaultValue
}
func kolCardConfigAllowsWebSearch(cardConfig map[string]interface{}) bool {
return kolCardConfigBoolValue(cardConfig["allow_web_search"], false)
}
func kolCardConfigAllowsUserKnowledge(cardConfig map[string]interface{}) bool {
return kolCardConfigBoolValue(cardConfig["allow_user_knowledge"], true)
}
func decodeKolSchema(raw []byte) (*KolSchemaJSON, error) {
@@ -542,7 +582,7 @@ func decodeKolSchema(raw []byte) (*KolSchemaJSON, error) {
func decodeKolCardConfig(raw []byte) (map[string]interface{}, error) {
trimmed := bytes.TrimSpace(raw)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
return map[string]interface{}{}, nil
return normalizeKolCardConfig(nil), nil
}
var cardConfig map[string]interface{}