为什么不能直接让模型自由分类
最简单的提示词通常是“请给这篇文章生成一个分类和几个标签”。这种方式适合演示,却不适合长期运行。模型可能今天返回“AI 工具”,明天返回“人工智能应用”,下周又返回“开发工具”。名称意思接近,但网站会因此生成多个分类页,稀释内部链接。
生产级自动分类至少要解决四件事:
- 分类只能从固定集合中选择;
- 输出必须能被程序解析和校验;
- 低把握结果不能直接发布;
- 每次运行需要保留模型、时间和原始结果。
const CATEGORIES = [
'AI 工具与应用',
'模型与研究',
'编程与架构',
'行业观察',
'产品与创业',
'教程与实践',
'开源项目',
'其他'
];
分类体系不应频繁变化。内容数量还不多时,少量明确的一级分类比大量空分类页更容易维护。
第一步:准备最小分类输入
不要把导航、广告、相关推荐和全部 HTML 都交给模型。分类通常只需要标题、摘要和清洗后的正文片段。
function buildArticleInput(article) {
return {
id: article.id,
title: String(article.title || '').trim(),
summary: String(article.summary || '').trim(),
excerpt: String(article.content || '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 4000)
};
}
4000 只是示例上限,应结合文章长度、模型上下文和实际账单调整。
第二步:要求模型返回受约束的 JSON
提示词应列出允许的分类,并要求返回主分类、候选分类、标签、置信度和简短理由。置信度只是模型对自身判断的描述,不等同于经过统计校准的正确率,只能作为分流信号。JSON 解析细节可阅读GLM 结构化 JSON 输出与解析。
function buildPrompt(article) {
return `你是中文技术内容编辑。
可选分类:${CATEGORIES.join('、')}
category 必须选择一个可选分类;tags 返回 2 到 4 个;
confidence 是 0 到 1 的数字;reason 不超过 50 个汉字;
只输出 JSON,不要输出 Markdown。
文章:${JSON.stringify(article)}`;
}
第三步:调用 OpenAI 兼容接口
API Key 必须从服务端配置读取,不要写进前端代码或提交到 Git。
async function classifyWithModel(article, config) {
const response = await fetch(config.baseUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${config.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: config.model,
temperature: 0,
messages: [{ role: 'user', content: buildPrompt(buildArticleInput(article)) }]
}),
signal: AbortSignal.timeout(30_000)
});
const rawText = await response.text();
if (!response.ok) {
throw new Error(`分类请求失败:HTTP ${response.status},${rawText.slice(0, 300)}`);
}
const payload = JSON.parse(rawText);
const content = payload.choices?.[0]?.message?.content;
if (!content) throw new Error('模型响应中没有正文');
return { parsed: JSON.parse(content), rawResponse: content, usage: payload.usage || null };
}
部分服务支持 JSON Schema 或 response_format,应以供应商当前官方文档为准。即使开启结构化输出,服务端仍需保留校验。
第四步:写入数据前严格校验
function validateClassification(result) {
const errors = [];
if (!CATEGORIES.includes(result.category)) errors.push('category 不在允许列表中');
if (!Array.isArray(result.tags) || result.tags.length < 2 || result.tags.length > 4) {
errors.push('tags 数量必须为 2 到 4 个');
}
if (typeof result.confidence !== 'number' || result.confidence < 0 || result.confidence > 1) {
errors.push('confidence 必须为 0 到 1 之间的数字');
}
return { ok: errors.length === 0, errors };
}
还可以增加标签长度、兜底分类、新标签审批和历史结果保留规则。
第五步:设置人工复核队列
function decideReview(result, validation) {
if (!validation.ok) return { status: 'rejected', reason: validation.errors.join(';') };
if (result.category === '其他') return { status: 'needs_review', reason: '兜底分类' };
if (result.confidence < 0.75) return { status: 'needs_review', reason: '低于示例阈值' };
return { status: 'auto_approved', reason: '通过格式和阈值检查' };
}
0.75 只是演示值,不是通用最佳阈值。应使用人工标注文章观察错误,再决定自动通过条件。建议保存文章 ID、模型、供应商、提示词版本、执行时间、解析结果、原始响应和 usage。
第六步:避免覆盖人工修改
后台应区分“AI 建议分类”和“当前正式分类”。编辑确认后设置 classificationLocked,重新运行任务时跳过锁定文章。批处理可沿用抓取、总结和失败重试拆分的管线设计,并按需加入多模型轮询与故障切换。
如何进行可核验的分类评估
不要凭感觉写“准确率很高”。更可靠的方法是:
- 从不同栏目抽取真实文章;
- 隐藏原分类,由人工重新标注;
- 固定模型名、提示词版本和测试时间;
- 比较模型主分类与人工分类;
- 分别统计自动通过、进入复核和格式失败;
- 保存原始结果,而不只保留汇总数字。
完成真实测试以前,不应发布推测出来的准确率、成本或性能数字。
上线前检查清单
- 分类名称来自固定白名单;
- API Key 只保存在服务端;
- 输出经过 JSON 和业务规则双重校验;
- 人工确认的分类不会被覆盖;
- 失败记录可查看并可单独重跑;
- 保存模型名、提示词版本和执行时间;
- 内容不足的分类页不应批量开放索引。
FAQ
为什么要求 JSON 后还需要再次校验?
提示词不是强制协议。模型、代理或网络异常都可能返回缺字段、代码块甚至错误页面。服务端校验是写入前最后一道保护。
置信度低于多少必须人工审核?
没有通用数字。应根据人工标注样本和可接受的错误成本确定。
可以让模型自由创建新分类吗?
不建议直接创建正式分类。可以把建议保存到候选区,由编辑合并、改名或拒绝。
更换模型后需要重新分类全部文章吗?
不一定。先用固定样本比较新旧模型,只重跑未锁定、低置信度或需要复审的文章。
参考资料
实现前应核对所用模型供应商的官方 API 文档、JSON Schema 官方文档、Node.js 官方文档及 Google Search Central 的站点结构指南。本站的测试与复审原则见作者与内容原则。