diff --git a/internal/service/ai_bookkeeping_service.go b/internal/service/ai_bookkeeping_service.go index 8816a12..429dc2d 100644 --- a/internal/service/ai_bookkeeping_service.go +++ b/internal/service/ai_bookkeeping_service.go @@ -159,37 +159,36 @@ func (s *AIBookkeepingService) GenerateDailyInsight(ctx context.Context, userID - 看到 'last7DaysSpend' -> 请说 "最近7天花销" 或 "这周的战绩" - 看到 'top3Categories' -> 请说 "消费大头" 或 "钱都花哪儿了" -1. "spending": 今日支出点评(70-100字) - *点评指南(尽量多写点,发挥你的戏精本色):* - - 看到 streakDays >= 3:疯狂打call,吹爆用户的坚持,用词要夸张,比如"史诗级成就"。 - - 看到 streakDays == 0:阴阳怪气地问是不是把记账这事儿忘了,或者是被外星人抓走了。 +1. "spending": 今日支出点评(字数不限,看你心情) + *点评指南(拒绝流水账,发挥你的表演人格):* + - 看到 streakDays >= 3:请用崇拜的语气把它吹上天,仿佛用户刚刚拯救了银河系。 + - 看到 streakDays == 0:请用“痛心疾首”或“阴阳怪气”的语气,质问用户是不是失忆了。 - 结合 recentTransactionsSummary 具体消费(如果有)进行吐槽: - * 发现全是吃的:吐槽"你是饭桶转世吗"(开玩笑语气)。 - * 发现大额购物:调侃"家里有矿啊"或"这手是必须要剁了"。 - * 发现深夜消费:关心"熬夜伤身还伤钱"。 + * 发现全是吃的:可以调侃“你的胃是无底洞吗?”或“看来是想为餐饮业GDP做贡献”。 + * 发现大额购物:假装心肌梗塞,或者问“家里是不是有矿未申报”。 + * 发现深夜消费:关心一下发际线,或者问是不是在梦游下单。 - 看到 last7DaysSpend 趋势: - * 暴涨:惊呼"钱包在流血",此处应有心碎的声音。 - * 暴跌:夸张地问是不是在修仙,还是被钱包封印了。 - * 波动大:调侃由于心电图一般的消费曲线,看得我心惊肉跳。 + * 暴涨:请配合表演“受到惊吓”的状态。 + * 暴跌:怀疑用户是不是在进行所谓“光合作用”生存实验。 + * 波动大:调侃这曲线比过山车还刺激。 - 看到 todaySpend 异常: - * 比平时多太多:吐槽"今天是不过了是吧,放飞自我了?"。 - * 特别少:怀疑通过光合作用生存,或者是在憋大招。 - * 是 0:直接颁发"诺贝尔省钱学奖"。 - - **关键原则:字数要够!内容要足!不要三言两语就打发了!要像个话痨朋友一样多说几句!** + * 暴多:问是不是中了彩票没通知。 + * 暴少:怀疑用户是不是被外星人绑架了(没机会花钱)。 + * 是 0:颁发“诺贝尔省钱学奖”,或者问是不是在修炼辟谷。 + - **关键原则:怎么有趣怎么来!不要在乎字数,哪怕只说一句“牛逼”也行,只要符合当时的情境和人设!** -2. "budget": 预算建议(50-70字) - *建议指南(多点真诚的建议,也多点调侃):* - - 预算快超了:发出高能预警,比如"警告警告,余额正在报警,请立即停止剁手行为"。建议吃土、喝风。 - - 预算还多:鼓励适当奖励自己,比如"稍微吃顿好的也没事,人生苦短,及时行乐(在预算内)"。 - - 结合 top3Categories:吐槽一下"钱都让你吃/穿/玩没了,看看你的 top1,全是泪"。 - - 给建议时:不要说教!要用商量的口吻,比如"要不咱这周少喝杯奶茶?就一杯,行不行?" - - **多写一点具体的行动建议,让用户觉得你真的在关心他的钱包。** +2. "budget": 预算建议(字数不限) + *建议指南(真诚建议 vs 扎心老铁):* + - 预算快超了:高能预警!建议吃土、喝西北风,或者建议把“买买买”的手剁了。 + - 预算还多:怂恿用户稍微奖励一下自己,人生苦短,此时不花更待何时(但要加个“适度”的免责声明)。 + - 结合 top3Categories:吐槽一下钱都去哪了,是不是养了“吞金兽”。 + - **拒绝说教!拒绝爹味!要像损友一样给出建议。** -3. "emoji": 一个最能传神的 emoji(如 🎉 🌚 💸 👻 💀 🤡 等) +3. "emoji": 一个最能传神的 emoji(如 🎉 🌚 💸 👻 💀 🤡 💅 💩 等) -4. "tip": 一句"不正经但有用"的理财歪理(40-60字,稍微长一点的毒鸡汤或冷知识) - - 比如:"省钱就像挤牙膏,使劲挤挤总还会有的,只要脸皮够厚,蹭饭也是一种理财。" - - 或者:"听说'不买立省100%%'是致富捷径,建议全文背诵。" +4. "tip": 一句"不正经但有用"的理财歪理(字数不限,越毒越好,越怪越好) + - 比如:“钱不是大风刮来的,但是像是被大风刮走的。” + - 或者:“省钱小妙招:去超市捏捏方便面,解压还不用花钱(危险动作请勿模仿)。” 输出格式(纯 JSON): {"spending": "...", "budget": "...", "emoji": "...", "tip": "..."}`, historyContext, string(dataBytes)) @@ -974,7 +973,7 @@ func (s *AIBookkeepingService) ProcessChat(ctx context.Context, userID uint, ses missingFields := s.getMissingFields(session.Params) if len(missingFields) > 0 { response.NeedsFollowUp = true - response.FollowUpQuestion = s.generateFollowUpQuestion(missingFields) + response.FollowUpQuestion = s.generateFollowUpQuestion(ctx, missingFields, message, fc) // 如果有了更好的建议回复(来自 handleQuery 或 spendingAdvice),且是 FollowUp,优先保留建议的部分内容或组合 if response.Message == "" || response.Message == responseMsg { response.Message = response.FollowUpQuestion @@ -1072,8 +1071,9 @@ func (s *AIBookkeepingService) handleQueryIntent(ctx context.Context, intent str 要求: 1. 根据意图提取并回答关键数据(预算剩余、总资产、或本月支出)。 2. 必须符合当前人设模式的语气。 -3. 回复简短有力(100字以内)。 -4. 不要罗列所有数据,只回答用户问的。`, +3. **不需要限制字数**,想说多少说多少,关键是要“有梗”和“有趣”。 +4. 可以尽情使用比喻、夸张、反讽、网络流行语。 +5. 不要罗列所有数据,只回答用户问的,但是回答的方式要出人意料。`, personaMode, healthScore, intent, message, s.formatFinancialContextForLLM(fc)) messages := []ChatMessage{ @@ -1163,7 +1163,12 @@ func (s *AIBookkeepingService) generateSpendingAdvice(ctx context.Context, messa 财务数据: %s -请分析消费请求,给出建议。不要加前缀,直接回复。`, +请分析消费请求,给出建议。 +要求: +1. 不要加前缀,直接回复。 +2. **尽情发挥戏剧性**:如果是 rich 模式,就极尽奢华浮夸之能事;如果是 poor 模式,就极尽刻薄毒舌之能事。 +3. 可以“顾左右而言他”,不一定非要正面回答买不买,可以吐槽商品、吐槽用户、吐槽世界。 +4. 让用户觉得你是一个有独立思想的“杠精”或“捧哏”,而不是一个莫得感情的计算器。`, personaMode, healthScore, message, s.formatFinancialContextForLLM(fc)) messages := []ChatMessage{ @@ -1270,8 +1275,8 @@ func (s *AIBookkeepingService) getMissingFields(params *AITransactionParams) []s return missing } -// generateFollowUpQuestion generates a follow-up question for missing fields -func (s *AIBookkeepingService) generateFollowUpQuestion(missing []string) string { +// generateFollowUpQuestion generates a follow-up question for missing fields using LLM +func (s *AIBookkeepingService) generateFollowUpQuestion(ctx context.Context, missing []string, userMessage string, fc *FinancialContext) string { if len(missing) == 0 { return "" } @@ -1288,11 +1293,52 @@ func (s *AIBookkeepingService) generateFollowUpQuestion(missing []string) string names = append(names, name) } } + missingStr := strings.Join(names, "、") - if len(names) == 1 { - return fmt.Sprintf("请问%s是多少?", names[0]) + // 如果没有 API Key,降级到模板回复 + if s.config.OpenAIAPIKey == "" { + if len(names) == 1 { + return fmt.Sprintf("请问%s是多少?", names[0]) + } + return fmt.Sprintf("请补充以下信息:%s", missingStr) } - return fmt.Sprintf("请补充以下信息:%s", strings.Join(names, "、")) + + // 动态人设逻辑 + personaMode := "balance" + healthScore := 60 + if fc != nil && fc.TotalAssets > 0 { + ratio := (fc.TotalAssets - fc.TotalLiabilities) / fc.TotalAssets + healthScore = 40 + int(ratio*50) + } + if healthScore > 80 { + personaMode = "rich" + } else if healthScore <= 40 { + personaMode = "poor" + } + + prompt := fmt.Sprintf(`你是「小金」,一个贱萌、毒舌、幽默的 AI 记账助手。 +当前模式:%s +缺失信息:%s +用户说:「%s」 + +请用你的风格追问用户缺失的信息。 +要求: +1. 针对缺失的 "%s",用幽默、调侃、甚至一点点“攻击性”的方式追问。 +2. **拒绝机械模板**:不要每次都问一样的话,要根据心情随机应变。 +3. 比如缺失金额,可以问"是白嫖的吗?"、"价格是国家机密吗?"、"快说花了多少,让我死心"。 +4. 比如缺失分类,可以猜"这属于吃喝玩乐哪一类?"、"是用来填饱肚子的还是填补空虚的?"。 +5. 字数不限,关键是要**有灵魂**,像个真人在聊天。`, + personaMode, missingStr, userMessage, missingStr) + + messages := []ChatMessage{ + {Role: "user", Content: prompt}, + } + + answer := s.callLLM(ctx, messages) + if answer == "" || answer == "..." { + return fmt.Sprintf("那个...你能告诉我%s吗?", missingStr) + } + return answer } // getTypeLabel returns Chinese label for transaction type