---
title: AI 编程对话沉淀为工程经验：质量评估与复用验证
description: 以合成样本核对来源与长度分母、错收漏收和空样本，并区分当前经验筛选、模型摘要与真实复用结果。
image: "https://zengliangyi.com/og/blog/conversation-knowledge-retrospective.png"
---

把很多 AI 编程对话归一化后，能更系统地复盘来源、任务、排查过程、决策与复用结果。数量本身不能证明效率提升。原稿的 5247 条对话、3811 条笔记、标签排名和个人八个月故事没有可审计数据集，本文不把它们当作事实，而给出可执行的统计和质量抽检方法。

## 先定义统计分母与缺失范围

[server/src/routes/status.ts](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/routes/status.ts) 的 stats 分别对 conversations、notes 和 tags 全表计数，包含库中的 demo 数据。排除 demo 的 realConvCount 只用于判断 isSeeded，会话关联查询只用于 recentNotes；不能把这些条件套到总计数上。这是应用当前库的快照，不是所有工具中的原始历史。CLI 的 [conversations](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/cli/commands/conversations.ts) 和 [notes list](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/cli/commands/notes.ts) 分页返回 items/total，[search](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/cli/commands/search.ts) 则返回数组。第一页数量、搜索命中数或标签使用次数不能互相替代。采集时保存目标实例、固定版本、日期、过滤条件、分页范围以及导入错误和未支持的来源；需要排除 demo 时，在自己的纳入规则里明确执行并记录。

| 问题 | 合适的单位/分母 | 容易混淆 |

| --- | --- | --- |

| 来源与项目分布 | 同一纳入范围的归一化会话 | 原始文件数、重复导入与缺失来源 |

| 长度分布 | 每篇归一化消息数，区间不重叠 | 消息总数与会话数，解析遗漏 |

| 标签分布 | 指定笔记集合上的使用次数 | 多标签累计不必等于笔记数 |

| 摘要覆盖 | 纳入会话的笔记/筛选结果分别计 | filtered 不等于摘要失败或已有笔记 |

| 复用效果 | 同任务可追溯的验收结果 | 命中标签或生成摘要不等于解决问题 |

原始副本、统一消息、模型摘要和可读导出是不同层，见[对话管理方案](/blog/ai-conversation-management-options/)。缺失思考、工具记录或构造时间会影响后续统计，不能把归一化库当无损的原始行为记录。

## 对四类模式提出可证伪的问题

短问答是否低价值，应看是否有明确问题、处理过程、结果和复用条件；短的完整修复也可能有用。调试笔记要保留被排除的假设与验证，不只抄最终答案。架构讨论应记录备选项、约束、取舍和实际验证，可另写 ADR。看起来重复的提问要核对项目、版本、根因和结果，语义相似不等于同一问题，更不能直接推算节约了几次工时。

当前 [server/src/services/experience/gate.ts](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/services/experience/gate.ts) 已有信号预筛、模型维度与合并规则，不是未来才计划“按少于五条消息且无代码过滤”。[server/src/services/summarize.ts](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/services/summarize.ts) 对未接受的会话记录 filtered 及原因，已有笔记和用户拒绝还有不同分支。摘要与嵌入/关系结果分别检查，避免把队列完成率当知识质量。

## 用合成标签演示分母和错判清单

保存为 retrospective.mjs，用 Node 24 执行 `node retrospective.mjs`。六条都是手写合成样本；expected 是示例人工标签，selected 是预先写好的判断，不调用当前 gate、模型、CLI、数据库或真实导出。程序核对来源分母、互斥长度区间、混淆计数和空分母，并保留错收与漏收的 ID。结果只属于这六条，不是用户数据上的精确率或召回率。



