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:
2026-07-10 20:10:39 +08:00
parent 418b3c4998
commit 0bf4b73ebc
12 changed files with 998 additions and 32 deletions
@@ -39,15 +39,16 @@ type PromptRuleGenerationService struct {
}
type promptRuleGenerationJob struct {
OperatorID int64
TenantID int64
ArticleID int64
TaskID int64
ReservationID int64
Prompt string
InputParams map[string]interface{}
InitialTitle string
WebSearch llm.WebSearchOptions
OperatorID int64
TenantID int64
ArticleID int64
TaskID int64
ReservationID int64
Prompt string
InputParams map[string]interface{}
InitialTitle string
WebSearch llm.WebSearchOptions
KnowledgeFactConstraints []KnowledgeFactConstraint
}
type GenerateFromRuleRequest struct {
@@ -282,6 +283,10 @@ func (s *PromptRuleGenerationService) enqueuePromptRuleGeneration(ctx context.Co
if rule.Status != "enabled" {
return nil, response.ErrBadRequest(40017, "prompt_rule_disabled", "prompt rule is disabled")
}
brandPromptContext, err := loadBrandPromptContext(ctx, s.pool, input.TenantID, *input.BrandID)
if err != nil {
return nil, err
}
extra := input.ExtraParams
generationMode := strings.TrimSpace(extractString(extra, "generation_mode"))
@@ -346,6 +351,7 @@ func (s *PromptRuleGenerationService) enqueuePromptRuleGeneration(ctx context.Co
inputParams["knowledge_group_ids"] = rule.KnowledgeGroupIDs
}
inputParams = attachPromptRuleSnapshot(inputParams, rule)
inputParams = attachBrandPromptContext(inputParams, brandPromptContext)
knowledgePrompt := ""
if len(rule.KnowledgeGroupIDs) > 0 {
@@ -363,6 +369,7 @@ func (s *PromptRuleGenerationService) enqueuePromptRuleGeneration(ctx context.Co
return nil, resolveErr
}
knowledgePrompt = s.knowledge.RenderPromptSection(knowledgeContext)
inputParams = attachKnowledgeFactConstraints(inputParams, knowledgeContext.FactConstraints)
if knowledgePrompt != "" {
inputParams[generationPayloadKnowledgePrompt] = knowledgePrompt
}
@@ -559,7 +566,7 @@ func (s *PromptRuleGenerationService) executeGeneration(ctx context.Context, job
req.WebSearch = &job.WebSearch
}
result, err := s.llm.Generate(ctx, req, func(delta string) {
result, err := GenerateArticleWithKnowledgeFactGuard(ctx, s.llm, req, job.KnowledgeFactConstraints, func(delta string) {
if runtimeCfg.StreamEnabled && s.streams != nil && delta != "" {
s.streams.AppendDelta(job.ArticleID, job.TaskID, title, delta)
}
@@ -838,6 +845,9 @@ func buildPromptRuleGenerationPrompt(rule *promptRuleGenerationRecord, params ma
if len(supplements) > 0 {
sections = append(sections, prompts.PromptRuleSupplementSection(supplements))
}
if brandPrompt := RenderBrandPromptContext(extractBrandPromptContext(params)); brandPrompt != "" {
sections = append(sections, brandPrompt)
}
if strings.TrimSpace(knowledgePrompt) != "" {
sections = append(sections, knowledgePrompt)