ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Karakeep 标签系统完全指南:从轻量标注到 AI 自动打标签的实践与原理

2026/9/11 18:21:29 拓冰建站 浏览量
Karakeep 标签系统完全指南:从轻量标注到 AI 自动打标签的实践与原理 Karakeep 标签系统完全指南从轻量标注到 AI 自动打标签的实践与原理【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder标签Tags是 Karakeep 中一种轻量的标注机制可以附着在任意书签链接、笔记、图片上用来表达主题、来源、人物或工作流状态而无需受制于死板的文件夹结构。本文基于仓库文档 tags.md 的核心内容结合数据表结构、tRPC 路由、标签模型与测试用例系统讲解标签的定位、数据模型、命名规则、AI 打标签机制、搜索过滤以及标签管理合并、清理、排序帮助你把这套能力真正用起来。标签是什么轻量标签而非刚性文件夹Karakeep 官方文档对标签的定义是轻量的标签lightweight labels可以附着到任意书签上为其附加语义而不需要建立僵化的文件夹层级。这意味着一个书签可以同时拥有多个标签标签之间是平等关系没有父子层级标签不属于某个固定的分类树你随时可以新建、改名、合并或删除标签会跟着书签走——无论书签出现在搜索结果、列表视图还是智能列表中标签都随之携带tags travel with a bookmark wherever it appears。用标签捕捉什么文档给出了四类典型用途并举了示例用途示例标签主题topicsai、design来源sources某博客名、某社区名人物people作者名、团队成员名工作流状态workflow statesto-read、to-review、done标签与列表Lists的分工这是文档中最关键的一条使用建议用标签做宽泛发现broad discovery用列表做精挑细选clean, hand-picked setup。标签适合低成本、高覆盖的标注尤其是 AI 自动生成的标签。AI 打出来的标签可能看起来有点乱AI tags might look a little messy但多出来的标签恰恰让找东西更容易——你不需要记住精确的分类只要模糊记得某个主题词就能检索到。**列表Lists**适合人工维护的干净集合比如周末要读的文章这种需要精心挑选的场景。换句话说标签负责撒网列表负责收网。两者可以结合使用——例如用#to-read标签标记所有待读内容再通过列表或搜索把它们组织起来。底层数据模型两个表撑起整个标签体系理解了定位之后我们看 Karakeep 是如何在数据库层面实现标签的。核心定义位于 packages/db/schema.ts由两张表构成bookmarkTags标签主表export const bookmarkTags sqliteTable(bookmarkTags, { id: text(id).notNull().primaryKey().$defaultFn(() createId()), name: text(name).notNull(), normalizedName: text(normalizedName).generatedAlwaysAs( (): SQL sqllower(replace(replace(replace(${bookmarkTags.name}, , ), -, ), _, )), { mode: virtual }, ), createdAt: createdAtField(), userId: text(userId) .notNull() .references(() users.id, { onDelete: cascade }), }, (bt) [ unique().on(bt.userId, bt.name), index(bookmarkTags_name_idx).on(bt.name), index(bookmarkTags_normalizedName_idx).on(bt.normalizedName), ]);关键点nameuserId联合唯一同一个用户下标签名不允许重复不同用户之间相互隔离normalizedName是生成列对标签名做lower()并移除空格、-、_得到规范化名称。这意味着Machine-Learning、machine_learning、machine learning在语义上会被视为同一个标签的变体。源码注释明确指出该函数需与tagging.ts中的tagNormalizer保持同步见 schema.ts确保 AI 打标签与人工打标签对同一个标签的判断一致索引覆盖name与normalizedName为按名查询和搜索提供加速。tagsOnBookmarks书签与标签的多对多关联表export const tagsOnBookmarks sqliteTable(tagsOnBookmarks, { bookmarkId: text(bookmarkId).notNull() .references(() bookmarks.id, { onDelete: cascade }), tagId: text(tagId).notNull() .references(() bookmarkTags.id, { onDelete: cascade }), attachedAt: integer(attachedAt, { mode: timestamp }).$defaultFn(() new Date()), attachedBy: text(attachedBy, { enum: [ai, human] }).notNull(), }, (tb) [ primaryKey({ columns: [tb.bookmarkId, tb.tagId] }), index(tagsOnBookmarks_tagId_bookmarkId_idx).on(tb.tagId, tb.bookmarkId), ]);这张关联表揭示了两个重要设计attachedBy区分标签来源每个关联都记录该标签是由AI自动打的还是人手工打的。这是AI 标签看起来乱但有用的底层支撑——UI 可以据此区分、过滤和统计两类标签复合索引按 tagId 优先源码注释写明这是为按标签过滤书签tag-first queries优化的schema.ts即从某个标签出发查询其下所有书签是高频操作。两张表都通过onDelete: cascade级联删除删除书签时自动摘除其标签关联删除标签时自动清理关联记录无需手动维护一致性。标签命名与规范化为什么输入##demo会变成demoKarakeep 对标签名有一套统一的规范化逻辑实现在 packages/shared/utils/tag.tsexport function normalizeTagName(raw: string): string { return raw.trim().replace(/^#/, ); // strip every leading # }normalizeTagName会去掉标签名开头的所有#。这样做的目的很明确用户在 UI 或搜索框里习惯用#ai来指代标签但如果把#存进数据库就会出现ai与#ai两个不同的标签。规范化保证无论你输入#ai、##ai还是ai最终落库的都是ai。这一行为在 packages/trpc/routers/tags.test.ts 中有两个专门的测试用例验证create strips extra leading hashes创建名为##demo的标签落库后名称为demoupdate normalizes leading hashes把#foo更新为##bar最终名称是bar。在创建/更新请求的 Zod schema 中名称同样经过normalizeTagName(...).trim()后要求非空packages/shared/types/tags.ts从 API 层就杜绝了空名和纯#名称。如何给书签打标签attach 与 detach给书签添加/移除标签的入口是bookmarks.updateTags变更定义于 packages/trpc/routers/bookmarks.ts它同时支持两种标识方式{ bookmarkId: 书签ID, attach: [ { tagName: ai }, // 方式一按名称不存在会自动创建 { tagId: 已有标签ID } // 方式二按 ID引用已有标签 ], detach: [ { tagId: 要摘除的标签ID } ] }实现细节值得注意按名称 attach 会自动建标签如果传入的tagName在库中不存在路由会先创建新标签再建立关联名称在 attach 前同样会被规范化源码中normalizeTagName(tag.tagName)在事务外统一执行注释说明这是为了缩短事务持续时间bookmarks.ts按 ID detach 引用既有标签摘除时不涉及新建只需要标签 ID。在 Web 端、移动端和浏览器扩展中你通常不需要直接调用这个接口——界面上有标签输入/选择组件行为与此一致输入一个新词即可创建并附加点击已有标签则直接复用。AI 自动打标签attachedBy 与标签风格Karakeep 的核心卖点之一是AI 自动打标签。当 AI 为书签生成标签时关联记录中的attachedBy会被标记为ai人工添加的则为human。这两种来源可以在标签列表和统计中分别查看。标签风格Tag Style配置AI 生成标签的风格不是固定的你可以在设置中选择。getTagStylePromptpackages/shared/utils/tag.ts针对每种风格生成对应的提示词风格值示例lowercase-hyphensmachine-learninglowercase-spacesmachine learninglowercase-underscoresmachine_learningtitlecase-spacesMachine Learningtitlecase-hyphensMachine-LearningcamelCasemachineLearningas-generated默认由模型按原始输出生成受控标签Curated Tags与候选标签为了让 AI 打标签更可控工具函数还支持两种提示注入预定义标签列表getCuratedTagsPrompt会生成只允许使用以下列表中的标签不得创建列表之外的任何新标签如果没有合适的标签就不输出的约束提示tag.ts相似书签的候选标签getPotentialRelevantTagsPrompt会把相似书签上已使用的标签提供给模型提示其尽量复用、无关则忽略tag.ts。这两个机制解释了文档中AI 标签看起来有点乱的成因与对策AI 自由发挥时标签会多而杂便于宽泛发现需要整洁时可以用受控标签列表把 AI 的输出限制在既定范围内。用标签搜索与过滤书签标签是与搜索查询语言深度集成的。根据 search-query-language.md 的说明在搜索框中可以用两种等价写法按标签过滤#tag或tag:tag匹配带指定标签的书签例如#important、tag:important带空格的标签需要用引号包裹#work in progress或tag:work in progress结合布尔逻辑组合条件is:archived and (list:reading or #work)结合日期与收藏状态is:fav after:2023-01-01 before:2023-12-31 #important。另外is:tagged限定符可以筛选带有一个或多个标签的书签-is:tagged则筛出完全未打标签的书签——这在盘点哪些内容还没整理时非常实用。标签管理列表、排序、合并与清理标签本身也是一等公民可以通过tags路由packages/trpc/routers/tags.ts进行完整管理包含create、get、list、update、delete、deleteUnused、merge七个操作全部通过createScopedAuthedProcedure(tags)保证仅限本人访问。标签列表的参数体系list查询是日常使用最频繁的接口其参数定义在 packages/shared/types/tags.ts参数说明nameContains按名称子串模糊过滤底层为 SQLLIKE %xxx%ids按标签 ID 集合精确过滤attachedByai/human/nonenone表示未被任何书签使用的孤儿标签sortByname按字母、usage按使用量默认、relevance按相关度cursor/limit分页游标limit上限为MAX_NUM_TAGS_PER_PAGE 1000几个值得展开的行为均有测试佐证见 tags.test.ts默认按使用量排序sortBy缺省为usage使用量关联书签数多的标签排前面tags.test.tsrelevance 排序必须搭配nameContainsZod schema 用refine强制校验Relevance sorting requires a nameContains filtertags.ts。相关度计算规则为精确匹配 前缀匹配 子串匹配且前缀匹配中名字更短的优先匹配不区分大小写模型实现见 packages/trpc/models/tags.tsattachedBy: none筛出未使用标签通过 SQLHAVING COUNT(tagId) 0实现便于发现需要清理的标签models/tags.ts分页采用 offset limit1 探测多取一行判断是否还有下一页并返回nextCursormodels/tags.ts。list返回的每个标签都带统计信息numBookmarks关联书签总数以及numBookmarksByAttachedType按ai/human分别统计的数量这让标签页能直接展示AI 打了多少、我打了多少。合并标签Merge当 AI 打出的标签与人工标签语义重复时合并是最常用的整理手段。merge接受intoTagId保留的标签和fromTagIds被合并的标签数组其实现models/tags.ts包含完整的防御逻辑禁止把标签合并进自身Cannot merge tag into itself所有涉及的标签必须属于当前用户否则返回 FORBIDDEN任一标签不存在则整体返回 NOT_FOUND合并过程在数据库事务中完成先摘除fromTagIds的所有关联再原样改挂到intoTagIdonConflictDoNothing处理重复最后删除被合并的标签行合并完成后对受影响书签触发搜索重建索引triggerSearchReindex保证全文搜索立即反映新的标签关系。对应测试验证了合并后书签的标签从tag2变为tag1tags.test.ts。删除与清理delete删除单个标签级联清理其关联记录并重建受影响书签的搜索索引models/tags.tsdeleteUnused一键删除所有未被任何书签使用的标签返回删除数量models/tags.ts。这是标签越攒越多场景下的标准清理手段。更新标签名时同样会触发受影响书签的搜索重建且如果新名字与已有标签冲突会得到提示Tag name already exists. You might want to consider a merge instead.——官方在错误信息里直接建议用户改用合并models/tags.ts。标签与规则引擎的联动标签不仅是标注工具还能作为自动化规则的输入与输出。在 packages/trpc/routers/rules.ts 中可以看到规则引擎把标签深度纳入其事件、条件与动作体系事件tagAdded标签被添加、tagRemoved标签被移除可作为触发事件条件hasTag可判断书签是否带有某个标签动作addTag/removeTag可自动为书签添加或移除标签。这意味着你可以建立诸如当某书签被标记to-read时自动添加reading标签并加入列表之类的自动化流程把人工打标签的行为进一步转化为工作流的一部分。数据所有权与隐私隔离标签体系遵循与书签一致的所有权模型bookmarkTags.userId与tagsOnBookmarks的归属检查贯穿所有操作。Tag.fromId在加载标签时会先校验tag.userId ! ctx.user.id并抛出 FORBIDDENmodels/tags.tslist查询的 WHERE 条件也固定携带eq(bookmarkTags.userId, ctx.user.id)models/tags.ts。测试privacy验证了用户 1 创建的标签不会出现在用户 2 的列表中tags.test.ts多用户自托管部署下各账户的标签完全隔离。小结与最佳实践回顾 Karakeep 文档对标签的定位结合源码可以总结出一套可落地的最佳实践标签用于宽泛发现列表用于精挑细选日常收藏交给标签尤其是 AI 标签重要合集交给列表用工作流状态标签驱动习惯如to-read、done配合规则引擎自动流转定期合并与清理用 merge 合并 AI 与人工的重复标签用deleteUnused清掉孤儿标签保持标签云干净善用搜索语法#tag、tag:带空格的标签、is:tagged组合出任意过滤视图必要时约束 AI 标签在设置中选择合适的标签风格或使用受控标签列表让 AI 只输出既定标签。标签体系的全部能力——从规范化命名、AI 来源标记到合并清理、规则联动——都有清晰的源码与测试支撑你可以放心地在自己的 Karakeep 实例上按上述方式组织内容。延伸阅读标签使用文档docs/versioned_docs/version-v0.30.0/04-using-karakeep/tags.md当前版本见 docs/docs/04-using-karakeep/tags.md搜索查询语法docs/docs/04-using-karakeep/search-query-language.md标签数据表定义packages/db/schema.ts标签 tRPC 路由packages/trpc/routers/tags.ts标签业务模型packages/trpc/models/tags.ts标签请求/响应类型packages/shared/types/tags.ts标签规范化与 AI 提示词工具packages/shared/utils/tag.ts标签路由测试用例packages/trpc/routers/tags.test.ts书签打标签接口packages/trpc/routers/bookmarks.ts规则引擎中的标签集成packages/trpc/routers/rules.ts【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考