feat(tenant): inject brand description context into every generation path
Include the current brand description in template, custom, imitation, and KOL article-generation prompts while bounding prompt growth. - Add a shared brand-context builder that resolves the current brand, compacts long descriptions, and caps rendered output at 2000 runes. - Snapshot a structured brand prompt context on queued tasks so old jobs stay reproducible and retries stay stable. - Route brand-profile facts through the existing typed fact guard so founding dates and year counts get the same pre-persistence validation as RAG facts, with brand facts outranking conflicting RAG facts. - Fix the KOL creator to attach the brand scope its worker consumes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -137,6 +137,10 @@ func (s *ArticleImitationService) Generate(ctx context.Context, req GenerateImit
|
||||
if strings.TrimSpace(req.BrandName) == "" {
|
||||
return nil, response.ErrBadRequest(40001, "brand_name_required", "brand_name is required")
|
||||
}
|
||||
brandPromptContext, err := loadBrandPromptContext(ctx, s.pool, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quotaRepo := repository.NewQuotaRepository(s.pool)
|
||||
balance, err := quotaRepo.GetCurrentBalance(ctx, actor.TenantID, "article_generation")
|
||||
@@ -146,6 +150,7 @@ func (s *ArticleImitationService) Generate(ctx context.Context, req GenerateImit
|
||||
|
||||
inputParams := buildImitationInputParams(sourceURL, req)
|
||||
inputParams["brand_id"] = brandID
|
||||
inputParams = attachBrandPromptContext(inputParams, brandPromptContext)
|
||||
inputJSON, _ := json.Marshal(inputParams)
|
||||
initialTitle := buildImitationInitialTitle(req.SourceTitle)
|
||||
wizardStateJSON, _ := json.Marshal(map[string]interface{}{
|
||||
@@ -318,6 +323,13 @@ func (s *ArticleImitationService) executeGeneration(ctx context.Context, job art
|
||||
}
|
||||
RecordGenerationTaskEvent(GenerationTaskEventTransition, articleImitationJobLogContext(job, "task_status_running", "", GenerationTaskStatusRunning, GenerationTaskResultSuccess))
|
||||
LogGenerationTaskInfo(s.logger, "article generation task marked running", articleImitationJobLogContext(job, "task_status_running", "", GenerationTaskStatusRunning, GenerationTaskResultSuccess))
|
||||
brandID, brandPromptContext, err := ResolveGenerationBrandPromptContext(ctx, s.pool, job.TenantID, job.ArticleID, job.InputParams)
|
||||
if err != nil {
|
||||
s.failGeneration(ctx, job, "brand_context_load", err)
|
||||
return
|
||||
}
|
||||
job.InputParams["brand_id"] = brandID
|
||||
job.InputParams = attachBrandPromptContext(job.InputParams, brandPromptContext)
|
||||
|
||||
sourceURL := strings.TrimSpace(extractString(job.InputParams, "source_url"))
|
||||
if sourceURL == "" {
|
||||
@@ -345,6 +357,7 @@ func (s *ArticleImitationService) executeGeneration(ctx context.Context, job art
|
||||
}
|
||||
|
||||
knowledgePrompt := ""
|
||||
var knowledgeFactConstraints []KnowledgeFactConstraint
|
||||
if knowledgeGroupIDs := extractKnowledgeGroupIDs(job.InputParams["knowledge_group_ids"]); len(knowledgeGroupIDs) > 0 {
|
||||
if s.knowledge == nil {
|
||||
s.failGeneration(ctx, job, "knowledge_resolve", fmt.Errorf("knowledge service is not configured"))
|
||||
@@ -353,7 +366,7 @@ func (s *ArticleImitationService) executeGeneration(ctx context.Context, job art
|
||||
knowledgeContext, resolveErr := s.knowledge.ResolveContext(
|
||||
ctx,
|
||||
job.TenantID,
|
||||
int64(extractInt(job.InputParams, "brand_id")),
|
||||
brandID,
|
||||
knowledgeGroupIDs,
|
||||
buildImitationKnowledgeQuery(job.InputParams),
|
||||
)
|
||||
@@ -362,6 +375,7 @@ func (s *ArticleImitationService) executeGeneration(ctx context.Context, job art
|
||||
return
|
||||
}
|
||||
knowledgePrompt = s.knowledge.RenderPromptSection(knowledgeContext)
|
||||
knowledgeFactConstraints = knowledgeContext.FactConstraints
|
||||
}
|
||||
|
||||
prompt := buildImitationGenerationPrompt(job.InputParams, sourceContent, knowledgePrompt)
|
||||
@@ -375,7 +389,8 @@ func (s *ArticleImitationService) executeGeneration(ctx context.Context, job art
|
||||
req.WebSearch = &job.WebSearch
|
||||
}
|
||||
|
||||
result, err := s.llm.Generate(ctx, req, func(delta string) {
|
||||
knowledgeFactConstraints = MergeKnowledgeFactConstraints(brandPromptContext.FactConstraints, knowledgeFactConstraints)
|
||||
result, err := GenerateArticleWithKnowledgeFactGuard(ctx, s.llm, req, knowledgeFactConstraints, func(delta string) {
|
||||
if runtimeCfg.StreamEnabled && s.streams != nil && delta != "" {
|
||||
s.streams.AppendDelta(job.ArticleID, job.TaskID, title, delta)
|
||||
}
|
||||
@@ -669,12 +684,19 @@ func buildImitationGenerationPrompt(params map[string]interface{}, sourceContent
|
||||
if knowledgePrompt != "" {
|
||||
knowledgeSection = "## 知识库参考\n" + knowledgePrompt
|
||||
}
|
||||
referenceSections := make([]string, 0, 2)
|
||||
if brandPrompt := RenderBrandPromptContext(extractBrandPromptContext(params)); brandPrompt != "" {
|
||||
referenceSections = append(referenceSections, brandPrompt)
|
||||
}
|
||||
if knowledgeSection != "" {
|
||||
referenceSections = append(referenceSections, knowledgeSection)
|
||||
}
|
||||
|
||||
return prompts.ArticleImitationPrompt(
|
||||
locale,
|
||||
sourceMeta.String(),
|
||||
settings.String(),
|
||||
knowledgeSection,
|
||||
strings.Join(referenceSections, "\n\n"),
|
||||
sourceContent,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user