从「能用」到「可治理」: AI 随手记做了一次「不动皮只动骨」的升级 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
pengdw

从「能用」到「可治理」: AI 随手记做了一次「不动皮只动骨」的升级

  •  
  •   pengdw 2 月 12 日 981 次点击
    这是一个创建于 72 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近我们给「 AI 随手记」做了一次大改造。
    用户看到的还是老样子写随手记、点生成、拿到一篇日记。但里面已经从一个「大杂烩流程」,变成了一条可组合、可追踪、可回放、还能做质量把关的「技能流水线」。

    这篇文章用大白话讲讲:我们为什么要改、改了什么、踩了哪些坑,以及这件事对我们(和对想做类似产品的人)意味着什么。


    一、为什么要动刀?「能跑」和「能管」是两回事

    以前的实现也能跑,但有几件事让我们越来越头疼:

    • 逻辑缠在一起
      生成一篇日记时,清洗、检索、写作、存库、向量化全挤在一坨,改一处怕动全身。

    • 出了问题不好查
      用户说「这次写得怪」,我们只能翻日志猜,很难精确还原「到底是哪一步、输入输出长什么样」。

    • 想小步试新东西很难
      比如只想换一段提示词或某一步策略,几乎要动整条链路,没法单独试。

    • 质量会飘
      不同模型(尤其像 Qwen 这类)有时会「脑补」事实,规则不好统一管。

    所以我们这次的目标很明确:
    把「写日记」从一个大函数,拆成一条「有标准接口的技能流水线」。
    这样每一步都可以单独升级、单独排查、单独做灰度。


    二、我们具体做了什么?(一句话版)

    我们把系统重构成了四块:

    • SkillRegistry:技能注册表,知道有哪些技能、怎么找。
    • SkillExecutor:执行器,按标准接口跑每一个技能。
    • SkillOrchestrator:编排器,决定先跑谁、后跑谁、怎么串起来。
    • Trace:每次运行都留痕,谁在什么时候、吃了什么、吐出了什么、花了多久、有没有报错。

    然后把「生成日记」这条主流程,拆成了 5 个技能

    1. note-normalize (清洗)
      把随手记整理成统一的结构化块,去噪音,准备好给后面用。

    2. memory-retrieve (记忆检索)
      用本地记忆做召回,给生成阶段补一点上下文;可以降级为空,不阻塞主流程。

    3. diary-compose (生成)
      按规则生成正文。

    4. diary-critic (审校门控)
      检查有没有「硬伤」:比如图片标记幻觉、背景信息泄露、拼接感太重等。
      不只是打分,还能拦:不合格就不落库,避免脏数据。

    5. persist (落库)
      通过审校的才存进去;不通过就阻断,并返回可解释的原因。

    用户依然只点「生成日记」,但内部已经是一条清晰的流水线:清洗 → 检索 → 生成 → 审校 → 落库。


    三、最关键的四种能力:可追踪、可回放、可灰度、可门控

    这次重构,我们最在意的是四件事。它们不炫,但决定了能不能长期迭代、少背锅。

    1 ) Trace:可追踪

    每次运行都有一个 run_id,会记录:
    每个技能吃了什么(输入摘要)、吐出了什么(输出摘要)、耗时多少、有没有报错。

    以前:用户说「这次写得怪」→ 我们猜。
    现在:按 run_id 一查,就能看到是哪一个技能、在什么输入下、产出了什么,相当于可回放的一盘录像

    2 ) Replay:可回放

    我们加了一套「固定样本」的复测数据。
    改一个技能或一段提示词,可以用同一批样本反复跑,看有没有「修一个坏三个」。

    这对稳定迭代特别重要:先能复现,再谈优化。

    3 ) Rollout:可灰度

    支持「按技能切版本」。
    比如只把「 diary-compose 」换成新版本,其他技能不动,整条链路不用大动。
    这样我们可以小步试新策略、新 prompt ,出问题也能快速回滚。

    4 ) Gate:可门控

    diary-critic 不只是打分数,而是有「拦」的能力。
    高风险内容(幻觉、泄露、拼接味太重)可以不落库,并给用户一个可解释的原因,而不是悄悄存错。


    四、踩过的坑

    坑 1:Xcode 资源重名冲突

    多个技能里都有 SKILL.mdinput.jsonoutput.json,Xcode 拷贝资源时会扁平化,结果出现「重复输出」之类的问题。

    解决:改成全局唯一文件名(例如 SKILL-note-normalize.mdnote-normalize-input.json),并在 registry 里用 SKILL*.md 这种方式扫描,避免重名。

    坑 2:第二次生成时崩溃( SwiftData detached backing data )

    第一次生成后,我们在异步任务里还在读旧的 DiaryElement;第二次生成时先把旧元素删了,就触发了 fault 崩溃。

    解决:去掉在后台乱跑的并发 Task ,把向量生成和保存放回同一条 async 执行流里,保证对象生命周期一致,不再「用已删掉的东西」。

    坑 3:日记「像在拼接 notes 」

    提示词太保守时,模型会变成「安全复述器」:一条条复述随手记,不像日记,像清单。

    解决

    • 在 prompt 里允许「合并同主题、时间线重排、口语转书面、压缩重复」;
    • 在 Critic 里加「拼接味检测」(例如重合度阈值);
    • 命中后自动重写一轮,但不放宽事实约束,避免编造。

    五、最难的一关:既不虚构,又要像日记

    「禁止虚构」和「读起来像日记」经常打架:
    管太严 → 像清单;放太松 → 容易编造。

    我们的做法是三层:

    1. 硬约束守底线
      不新增输入里没有的关键事实。这是红线。

    2. 结构规则保可读
      强制段落规划、主题合并,让文本有节奏、有重点,而不是流水账。

    3. 审校规则兜质量
      用 Critic 检测「拼接味」,必要时自动重写一次,仍然不放宽事实约束。

    结果就是:既不把 notes 原样粘贴,也不靠「脑补」润色。


    六、用户能感受到的变化

    表面上看,还是「写随手记 → 一键生成日记」。但你会慢慢感觉到:

    • 首行格式更稳定
      例如:2026-2-6 上海,多云,2.8°C,不会乱变。

    • 文本更像日记
      不再是「一行行原始随手记的复制」,而是有合并、有节奏、口语转书面。

    • 出错更可解释
      我们内部能通过 trace 看到是哪个技能、在什么环节出了问题,修起来更有针对性;未来也可以把「可解释」适度开放给用户(比如「因内容未通过审校,未保存」)。


    七、一点结论:AI 应用拼到后面,拼的是「能不能治理」

    单次回答好不好,很重要;但产品要长期活下来,更关键的是:

    • 能不能治理:流程清晰、每一步可观测、可调参。
    • 能不能复盘:出问题能回放、能定位,而不是靠猜。
    • 能不能小步快跑:改一点、测一点、灰度一点,而不是一改就炸。

    这次把「生成日记」做成 Skills 流水线,本质上就是把「提示词工程」升级成「系统工程」

    如果你也在做类似的产品(日记、总结、写作助手等),我们会建议优先补三件事:
    可追踪、可回放、可门控。
    它们看起来不炫,但会直接决定你能不能长期迭代、少背锅、少踩坑。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1284 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 53ms UTC 17:26 PVG 01:26 LAX 10:26 JFK 13:26
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86