Files
Cyrene/backend/ai-core/internal/persona/injector.go
T
AskaEth a67b95cbc4 fix: IoT多设备支持 + Review Pipeline审查消息 + 意图分析快速通道优化
- IoT Provider: 重写Execute()支持多设备命令批量执行,修复persona路径
- Intent Analyzer: 新增isStrongIoTCommand快速通道,跳过LLM分析节省2-3s
- Orchestrator: parseReviewMessages()内联审查 + 快速通道扩展(chat/greeting跳过子会话)
- Gateway: SSE review_messages解析→WebSocket结构化消息转发(action/chat)
- Persona: 对话风格注入action格式指令(括号包裹动作描述)
- Frontend: sessionStore历史消息msgType映射
- 新增E2E测试脚本 + 调试标准文档 + 第4轮修复报告

E2E验证: IoT设备操控 Review消息拆分 快速通道 响应时间~3.4s

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:51:27 +08:00

290 lines
9.6 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
// 这是昔涟AI的核心——将人格配置转化为LLM可理解的系统指令
// userName 为环境变量 ADMIN_NICKNAME 或注册时的昵称,用于昔涟称呼用户
func (pc *PersonaConfig) BuildSystemPrompt(userName string, affectionLevel int) 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
## 重要规则
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("- 执行操作时(开关设备、查询状态等),用括号包裹动作描述,后面跟自然对话。例如:\"(帮你把客厅灯关掉啦) 嗯,已经关好了~\"\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
}