Memoria 项目实训日志 #8 | AI Agent 演进与数字相册排版引擎的工程落地
项目名称: 智能影记 - Memoria
团队名称: Mnemosyne
时间: 2026.05.20 - 2026.05.30
当前阶段: Phase 5 (核心体验深挖与性能调优)
一、 本周工作概述
随着 Memoria 核心的视听渲染管线在前期基本跑通,本周团队进入了对核心业务逻辑的深度打磨期。在实际测试中我们发现两个亟待优化的痛点:一是 AI 助手在多轮交互中容易遗漏工具调用指令,对时间概念缺乏感知;二是本地图库在展示大量高分辨率图片时,存在偶发的滑动掉帧现象。
为此,本周团队重点完成了以下两项开发任务:
-
AI Agent 逻辑重构:优化了 LLM 的 Prompt 注入策略,引入了动态时间上下文感知,并改进了 Tool-use(工具调用)的触发机制。
-
数字相册与高性能图库上线:实现了基于 JSON 动态解析的数字相册排版引擎,重构了相册标签聚类逻辑,并自研了基于队列控制的图片并发解码调度器,显著优化了长列表滑动性能。
二、 AI Agent 优化:上下文强化与意图约束
在此前的版本中,AI 虽然能处理明确的搜索指令,但在面对如“推荐一些最近适合发朋友圈的照片”这类模糊语义指令时,检索准确率较低。
- 动态时间感知注入
为了让大模型理解“最近”或“当前季节”的概念,我们将原本静态的系统提示词(_systemPrompt)重构为动态生成的 Getter 属性。在每次发起请求前,系统会自动计算当前的真实时间与季节,并将其作为前置约束传入:
// 截取自 chat_service.dart:动态生成时间约束
static String get _systemPrompt {
final now = DateTime.now();
String season = '冬天';
if (now.month >= 3 && now.month <= 5) season = '春天';
// ...省略其它季节判断...
return '''
你叫 Memoria,是一个极具同理心、文艺且专业的“生活记忆导演”。
当前真实世界时间是:${now.year}年${now.month}月${now.day}日,季节是$season。
当用户提到“最近”、“今天”或“这个季节”时,请严格参考上述时间进行情感联想和照片检索。
''';
}
- 结构化 XML 检索与尾部指令强化
我们规范了 Agent 触发本地搜索的数据格式,要求其必须输出包含 tags, year, location 的结构化 XML 标签。
同时,为解决大模型在多轮对话中容易产生“注意力偏移”而忘记输出工具指令的问题,我们采用了一种尾部上下文强化注入的策略。在拼接用户的最后一条消息时,静默追加一段系统规则约束:
// 尾部指令强化:强制唤醒大模型的意图判断
final String reinforcedUserText = '''
$userText
[系统强制提醒:请立刻判断上述用户意图!如果是让你查找、推荐照片,你必须在回复末尾携带 <SEARCH>{"tags": [], "year": null, "location": null}</SEARCH> 结构!当前季节的标签也可以直接作为搜索条件!]
''';
messages.add({'role': 'user', 'content': reinforcedUserText});
这种在 User Prompt 尾部追加强约束的做法,有效避免了模型只闲聊不检索的现象,提升了混合检索的触发稳定性。
三、 数字相册引擎:样式令牌化与标签聚类
为了让照片回顾更具层次感,本周我们对本地图库的展示形态进行了全面升级。
-
标签层次聚类
在 album_tag_browser_service.dart 中,我们引入了层级化的标签组织策略。系统会将端侧 AI 跑出的离散细粒度标签,向上聚类映射为粗粒度的主题(Coarse Tags,如“户外”、“美食”、“聚会”)。通过 buildCoarseClusters 方法,自动为每个主题计算包含的照片总数,并提取最具代表性的封面照片,从而在 UI 层构建结构化的相册流。 -
样式令牌化映射
对于云端 LLM 生成的数字相册排版,我们构建了一套映射模型。在 album_book_models.dart 中,定义了如 AlbumFontPreset 和 colorTokens 等枚举变量。系统接收到云端返回的轻量级 JSON 后,即可将其解析为对应的 Flutter 渲染参数:
// 从 JSON 动态解析排版样式
factory AlbumElementStyle.fromJson(Map<String, dynamic> json) {
return AlbumElementStyle(
fontId: json['font_id']?.toString() ?? 'sans_clean',
fontSize: _asDouble(json['font_size'], fallback: 20),
colorToken: json['color_token']?.toString() ?? 'ink_soft',
align: AlbumTextAlignValue.values.firstWhere(
(value) => value.name == json['align']?.toString(),
orElse: () => AlbumTextAlignValue.left,
),
// 映射圆角与阴影等 UI 属性...
);
}
四、 性能调优:图片并发解码调度器
在数字相册及标签瀑布流中,同屏常常需要展示数十张高清图片。如果依赖 Flutter 默认的 Image 组件逻辑,快速滑动时会并发发起海量的 I/O 读取和图片解码请求,瞬间拉高 CPU 占用,造成 UI 线程卡顿。
为解决这一工程痛点,我们在 album_page_deferred_image.dart 中实现了一个自定义的图片并发解码调度器(Deferred Scheduler)。
核心设计思路如下:
-
全局并发锁:设置 _maxConcurrent = 4,严格限制同一时刻处于解码状态的图片数量。
-
基于 Hash 的微秒级错峰:即便多张图片同时进入屏幕,我们也为其分配了离散的延迟时间,打散解码请求,防止主线程发生阻塞。
// 截取自 album_page_deferred_image.dart
void _startDeferredLoad() {
// 利用图片路径的 hash 值生成随机的延迟时间,错峰发起 I/O 与解码请求
final delayMs = 30 + (widget.path.hashCode.abs() % 11) * 28;
_timer = Timer(Duration(milliseconds: delayMs), () {
if (!mounted || _ticket.completed) return;
// 延迟结束后,真正挂载 Widget,交由底层引擎解码
setState(() {
_ready = true;
});
});
}
结合首帧渲染完毕的回调(_onFirstFrame)来释放全局并发锁,该调度器成功将原本拥挤在一个 16ms 渲染周期内的任务平滑摊派到了几百毫秒中。实测结果表明,在加载上千张本地照片的极端场景下,列表滑动的掉帧率得到了显著控制。
效果图:

五、 本周小结
本周,团队将研发精力下沉至具体的工程实现细节。通过 Prompt 优化解决了 LLM 的调度稳定性问题,通过构建状态机与并发队列解决了移动端海量图片渲染的性能瓶颈。项目整体的可用性与流畅度有了质的提升。
更多推荐



所有评论(0)