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:
@@ -0,0 +1,19 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildKolGenerationPromptPlacesBrandBeforeKnowledge(t *testing.T) {
|
||||
prompt := buildKolGenerationPrompt("TASK_SENTINEL", "BRAND_SENTINEL", "KNOWLEDGE_SENTINEL")
|
||||
markers := []string{"TASK_SENTINEL", "BRAND_SENTINEL", "KNOWLEDGE_SENTINEL"}
|
||||
previous := -1
|
||||
for _, marker := range markers {
|
||||
index := strings.Index(prompt, marker)
|
||||
if index < 0 || index <= previous {
|
||||
t.Fatalf("buildKolGenerationPrompt() = %q, markers out of order: %#v", prompt, markers)
|
||||
}
|
||||
previous = index
|
||||
}
|
||||
}
|
||||
@@ -114,8 +114,16 @@ func (w *KolGenerationWorker) Process(ctx context.Context, task tenantapp.Claime
|
||||
w.HandleTaskFailure(ctx, task, "task_load", err)
|
||||
return
|
||||
}
|
||||
brandID, brandPromptContext, err := tenantapp.ResolveGenerationBrandPromptContext(ctx, w.pool, task.TenantID, task.ArticleID, task.InputParams)
|
||||
if err != nil {
|
||||
w.HandleTaskFailure(ctx, task, "brand_context_load", err)
|
||||
return
|
||||
}
|
||||
task.InputParams["brand_id"] = brandID
|
||||
brandPrompt := tenantapp.RenderBrandPromptContext(brandPromptContext)
|
||||
|
||||
knowledgePrompt := ""
|
||||
var knowledgeFactConstraints []tenantapp.KnowledgeFactConstraint
|
||||
if knowledgeGroupIDs := kolKnowledgeGroupIDs(task.InputParams["knowledge_group_ids"]); len(knowledgeGroupIDs) > 0 {
|
||||
if w.knowledge == nil {
|
||||
w.HandleTaskFailure(ctx, task, "knowledge_resolve", fmt.Errorf("knowledge service is not configured"))
|
||||
@@ -125,7 +133,7 @@ func (w *KolGenerationWorker) Process(ctx context.Context, task tenantapp.Claime
|
||||
knowledgeContext, err := w.knowledge.ResolveContext(
|
||||
ctx,
|
||||
task.TenantID,
|
||||
int64(kolIntInput(task.InputParams, "brand_id")),
|
||||
brandID,
|
||||
knowledgeGroupIDs,
|
||||
buildKolKnowledgeQuery(task.InputParams),
|
||||
)
|
||||
@@ -134,12 +142,10 @@ func (w *KolGenerationWorker) Process(ctx context.Context, task tenantapp.Claime
|
||||
return
|
||||
}
|
||||
knowledgePrompt = w.knowledge.RenderPromptSection(knowledgeContext)
|
||||
knowledgeFactConstraints = knowledgeContext.FactConstraints
|
||||
}
|
||||
|
||||
prompt := renderedPrompt
|
||||
if strings.TrimSpace(knowledgePrompt) != "" {
|
||||
prompt = strings.TrimSpace(renderedPrompt + "\n\n" + knowledgePrompt)
|
||||
}
|
||||
prompt := buildKolGenerationPrompt(renderedPrompt, brandPrompt, knowledgePrompt)
|
||||
|
||||
articleTimeout, maxOutputTokens := w.runtimeConfig()
|
||||
generateReq := llm.GenerateRequest{
|
||||
@@ -151,7 +157,8 @@ func (w *KolGenerationWorker) Process(ctx context.Context, task tenantapp.Claime
|
||||
generateReq.WebSearch = &llm.WebSearchOptions{Enabled: true}
|
||||
}
|
||||
|
||||
result, err := w.llm.Generate(ctx, generateReq, nil)
|
||||
knowledgeFactConstraints = tenantapp.MergeKnowledgeFactConstraints(brandPromptContext.FactConstraints, knowledgeFactConstraints)
|
||||
result, err := tenantapp.GenerateArticleWithKnowledgeFactGuard(ctx, w.llm, generateReq, knowledgeFactConstraints, nil)
|
||||
if err != nil {
|
||||
w.HandleTaskFailure(ctx, task, "llm_generate", err)
|
||||
return
|
||||
@@ -263,6 +270,16 @@ func (w *KolGenerationWorker) Process(ctx context.Context, task tenantapp.Claime
|
||||
})
|
||||
}
|
||||
|
||||
func buildKolGenerationPrompt(renderedPrompt string, brandPrompt string, knowledgePrompt string) string {
|
||||
sections := make([]string, 0, 3)
|
||||
for _, section := range []string{renderedPrompt, brandPrompt, knowledgePrompt} {
|
||||
if section = strings.TrimSpace(section); section != "" {
|
||||
sections = append(sections, section)
|
||||
}
|
||||
}
|
||||
return strings.Join(sections, "\n\n")
|
||||
}
|
||||
|
||||
func (w *KolGenerationWorker) HandleTaskFailure(ctx context.Context, task tenantapp.ClaimedGenerationTask, stage string, taskErr error) {
|
||||
if err := tenantapp.HandleKolGenerationTaskFailure(ctx, w.pool, w.cache, task, stage, taskErr); err != nil && w.logger != nil {
|
||||
w.logger.Warn("kol generation task failure handling failed",
|
||||
|
||||
Reference in New Issue
Block a user