# LLM Prompt 设计 本系统调用 LLM 完成三类任务,所有 prompt 要求 LLM 返回**严格 JSON**。 --- ## 1. 公共约束 - **模型**:用户在前端配置(base_url + api_key + model),如 `gpt-4o-mini`、`deepseek-chat`、`qwen-plus` 等 - **temperature**:默认 0.3(归类需要稳定,不要太高) - **max_tokens**:4000(聚合生成内容可能较长) - **响应格式**:所有 prompt 都要求输出 JSON,并在系统消息中强约束 - **错误处理**:解析失败时记录 error_msg,前端可重试 --- ## 2. Prompt A:文章归类 **触发**:录入新文章后或用户点击"重新归类"按钮 **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` 在表中按 `/` 分段查找;找不到父节点则忽略该项(前端展示警告) - 创建新 tag,加入 candidateTagIds - 调用 `setEntityTags("article", articleId, allTagIds)` --- ## 3. Prompt B:聚合判定 + 内容生成 **触发**: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 分两种情况: ### 3a. action = create(新建知识) **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) ### 3b. action = merge(并入已有) **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) --- ## 4. Diff 计算 聚合完成后,**后端**计算 diff(不依赖 LLM): ```java // 伪代码 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) - 增行绿色背景,删行红色背景,未变行灰色 --- ## 5. 错误与重试 | 场景 | 处理 | |---|---| | LLM 返回非 JSON | 后端正则提取 `{...}` 段重试一次;失败则记录 error_msg | | LLM 调用网络异常 | 重试 2 次(指数退避),仍失败则 article.status=error | | LLM 建议新增标签但父路径不存在 | 跳过该项,记录 warning,其他流程正常进行 | | 用户配置错误(base_url/api_key) | `/api/llm/config/test` 接口先测试连通性 |