经过前面三篇文章的铺垫我们先后完成了硅基流动平台的接入配置、大模型接入方式的技术选型以及 LLM 调用层的统一封装孢子记账的智能化基础设施已经基本就绪。从现在开始我们的目光将从怎么接入 AI转向用 AI 做什么将大模型能力真正嵌入到具体的业务场景中让用户能够实实在在地感受到智能化带来的体验提升。而我们将要攻克的第一个智能化功能就是智能生成预算。一、痛点预算管理是孢子记账的核心功能模块之一也是个人财务管理中承上启下的关键环节。如果说记账是对过去的记录那么预算就是对未来的规划它帮助用户在消费发生之前建立一道理性防线避免冲动支出从而真正实现管住钱的目标。然而在传统的记账流程中这道防线恰恰是最容易被用户绕过的。制定预算这件事本质上需要用户完成一次小型的自我财务审计。用户需要回顾自己过去一段时间在各个分类上的支出情况结合当月的收入变化、季节性消费波动以及计划内的大额支出等因素综合判断每个分类该分配多少额度。对于本身就具备财务意识、对自己的消费习惯了然于胸的用户来说这或许只是几分钟的事情。但对于占绝大多数的普通用户而言这已经构成了一道不低的认知门槛。很多用户打开预算设置页面之后面对一排空白输入框和一串分类名称脑子里只有一个念头 我该填多少当这个问题没有明确答案时放弃就成了最自然的选择。二、智能生成预算的设计思路针对用户在制定预算时面临的认知挑战我们的设计思路可以概括为一句话让 AI 来帮用户算账。用户不再需要自己对着空白表单绞尽脑汁而是由系统自动拉取历史消费数据交给大模型进行分析和推理最终产出一份可直接使用或微调的预算方案。整个功能的交互流程可以拆解为数据采集、数据聚合、智能推理和结果确认四个阶段。在数据采集阶段用户首先选择想要制定的预算类型月度、季度、年度这个选择决定了后续预算的时间粒度。系统收到请求之后会从数据库中拉取用户过去十二个月的支出明细数据。之所以选择十二个月作为数据窗口是因为一个完整的年度周期能够覆盖工资调整、节假日消费高峰、季节性支出波动等各类周期性因素为大模型提供足够丰富的分析素材避免因数据窗口过窄而导致的判断偏差。接下来进入数据聚合阶段。原始的交易明细是零散的、逐条的直接将这些数据丢给大模型不仅会超出上下文窗口的限制也会让模型难以从中提取有意义的规律。因此我们需要在服务端对数据进行一次预处理将过去十二个月的所有支出按照分类进行分组统计出每个分类在每个月的支出总额以及该分类在过去十二个月的总支出。这样一来原本杂乱无章的交易流水就被整理成了一张结构清晰的支出结构图谱大模型拿到的不再是一堆数字而是一份能够直接用于推理的财务画像。有了这份支出结构图谱作为输入流程进入最核心的智能推理阶段。我们将聚合后的数据连同用户选择的预算类型一起构造为结构化的提示词发送给大模型。大模型的任务不是简单地计算平均数然后原样返回而是需要结合数据中体现的消费趋势、周期性波动以及各分类之间的比例关系综合判断出一个既不过于激进也不过于保守的预算方案。最后一个阶段是结果确认。大模型生成的预算方案会以前端可视化的方式呈现给用户用户可以逐一查看每个分类的建议额度也可以对比系统建议与历史均值之间的差异。如果用户对方案整体满意点击保存即可将预算写入数据库如果方案不合适用户也可以直接拒绝并重新生成。在整个流程中AI 扮演的是参谋角色最终的决策权始终掌握在用户手中这种人机协同的交互模式既发挥了 AI 的数据分析能力也保留了用户对自身财务的掌控感。三、核心代码实现在讲解了智能生成预算的设计思路之后接下来我们来看看这个功能的核心代码实现其他部分的代码由于不是核心逻辑代码因此在此不再赘述。以下是核心代码usingSP.FinanceService.Models.Request;usingSP.FinanceService.Models.Response;usingSP.FinanceService.Models.Enumeration;usingSystem.Text.Json;usingSP.Common.LLM;usingSP.Common.Redis;usingSP.Common;usingSP.Common.ExceptionHandling.Exceptions;namespaceSP.FinanceService.Service.Impl;/// summary/// 预算生成服务实现类/// /summarypublicclassBudgetGenerationServerImpl:IBudgetGenerationServer{/// summary/// 预算服务接口/// /summaryprivatereadonlyIBudgetServer_budgetService;/// summary/// 记账服务接口/// /summaryprivatereadonlyIAccountingServer_accountingServer;/// summary/// 记账分类服务接口/// /summaryprivatereadonlyITransactionCategoryServer_transactionCategoryServer;/// summary/// 大模型服务/// /summaryprivatereadonlyIOpenAIService_openAIService;/// summary/// Redis缓存服务接口/// /summaryprivatereadonlyIRedisService_redisService;/// summary/// 上下文服务/// /summaryprivatereadonlyContextSession_contextSession;/// summary// redis缓存键前缀// /summaryprivateconststringRedisCacheKeyPrefixbudget_generation_preview:;/// summary/// 构造函数/// /summary/// param namebudgetService预算服务接口/param/// param nameaccountingServer记账服务接口/param/// param nametransactionCategoryServer记账分类服务接口/param/// param nameopenAIService大模型服务接口/param/// param nameredisServiceRedis缓存服务接口/param/// param namecontextSession上下文服务接口/parampublicBudgetGenerationServerImpl(IBudgetServerbudgetService,IAccountingServeraccountingServer,ITransactionCategoryServertransactionCategoryServer,IOpenAIServiceopenAIService,IRedisServiceredisService,ContextSessioncontextSession){_budgetServicebudgetService;_accountingServeraccountingServer;_transactionCategoryServertransactionCategoryServer;_openAIServiceopenAIService;_redisServiceredisService;_contextSessioncontextSession;}/// summary/// AI生成预算返回预览数据/// /summary/// param namerequest生成请求/param/// returns预览数据/returnspublicasyncTaskListBudgetGenerationResponseGenerateBudget(BudgetGenerationRequestrequest){// 1. 构建AI输入数据varaiInputDataBuildAiInputData();if(aiInputDatanull)returnnewListBudgetGenerationResponse();// 2. 获取预算周期信息var(month,beginDate,endDate)GetBudgetPeriodInfo(request.Period);// 3. 构建提示词并调用AI服务varpromptBuildBudgetPrompt(aiInputData,month,beginDate,endDate);varbudgetResponsesawait_openAIService.ChatStructuredAsyncListBudgetGenerationResponse(prompt);// 4. 缓存预览数据awaitCacheBudgetPreviewAsync(budgetResponses);// 5. 返回预览数据returnbudgetResponses??newListBudgetGenerationResponse();}/// summary/// 确认/取消生成预算返回生成结果/// /summary/// param nameidAI生成预算id/param/// param nameconfirm是否确认生成/param/// returns任务/returnspublicasyncSystem.Threading.Tasks.TaskConfirmBudget(longid,boolconfirm){// 查询缓存中的预算预览数据varcacheKey${RedisCacheKeyPrefix}{_contextSession.UserId};varcachedDataawait_redisService.GetAsyncListBudgetGenerationResponse(cacheKey);if(cachedDatanull){thrownewBusinessException(未找到预算预览数据请重新生成预算);}if(!confirm){// 取消生成删除缓存并返回0await_redisService.RemoveAsync(cacheKey);return;}// 确认生成将预览数据保存为正式预算varbudgetAddsnewListBudgetAddRequest();foreach(varitemincachedData){budgetAdds.Add(newBudgetAddRequest{TransactionCategoryIditem.TransactionCategoryId,Amountitem.Amount,Period(PeriodEnum)item.Period,Remarkitem.Remark,StartTimeitem.StartTime,EndTimeitem.EndTime});}await_budgetService.Adds(budgetAdds);}/// summary/// 构建AI生成预算所需的输入数据/// /summary/// returns输入数据若无数据则返回null/returnsprivateobject?BuildAiInputData(){// 查询用户最近12个月的记账数据varaccountingResponses_accountingServer.GetAccountingsByTimeRange(DateTime.Now.AddMonths(-12),DateTime.Now);if(accountingResponsesnull)returnnull;// 获取支出分类数据varcategoryResponses_transactionCategoryServer.QueryByType(TransactionCategoryEnmu.Expenditure);if(categoryResponsesnull)returnnull;varcategoryIdscategoryResponses.Select(cc.Id).ToList();// 过滤出支出的记账数据varcategorySummariesaccountingResponses.Where(acategoryIds.Contains(a.TransactionCategoryId));// 按支出分类分组计算每个支出分类的总额varcategoryTotalAmountscategorySummaries.GroupBy(aa.TransactionCategoryId).Select(gnew{CategoryIdg.Key,TotalAmountg.Sum(aa.Amount)}).ToList();// 按支出分类和月份分组计算每个月的支出分类总额varmonthlyCategoryAmountscategorySummaries.GroupBy(anew{a.TransactionCategoryId,Montha.RecordDate.Month}).Select(gnew{CategoryIdg.Key.TransactionCategoryId,Monthg.Key.Month,TotalAmountg.Sum(aa.Amount)}).ToList();// 组装为AI输入格式returncategoryTotalAmounts.Select(cnew{CategoryIdc.CategoryId,CategoryNamecategoryResponses.FirstOrDefault(rr.Idc.CategoryId)?.Name,TotalAmountc.TotalAmount,MonthlyAmountsmonthlyCategoryAmounts.Where(mm.CategoryIdc.CategoryId).Select(mnew{m.Month,m.TotalAmount}).ToList()}).ToList();}/// summary/// 根据预算周期获取月份数、起始日期和结束日期/// /summary/// param nameperiod预算周期/param/// returns月份数、起始日期、结束日期/returnsprivatestatic(intmonth,DateTime beginDate,DateTime endDate)GetBudgetPeriodInfo(PeriodEnumperiod){varbeginDateDateTime.Now;varendDateperiodswitch{PeriodEnum.MonthDateTime.Now.AddMonths(1),PeriodEnum.QuarterDateTime.Now.AddMonths(3),PeriodEnum.YearDateTime.Now.AddMonths(12),_DateTime.Now};varmonthperiodswitch{PeriodEnum.Month1,PeriodEnum.Quarter3,PeriodEnum.Year12,_0};return(month,beginDate,endDate);}/// summary/// 构建AI生成预算的提示词/// /summary/// param nameaiInputDataAI输入数据/param/// param namemonth预算月数/param/// param namebeginDate预算起始日期/param/// param nameendDate预算结束日期/param/// returns提示词字符串/returnsprivatestaticstringBuildBudgetPrompt(objectaiInputData,intmonth,DateTimebeginDate,DateTimeendDate){return$请根据以下数据生成未来{month}个月的预算预算起止日期为{beginDate:yyyy-MM-dd}至{endDate:yyyy-MM-dd}要求按照支出分类进行预算分配并且给出每个支出分类的预算金额{JsonSerializer.Serialize(aiInputData)};}/// summary/// 将生成的预算预览数据缓存到Redis过期时间30分钟/// /summary/// param namedata预算预览数据/paramprivateasyncSystem.Threading.Tasks.TaskCacheBudgetPreviewAsync(ListBudgetGenerationResponsedata){varcacheKey${RedisCacheKeyPrefix}{_contextSession.UserId};await_redisService.SetAsync(cacheKey,JsonSerializer.Serialize(data),30*60);}}BudgetGenerationServerImpl是整个智能生成预算功能的核心实现类它通过依赖注入持有了六个关键服务接口。IAccountingServer负责从数据库中拉取用户的记账明细ITransactionCategoryServer提供支出分类的元数据分类 ID 与名称的映射这两者共同构成了数据采集层的基础。IOpenAIService是我们在上一篇文章中封装的 LLM 调用层本次直接以ChatStructuredAsyncT的方式使用调用大模型并自动将返回的 JSON 反序列化为强类型的ListBudgetGenerationResponse。IRedisService承担了预览数据的临时存储职责而IBudgetServer则在用户确认后将预览数据正式持久化到预算表。ContextSession从当前请求上下文中提取用户 ID用于构造 Redis 缓存键的隔离前缀确保不同用户之间的预览数据互不干扰。整个生成流程的入口是GenerateBudget方法它按照五个步骤依次执行。首先调用BuildAiInputData构建大模型所需的输入数据这一步会查询用户过去十二个月的支出明细并按分类进行两次分组聚合一次仅按分类汇总出该分类的十二个月总支出另一次按分类加月份汇总出每个分类每个月的支出额最终组装成一个同时包含分类名称、总支出和月度明细的结构化对象。如果用户没有任何历史记账数据方法直接返回空列表。接下来通过GetBudgetPeriodInfo根据用户选择的周期类型月度、季度、年度计算出预算覆盖的月份数和起止日期这里利用 C# 的switch表达式将枚举值映射为具体的月份偏移量代码简洁且不易出错。然后调用BuildBudgetPrompt将聚合后的数据与预算周期信息拼接为一段自然语言提示词核心指令是根据以下数据生成未来 N 个月的预算按照支出分类进行预算分配并给出每个分类的预算金额。提示词末尾附上 JSON 序列化后的数据大模型拿到这段提示词后就可以同时理解任务指令和数据上下文从而产出结构化的预算建议。大模型返回的ListBudgetGenerationResponse并不会直接写入数据库而是先通过CacheBudgetPreviewAsync存入 Redis设置30分钟的过期时间。这样做的目的是将生成和确认两个操作解耦——用户可以在预览页面上仔细审视每个分类的建议金额甚至反复对比不同周期下的方案差异而不必担心每次查看都重新调用一次大模型既浪费算力也增加延迟。当用户做出最终决定后前端调用ConfirmBudget方法传入是否确认的布尔标记。如果用户选择取消方法会直接清除 Redis 中的预览缓存不留下任何数据痕迹如果用户选择确认则将缓存中的BudgetGenerationResponse列表转换为BudgetAddRequest列表调用IBudgetServer.Adds批量写入预算表。这种先预览、后确认的两阶段提交模式既保证了用户体验的流畅性也为后续可能的扩展比如在确认前支持用户手动微调某个分类的金额预留了灵活的改造空间。四、小结智能生成预算功能的实现充分体现了我们在前面几篇文章中构建的智能化基础设施的价值。通过大模型的强大分析能力我们成功地将用户过去的消费行为转化为对未来的理性规划帮助用户跨过了制定预算时的认知门槛让管住钱不再是一个抽象的目标而是一个具体可行的行动方案。在后续的文章中我们还将继续挖掘大模型在个人财务管理中的更多应用场景持续提升孢子记账的智能化水平。