feat: 完善来源标注徽章功能和配置优化
后端改进: - 改进 addCitationsToResponse 函数,更激进地添加来源标注 - 支持识别所有有实质内容的行(长度>5) - 优化特殊行的识别逻辑,保留'依据引用'、'分析'、'建议'等内容行 - 修复 go.mod 中 lib/pq 的依赖问题 - 优化字符串拼接效率(使用 fmt.Fprintf) - 添加 Scanner 错误检查 前端改进: - 优化徽章样式,增大尺寸和间距 - 改用 bg-blue-100 和 bg-orange-100 背景 - 添加 hover 效果和 cursor-pointer 配置优化: - LLM 服务地址改为 IP(192.168.1.6:18888),支持外部调用 - 本地服务保持 localhost,性能更优 测试: - 新增 6 个单元测试用例,全部通过 - 验证清单项、列表项、陈述句等各种格式的标注 文档: - 添加实现方案文档 - 添加用户使用指南
This commit is contained in:
@@ -279,18 +279,24 @@ func (h *LLMChatHandler) enhanceCitations(response string, hasKnowledge bool, kn
|
||||
hasKBCitation := strings.Contains(response, "[[知识库:")
|
||||
hasAICitation := strings.Contains(response, "[[AI建议]]")
|
||||
|
||||
// 如果已经有完整标注,直接返回
|
||||
if hasKBCitation && hasAICitation {
|
||||
return response
|
||||
}
|
||||
|
||||
var result string
|
||||
// 如果完全没有标注,进行智能补充
|
||||
if !hasKBCitation && !hasAICitation {
|
||||
return h.addCitationsToResponse(response, hasKnowledge, knowledgeSources)
|
||||
result = h.addCitationsToResponse(response, hasKnowledge, knowledgeSources)
|
||||
} else if hasKBCitation && hasAICitation {
|
||||
// 如果已经有完整标注,检查是否需要添加来源说明块
|
||||
result = response
|
||||
} else {
|
||||
// 部分标注的情况:检查是否需要补充
|
||||
result = h.fillMissingCitations(response, hasKnowledge, knowledgeSources)
|
||||
}
|
||||
|
||||
// 部分标注的情况:检查是否需要补充
|
||||
return h.fillMissingCitations(response, hasKnowledge, knowledgeSources)
|
||||
// 确保末尾有来源说明块(如果还没有)
|
||||
if !strings.Contains(result, "**来源说明**") && !strings.Contains(result, "> **来源说明**") {
|
||||
result += h.generateSourceSummary(hasKnowledge, knowledgeSources)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// addCitationsToResponse 为完全没有标注的回答添加来源标注
|
||||
@@ -330,31 +336,48 @@ func (h *LLMChatHandler) addCitationsToResponse(response string, hasKnowledge bo
|
||||
continue
|
||||
}
|
||||
|
||||
// 跳过空行、标题行、列表标记行
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
||||
// 跳过空行
|
||||
if trimmed == "" {
|
||||
enhanced = append(enhanced, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// 跳过纯列表标记行(只有 - * 1. 等)
|
||||
if len(trimmed) <= 3 && (strings.HasPrefix(trimmed, "-") || strings.HasPrefix(trimmed, "*") || strings.HasPrefix(trimmed, "1.")) {
|
||||
// 跳过标题行(仅一级和二级标题)
|
||||
if strings.HasPrefix(trimmed, "# ") || strings.HasPrefix(trimmed, "## ") {
|
||||
enhanced = append(enhanced, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否是陈述句(以句号、问号、感叹号结尾)
|
||||
needsCitation := strings.HasSuffix(trimmed, "。") || strings.HasSuffix(trimmed, ".") ||
|
||||
// 跳过某些特殊行(如"来源说明"、"免责声明"等)
|
||||
// 但保留"依据引用"、"分析"、"建议"等内容行
|
||||
if (strings.Contains(trimmed, "来源说明") || strings.Contains(trimmed, "免责声明")) &&
|
||||
!strings.Contains(trimmed, "依据") && !strings.Contains(trimmed, "分析") &&
|
||||
!strings.Contains(trimmed, "建议") {
|
||||
enhanced = append(enhanced, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// 跳过已有标注的"知识库引用"和"AI建议"标题行
|
||||
if (strings.HasPrefix(trimmed, "知识库引用") || strings.HasPrefix(trimmed, "AI建议")) &&
|
||||
!strings.Contains(trimmed, "【") {
|
||||
enhanced = append(enhanced, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// 对列表项内容也进行标注(包括 - * 和数字列表)
|
||||
isListItem := strings.HasPrefix(trimmed, "-") || strings.HasPrefix(trimmed, "*") ||
|
||||
(len(trimmed) > 2 && trimmed[0] >= '0' && trimmed[0] <= '9' && trimmed[1] == '.')
|
||||
|
||||
// 检查是否需要标注:
|
||||
// 1. 以句号、问号、感叹号结尾的陈述句
|
||||
// 2. 以问号结尾的问题(包括清单中的"是否"问题)
|
||||
// 3. 列表项(无论是否以标点符号结尾)
|
||||
// 4. 其他有实质内容的行(长度>5且不是特殊标记)
|
||||
needsCitation := (strings.HasSuffix(trimmed, "。") || strings.HasSuffix(trimmed, ".") ||
|
||||
strings.HasSuffix(trimmed, "!") || strings.HasSuffix(trimmed, "!") ||
|
||||
strings.HasSuffix(trimmed, "?") || strings.HasSuffix(trimmed, "?")
|
||||
|
||||
// 对列表项内容也进行标注
|
||||
if strings.HasPrefix(trimmed, "-") || strings.HasPrefix(trimmed, "*") ||
|
||||
(len(trimmed) > 2 && trimmed[0] >= '0' && trimmed[0] <= '9' && trimmed[1] == '.') {
|
||||
// 检查列表项是否以句子结束
|
||||
if strings.HasSuffix(trimmed, "。") || strings.HasSuffix(trimmed, ".") {
|
||||
needsCitation = true
|
||||
}
|
||||
}
|
||||
strings.HasSuffix(trimmed, "?") || strings.HasSuffix(trimmed, "?") ||
|
||||
isListItem) ||
|
||||
(len(trimmed) > 5 && !strings.HasPrefix(trimmed, "【") && !strings.HasPrefix(trimmed, "---"))
|
||||
|
||||
if needsCitation {
|
||||
// 决定使用哪种标注
|
||||
@@ -362,8 +385,9 @@ func (h *LLMChatHandler) addCitationsToResponse(response string, hasKnowledge bo
|
||||
if hasKnowledge && len(knowledgeSources) > 0 {
|
||||
// 如果内容较长且像是说明性内容,用AI建议
|
||||
// 如果内容较短且像是事实陈述,用知识库
|
||||
if len(trimmed) > 100 || strings.Contains(trimmed, "建议") ||
|
||||
strings.Contains(trimmed, "注意") || strings.Contains(trimmed, "可以") {
|
||||
if len(trimmed) > 100 || strings.Contains(trimmed, "建议") ||
|
||||
strings.Contains(trimmed, "注意") || strings.Contains(trimmed, "可以") ||
|
||||
strings.Contains(trimmed, "分析") || strings.Contains(trimmed, "风险") {
|
||||
citation = " [[AI建议]]"
|
||||
} else {
|
||||
citation = " [[知识库:" + knowledgeSources[0] + "]]"
|
||||
@@ -381,14 +405,7 @@ func (h *LLMChatHandler) addCitationsToResponse(response string, hasKnowledge bo
|
||||
}
|
||||
}
|
||||
|
||||
result := strings.Join(enhanced, "\n")
|
||||
|
||||
// 如果末尾没有来源说明块,添加一个
|
||||
if !strings.Contains(result, "**来源说明**") && !strings.Contains(result, "> **来源说明**") {
|
||||
result += h.generateSourceSummary(hasKnowledge, knowledgeSources)
|
||||
}
|
||||
|
||||
return result
|
||||
return strings.Join(enhanced, "\n")
|
||||
}
|
||||
|
||||
// fillMissingCitations 为部分标注的回答补充缺失的标注
|
||||
@@ -431,7 +448,7 @@ func (h *LLMChatHandler) addFirstKBCitation(response string, source string) stri
|
||||
func (h *LLMChatHandler) addAICitationToSuggestions(response string) string {
|
||||
keywords := []string{"建议", "推荐", "可以", "应该", "注意", "提示", "提醒"}
|
||||
lines := strings.Split(response, "\n")
|
||||
|
||||
|
||||
for i, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
// 检查是否包含建议性关键词
|
||||
@@ -444,7 +461,7 @@ func (h *LLMChatHandler) addAICitationToSuggestions(response string) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
@@ -453,11 +470,11 @@ func (h *LLMChatHandler) generateSourceSummary(hasKnowledge bool, knowledgeSourc
|
||||
var summary strings.Builder
|
||||
summary.WriteString("\n\n---\n\n")
|
||||
summary.WriteString("> **来源说明**\n>\n")
|
||||
|
||||
|
||||
if hasKnowledge && len(knowledgeSources) > 0 {
|
||||
summary.WriteString("> **知识库引用:**\n")
|
||||
for _, source := range knowledgeSources {
|
||||
summary.WriteString("> - 【" + source + "】\n")
|
||||
fmt.Fprintf(&summary, "> - 【%s】\n", source)
|
||||
}
|
||||
summary.WriteString(">\n")
|
||||
summary.WriteString("> **AI建议:**\n")
|
||||
@@ -466,7 +483,7 @@ func (h *LLMChatHandler) generateSourceSummary(hasKnowledge bool, knowledgeSourc
|
||||
summary.WriteString("> **AI建议:**\n")
|
||||
summary.WriteString("> - 以上内容为AI建议,仅供参考\n")
|
||||
}
|
||||
|
||||
|
||||
return summary.String()
|
||||
}
|
||||
|
||||
@@ -678,7 +695,7 @@ func (h *LLMChatHandler) buildMessages(systemPrompt, knowledgeContext string, ha
|
||||
|
||||
### ⚠️ 强制要求(不允许任何例外)
|
||||
|
||||
你的回答中**每一句事实陈述、每一个观点**都必须在句子末尾标注来源徽章:
|
||||
你的回答中**每一句话、每一个观点、每一个列表项**都必须在句子末尾标注来源徽章。这是最高优先级要求,必须100%执行,不允许遗漏。
|
||||
|
||||
**格式1:知识库引用(蓝色徽章)**
|
||||
在引用知识库内容的句子末尾加:[[知识库:文献名称]]
|
||||
@@ -745,6 +762,11 @@ func (h *LLMChatHandler) buildMessages(systemPrompt, knowledgeContext string, ha
|
||||
- [ ] 末尾有完整的来源汇总块
|
||||
- [ ] 来源汇总中列出了知识库原文摘录
|
||||
|
||||
### 🚨 关键提醒
|
||||
- 如果你的回答中有任何句子、列表项、问题没有标注来源,系统会自动补充标注
|
||||
- 为了避免系统自动补充,你必须在生成回答时就添加所有标注
|
||||
- 特别是对于清单、问卷、检查表等列表格式的内容,每一项都必须标注
|
||||
|
||||
`
|
||||
if knowledgeContext != "" {
|
||||
finalSystem += "### 📚 知识库检索结果\n\n以下是从知识库中检索到的相关文献,请优先基于这些内容回答,并在每个引用处标注 [[知识库:文献名称]]:\n\n" + knowledgeContext
|
||||
|
||||
Reference in New Issue
Block a user