我手机里躺着900多条收藏,真正被第二次打开的不到10条;微信文件传输助手被我当成了临时仓库,真到找的时候什么都搜不到;每周五下午写周报,我都要对着聊天记录发呆快一个小时。这些事听起来都不大,但叠加起来就是每天的信息焦虑。fox_charon 就是冲着这事儿来的一个小项目,把随手扔进来的碎片信息自动整理、打标、去重,再按预设规则分发到笔记、待办、周报草稿这些地方。代码不多,逻辑也不复杂,但用下来之后,我确确实实把每周整理信息的时间从两小时压到了二十分钟。
这个项目我独立开发,前后迭代了几个版本,核心思路一句话就能说清:给碎片信息配一个“渡船人”。下面把这套设计、实现和踩坑记录完整写下来,给同样被信息流淹没的朋友做个参考。
1. fox_charon 是什么:一个把碎片信息“摆渡”进工作流的个人工具
1.1 项目名字的由来
“fox”取的是狐狸的机敏和轻巧,“charon”是摆渡人的意象。两者放在一起,想表达的是:用灵巧的方式,把信息从“随手一扔”的地方渡到“真正该待”的地方。
很多工具都把精力花在“存”上面,存得越多越乱。fox_charon 反过来,核心动作是“渡”,也就是流转:你扔进来一条链接、一句话、一段灵感、一封待办邮件,它负责判断这东西是什么、该去哪、要不要留。整个系统像一条河边的小渡船,不是在岸上盖一个大仓库。
1.2 它解决的是哪一类烦恼
我自己的场景比较典型:
- 读到一篇好文章,收藏了,但再也没打开过。
- 突然想到一个点子,记在手机备忘录里,翻不到了。
- 领导布置了一个小任务,当时回复“收到”,三天后忘了。
- 每周五写周报,回忆不起来这周到底干了什么。
- 资料分散在笔记软件、微信收藏、邮箱、本地文件里,想用的时候永远不在手边。
这些问题都属于“信息熵过高”,不是再加一个笔记软件能解决的。fox_charon 的思路是做一个中间层:先收拢所有碎片,再让模型做一次粗加工,最后用规则决定去向。人只需要在最后看一眼结果,微调一下,不用再从零开始整理。
1.3 适合谁,不适合谁
用下来我的判断是:这个工具最适合个人开发者、写作者、研究者、以及对周报/日报有硬性要求的职场人。它不是一个团队级知识管理系统,不能多人协作,也没有复杂的权限模型。它更适合那种“信息入口很多、出口很少”的个人使用场景。
如果你已经有成熟的知识库协同流程,或者团队需要严格审计,那 fox_charon 的定位就不太合适。它能承受的数据量级是个人级别,每天几十上百条信息很稳,再往上就跑不动了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构:收集、梳理、分发三层管道
2.1 收集层:三个不显眼但好用的入口
我把收集入口控制在三个,没有做更多。理由很简单:入口越多,维护成本越大,而且人根本不会用。
第一个入口是邮件转发。给 fox_charon 配一个专用收件箱地址,订阅的 newsletter、同事发的任务邮件、自己给自己发的备忘,都可以转发过去。服务端定时拉取邮件,解析正文和附件链接。这个入口的好处是全平台通用,手机上、电脑上、甚至别人的电脑上都能转发。
第二个入口是 Web 表单。一个简单的页面,输入框 + 粘贴区,支持文本、URL、JSON 片段。配合浏览器插件或者快捷书签,选中网页文字直接送进来。这个入口主要是给“浏览器阅读场景”用。
第三个入口是本地脚本。命令行执行 charon add "内容",或者通过系统共享菜单调用。很多临时想法是在电脑上写代码、写文档时蹦出来的,这时候命令行最快。
三个入口共用同一个 HTTP 接口,内部逻辑完全一致。我特意没做手机 App,因为短期内不值得为它做原生客户端,PWA 页面已经够用。
2.2 整理层:分类、打标、定级
收集层拿到内容后,整理层负责三件事:
- 正文提取:如果是链接,用 readability 类库抽取网页正文,去掉广告和导航,得到一个干净的标准文本。
- 分类与打标:把文本交给大模型,让它判断内容类型和标签。我分别了两套体系:一是“类型”,包括文章、待办、灵感、工作日志、资料存档,二是“标签”,对应具体主题,自维护规则。
- 重要程度定级:让模型给一个 0 到 1 的分数,高于 0.8 的进“今日必看”,低于 0.3 的进“冷宫存档”,中间的先放着。
这三个结果不是给用户看的,是给分发层用的。整理层输出一个结构化 JSON,附加到原始记录上,存进 SQLite。我试过把原始内容和整理结果拆成两张表,后来发现单表加几个 JSON 字段更简单,查起来还方便。
2.3 分发层:写文件、写待办、写周报
分发层读整理结果,按照一套可配置的规则,把内容写到不同的目标:
- 笔记类内容追加到 Obsidian 仓库里的对应 Markdown 文件。
- 待办类内容写入一个本地 todo.txt 文件,或者直接推送到手机上的待办 App。
- 工作日志类内容写入“当天工作记录”的汇总文件。
- 所有内容都写一条 JSON 记录到存档表,方便以后检索。
分发动作是异步的,收到整理结果后先写数据库,再陆续执行分发,避免收集接口被分发动作拖慢。
2.4 关于“过度设计”的提醒
这个项目最容易犯的错,是上来就设计多个微服务、搞消息队列、上向量数据库。我踩过这个坑,第一版就是这么做的,结果开发了整整两周,每天花大量时间处理基础设施问题,核心功能反而没打磨好。
后来我推倒重来,全部收敛成一个 Python 进程,SQLite 做存储,FastAPI 做 HTTP 接口,APScheduler 做定时任务。部署也简单,一台小机器或者本机跑着就行。你要理解:这种工具的瓶颈从来不是并发,而是你有没有持续往里投喂信息。系统再复杂,没人用也是白搭。
3. 核心:如何让模型稳定输出可用的结构
3.1 为什么不能靠“聊天式”的整理
我第一版用的是纯对话式 prompt,让模型直接输出“这个内容是文章,标签是 AI、效率”,结果五花八门,有时候它会在前面加一堆解释,有时候直接输出一段话而不是分类结果,解析起来特别痛苦。
所以第二版起,我强制要求模型输出 JSON,设备完成分类。所有下游系统都以这个 JSON 为准,不再做自然语言猜测。
3.2 一套可以直接抄的 prompt 方案
我的 prompt 分两层。第一层是系统提示,给模型设定角色和输出 schema;第二层是用户消息,只放需要整理的真实内容。
系统提示大概是这样的:
text复制你是一个信息整理助手。用户会给你一条原始信息,可能是文章、链接、便签、任务、灵感或工作记录。
请输出一个 JSON 对象,格式如下:
{
"type": "article|todo|idea|worklog|archive",
"tags": ["标签1", "标签2"],
"importance": 0.0,
"summary": "一句话摘要",
"reason": "分类理由"
}
要求:
1. type 只能取给定枚举值,不要自创。
2. tags 选取 1 到 5 个,尽量具体,不要过度概括。
3. importance 在 0 到 1 之间,0.9 以上表示紧急重要,0.3 以下表示可长期存档。
4. 只依据输入文本判断,不要联想输入文本之外的信息。
5. reason 用不超过 20 个字的一句话说明分类依据。
用户消息就直接放正文:
text复制<这里放提取后的正文,或者原始文本>
这里有个关键细节:prompt 里加了“只依据输入文本判断,不要联想”一句,目的是压制模型的脑补倾向。后面踩坑部分会详细说,反正没有这句之前,模型会把一句“明天下午三点开会”脑补成“工作计划推进的重要节点”,分类和重要度全偏掉。
3.3 JSON 解析与兜底
即使 prompt 写了输出 JSON,模型偶尔也会在 JSON 外面套一层 ```json 标记,或者夹杂解释。所以我封装了一个容错解析逻辑:
python复制import json
import re
def parse_model_output(raw: str) -> dict:
text = raw.strip()
# 去掉代码块标记
text = re.sub(r"^```(?:json)?\s*", "", text)
text = re.sub(r"\s*```$", "", text)
try:
return json.loads(text)
except json.JSONDecodeError:
# 提取第一个 { 到最后一个 }
start = text.find("{")
end = text.rfind("}")
if start != -1 and end != -1:
return json.loads(text[start:end + 1])
raise ValueError(f"无法解析模型输出: {raw[:200]}")
解析之前,还做了一个字段校验:如果 type 不是五个枚举值之一,就直接丢弃,走人工介入队列。宁可不自动处理,也不要胡写乱写。毕竟下游是真实文件,误写了会污染整年的工作记录。
3.4 本地模型还是云端 API
我一开始用云端 API,主要图省事,速度也能接受。后来因为隐私考虑,试了一遍本地模型,用 Qwen2.5-7B-Instruct 的 GGUF 量化版本跑在本地,效果和速度都够用,单条分类耗时大概两秒左右。
本地部署的好处是一旦跑起来就没有额外调用成本,数据也不出机器。缺点是显存要求不低,笔记本上勉强能跑,风扇会呼啸;分类质量偶尔不稳定,特别是长文本摘要会偷懒,只抄开头两句。
给我的建议是:如果你是普通用户,直接用一个兼容 OpenAI 接口的云端 API 就行,省下折腾时间;如果你对隐私有要求,再考虑本地模型,做好两条腿走路的准备。架构上我把模型调用封装成了一个接口,切换只需改配置,不用改业务代码。
4. 分发规则引擎:从待办到周报草稿的自动化路径
4.1 一条规则配置的长什么样
分发层本质上是一个规则引擎。我在项目里维护了一份 YAML 配置,直接定义“哪种类型、哪种标签、哪种重要度的信息,送去哪个目标”。
yaml复制rules:
- name: "todo_to_personal_list"
when:
type: "todo"
action:
target: "file:///home/user/todos/todo.txt"
format: "- [ ] {summary} | 来源: {source} | 日期: {date}"
- name: "high_importance_article_to_inbox"
when:
type: "article"
importance: ">0.8"
action:
target: "file:///home/user/notes/Inbox.md"
format: "## {title}\n\n> 标签: {tags}\n\n{summary}\n\n[原文链接]({url})\n"
- name: "worklog_to_weekly_draft"
when:
type: "worklog"
action:
target: "weekly://{year}-W{week}"
format: "- {date} {summary} #来源: {source}"
- name: "idea_to_idea_bank"
when:
type: "idea"
action:
target: "file:///home/user/notes/IdeaBank.md"
format: "### {summary}\n\n{content}\n"
这套配置的优点是可读性强,哪条信息去了哪里一目了然。改规则不用碰代码,编辑 YAML 再重新加载就行。
4.2 幂等性与失败补偿
分发动作最容易出问题的不是规则写错,而是重复执行。比如网络中断导致重试,一条信息被追加到文件里两遍,非常恶心。
解决办法是给每条信息生成一个唯一标识。我用的是原始内容的 SHA-256 哈希,加上接收时间戳组合成处理 ID,在数据库里建唯一索引。任何分发动作执行前先检查这个 ID 是否已处理过,处理过就直接跳过。
sql复制CREATE TABLE IF NOT EXISTS processed_items (
id TEXT PRIMARY KEY,
source_hash TEXT NOT NULL,
raw_text TEXT,
parsed_data TEXT,
created_at TEXT,
dispatched_at TEXT
);
这里的 source_hash 是内容哈希,用于跨时间维度识别重复内容,和 id 用途不同。同样的文章如果几天前已经收过,哪怕这次来源不同,也能通过 source_hash 识别出来。
失败补偿我做得比较朴素:每个分发动作记录成功或失败状态,失败的分发进入 retry 队列,每五分钟重试一次,最多重试三次。三次都失败就把这条标记为 dead_letter,在管理页面里可以手动重放。这个机制名字听着唬人,其实就是一张表加一个定时任务。
4.3 周报草稿的汇总逻辑
周报是 fox_charon 里给我省时间最多的功能。每周日晚上,系统会跑一个汇总任务,把本周所有 worklog 类型的信息集合起来,按时间排序,生成一份带日期、来源、摘要的流水记录。
如果只是流水记录,那还差口气,我后面又接了一层生成逻辑:把流水记录作为内容,让大模型生成三个版本的周报,分别是“写给领导看的正式版”“写给团队看的简洁版”和“给自己看的复盘版”。我只需要挑一个,微调两句话就能发出去。
这个功能有两个前置条件:一是平时要把工作内容随手扔进来,不然没素材;二是要养成分发到 worklog 的习惯,看到任何和工作相关的消息,先转一笔进来。一开始可能觉得麻烦,等用上一周,周五下午那种大脑一片空白的状态基本就消失了。
5. 实测数据与踩坑记录
5.1 连续跑了一个半月的数据
从我调整完架构、稳定使用到现在,连续跑了 42 天,一共收集 486 条信息。这是真实使用数据,不是压测跑出来的数字。
| 项目 | 数量 | 说明 |
|---|---|---|
| 总收集条数 | 486 | 邮件转发 172 条,Web 表单 233 条,本地脚本 81 条 |
| 自动归档成功 | 424 | 占比 87.2% |
| 加入人工介入队列 | 62 | 主要是格式无法解析或分类置信度不足 |
| 标签准确率(抽查) | 约 91% | 抽查了 100 条,人工复核 |
| 重复内容拦截 | 47 | 同一篇文章/同一段话被多次提交 |
| 自动分发成功 | 415 | 失败后经重试成功的 37 条也在内 |
| 最终遗漏 | 9 | 三次重试仍失败,手动处理 |
单条信息从接收到完成分发的平均耗时是 6.8 秒,其中大头是大模型推理时间。如果批量导入历史数据,一次导入 20 条,总耗时大约 25 秒,可以接受。
5.2 踩坑:标签越打越多,最后变成“每篇都有自己专属标签”
第一次上线时,我让模型自由发挥打标签,结果两周后标签数量膨胀到 300 多个。每篇文章看着都像有专属标签,实际上完全无法聚合检索。
解决方案是引入标签白名单机制。系统内置一个核心标签列表,比如“AI”“效率工具”“项目管理”“读书笔记”“健康”“财务”“生活记录”等,大约 30 个。模型输出标签后,程序会做一次过滤,只保留白名单里的标签;如果模型认为有必要新增标签,那它必须先走一个“标签申请”流程,在管理页面里人工审核。这一步直接把标签漂移问题治住了。
5.3 踩坑:同一条内容在不同天被放进两次
有一段时间我发现笔记文件里出现了同一篇文章两次,但时间戳差了好几天。查了一下,第一次是从邮件转发的,第二次是同一周我手动复制粘贴的。两次来源不同,所以首先用 id 查重没拦住。
后来给整条流程里加了 source_hash,也就是对纯文本内容取哈希。不管来源是什么,只要正文基本相同,就能在入库时被识别出来。这个阈值我调了好几次,最后用正文的前 200 个字符生成一个“指纹”,配合编辑距离做弱匹配,效果更稳。
5.4 踩坑:模型“太聪明”反而误判
这是最让我意外的一个坑。有一次我扔进去一条“昨天下午和张总聊了一下项目进度的问题”,模型的返回是 type: worklog, importance: 0.9。追了一下 prompt,发现模型“自动脑补”了背景:它认为“和张总聊项目”一定是重要工作,所以直接给了高重要度。
这显然超出了输入文本的实际信息量。解决方式是两步:第一,在 prompt 里加了一句“只依据输入文本判断,不要联想输入文本之外的信息”,效果立竿见影;第二,对 importance 做了二次校准,凡是超过 0.9 的信息都会在分发前额外标注“可能需要人工确认”,避免一条轻飘飘的闲聊被直接顶进“今日必看”。
另外一个相关经验是,别让模型自己给自己的失误“开脱”。如果你问它“你确定吗”,它大概率会坚持原判。正确做法是引入独立的规则校验,比如 type 是 todo 的重要度默认不超过 0.6,因为个人待办通常不是紧急计划,真正紧急的事项应该是被单独标记的。规则比模型更稳的地方就在这里。
6. 后续想做的方向与一些实际经验
6.1 离线部署与多端同步的可能
当前版本依赖一个固定地址的 HTTP 服务,最佳部署位置是一台常年开机的机器,或者一个小型 NAS。下一步我打算把模型调用封装成可插拔的本地接口,这样完全离线也能跑。
离线部署的价值不只是隐私,更在于稳定性。云端 API 偶尔会超时,会影响整体分发链路;本地模型虽然慢一点,但可控性强。
6.2 几个对后来者有参考价值的判断
第一,先跑通最窄的闭环再扩展功能。我第一版上来就做了一堆入口、多种分发目标,最后发现真正高频使用的只有一两个入口和两三个出口。先把“邮件转发进来 -> 自动分类 -> 落到笔记文件”这条链路跑通,再加别的入口,效率会高得多。
第二,自动化要留退路。不是所有信息都适合全自动分发,保留一个人工介入队列,比强行让模型处理所有边缘情况更靠谱。我宁愿多花三秒钟扫一眼队列,也不愿意让模型把一条重要信息分错地方。
第三,所有文本类数据都要留原始记录。分发出去的是加工后的产物,但原始内容必须完整保留在数据库里。万一哪次模型抽风,摘要写得驴唇不对马嘴,你还能回去翻原文。
最后分享一个小技巧:别把 fox_charon 做成一个“收藏夹”,要把它当成“处理管道的入口”。收藏夹是终点,处理管道是起点。换了这个心智模型,整个工具的价值完全不一样了。你要是也想搭一个类似的东西,我建议从周报生成和待办提取这两个功能入手,最容易见效,也最容易被坚持用下去。
