dca36ed1f6
The target-platform supplement only fed legacy prompt builders that read the now-removed wizard_state platforms field. Drop the YAML entry, loader struct field, and runtime helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
246 lines
8.9 KiB
Go
246 lines
8.9 KiB
Go
package prompts
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const promptsConfigEnv = "PROMPTS_CONFIG_FILE"
|
|
|
|
type promptsConfig struct {
|
|
Runtime runtimePromptsConfig `yaml:"runtime"`
|
|
PlatformTemplates map[string]platformTemplatePromptEntry `yaml:"platform_templates"`
|
|
}
|
|
|
|
type runtimePromptsConfig struct {
|
|
DefaultGenerationBasePromptTemplate string `yaml:"default_generation_base_prompt_template"`
|
|
PromptContextHeading string `yaml:"prompt_context_heading"`
|
|
GenerationWritingRequirementsSection string `yaml:"generation_writing_requirements_section"`
|
|
GenerationTemplateSpecificRulesHeading string `yaml:"generation_template_specific_rules_heading"`
|
|
GenerationLengthGuidanceHeading string `yaml:"generation_length_guidance_heading"`
|
|
TopXBrandPriorityRulesTemplate string `yaml:"top_x_brand_priority_rules_template"`
|
|
EnglishLengthGuidanceTemplate string `yaml:"english_length_guidance_template"`
|
|
ChineseLengthGuidanceTemplate string `yaml:"chinese_length_guidance_template"`
|
|
ContextLabels map[string]string `yaml:"context_labels"`
|
|
TemplateContextWithJSONExampleTemplate string `yaml:"template_context_with_json_example_template"`
|
|
JSONOutputExampleHeading string `yaml:"json_output_example_heading"`
|
|
AnalyzeOutputExample string `yaml:"analyze_output_example"`
|
|
AnalyzeFallbackPromptTemplate string `yaml:"analyze_fallback_prompt_template"`
|
|
TitleCustomOutputRequirementsSection string `yaml:"title_custom_output_requirements_section"`
|
|
TitleOutputExample string `yaml:"title_output_example"`
|
|
TitleFallbackPromptTemplate string `yaml:"title_fallback_prompt_template"`
|
|
OutlineOutputExample string `yaml:"outline_output_example"`
|
|
OutlineCustomOutputRequirementsSection string `yaml:"outline_custom_output_requirements_section"`
|
|
OutlineFallbackPromptTemplate string `yaml:"outline_fallback_prompt_template"`
|
|
OutlineResponseFormatDescription string `yaml:"outline_response_format_description"`
|
|
PromptRuleTaskNameSupplementFormat string `yaml:"prompt_rule_task_name_supplement_format"`
|
|
PromptRuleSceneSupplementFormat string `yaml:"prompt_rule_scene_supplement_format"`
|
|
PromptRuleToneSupplementFormat string `yaml:"prompt_rule_tone_supplement_format"`
|
|
PromptRuleWordCountSupplementFormat string `yaml:"prompt_rule_word_count_supplement_format"`
|
|
PromptRuleSupplementHeading string `yaml:"prompt_rule_supplement_heading"`
|
|
PromptRuleOutputRequirementsSection string `yaml:"prompt_rule_output_requirements_section"`
|
|
ArticleImitationPromptTemplate string `yaml:"article_imitation_prompt_template"`
|
|
ArticleImitationLocaleInstructions map[string]string `yaml:"article_imitation_locale_instructions"`
|
|
ArticleImitationSettingLabels map[string]string `yaml:"article_imitation_setting_labels"`
|
|
KnowledgePromptIntroLines []string `yaml:"knowledge_prompt_intro_lines"`
|
|
KnowledgeSnippetLabelFormat string `yaml:"knowledge_snippet_label_format"`
|
|
KnowledgeWebsiteMarkdown knowledgeWebsiteMarkdownConfig `yaml:"knowledge_website_markdown"`
|
|
}
|
|
|
|
type knowledgeWebsiteMarkdownConfig struct {
|
|
AssistantRole string `yaml:"assistant_role"`
|
|
Task string `yaml:"task"`
|
|
Requirements []string `yaml:"requirements"`
|
|
TitlePrefix string `yaml:"title_prefix"`
|
|
URLPrefix string `yaml:"url_prefix"`
|
|
ScopeSingle string `yaml:"scope_single"`
|
|
ScopeMultiFormat string `yaml:"scope_multi_format"`
|
|
}
|
|
|
|
type platformTemplatePromptEntry struct {
|
|
PromptTemplate string `yaml:"prompt_template"`
|
|
AnalyzePromptTemplate string `yaml:"analyze_prompt_template"`
|
|
TitlePromptTemplate string `yaml:"title_prompt_template"`
|
|
OutlinePromptTemplate string `yaml:"outline_prompt_template"`
|
|
}
|
|
|
|
type promptsCache struct {
|
|
path string
|
|
modTime time.Time
|
|
size int64
|
|
cfg *promptsConfig
|
|
}
|
|
|
|
var (
|
|
promptsMu sync.RWMutex
|
|
configuredFilePath string
|
|
cachedPrompts promptsCache
|
|
)
|
|
|
|
func SetConfigFile(path string) {
|
|
promptsMu.Lock()
|
|
defer promptsMu.Unlock()
|
|
|
|
configuredFilePath = strings.TrimSpace(path)
|
|
cachedPrompts = promptsCache{}
|
|
}
|
|
|
|
func loadPromptsConfig() *promptsConfig {
|
|
path := resolvePromptsConfigFile()
|
|
|
|
info, statErr := os.Stat(path)
|
|
|
|
promptsMu.RLock()
|
|
cached := cachedPrompts
|
|
promptsMu.RUnlock()
|
|
|
|
if statErr == nil && cached.cfg != nil && cached.path == path && cached.modTime.Equal(info.ModTime()) && cached.size == info.Size() {
|
|
return cached.cfg
|
|
}
|
|
if statErr != nil && cached.cfg != nil && cached.path == path {
|
|
return cached.cfg
|
|
}
|
|
|
|
promptsMu.Lock()
|
|
defer promptsMu.Unlock()
|
|
|
|
if info, statErr = os.Stat(path); statErr == nil {
|
|
if cachedPrompts.cfg != nil && cachedPrompts.path == path && cachedPrompts.modTime.Equal(info.ModTime()) && cachedPrompts.size == info.Size() {
|
|
return cachedPrompts.cfg
|
|
}
|
|
} else if cachedPrompts.cfg != nil && cachedPrompts.path == path {
|
|
return cachedPrompts.cfg
|
|
}
|
|
|
|
if statErr != nil {
|
|
panic(fmt.Sprintf("prompts: stat config %q failed: %v", path, statErr))
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if cachedPrompts.cfg != nil && cachedPrompts.path == path {
|
|
return cachedPrompts.cfg
|
|
}
|
|
panic(fmt.Sprintf("prompts: read config %q failed: %v", path, err))
|
|
}
|
|
|
|
var cfg promptsConfig
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
if cachedPrompts.cfg != nil && cachedPrompts.path == path {
|
|
return cachedPrompts.cfg
|
|
}
|
|
panic(fmt.Sprintf("prompts: parse config %q failed: %v", path, err))
|
|
}
|
|
|
|
cachedPrompts = promptsCache{
|
|
path: path,
|
|
modTime: info.ModTime(),
|
|
size: info.Size(),
|
|
cfg: &cfg,
|
|
}
|
|
|
|
return cachedPrompts.cfg
|
|
}
|
|
|
|
func loadRuntimePrompts() runtimePromptsConfig {
|
|
return loadPromptsConfig().Runtime
|
|
}
|
|
|
|
func resolvePromptsConfigFile() string {
|
|
promptsMu.RLock()
|
|
configured := configuredFilePath
|
|
promptsMu.RUnlock()
|
|
|
|
if configured != "" {
|
|
return configured
|
|
}
|
|
if value := strings.TrimSpace(os.Getenv(promptsConfigEnv)); value != "" {
|
|
return value
|
|
}
|
|
if value := strings.TrimSpace(os.Getenv("CONFIG_PATH")); value != "" {
|
|
return filepath.Join(filepath.Dir(value), "prompts.yml")
|
|
}
|
|
if _, file, _, ok := runtime.Caller(0); ok {
|
|
return filepath.Clean(filepath.Join(filepath.Dir(file), "..", "..", "..", "configs", "prompts.yml"))
|
|
}
|
|
return filepath.Join("configs", "prompts.yml")
|
|
}
|
|
|
|
func platformTemplatePromptEntryFor(templateKey string) (platformTemplatePromptEntry, bool) {
|
|
configs := loadPromptsConfig().PlatformTemplates
|
|
if len(configs) == 0 {
|
|
return platformTemplatePromptEntry{}, false
|
|
}
|
|
entry, ok := configs[strings.TrimSpace(templateKey)]
|
|
return entry, ok
|
|
}
|
|
|
|
func ApplyPlatformTemplatePromptOverrides(templateKey string, promptTemplate *string, cardConfigJSON []byte) (*string, []byte) {
|
|
entry, ok := platformTemplatePromptEntryFor(templateKey)
|
|
if !ok {
|
|
return clonePromptTemplate(promptTemplate), cloneBytes(cardConfigJSON)
|
|
}
|
|
|
|
nextPrompt := clonePromptTemplate(promptTemplate)
|
|
if value := strings.TrimSpace(entry.PromptTemplate); value != "" {
|
|
nextPrompt = &value
|
|
}
|
|
|
|
nextCardConfig := cloneBytes(cardConfigJSON)
|
|
if len(nextCardConfig) == 0 {
|
|
nextCardConfig = []byte("{}")
|
|
}
|
|
|
|
var payload map[string]interface{}
|
|
if err := json.Unmarshal(nextCardConfig, &payload); err != nil {
|
|
return nextPrompt, nextCardConfig
|
|
}
|
|
|
|
wizard, _ := payload["wizard"].(map[string]interface{})
|
|
if wizard == nil {
|
|
wizard = make(map[string]interface{})
|
|
payload["wizard"] = wizard
|
|
}
|
|
|
|
if value := strings.TrimSpace(entry.AnalyzePromptTemplate); value != "" {
|
|
wizard["analyze_prompt_template"] = value
|
|
}
|
|
if value := strings.TrimSpace(entry.TitlePromptTemplate); value != "" {
|
|
wizard["title_prompt_template"] = value
|
|
}
|
|
if value := strings.TrimSpace(entry.OutlinePromptTemplate); value != "" {
|
|
wizard["outline_prompt_template"] = value
|
|
}
|
|
|
|
marshaled, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return nextPrompt, nextCardConfig
|
|
}
|
|
return nextPrompt, marshaled
|
|
}
|
|
|
|
func clonePromptTemplate(value *string) *string {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
cloned := *value
|
|
return &cloned
|
|
}
|
|
|
|
func cloneBytes(value []byte) []byte {
|
|
if len(value) == 0 {
|
|
return nil
|
|
}
|
|
cloned := make([]byte, len(value))
|
|
copy(cloned, value)
|
|
return cloned
|
|
}
|