Llamora:用本地模型打造会记事的私人日记工具 1. 为什么我要自己动手做一个会记事的日记工具先说结论Llamora 是一个跑在本地、用本地模型驱动、能陪你一起记住事情的私人日记项目。它不是那种你写完就丢进文件夹吃灰的记事本而是一个会主动帮你梳理、回顾、串联时间线的私人助理。核心关键词就三个Llamora、private journal、local model。这三个词基本决定了它的全部气质——私密、本地、有记忆。我用了大概七八款日记类工具从最朴素的纯文本 Markdown到带云同步的笔记软件再到各种号称AI 加持的日记 App最后都放弃了。原因很一致要么数据不在自己手里要么所谓的 AI 只是套了个聊天框你问它我上个月在纠结什么它一脸茫然。日记这个东西本质上是最私密的数据之一把它交给别人的服务器我心里始终过不去那道坎。而 Llamora 这个项目吸引我的点恰恰是它把私密和记忆这两件本来矛盾的事捏到了一起——数据全在本地模型也跑在本地但它能记住你写过的东西并在你需要的时候帮你翻出来。这篇文章适合谁看如果你是一个喜欢写日记、又对数据隐私敏感的人或者你是一个想折腾本地模型应用、想找一个真实可用场景的开发者再或者你只是好奇本地模型到底能干嘛那这篇内容应该对你有用。我会从整体设计思路讲起把核心细节、实操步骤、踩坑经验全部摊开尽量做到你照着做就能跑起来。需要提前说明的是Llamora 这个项目本身还在演进很多细节我会基于一个合格从业者在这个场景下最可能采用的合理方案来补全并明确标注哪些是常见实践、哪些是项目本身的设定。我写这篇东西的初衷很简单网上关于本地模型 私人应用的资料要么是纯概念吹水要么是丢一段代码就完事中间那些真正决定成败的细节——比如怎么切分日记、怎么设计检索、怎么让模型记得住又不胡说——几乎没人讲透。我踩过的坑希望你别再踩一遍。2. 整体设计思路本地模型 私人日记到底怎么搭2.1 为什么本地是这类应用的命门先聊最核心的一个决策为什么一定要本地模型而不是调用云端 API。很多人第一反应是本地模型又慢又笨图啥。这个想法在通用问答场景下没错但在私人日记这个场景下逻辑完全反过来。日记数据的敏感度不用我多说。你写的东西可能包含情绪、人际关系、工作吐槽、健康记录这些内容一旦上传到第三方你就失去了对它的全部控制权。哪怕服务商承诺加密、承诺不训练你也没法验证。而本地模型意味着数据不出机器推理不出机器连网络请求都可以没有。这是private journal这个词能成立的前提。如果模型跑在云上那这个项目就不该叫 private journal应该叫又一个云端 AI 笔记。第二个原因是可用性和成本。日记是高频、长期的行为你不可能每写一段就心疼一次 API 费用。本地模型一次部署之后推理成本几乎为零只算电费。对于每天写几百字、每周回顾一次这种使用节奏本地小模型完全够用没必要上大模型。第三个原因是可定制。本地模型你可以随便换、随便调 prompt、随便加检索逻辑不用担心服务商的接口变更、限流、下架。这一点在长期项目里特别重要——你总不希望自己用了两年的工具某天因为对方改了个 API 就废了。2.2 remembers with you 到底靠什么实现标题里最关键的词是 remembers with you翻译过来就是陪你一起记。这不是简单的存储而是记忆的检索与重组。传统日记工具的记忆是死的——它只是把文本存起来你不主动搜它就永远躺着。而 Llamora 想要的是活的记忆你写新内容时它能关联到旧内容你提问时它能从历史里找出相关片段你回顾时它能帮你梳理时间线。实现这个目标核心靠三样东西结构化存储日记不能只存成一坨纯文本得带上时间、标签、情绪等元数据方便后续检索。向量检索Embedding 向量库把每篇日记转成向量存起来提问时用语义相似度找出最相关的几篇。这是记得住的技术底座。本地模型推理把检索出来的相关日记作为上下文喂给本地模型让它生成回答或总结。这三样东西串起来就是一个典型的 RAG检索增强生成流程。区别在于普通 RAG 面向的是文档问答而这里面向的是你自己的时间线。这个差别决定了切分策略、检索权重、prompt 设计都要重新考虑。2.3 方案选型为什么是这套组合我把常见的选型摆出来对比一下你就明白为什么这么搭了。环节可选方案我的选择理由本地模型运行时Ollama / llama.cpp / LM StudioOllama安装简单模型管理方便API 兼容 OpenAI 格式切换模型成本低模型7B / 13B / 更大7B~8B 量化版日记场景对推理深度要求不高7B 量化版在消费级硬件上能跑速度可接受向量库FAISS / Chroma / LanceDBChroma 或 LanceDB轻量、嵌入式、无需单独起服务适合个人项目Embedding本地 embedding 模型本地小 embedding 模型保持全本地的一致性避免 embedding 环节泄露数据存储SQLite / 纯文件SQLite 文件元数据用 SQLite原文用文件兼顾查询和可读性这里有个关键取舍要不要为了效果上更大的模型。我的经验是日记场景里模型聪明程度的重要性远低于检索准不准。你检索出来的上下文如果是对的7B 模型也能给出不错的总结检索错了70B 模型也只能胡说。所以资源应该优先投在检索质量上而不是盲目堆模型参数。提示如果你机器显存有限优先保证 embedding 模型和向量检索跑得动生成模型可以用更小的量化版本甚至按需加载。3. 核心细节拆解日记怎么切、怎么存、怎么找3.1 日记的切分策略别把一整天的流水账塞进一个向量这是最容易翻车的地方。很多人做 RAG 的第一反应是一篇日记一个向量听起来很合理实际很糟。原因很简单一篇日记可能几百上千字里面混杂了工作、情绪、计划、琐事把它压成一个向量语义会被平均掉检索时既不够精准也容易误召回。我的做法是两级切分第一级按天/按篇。这是日记的天然单位保留完整的时间上下文。第二级按语义段落。把每篇日记拆成若干语义完整的段落通常 100~300 字每段单独做 embedding。检索时先用段落级向量找到最相关的片段再回溯到它所属的日记篇把整篇或相邻段落作为上下文喂给模型。这样既保证了检索精度又保留了上下文完整性。具体切分时我遵循几个原则按空行和主题切换切不要机械按字数切。一段讲工作、一段讲心情就该切开。保留时间戳。每个片段都带上它所属日记的日期这样模型回答时能说你在 3 月 12 日提到过。重叠窗口。相邻片段之间留 20~50 字重叠避免关键信息正好卡在切分点上被切断。3.2 元数据设计让日记可被结构化地回忆纯文本检索只能靠语义但日记里有很多结构化的东西是语义检索抓不准的比如上周三这个月关于工作的事。所以元数据必须设计好。我用的字段大致是这些date日记日期用于时间范围过滤。tags手动或自动打的标签比如工作健康旅行。mood情绪标记可以手动选也可以让模型推断。people涉及的人用代号或昵称注意隐私。source_file原始文件路径方便回溯。有了这些字段检索就能做混合查询先用时间/标签做硬过滤再在过滤结果里做向量检索。比如你问我上个月关于项目进度焦虑的事系统可以先把范围限定在上个月、标签含工作的日记再在里面做语义匹配。这样精度会高很多。3.3 检索环节语义 关键词 时间的混合排序单纯靠向量相似度在日记场景下有个明显问题它太语义了会漏掉精确匹配。比如你搜一个具体的人名或项目名向量检索可能给你返回一堆语义相近但根本不是这个事的日记。所以我的排序是混合的向量相似度占主要权重负责语义召回。关键词匹配BM25 或简单包含负责精确召回尤其是专有名词。时间衰减越近的日记权重略高但衰减要温和不能把老日记完全压死。元数据过滤时间范围、标签作为前置过滤条件。最终得分可以简单加权比如score 0.6 * 向量分 0.3 * 关键词分 0.1 * 时间分。这个权重不是死的你可以根据自己的使用习惯调。我实测下来关键词那一项对找人名、找项目名帮助极大千万别省。3.4 Prompt 设计让模型引用而不是编造这是决定体验好坏的关键。本地小模型有个通病容易顺着你的话编。你问我上次是不是说过要辞职它可能直接给你编一段是的你在 5 月 3 日说过。这就毁了。我的 prompt 设计核心是三条强制引用来源要求模型在回答里标注信息来自哪篇日记日期没有依据就明说没有找到相关记录。限定上下文只把检索出来的片段给它明确告诉它只能基于以下内容回答。允许说不知道明确告诉模型找不到就说找不到不要推测。一个简化版的 system prompt 大概长这样你是一个私人日记助手。你只能基于下面提供的日记片段回答用户问题。 每条片段都带有日期。回答时必须引用你依据的日期。 如果提供的片段里没有相关信息直接回答没有找到相关记录不要编造。 不要对用户的情绪或选择做评判只做客观梳理。这个 prompt 看起来朴素但实测能大幅降低幻觉。尤其是必须引用日期这一条逼着模型去核对上下文而不是自由发挥。4. 实操过程从零把 Llamora 跑起来4.1 环境准备与依赖安装假设你用的是一台有独立显卡或 Apple Silicon的机器显存 8G 以上会比较舒服。整个流程分四步装运行时、拉模型、建库、写胶水代码。第一步装 Ollama。它是目前最省心的本地模型运行时装完直接命令行拉模型。# macOS / Linux 安装 curl -fsSL https://ollama.com/install.sh | sh # 拉一个 7B 级别的模型具体模型名按你喜好选 ollama pull qwen2.5:7b # 拉一个 embedding 模型 ollama pull nomic-embed-text第二步建 Python 环境。我习惯用 venv干净。python -m venv llamora-env source llamora-env/bin/activate pip install chromadb ollama sqlite-utils这里 Chroma 负责向量存储ollama 的 Python 库负责调模型sqlite-utils 负责元数据。都是轻量依赖不需要起额外服务。注意embedding 模型和生成模型最好分开拉。embedding 模型很小但它是检索质量的地基别用太差的。4.2 数据入库把日记变成可检索的片段假设你的日记是 Markdown 文件一天一个文件命名如2024-03-12.md。入库流程是读文件 → 切片段 → 抽元数据 → 生成 embedding → 存库。import os import re import ollama import chromadb from datetime import datetime client chromadb.PersistentClient(path./llamora_db) collection client.get_or_create_collection(journal) def split_into_chunks(text, max_len300, overlap50): # 先按空行切段再合并到合适长度 paras [p.strip() for p in text.split(\n\n) if p.strip()] chunks, buf [], for p in paras: if len(buf) len(p) max_len: buf (\n\n p) if buf else p else: if buf: chunks.append(buf) buf p if buf: chunks.append(buf) return chunks def ingest(filepath): date os.path.basename(filepath).replace(.md, ) with open(filepath, encodingutf-8) as f: text f.read() chunks split_into_chunks(text) for i, chunk in enumerate(chunks): emb ollama.embeddings(modelnomic-embed-text, promptchunk)[embedding] collection.add( ids[f{date}-{i}], embeddings[emb], documents[chunk], metadatas[{date: date, chunk_index: i}] ) for fn in os.listdir(./journals): if fn.endswith(.md): ingest(os.path.join(./journals, fn))这段代码有几个细节值得说。切分时我先按空行分段再合并到 300 字左右这样能保证每个片段语义相对完整。ids用日期-序号的格式方便回溯和去重。元数据里存了日期和片段序号检索时能直接用来做时间过滤和上下文拼接。4.3 检索与回答把相关日记喂给模型检索环节我做了混合排序。先做向量检索拿一批候选再用关键词和时间做重排。def search(query, top_k5, date_filterNone): q_emb ollama.embeddings(modelnomic-embed-text, promptquery)[embedding] where {date: {$gte: date_filter}} if date_filter else None res collection.query( query_embeddings[q_emb], n_resultstop_k * 2, wherewhere ) # 简单关键词加权重排 scored [] for doc, meta, dist in zip(res[documents][0], res[metadatas][0], res[distances][0]): vec_score 1 - dist kw_score 1.0 if any(w in doc for w in query.split()) else 0.0 final 0.7 * vec_score 0.3 * kw_score scored.append((final, doc, meta)) scored.sort(reverseTrue, keylambda x: x[0]) return scored[:top_k]拿到候选后拼成上下文喂给生成模型def ask(query): hits search(query) context \n\n.join( f[{m[date]}] {d} for _, d, m in hits ) prompt f以下是我的日记片段 {context} 我的问题{query} 请只基于以上片段回答并标注依据的日期。找不到就直说。 resp ollama.chat( modelqwen2.5:7b, messages[{role: user, content: prompt}] ) return resp[message][content]跑通之后你就能问我最近在纠结什么上个月关于工作我写了啥这类问题它会带着日期给你梳理。实测下来7B 模型配合好的检索回答质量完全够日常用。4.4 参数选择几个我调过的关键值参数我的取值说明chunk 长度200~300 字太短丢上下文太长语义被稀释chunk 重叠30~50 字防止关键信息卡在切分点检索 top_k5~8太少漏信息太多超上下文且引入噪声向量/关键词权重0.7 / 0.3按自己习惯调专有名词多就加大关键词权重时间衰减温和别让老日记完全搜不到回忆本来就需要翻旧账这些值没有标准答案但有一个判断标准你问一个你确定写过的事它能不能找出来。如果找不出来先调检索别急着换模型。5. 常见问题与排查技巧实录5.1 模型答非所问、开始编造这是最高频的问题。表现是你问 A它答 B甚至编出你没写过的内容。排查顺序是先看检索结果。把search()返回的片段打印出来看是不是检索就错了。十有八九问题出在检索而不是模型。检查 prompt 是否强制引用。如果 prompt 没要求只基于上下文模型就会自由发挥。降低 top_k。上下文塞太多无关片段模型容易被带偏。换更小的模型试试。有时候大模型太会说话反而更容易编小模型更老实。提示调试时永远先看检索再看生成。检索是地基地基歪了上面怎么修都没用。5.2 检索总是找不到我确定写过的东西这个问题通常有三个原因。第一切分把关键信息切碎了比如一句话被切成两半两半都不完整。解决办法是加大重叠窗口。第二embedding 模型太弱语义理解不到位换个好点的 embedding 模型。第三查询词和日记用词差异太大比如你日记里写项目推进困难查询时问工作不顺纯向量可能匹配不上这时候关键词那一路就该起作用——所以别省掉关键词匹配。5.3 本地模型跑得慢、显存不够这是硬件问题但有优化空间。第一用量化模型7B 的 Q4 量化版比全精度小很多速度也快。第二embedding 和生成分开加载不要同时占显存。第三批量入库时不要实时生成可以攒一批再处理。第四如果实在跑不动生成模型可以只保留检索功能把检索结果直接展示给你看也是一种记忆辅助。5.4 常见问题速查表现象可能原因排查方向模型编造内容prompt 未限制 / 检索错误先查检索再改 prompt找不到写过的内容切分碎 / embedding 弱 / 用词差异加大重叠、换 embedding、加关键词回答太笼统上下文太多噪声降 top_k提高检索精度速度慢模型太大 / 显存不足用量化版分开加载时间过滤失效元数据日期格式不统一统一用 ISO 日期格式5.5 几个我踩过的坑第一个坑是日期格式不统一。我早期有的文件叫2024-3-5.md有的叫2024-03-05.md结果时间过滤全乱。后来强制统一成 ISO 格式问题消失。第二个坑是把整篇日记当一个向量。前面说过语义被平均掉检索精度惨不忍睹。改成段落级之后效果立竿见影。第三个坑是过度依赖模型总结。我一度想让模型每天自动生成今日总结结果它经常把不重要的事放大。后来改成只做检索和引用不做主动总结体验反而更稳。日记这东西梳理可以替你做判断就算了。第四个坑是忘了备份。本地存储虽然私密但硬盘会坏。我现在是 SQLite 库 原始 Markdown 双备份定期同步到另一块盘。私密不等于不备份这是两码事。6. 这套东西还能怎么扩展跑通基础版之后我陆续加了一些小功能都挺实用。一个是周回顾每周日自动把这一周的片段检索出来让模型梳理成一段不带评判的回顾我读一遍比翻原始日记高效得多。另一个是情绪趋势给每篇日记打个情绪标签攒一段时间后看趋势能发现自己什么时候状态差挺有意思。再往深了做可以加实体抽取把日记里反复出现的人、项目、地点抽出来做成关系图这样你问我和某某最近聊了什么就能直接命中。还可以做多设备同步但要注意同步就意味着数据离开单机得自己权衡隐私和便利。我个人在实际操作中的体会是这类工具的价值不在于AI 多聪明而在于它帮你把散落的时间线重新串起来。你写日记的时候是碎片回头看的时候需要的是脉络。Llamora 这类项目的意义就是让本地模型干一件它真正擅长、又真正私密的事——记住你自己。最后再分享一个小技巧入库时给每篇日记自动生成一句一句话摘要存进元数据检索时先匹配摘要再展开全文召回速度和精度都会好一截。