```js
import   assert   from   'node:assert/strict'  ;  
  
  // Hand-labelled synthetic cohort, not exported conversations or model judgments.  
  const   cohort   =   [  
    {  
      source:   'fixture-a'  ,  
      id:   'short-fix'  ,  
      messages:   4  ,  
      project:   'one'  ,  
      expected:   true  ,  
      selected:   true  ,  
    },  
    {  
      source:   'fixture-a'  ,  
      id:   'long-chat'  ,  
      messages:   100  ,  
      project:   'one'  ,  
      expected:   false  ,  
      selected:   true  ,  
    },  
    {  
      source:   'fixture-b'  ,  
      id:   'quick-info'  ,  
      messages:   2  ,  
      project:   'two'  ,  
      expected:   false  ,  
      selected:   false  ,  
    },  
    {  
      source:   'fixture-b'  ,  
      id:   'missed-fix'  ,  
      messages:   24  ,  
      project:   'two'  ,  
      expected:   true  ,  
      selected:   false  ,  
    },  
    {  
      source:   'fixture-c'  ,  
      id:   'decision'  ,  
      messages:   45  ,  
      project:   'three'  ,  
      expected:   true  ,  
      selected:   true  ,  
    },  
    {  
      source:   'fixture-c'  ,  
      id:   'unresolved'  ,  
      messages:   80  ,  
      project:   'three'  ,  
      expected:   false  ,  
      selected:   false  ,  
    },  
  ];  
  function   audit  (  items  ) {  
    const   seen   =   new   Set  ();  
    const   sources   =   {};  
    const   bins   =   [  0  ,   0  ,   0  ,   0  ];   // <10, 10–29, 30–79, >=80  
    let   tp   =   0  ,  
      fp   =   0  ,  
      fn   =   0  ,  
      tn   =   0  ;  
    const   falseAccepts   =   [],  
      falseRejects   =   [];  
    for   (  const   item   of   items) {  
      const   key   =   `${  item  .  source  }:${  item  .  id  }`  ;  
      assert.  equal  (seen.  has  (key),   false  );  
      seen.  add  (key);  
      assert.  ok  (Number.  isInteger  (item.messages)   &&   item.messages   >=   0  );  
      sources[item.source]   =   (sources[item.source]   ??   0  )   +   1  ;  
      bins[item.messages   <   10   ?   0   :   item.messages   <   30   ?   1   :   item.messages   <   80   ?   2   :   3  ]  ++  ;  
      if   (item.selected   &&   item.expected) tp  ++  ;  
      else   if   (item.selected) {  
        fp  ++  ;  
        falseAccepts.  push  (key);  
      }   else   if   (item.expected) {  
        fn  ++  ;  
        falseRejects.  push  (key);  
      }   else   tn  ++  ;  
    }  
    const   ratio   =   (  a  ,   b  )   =>   (b   ===   0   ?   null   :   a   /   b);  
    assert.  equal  (  
      Object.  values  (sources).  reduce  ((  a  ,   b  )   =>   a   +   b,   0  ),  
      items.  length  
    );  
    assert.  equal  (  
      bins.  reduce  ((  a  ,   b  )   =>   a   +   b,   0  ),  
      items.  length  
    );  
    assert.  equal  (tp   +   fp   +   fn   +   tn, items.  length  );  
    return   {  
      total: items.  length  ,  
      sources,  
      bins,  
      tp,  
      fp,  
      fn,  
      tn,  
      precision:   ratio  (tp, tp   +   fp),  
      recall:   ratio  (tp, tp   +   fn),  
      falseAccepts,  
      falseRejects,  
    };  
  }  
  const   result   =   audit  (cohort);  
  assert.  deepEqual  (result.bins, [  2  ,   1  ,   1  ,   2  ]);  
  assert.  equal  (result.precision,   2   /   3  );  
  assert.  equal  (result.recall,   2   /   3  );  
  assert.  deepEqual  (result.falseAccepts, [  'fixture-a:long-chat'  ]);  
  assert.  deepEqual  (result.falseRejects, [  'fixture-b:missed-fix'  ]);  
  assert.  equal  (  audit  ([]).precision,   null  );  
  assert.  throws  (()   =>   audit  ([  ...  cohort, cohort[  0  ]]));  
  const   lengthOnly   =   cohort.  map  ((  item  )   =>   ({   ...  item, selected: item.messages   >=   5   }));  
  assert.  ok  (  audit  (lengthOnly).falseRejects.  includes  (  'fixture-a:short-fix'  ));  
  console.  log  (  JSON  .  stringify  ({ scope:   'six synthetic labelled fixtures only'  , result },   null  ,   2  ));
```



