Karakeep 模型对比工具(compare-models):基于真实书签数据的 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
本文围绕 Karakeep 仓库中 tools/compare-models/README.md 所描述的独立 CLI 工具展开。该工具用你实例中真实存在的书签数据,以「盲测 + 随机乱序」的方式对比不同 AI 模型的自动打标签质量,是你在更换/升级打标签模型、做 AI 标签质量验收时的实用评估方案。读完本文,你将掌握两种对比模式(模型 vs 模型、模型 vs 现有 AI 标签)的完整配置、运行与投票交互流程,并理解其背后复用的 Karakeep 共享推理与提示词基础设施。
工具定位:为什么需要对比模型打标签效果
Karakeep 的核心能力之一是「AI 自动打标签」:系统会把书签内容交给配置好的大模型,生成一组规范化标签。当你想从gpt-4o-mini换到claude-3-5-sonnet,或想验证某个新模型是否比现有模型更优秀时,单纯跑一两次推理很难得出可靠结论——标签质量带有较强主观性,且不同模型对同一内容的标签风格差异很大。
compare-models工具正是为此设计:它从你的 Karakeep 实例拉取真实书签,对每个书签分别让两个候选方(两个模型,或一个模型 vs 现有 AI 标签)生成标签,然后由你人工投票选出更优的一方。整个过程是盲测的——投票界面只显示 "Model A" / "Model B",模型真实名称直到最终结果才揭晓;同时每个书签的左右位置会随机打乱,消除位置偏见。正如 README 所强调的,这是一个「human-in-the-loop」(人在回路)的手动评估工具,结果只输出到控制台、不做持久化,适合作为模型选型与质量验收的决策依据。
前置条件与整体架构
你需要准备什么
- 一个可访问的 Karakeep 实例(本机或远程),并拥有 API Key;
- 实例中已有若干链接型(link)书签;在「模型 vs 现有」模式下还需这些书签已带 AI 生成的标签;
- 一个兼容 OpenAI 接口的推理服务:可以是 OpenAI 官方,也可以是 OpenRouter 等聚合服务(工具通过
OPENAI_BASE_URL切换,见下文); - 支持
pnpm的 Node.js 环境(推荐使用 pnpm workspace 运行)。
从源码看工具的模块划分
工具位于 tools/compare-models/src,源码结构清晰地对应了 README 描述的功能:
- index.ts:主流程入口,负责模式判断、取数、逐条推理、投票统计与最终结果展示;
- config.ts:用 zod 对全部环境变量做校验与默认值处理;
- apiClient.ts:基于
@karakeep/sdk封装 Karakeep API 客户端,负责分页拉取书签; - bookmarkProcessor.ts:把书签内容拼装成提示词输入;
- inferenceClient.ts:创建 OpenAI 兼容推理客户端并解析结构化标签输出;
- interactive.ts:终端交互(投票提问、盲测展示、进度与最终结果渲染);
- types.ts:
Bookmark、ComparisonResult、FinalResults等核心类型定义。
该工具本身不重复造轮子,而是直接复用 Karakeep 主应用的共享基础设施:API 走@karakeep/sdk(类型安全、自带鉴权),推理走@karakeep/shared/inference的 OpenAI 客户端(支持结构化输出),提示词走@karakeep/shared/prompts的统一标签提示词构建器(含 token 管理)。这一点在 package.json 的依赖声明中可以得到印证:@karakeep/sdk、@karakeep/shared均为 workspace 依赖,另有chalk(终端着色)与zod(校验/解析)。
环境变量与配置详解
工具的全部配置都通过环境变量注入,并在 config.ts 中由 zod schema 统一校验。README 给出的必填项如下:
# Karakeep API 配置 KARAKEEP_API_KEY=your_api_key_here KARAKEEP_SERVER_ADDR=https://your-karakeep-instance.com # 对比模式(默认: model-vs-model) # - "model-vs-model": 两个模型互相比较 # - "model-vs-existing": 新模型 vs 现有 AI 标签 COMPARISON_MODE=model-vs-model # 待比较的模型 # MODEL1_NAME: 要测试的新模型(始终必填) # MODEL2_NAME: 第二个参与对比的模型(仅 model-vs-model 模式必填) MODEL1_NAME=gpt-4o-mini MODEL2_NAME=claude-3-5-sonnet # OpenAI/OpenRouter API 配置(用于推理) OPENAI_API_KEY=your_openai_or_openrouter_key OPENAI_BASE_URL=https://openrouter.ai/api/v1 # 可选,默认直连 OpenAI # 可选:测试书签数量(默认: 10) COMPARE_LIMIT=10源码中隐藏的可选进阶参数
对照 config.ts,除 README 列出的变量外,源码还支持以下几组进阶推理参数(README 未展开,但实战中很有用):
| 环境变量 | 类型/取值范围 | 默认值 | 作用 |
|---|---|---|---|
COMPARISON_MODE | model-vs-model/model-vs-existing | model-vs-model | 对比模式 |
COMPARE_LIMIT | 整数 | 10 | 参与测试的书签数量 |
INFERENCE_CONTEXT_LENGTH | 整数 | 8000 | 推理上下文长度,控制送入模型的 token 上限 |
INFERENCE_MAX_OUTPUT_TOKENS | 整数 | 2048 | 模型最大输出 token 数 |
INFERENCE_USE_MAX_COMPLETION_TOKENS | true/false | false | 是否使用最大补全 token 上限 |
INFERENCE_REASONING_EFFORT | low/medium/high/none/xhigh | 未设置 | 推理强度,适配支持 reasoning effort 的模型 |
OPENAI_SERVICE_TIER | auto/default/flex | 未设置 | OpenAI 服务等级 |
需要说明的是:config.ts用z.string().min(1)校验KARAKEEP_API_KEY、MODEL1_NAME、OPENAI_API_KEY,用z.string().url()校验KARAKEEP_SERVER_ADDR与OPENAI_BASE_URL,因此缺失或格式错误的环境变量会让工具启动即报错退出——这也是 README「错误处理」一节中「缺失必填环境变量会以明确错误信息退出」的源码依据。若配置非法,zod 抛出的校验错误会被 index.ts 末尾的main().catch()捕获并以红色✗ Fatal error打印。
直连 OpenAI 与使用 OpenRouter
工具通过OPENAI_BASE_URL决定推理端点,README 给出了两种典型配置:
使用 OpenRouter(可在一个 Key 下访问多个模型):
OPENAI_BASE_URL=https://openrouter.ai/api/v1 OPENAI_API_KEY=your_openrouter_key直连 OpenAI:
OPENAI_API_KEY=your_openai_key # OPENAI_BASE_URL 省略即可直连 OpenAI从 inferenceClient.ts 的源码可以看到,createInferenceClient会把上述变量连同textModel、imageModel(此处均设为待比较的模型名)、contextLength、maxOutputTokens、outputSchema: "structured"等一起传给共享的OpenAIInferenceClient。这也意味着:只要是 OpenAI 兼容协议的服务(OpenRouter、各类自建网关等),都可以通过OPENAI_BASE_URL接入。
安装与三种运行方式
方式一:pnpm 运行(推荐)
README 推荐的开发运行方式,利用tsx直接执行 TypeScript 源码,并自动加载.env文件:
cd tools/compare-models pnpm install pnpm runpnpm run实际执行的是 package.json 中的脚本tsx --env-file=./.env src/index.ts——注意它要求当前目录下存在.env文件。
方式二:使用 .env 文件
先创建.env:
KARAKEEP_API_KEY=your_api_key KARAKEEP_SERVER_ADDR=https://your-karakeep-instance.com MODEL1_NAME=gpt-4o-mini MODEL2_NAME=claude-3-5-sonnet OPENAI_API_KEY=your_openai_key COMPARE_LIMIT=10然后同样执行:
pnpm run方式三:编译后直接用 node 运行
先构建出dist/index.js,再用环境变量注入方式运行:
pnpm build export KARAKEEP_API_KEY=your_api_key export KARAKEEP_SERVER_ADDR=https://your-karakeep-instance.com export MODEL1_NAME=gpt-4o-mini export MODEL2_NAME=claude-3-5-sonnet export OPENAI_API_KEY=your_openai_key node dist/index.jspnpm build对应脚本为tsc && chmod +x dist/index.js,构建产物位于dist/index.js;同时 package.json 声明了bin: { "compare-models": "dist/index.js" },即构建后可获得compare-models命令行入口。README 特别提示:推荐pnpm run(走 tsx 开发模式)以获得最佳体验。
两种对比模式与使用流程
Model vs Model:两个模型互相对比
COMPARISON_MODE=model-vs-model MODEL1_NAME=gpt-4o-mini MODEL2_NAME=claude-3-5-sonnet该模式下,每个书签会让两个模型各自独立推理生成标签,然后由你投票选择哪一组的标签更好。源码 index.ts 在启动时会校验MODEL2_NAME是否提供,缺失则直接红色报错并以退出码 1 终止。
Model vs Existing:新模型 vs 现有 AI 标签
COMPARISON_MODE=model-vs-existing MODEL1_NAME=gpt-4o-mini # 该模式不需要 MODEL2_NAME此模式不再让第二个模型跑推理,而是把书签上已经存在的 AI 标签作为对手。它适合三种典型场景:
- 测试新模型是否比当前模型产出更好的标签;
- 评估是否值得从当前模型切换到另一个模型;
- 对现有 AI 标签做质量抽检(QA)。
关键过滤规则:该模式只对比已带 AI 标签的书签。README 明确说明只保留attachedBy: "ai"的标签;源码 index.ts 中对应实现为bookmarks.filter((b) => b.tags.some((t) => t.attachedBy === "ai"))。attachedBy字段的取值在数据库层被约束为"ai" | "human",见 packages/db/schema.ts。若过滤后没有符合条件的书签,工具会打印黄色提示并直接结束。
完整使用流程(结合源码)
拉取书签:
KarakeepAPIClient.fetchBookmarks调用 SDK 的GET /bookmarks,以 50 条一批分页拉取,携带includeContent: true与archived: false参数,并在客户端侧过滤出content.type === "link"的书签,最终截取前COMPARE_LIMIT条。这一点对应 README「Bookmark Filtering」中「只测链接型书签、非归档、最新 N 条」的说明(apiClient.ts)。逐条推理:对每个书签,先由
extractBookmarkContent把 URL、标题、描述、HTML 内容拼接为文本,再调用共享的buildTextPrompt构建打标签提示词(空自定义提示词、标签风格为as-generated,即保留模型原始生成结果),见 bookmarkProcessor.ts。随后用 zod 定义的{ tags: string[] }结构约束模型输出,并解析为标签数组,见 inferenceClient.ts。推理按顺序逐条执行,保证状态管理简单(README 已注明)。随机乱序 + 盲测展示:源码用
Math.random() < 0.5决定模型 1 还是模型 2 显示在 "Model A" 位置,投票界面只显示 "Model A" / "Model B"(displayComparison的blind参数为true),模型真名在投票阶段完全隐藏。人工投票:界面形如 README 给出的示例:
=== Bookmark 1/10 === How to Build Better AI Systems https://example.com/article This article explores modern approaches to... ───────────────────────────────────── Model A (blind): • ai • machine-learning • engineering Model B (blind): • artificial-intelligence • ML • software-development ───────────────────────────────────── Which tags do you prefer? [1=Model A, 2=Model B, s=skip, q=quit] >合法输入与对应行为(interactive.ts 的
askQuestion+ index.ts 的计票逻辑):1:投给 Model A(计票时按乱序结果映射回真实模型);2:投给 Model B;s/skip:跳过本次对比(计入 Skipped);q/quit:提前退出并展示当前累计结果。
展示最终结果:全部完成或提前退出后,控制台输出投票汇总与胜者:
─────────────────────────────────────── === FINAL RESULTS === ─────────────────────────────────────── gpt-4o-mini: 6 votes claude-3-5-sonnet: 3 votes Skipped: 1 Errors: 0 ─────────────────────────────────────── Total bookmarks tested: 10 🏆 WINNER: gpt-4o-mini ───────────────────────────────────────从 interactive.ts 的
displayFinalResults源码可见,票数相同会显示🏁 RESULT: TIE,否则显示🏆 WINNER: <模型名>。只有在这个最终结果里,真实模型名才会揭晓。
错误处理
- 任一模型在某书签上推理失败时,会打印红色错误信息(
✗ Error: ...),该次对比计入Errors,流程继续(index.ts 中的try/catch+counters.errors++); - 错误数在最终结果中单独一行展示(
Errors: N); - 必填环境变量缺失/非法时,工具以清晰的错误信息退出(见上文配置校验部分)。
书签过滤规则的实现细节
README 明确当前工具只测试以下书签,这些规则都可以在源码中找到对应实现:
- 仅链接型书签:
fetchBookmarks中用b.content?.type === "link"过滤,不含文本笔记(text notes)与资产(assets)类书签; - 非归档:请求参数固定携带
archived: false; - 最新 N 条:分页拉取后
slice(0, limit),limit即COMPARE_LIMIT; - model-vs-existing 模式附加条件:仅保留含
attachedBy: "ai"标签的书签,且该模式下模型 2 的「标签」直接取自书签上现有的 AI 标签(bookmark.tags.filter((t) => t.attachedBy === "ai").map((t) => t.name)),不再触发推理。
从源码看架构复用与设计取舍
README「Architecture」一节总结了工具对 Karakeep 共享基础设施的复用,结合源码可以看得更具体:
- API 客户端:
apiClient.ts使用createKarakeepClient(来自@karakeep/sdk),以Bearer ${KARAKEEP_API_KEY}鉴权,baseUrl 为${KARAKEEP_SERVER_ADDR}/api/v1/,请求GET /bookmarks并支持 cursor 分页——与 Karakeep 主应用使用同一套类型安全 SDK。 - 推理:
inferenceClient.ts复用@karakeep/shared/inference的OpenAIInferenceClient,配置outputSchema: "structured",配合 zod schema 获得结构化、可解析的标签输出。 - 提示词:
bookmarkProcessor.ts复用@karakeep/shared/prompts.server的buildTextPrompt(带contextLengthtoken 管理),并以as-generated保留模型原始标签风格,保证对比的是模型真实能力而非被提示词改写后的结果。
这种「零代码重复、全量复用主应用核心逻辑」的设计,保证了对比结论与 Karakeep 实际打标签路径的一致性——你在工具里测出的模型差异,就是将来接入主应用后的真实差异。
注意事项与使用建议
- 评估方式:工具面向人工、人在回路的评估,结果只打印在控制台,不持久化、不写回 Karakeep(README「Notes」已明确);
- 内容拉取:书签以
includeContent=true拉取,因此评估依据的是真实抓取内容; - 推理顺序:推理串行执行,模型数量与书签数量较多时耗时较长,建议先用较小的
COMPARE_LIMIT(如默认 10)试跑; - 样本选择:测试结果受所选书签样本影响较大,若要评估模型切换,建议覆盖不同类型、不同领域的书签,并可多次运行观察一致性;
- 现有 AI 标签的前提:使用
model-vs-existing前,请确认实例中确有 AI 生成的标签(无则工具会提示 No bookmarks found with AI tags 并退出)。
构建与产物
如需生成独立可执行产物:
pnpm build构建产物为dist/index.js(可执行位已在构建脚本中设置),配合 package.json 中的bin声明,可通过compare-models命令直接调用。产物同样依赖上述环境变量,运行方式与「方式三」一致。
小结
compare-models是 Karakeep 生态中一个轻量而严谨的模型评估工具:以真实书签为样本、以共享推理管线为引擎、以盲测与随机乱序消除主观偏差、以人工投票作为最终裁决。无论你是想换一个更便宜的模型、评估开源模型能否替代商业模型,还是例行抽检已有 AI 标签质量,都可以用 tools/compare-models/README.md 与本文提供的源码级细节,在几分钟内搭建起一次可复现的对比实验。
【免费下载链接】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),仅供参考