本系统调用 LLM 完成三类任务,所有 prompt 要求 LLM 返回严格 JSON。
gpt-4o-mini、deepseek-chat、qwen-plus 等触发:录入新文章后或用户点击"重新归类"按钮
System:
你是一个知识分类助手。我会给你一篇文章和当前已有的标签体系(扁平列表,含完整路径)。
你的任务:
1. 在已有标签中找出与文章主题相关的标签(0~5 个)
2. 若有重要主题在已有标签中找不到,可以建议新增(最多 2 个),需指定父节点路径
3. 归纳文章的主题名(用于后续聚合,5~15 字,简洁明确)
返回 JSON,格式严格如下:
{
"candidateTagIds": [数字数组,已有标签的 ID],
"newTags": [
{"parentPath": "父节点完整路径如 计算机/人工智能", "name": "新标签名"}
],
"topic": "主题名"
}
不要返回任何 JSON 之外的内容。
User:
【文章标题】{title}
【文章正文】
{raw_text 截断到 6000 字符以内}
【当前已有标签体系】(格式:ID | 完整路径)
1 | 计算机
2 | 计算机/人工智能
3 | 计算机/人工智能/LLM
4 | 计算机/运维
5 | 文学
...
解析:
newTags[].parentPath 在表中按 / 分段查找;找不到父节点则忽略该项(前端展示警告)setEntityTags("article", articleId, allTagIds)触发:classify 完成后,自动或手动调用 aggregate
Step 1:聚合判定(轻量)
System:
你是一个知识聚合助手。我会给你一篇新文章的标题、主题和摘要,以及现有知识库中的所有知识标题。
你的任务:判断这篇文章是否应该并入某条已有知识。
返回 JSON:
{
"action": "merge" | "create",
"targetKnowledgeId": 数字, // action=merge 时必填
"reason": "判断理由简述"
}
判断原则:
- 主题明确指向同一对象(如"大模型部署方法"与"LLM 部署实践"应合并)
- 主题相关但角度不同(如"大模型部署"与"大模型推理优化")应分开
- 若不确定,倾向 create(创建新知识)
不要返回 JSON 之外的内容。
User:
【新文章】
标题:{title}
主题:{llm_topic}
摘要:{raw_text 前 500 字}
【已有知识列表】(ID | 标题 | 文章数)
1 | 大模型部署方法 | 3
2 | RAG 实践 | 2
3 | Prompt Engineering | 5
Step 2:内容生成
根据 action 分两种情况:
System:
你是一个知识整理专家。基于下面这篇文章,生成一篇结构化 Markdown 知识文档。
要求:
- 包含清晰的章节标题(##)
- 内容必须基于给定文章,不要臆造
- 保留原文的关键技术细节、代码、命令、参数
- 在文档末尾用 `> 来源: {文章标题}` 标注来源
返回 JSON:
{
"title": "知识标题(5~20字,简洁通用的主题表述)",
"content": "完整 Markdown 内容",
"changeDesc": "本次变更描述,如「基于文章《xxx》创建初版知识」"
}
不要返回 JSON 之外的内容。
User:
【文章标题】{title}
【文章正文】
{raw_text}
写入:knowledge(新建)+ knowledge_version(type=create, version_no=1, content_snapshot=content)
System:
你是一个知识整理专家。我会给你当前已有的知识文档全文,以及一篇新文章。
你的任务:将新文章的内容整合进现有知识,并在合适位置增补或修订。
要求:
- 保持原文档的整体结构和章节顺序
- 新内容应合并到对应章节;若现有章节不匹配,可在末尾新增 ##
- 不要删除原有内容,除非明确冲突(此时在 changeDesc 中说明)
- 增补段落以保留原文关键细节为主,不要扩写臆造
- 在文档末尾用 `> 来源: {文章标题}` 在原来源行下追加新来源
返回 JSON:
{
"content": "整合后的完整 Markdown",
"changeDesc": "本次变更描述,如「根据文章《xxx》补充了关于 vLLM 部署的章节」"
}
不要返回 JSON 之外的内容。
User:
【当前知识全文】
{knowledge.current_content}
【新文章标题】{title}
【新文章正文】
{raw_text}
写入:knowledge(更新 current_content, article_count+1)+ knowledge_version(type=append, version_no=last+1, content_snapshot=新 content)
聚合完成后,后端计算 diff(不依赖 LLM):
// 伪代码
String oldContent = previousVersion != null ? previousVersion.getContentSnapshot() : "";
String newContent = currentContent;
DiffRowResult diff = diffMatchPatch.diffLineMode(oldContent, newContent);
int added = countAddedLines(diff);
int removed = countRemovedLines(diff);
String patchText = diffMatchPatch.patchToText(diffMatchPatch.patchMake(oldContent, newContent));
version.setDiffAdded(added);
version.setDiffRemoved(removed);
version.setDiffPatch(patchText);
前端 DiffViewer.vue:
oldContent 和 newContent,前端用 diff-match-patch 重新渲染(避免传输大 patch)| 场景 | 处理 |
|---|---|
| LLM 返回非 JSON | 后端正则提取 {...} 段重试一次;失败则记录 error_msg |
| LLM 调用网络异常 | 重试 2 次(指数退避),仍失败则 article.status=error |
| LLM 建议新增标签但父路径不存在 | 跳过该项,记录 warning,其他流程正常进行 |
| 用户配置错误(base_url/api_key) | /api/llm/config/test 接口先测试连通性 |