一、想法萌生
这一波 AI 能力爆发之后,我一直在想一个问题:个人端的 AI 生产力为什么始终差一口气?
工具不缺,模型不缺,缺的是责任的界定。当我让 AI 替我写一份报告、做一次分析、给一个判断,产出的东西到底算谁的?如果我直接署名,是欺骗受众;如果完全不认,那 AI 又永远只是个玩具,无法真正进入我的工作流。
进一步想,这背后其实是两个层面的问题:
- 能力的强化 vs. 能力的伪造
AI 能放大个人能力,但放大的前提是这个能力本来就在你身上。在认知范围内强化,是杠杆;在认知范围外强行扩充,是泡沫——早晚会破,而且破的时候代价更大。一个不懂信贷风控的人,让 AI 写出一份漂亮的风控报告,短期是效率,长期是事故。
- 产出的归属
如果能让 AI 清晰地知道"这部分能力确实是我的,那部分不是",那 AI 的产出就可以分层归属:在我能力边界内的部分,由我承担收益和风险;超出边界的部分,明确标注为 AI 辅助或需要我后续补强。
这就是做 digital-twin 的初衷:
把"人"身上的能力(教育背景 + 工作经验)抽象成结构化的 Capability Card,让 AI 在需要时按意图检索调用,避免每次对话都要从头描述背景;同时通过责任确认机制,明确每张 Card 的产出责任归属。
下一阶段的开发目标,是探索数字身份和数字签名机制——让数字分身的产出可追溯、可签发、可担责,真正让 AI 的产出从"玩具"变成"工作流的一部分"。
二、整体架构与扩展接口
设计原则
一句话:每一层都是 Protocol,默认实现可整体替换。
SDK 内部七层抽象,从 LLM 到存储全部接口化,可以在不改业务代码的前提下替换任何一层。
架构图
┌──────────────────────────────────────────────────┐
│ Trae(触发词识别 + MCP Client) │
└────────────────────┬─────────────────────────────┘
│ stdio MCP
┌────────────────────▼─────────────────────────────┐
│ Digital Twin SDK(MCP Server, 14 个 tool) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Distill │ │ Invoke │ │
│ │ Orchestrator │ │ Orchestrator │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────▼───────┐ ┌──────▼───────┐ │
│ │ LLMClient │ │Retriever │ │
│ └──────────────┘ │(Hybrid v2.0) │ │
│ └──────┬───────┘ │
│ ┌──────────────┐ │ │
│ │ CardStore │
───────┘ 索引同步钩子 │
│ └──────────────┘ │
└───────────────────────────────────────────────────┘
七层抽象接口
LLM 层
抽象接口:LLMClient
默认实现:OpenAIClient
扩展点:任何 OpenAI 兼容端点(DeepSeek / Moonshot / Azure)
卡片存储层
抽象接口:CardStore
默认实现:FileSystemCardStore(每卡一 JSON)
扩展点:想换 PostgreSQL / MongoDB?实现 Protocol 即可
会话存储层
抽象接口:SessionStore
默认实现:SqliteSessionStore(单表 SQLite)
扩展点:想换 Redis?实现 Protocol 即可
学习计划存储层
抽象接口:PlanStore
默认实现:FileSystemPlanStore
扩展点:同上
学习记录存储层
抽象接口:RecordStore
默认实现:FileSystemRecordStore
扩展点:同上
向量存储层(v2.0 新增)
抽象接口:EmbeddingStore
默认实现:NpyEmbeddingStore(npz 集中存储)
扩展点:想换 FAISS / Milvus?实现 Protocol 即可
文本向量化层(v2.0 新增)
抽象接口:Embedder
默认实现:LocalEmbedder(sentence-transformers)
扩展点:想换 OpenAI Embedding API?实现 Protocol 即可
检索层
抽象接口:Retriever
默认实现:HybridRetriever(BM25 + 向量融合)
扩展点:想换重排序模型?实现 Protocol 即可
14 个 MCP Tool(按职责分四类)
配置类(3 个)
configure_identity — 首次配置主体身份(write-once)
configure_triggers — 配置或修改两个触发词(可覆盖)
get_config — 拉取当前 identity + triggers
蒸馏类(5 个)
distill_start — 启动蒸馏会话
distill_continue — 继续蒸馏(多轮)
distill_finalize — 结束蒸馏,生成 draft Card
distill_cancel — 取消并删除 session
confirm_responsibility — 责任确认,draft Card 转正
调用类(2 个)
invoke — 按意图检索卡片(含学习历史摘要)
list_cards — 列出全部已确认 Card 的简要目录(v2.1 新增)
学习管理类(4 个)
save_learning_plan — 保存智能体生成的学习计划
list_learning_plans — 列出学习计划(可按 card 过滤)
save_learning_record — 保存单次学习记录
list_learning_records — 列出某计划下的学习记录
两个核心动作
Distill(蒸馏):多轮对话把人身上的能力提炼成 Capability Card(含 14 个字段,含完整度评分、风险提示、术语表、业务术语映射等)
Invoke(调用):智能体按意图检索 Card,附带学习历史摘要,避免重复推荐已学内容
三、开发过程中的困难和思考
- LLM 知识边界必须显式声明
早期版本没有声明 LLM 的知识截止时间,结果 LLM 在蒸馏过程中自信地引用了 2024 年才发生的事件。后来在 distill_start 返回值里强制加入 llm_knowledge_boundary(含 cutoff_date + source),并把"不要收集或回显用户出生年月"等约束写进所有 prompt 模板。
教训:LLM 的边界不能靠模型自觉,必须写进协议。
- LLM 过度推理标签语义
我把能力分成 education 和 work 两类,结果 LLM 自由发挥,把 education 推理成"教育学专业分支",把 work 推理成"劳动学研究"——一个完全无关的学术领域。
修复:引入 CAPABILITY_TYPE_DEFINITIONS 常量,把标签的权威语义解读注入到所有相关 prompt 中。LLM 的推理空间必须被显式约束,不能给它自由发挥的余地。
- LLM 越权制定学习计划
蒸馏的目的是厘清能力边界和 gap,但 LLM 会自作主张地生成分阶段学习计划、推荐教材、安排每日学习时长。这超出了 distill 阶段的职责。
修复:引入 LLM_RESPONSIBILITY_BOUNDARY 常量,明确 LLM 只负责"罗列 gaps",详细学习计划由智能体在 invoke 之后基于 Card gaps 生成。这是"LLM 与智能体各司其职"的一次实践。
- 蒸馏过程中生成的术语映射库丢失
v2.0 测试时发现一个 bug:蒸馏过程中 LLM 自发生成了一份"工商银行术语-业务映射库"(HTML 形式),但 finalize 阶段没有结构化提取,只以纯文本残留在 session.dialog_history。session 24h 后自动清理,这部分知识彻底丢失。
修复(v2.1):在 CapabilityCard 新增 term_mapping: dict[str, str] 字段,与已有的 glossary 语义分离——
glossary:消解跨领域歧义("模型"指 BS 公式还是神经网络)
term_mapping:绑定业务语义("银团"指银团贷款业务)
两者都纳入 BM25 索引,让业务术语查询命中正确的 Card。
思考:这个问题暴露了一个通用模式——LLM 在对话过程中生成的结构化知识,如果没有显式提取并持久化,就会随 session 清理而丢失。这是"对话即数据"模式的陷阱。
- invoke 只返回最新一张卡,无法列出全部能力
用户反馈:“蒸馏完再召唤,只显示最新一次的能力,我希望列出分身的全部能力。”
一开始想改造 invoke 让它返回所有卡,但这违背了 invoke"按 intent 检索"的设计语义。检索和列目录是两件事,不该混在一起。
修复(v2.1):新增 list_cards 工具,与 invoke 职责分离。
invoke:按意图检索最相关的 Card(带相关性排序)
list_cards:直接列出全部 Card 的简要目录(8 个字段,不含大字段,避免 token 浪费)
思考:当一个工具的职责被滥用时,正确的做法不是加参数让它做更多事,而是拆分出新的工具。
- 向量检索的本地化抉择
v2.0 引入向量检索时,考虑过云 API 方案,但最终选择本地 embedding 模型(bge-small-zh-v1.5)。原因:
个人能力数据是高度敏感的 PII,不应发到云端
本地模型 + numpy 文件持久化的方案,与项目"单用户本地使用"的定位一致
通过 Protocol 抽象,未来要换成云 API 也只是实现一个 Embedder 子类的事
代价是首次加载模型需要 5-10 秒,且 sentence-transformers / torch 是重依赖。解决方案是设为可选依赖([project.optional-dependencies] vector),无模型路径时退化为 FakeEmbedder,模型加载失败时退化为纯 BM25。
- HybridRetriever 的融合权重 α
BM25 擅长精确关键词匹配,向量检索擅长语义相似。两者如何融合?
最终选择加权线性融合:
final = α · bm25_norm + (1 - α) · vec_sim
α 默认 0.4(偏向量)。归一化用 min-max,避免量纲不一致。
α 的选择是个 trade-off:
α = 1.0:纯 BM25,精确匹配强,同义词召回弱
α = 0.0:纯向量,语义召回强,精确匹配弱
α = 0.4:偏向量,兼顾两者,适合中文能力描述这种语义丰富的场景
α 通过环境变量 HYBRID_ALPHA 可调,用户可根据自己的能力卡分布微调。
四、下一步计划
v3.0 — 数字身份与签名机制(核心目标)
这是项目的核心愿景:让数字分身的产出可追溯、可签发、可担责。
数字身份:基于 identity.json 扩展为完整的数字身份证书(含公钥)
数字签名:每张 Card 在 confirm_responsibility 时由主体私钥签名,后续 invoke 返回的 Card 携带签名链
产出归属:智能体调用 invoke 后生成的产出,可标注"基于 Card XXX 的能力,由主体 YYY 签名认证"
风险承担:签名即承诺,主体需承担该 Card 范围内产出的责任
这一步走通之后,AI 产出就有了"署名"机制——在能力边界内的部分由人承担,超出边界的部分标注为 AI 辅助。这是个人端 AI 生产力真正进入工作流的前提。
v3.0 — 其他计划
能力进化:基于 LearningRecord 自动更新 Card 的 completeness 分数
能力壁垒(moat)评估:评估某项能力的稀缺性,识别个人核心竞争力
level 分层调用:overview / working / deep 三档分层目前仅占位,v3 计划落地调用逻辑
ANN 索引:v2.0 的向量检索是暴力 cosine,Card 数量超过 1000 时需引入 FAISS / HNSW
长期愿景
希望 digital-twin 能成为一个个人 AI 生产力的责任协议层——不替代任何 AI 工具,但为所有 AI 工具的产出提供能力背书和责任界定。
说实话,对于开发本身并不是我的专业,但如果只是交流不用于商业,也就不考虑在不在我的能力边界范围内啦。如果这个方向有同好,欢迎交流。GitHub - jkfzhsz/digital-twin: Digital Twin v1 SDK - capability distill/invoke MCP server · GitHub