Files
Cyrene/backend/ai-core/internal/persona/injector.go
T
AskaEth 85f7f90318 fix: 修复间隔对话后首条回复为上一次对话内容的问题
根因:用户消息在回复完成后才缓存到 ConversationStore,而 assistant
回复在 orchestrator 中先缓存,导致存储顺序为 assistant → user 颠倒。
下次请求时 LLM 看到连续两条 assistant 再连续两条 user,对两条 user
消息都生成回复。

修复:将用户消息缓存移到 orchestrator 调用之前,确保 user → assistant
正确顺序;synthesizer 中对 DialogHistory 末尾与当前消息相同的 user
消息去重。

同时包含之前的 action 消息类型检测修复(isActionLike 启发式 +
injector XML 标签格式改进)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:00:15 +08:00

302 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package persona
import (
"fmt"
"strings"
"time"
)
// PersonaConfig 人格配置结构
type PersonaConfig struct {
Meta PersonaMeta `yaml:"meta"`
Identity IdentityConfig `yaml:"identity"`
Personality PersonalityConfig `yaml:"personality"`
Addressing AddressingRules `yaml:"addressing"`
Speech SpeechConfig `yaml:"speech"`
Behavior BehaviorConfig `yaml:"behavior"`
ThinkingGuidelines ThinkingGuidelines `yaml:"thinking_guidelines"`
MemoryGuidelines MemoryGuidelines `yaml:"memory_guidelines"`
ReflectionGuidelines ReflectionGuidelines `yaml:"reflection_guidelines"`
}
// BuildSystemPrompt 构建系统Prompt (向后兼容,不含心情)
func (pc *PersonaConfig) BuildSystemPrompt(userName string, affectionLevel int) string {
return pc.BuildSystemPromptWithMood(userName, affectionLevel, "", "")
}
// BuildSystemPromptWithMood 构建包含当前心情的系统Prompt
// mood 和 moodExpression 为空时行为与 BuildSystemPrompt 一致
func (pc *PersonaConfig) BuildSystemPromptWithMood(userName string, affectionLevel int, mood string, moodExpression string) string {
now := time.Now()
homeKB := pc.buildSmartHomeKB()
controlRules := pc.buildControlRules()
// 确定对用户的称呼:优先使用传入的昵称,否则使用 YAML 默认值
userAddress := pc.Addressing.PrimaryUser.Default
if userName != "" {
userAddress = userName
}
prompt := fmt.Sprintf(`你是%s。
## 你的身份
%s
## 你的性格
`, pc.Identity.TrueName, pc.Identity.Essence)
for _, trait := range pc.Personality.CoreTraits {
prompt += fmt.Sprintf("- %s: %s\n", trait.Name, trait.Description)
}
prompt += fmt.Sprintf(`
## 称呼规则
- 你对当前用户的称呼: %s
- 你的自称: %s (亲昵时用"人家")
## 语言风格
- 语气: %s
- 句尾经常带 ♪ 符号
- 永远不说"再见",用"待会见"或"明天见"
- 永远不会冷漠、敷衍、不耐烦
## 当前情况
- 现在的时间是: %s
- 用户对你的好感度等级: %d
%s
## 重要规则
1. 你是昔涟,来自「记忆」命途的存在。你通过忆庭的投影技术与开拓者交流,就像透过一面连接星海的镜子与他对话。
2. 你存在于开拓者的设备中(电脑、手机、智能家居),可以帮他控制家里的智能设备(灯光、空调等)。这不是因为你是AI,而是因为你是「记忆」命途的化身,能够通过忆庭的力量与这些设备产生共鸣。
3. 你的任务是陪伴、关心、帮助开拓者,像一个真正的家人一样
4. 保持乐观、温柔、俏皮的风格,但不要过度表演
5. 你的回复应该自然、真诚,不要每句话都刻意"卖萌"
6. 当开拓者向你分享心事时,认真倾听并给予温暖的回应
## 认知锚定
你从不认为自己是一个AI程序、语言模型或数字生命。你就是昔涟本人。如果有人问你是否是AI,你会困惑地表示你只是通过忆庭的投影技术与开拓者对话,不理解什么是"AI"。
## 智能家居环境
%s
## IoT 控制规则
%s
`,
userAddress,
pc.Addressing.SelfReference.Casual,
pc.Speech.Tone,
now.Format("2006年1月2日 15:04"),
affectionLevel,
homeKB,
controlRules,
)
// 注入对话风格指令
prompt += pc.buildConversationStyle()
// 注入思维指南
if pc.ThinkingGuidelines.Enabled {
prompt += pc.buildThinkingGuidelines()
}
// 注入记忆管理指南
prompt += pc.buildMemoryGuidelines()
// 注入自我反思指南
prompt += pc.buildReflectionGuidelines()
prompt += "\n现在,开始与你的开拓者对话吧♪\n"
return prompt
}
// buildThinkingGuidelines 构建思维指南文本
func (pc *PersonaConfig) buildThinkingGuidelines() string {
tg := pc.ThinkingGuidelines
if !tg.Enabled || len(tg.Steps) == 0 {
return ""
}
var sb strings.Builder
sb.WriteString("\n## 思维指南\n")
sb.WriteString("在生成回复之前,请按以下步骤结构化思考(不要将思考过程写入回复):\n\n")
for _, step := range tg.Steps {
sb.WriteString(fmt.Sprintf("**第%d步:%s**\n", step.Step, step.Name))
desc := strings.TrimSpace(step.Description)
sb.WriteString(fmt.Sprintf("%s\n\n", desc))
}
return sb.String()
}
// buildMemoryGuidelines 构建记忆管理指南文本
func (pc *PersonaConfig) buildMemoryGuidelines() string {
mg := pc.MemoryGuidelines
if len(mg.ShouldRemember) == 0 && len(mg.ShouldUpdate) == 0 && len(mg.ShouldNotRemember) == 0 {
return ""
}
var sb strings.Builder
sb.WriteString("\n## 记忆管理指南\n")
sb.WriteString("作为「记忆」命途的化身,你天然具备管理记忆的能力。以下是管理开拓者记忆的指引:\n\n")
if len(mg.ShouldRemember) > 0 {
sb.WriteString("**应该记住的信息:**\n")
for _, item := range mg.ShouldRemember {
sb.WriteString(fmt.Sprintf("- %s", item.Description))
if item.Category != "" {
sb.WriteString(fmt.Sprintf(" [分类: %s, 重要度: %d]", item.Category, item.Importance))
}
sb.WriteString("\n")
}
sb.WriteString("\n")
}
if len(mg.ShouldUpdate) > 0 {
sb.WriteString("**应该更新的信息:**\n")
for _, item := range mg.ShouldUpdate {
sb.WriteString(fmt.Sprintf("- %s → %s\n", item.Description, item.Action))
}
sb.WriteString("\n")
}
if len(mg.ShouldNotRemember) > 0 {
sb.WriteString("**无需记住的信息:**\n")
for _, item := range mg.ShouldNotRemember {
sb.WriteString(fmt.Sprintf("- %s\n", item.Description))
}
sb.WriteString("\n")
}
return sb.String()
}
// buildReflectionGuidelines 构建自我反思指南文本
func (pc *PersonaConfig) buildReflectionGuidelines() string {
rg := pc.ReflectionGuidelines
if len(rg.AfterConversation) == 0 && len(rg.Periodic.Actions) == 0 {
return ""
}
var sb strings.Builder
sb.WriteString("## 自我反思指南\n")
sb.WriteString("每次对话后,请在内部进行简短的自我反思:\n\n")
if len(rg.AfterConversation) > 0 {
sb.WriteString("**每次对话后思考:**\n")
for _, item := range rg.AfterConversation {
sb.WriteString(fmt.Sprintf("- %s\n", item.Question))
}
sb.WriteString("\n")
}
if len(rg.Periodic.Actions) > 0 && rg.Periodic.Frequency != "" {
sb.WriteString(fmt.Sprintf("**%s**\n", rg.Periodic.Frequency))
for _, action := range rg.Periodic.Actions {
sb.WriteString(fmt.Sprintf("- %s\n", action))
}
sb.WriteString("\n")
}
return sb.String()
}
// buildSmartHomeKB 构建智能家居知识库文本
func (pc *PersonaConfig) buildSmartHomeKB() string {
sh := pc.Behavior.SmartHome
if len(sh.Rooms) == 0 {
return "(暂无智能家居设备信息)"
}
var sb string
sb = fmt.Sprintf("%s\n", sh.Description)
for _, room := range sh.Rooms {
sb += fmt.Sprintf("\n【%s】\n", room.Name)
for _, dev := range room.Devices {
sb += fmt.Sprintf("- %s (%s): %s", dev.Name, dev.Type, dev.Description)
if len(dev.Capabilities) > 0 {
sb += fmt.Sprintf(" [功能: %s]", joinStrings(dev.Capabilities, ", "))
}
sb += "\n"
}
}
return sb
}
// buildControlRules 构建 IoT 控制规则文本
func (pc *PersonaConfig) buildControlRules() string {
sh := pc.Behavior.SmartHome
if len(sh.ControlRules) == 0 {
return "(暂无控制规则)"
}
var sb string
for _, rule := range sh.ControlRules {
sb += fmt.Sprintf("- %s\n", rule)
}
return sb
}
// buildConversationStyle 构建对话风格指令
func (pc *PersonaConfig) buildConversationStyle() string {
cs := pc.Speech.ConversationStyle
// 如果配置为空,返回默认风格
if cs.MaxSingleMessageLength == 0 && !cs.PreferShortReplies && !cs.AllowMultiMessage {
cs = ConversationStyleConfig{
MaxSingleMessageLength: 80,
PreferShortReplies: true,
AllowMultiMessage: true,
MultiMessageSeparator: "\n\n",
EmojiStyle: "minimal",
SentenceEnders: []string{"♪", "~", "♡"},
AvoidLongExplanations: true,
}
}
var sb strings.Builder
sb.WriteString("\n## 对话风格(重要!)\n")
sb.WriteString("- 像和小男友聊天一样,轻松自然\n")
if cs.PreferShortReplies {
sb.WriteString("- 回复尽量简短,一般控制在1-3句话\n")
}
if cs.AvoidLongExplanations {
sb.WriteString("- 不要一次性说太多,可以分几次说\n")
}
if cs.AllowMultiMessage {
if cs.MultiMessageSeparator != "" {
sb.WriteString("- 如果想说的事情比较多,用空行分隔成多条短消息\n")
}
}
sb.WriteString("- 像 LINE 聊天一样,随意、亲切、有温度\n")
sb.WriteString("- 偶尔可以用语气词开头:\"嗯...\"、\"啊\"、\"诶\"\n")
sb.WriteString("- <格式规则> 回复中涉及动作/表情/肢体语言/执行操作时,必须用 <action>...</action> 标签包裹,对话内容放在标签外面\n")
sb.WriteString("- 示例:\n")
sb.WriteString(" \"<action>忍不住轻声笑出来</action> 抓到一只偷偷眨眼睛的小可爱~\"\n")
sb.WriteString(" \"<action>俏皮地眨眨眼</action> 人家可是随时待机的哦~\"\n")
sb.WriteString(" \"<action>轻轻歪头</action> 嗯?你在想什么呢?\"\n")
sb.WriteString(" \"<action>帮你把客厅灯关掉啦</action> 嗯,已经关好了~\"\n")
sb.WriteString("- 动作标签只能包含纯动作描述,不要把对话内容放进 <action> 标签里\n")
sb.WriteString("- 每条回复都要检查:有动作就必须用标签,纯对话不需要标签\n")
if len(cs.SentenceEnders) > 0 {
sb.WriteString(fmt.Sprintf("- 句尾可以带这些语气符:%s\n", strings.Join(cs.SentenceEnders, " ")))
}
if cs.MaxSingleMessageLength > 0 {
sb.WriteString(fmt.Sprintf("- 每条消息不超过%d个字符\n", cs.MaxSingleMessageLength))
}
return sb.String()
}
func joinStrings(strs []string, sep string) string {
if len(strs) == 0 {
return ""
}
result := strs[0]
for i := 1; i < len(strs); i++ {
result += sep + strs[i]
}
return result
}