Files
geo/server/internal/tenant/app/article_generation_payload.go
T
root 0bf4b73ebc 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>
2026-07-10 20:21:59 +08:00

163 lines
5.2 KiB
Go

package app
import (
"encoding/json"
"github.com/geo-platform/tenant-api/internal/tenant/repository"
)
const (
generationPayloadTemplateKey = "__snapshot_template_key"
generationPayloadTemplateName = "__snapshot_template_name"
generationPayloadTemplatePromptTemplate = "__snapshot_template_prompt_template"
generationPayloadPromptRuleID = "__snapshot_prompt_rule_id"
generationPayloadPromptRuleName = "__snapshot_prompt_rule_name"
generationPayloadPromptRuleContent = "__snapshot_prompt_rule_content"
generationPayloadPromptRuleScene = "__snapshot_prompt_rule_scene"
generationPayloadPromptRuleTone = "__snapshot_prompt_rule_tone"
generationPayloadPromptRuleWordCount = "__snapshot_prompt_rule_word_count"
generationPayloadKnowledgePrompt = "__snapshot_knowledge_prompt"
generationPayloadKnowledgeFacts = "__snapshot_knowledge_fact_constraints"
generationPayloadBrandPromptContext = "__snapshot_brand_prompt_context"
)
func attachTemplateSnapshot(payload map[string]interface{}, record *repository.TemplateRecord) map[string]interface{} {
if payload == nil {
payload = map[string]interface{}{}
}
if record == nil {
return payload
}
payload[generationPayloadTemplateKey] = record.TemplateKey
payload[generationPayloadTemplateName] = record.TemplateName
if record.PromptTemplate != nil {
payload[generationPayloadTemplatePromptTemplate] = *record.PromptTemplate
}
return payload
}
func attachBrandPromptContext(payload map[string]interface{}, brandCtx *BrandPromptContext) map[string]interface{} {
if payload == nil {
payload = map[string]interface{}{}
}
if brandCtx == nil {
delete(payload, generationPayloadBrandPromptContext)
return payload
}
normalized := normalizeBrandPromptContext(*brandCtx)
payload[generationPayloadBrandPromptContext] = &normalized
return payload
}
func extractBrandPromptContext(payload map[string]interface{}) *BrandPromptContext {
if payload == nil || payload[generationPayloadBrandPromptContext] == nil {
return nil
}
encoded, err := json.Marshal(payload[generationPayloadBrandPromptContext])
if err != nil {
return nil
}
var brandCtx BrandPromptContext
if err := json.Unmarshal(encoded, &brandCtx); err != nil {
return nil
}
if brandCtx.BrandID <= 0 {
return nil
}
brandCtx = normalizeBrandPromptContext(brandCtx)
return &brandCtx
}
func attachKnowledgeFactConstraints(payload map[string]interface{}, constraints []KnowledgeFactConstraint) map[string]interface{} {
if payload == nil {
payload = map[string]interface{}{}
}
if len(constraints) == 0 {
delete(payload, generationPayloadKnowledgeFacts)
return payload
}
payload[generationPayloadKnowledgeFacts] = constraints
return payload
}
func extractKnowledgeFactConstraints(payload map[string]interface{}) []KnowledgeFactConstraint {
if payload == nil || payload[generationPayloadKnowledgeFacts] == nil {
return nil
}
encoded, err := json.Marshal(payload[generationPayloadKnowledgeFacts])
if err != nil {
return nil
}
var constraints []KnowledgeFactConstraint
if err := json.Unmarshal(encoded, &constraints); err != nil {
return nil
}
return constraints
}
func extractTemplateSnapshot(payload map[string]interface{}) (string, string, *string) {
templateKey := extractString(payload, generationPayloadTemplateKey)
templateName := extractString(payload, generationPayloadTemplateName)
promptTemplateText := extractString(payload, generationPayloadTemplatePromptTemplate)
if templateKey == "" || templateName == "" {
return "", "", nil
}
if promptTemplateText == "" {
return templateKey, templateName, nil
}
return templateKey, templateName, &promptTemplateText
}
func attachPromptRuleSnapshot(payload map[string]interface{}, rule *promptRuleGenerationRecord) map[string]interface{} {
if payload == nil {
payload = map[string]interface{}{}
}
if rule == nil {
return payload
}
payload[generationPayloadPromptRuleID] = rule.ID
payload[generationPayloadPromptRuleName] = rule.Name
payload[generationPayloadPromptRuleContent] = rule.PromptContent
if rule.Scene != nil {
payload[generationPayloadPromptRuleScene] = *rule.Scene
}
if rule.DefaultTone != nil {
payload[generationPayloadPromptRuleTone] = *rule.DefaultTone
}
if rule.DefaultWordCount != nil {
payload[generationPayloadPromptRuleWordCount] = *rule.DefaultWordCount
}
return payload
}
func extractPromptRuleSnapshot(payload map[string]interface{}) *promptRuleGenerationRecord {
ruleID := int64(extractInt(payload, generationPayloadPromptRuleID))
name := extractString(payload, generationPayloadPromptRuleName)
content := extractString(payload, generationPayloadPromptRuleContent)
if ruleID <= 0 || content == "" {
return nil
}
record := &promptRuleGenerationRecord{
ID: ruleID,
Name: name,
PromptContent: content,
Status: "enabled",
}
if scene := extractString(payload, generationPayloadPromptRuleScene); scene != "" {
record.Scene = &scene
}
if tone := extractString(payload, generationPayloadPromptRuleTone); tone != "" {
record.DefaultTone = &tone
}
if wordCount := extractInt(payload, generationPayloadPromptRuleWordCount); wordCount > 0 {
value := wordCount
record.DefaultWordCount = &value
}
return record
}