## 让错误口径被检查捕获

去掉重复 source:id 的拒绝断言，重复样本的 assert.throws 应失败；把空分母 ratio 改为 0，空样本 precision=null 的断言应失败。恢复代码再运行。长度门槛漏掉 short-fix 只是这里的反例，不给某模型或当前产品排名。

真实抽检先保留全部纳入/排除 ID，按来源、任务、长度和决策状态抽样，对同一原文独立标记遗漏、虚构、错误归因和复用条件；保留分歧及裁决。训练/规则调整和留出验收分开，报告具体错收、漏收及分母，不只报告一项总通过率。[server/src/services/experience/eval-samples.json](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/services/experience/eval-samples.json) 的固定样本明确 provenance 为 synthetic_calibration_cases、contains_real_user_data=false；其规则校准结果不能外推为个人库或真实模型质量。

## 从问答习惯到可追溯复用

开始任务前按实际症状和项目查历史；处理中将命中的原始证据、版本和适用条件提供给助手；结束后检查问题、过程、结果与来源是否闭合。需要更正时保留修改理由和验收记录，现有 [server/src/services/experience/reviews.ts](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/services/experience/reviews.ts) 的删除反馈会记录质量原因，不意味着所有编辑已具备完整版本史。

标签、摘要和关系服务不同用途；“标签比摘要更重要”“80 分乘大量覆盖更好”都需要同任务对照数据，不能当普遍结论。数量也不能证明自己测试不足或编码速度翻倍。先固定任务成功标准，对比检索是否找到可用证据、执行是否成功、是否需要人工纠正，再讨论收益。

更完整的决策记录、代码依赖验证、成员权限或跨库检索可以是后续设计目标，必须与当前已有过滤、写回 receipt 和远端导入分开。沿[图谱辅助重构](/blog/graph-assisted-refactoring/)把关系线索转成故障检查，沿[团队共享边界](/blog/team-knowledge-sharing-plan/)保留审批和身份责任；系列的收官是可复核的使用方法，不是未经验证的增长预测。

分享：

##  相关文章

[##  研发团队技术知识库共享方案：访问令牌与审批流设计

区分当前云令牌与拟议成员权限，以合成网关规则验证只读操作和整批审批，并明确共享与备份责任。          曾亮懿       2026年10月7日      阅读约 5 分钟

#ChatCrystal

#Knowledge Management

#Security](/blog/team-knowledge-sharing-plan)    [##  AI 对话存储格式对比：JSONL 事件流与 SQLite 快照

对照五个固定适配器，区分 JSONL 容器、事件语义与 SQLite 键布局，用两个内存数据库观察消息索引、孤立数据和未知版本。          曾亮懿       2026年10月7日      阅读约 4 分钟

#ChatCrystal

#Node.js

#SQLite](/blog/ai-conversation-format-divergence)    [##  Claude Code、Cursor 与 Copilot 对话存储路径与迁移

比较 Claude Code、Cursor、Copilot、Trae 的固定适配器输入，区分原始副本、可读导出与知识库，并用合成清单验证身份和信息保留。          曾亮懿       2026年10月7日      阅读约 4 分钟

#ChatCrystal

#Node.js

#SQLite](/blog/ai-conversation-management-options)

##  从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录，欢迎交流实现细节。

[访问我的 GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "AI 编程对话沉淀为工程经验：质量评估与复用验证",
  "description": "以合成样本核对来源与长度分母、错收漏收和空样本，并区分当前经验筛选、模型摘要与真实复用结果。",
  "url": "https://zengliangyi.com/blog/conversation-knowledge-retrospective/",
  "image": "https://zengliangyi.com/images/blog/conversation-knowledge-retrospective.svg",
  "datePublished": "2026-10-07T00:00:00.000Z",
  "dateModified": "2026-10-23T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/blog/conversation-knowledge-retrospective/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "首页",
      "item": "https://zengliangyi.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "文章",
      "item": "https://zengliangyi.com/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "AI 编程对话沉淀为工程经验：质量评估与复用验证",
      "item": "https://zengliangyi.com/blog/conversation-knowledge-retrospective/"
    }
  ]
}
```
