From eaabd6056e3f231f7427c49abd7bc9971f0edda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Thu, 13 Aug 2026 09:23:16 +0800 Subject: [PATCH] 1 --- data/chat.db | Bin 77824 -> 77824 bytes kb/consts/consts.go | 2 +- kb/service/chat_service.go | 28 ++++++++++++++++++++++++++-- 技术设计.md | 4 +++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/data/chat.db b/data/chat.db index ff4fc75cbb0da60990a9c916a51de32300d9ceb1..0b540fa721c39d54490a4fbca11c0877074c4e2d 100644 GIT binary patch delta 13913 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 delta 8937 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 Cth{9a diff --git a/kb/consts/consts.go b/kb/consts/consts.go index 8fced38..f2fd307 100644 --- a/kb/consts/consts.go +++ b/kb/consts/consts.go @@ -10,7 +10,7 @@ const ( RerankTopK = 10 // 喂给 LLM 重排器的候选数(RRF 融合后截取;数据集可覆盖) RerankKeepRatio = 0.5 // 重排分低于最高分该比例的条目剔除(门槛作用在语义分上,RRF 分因区间过窄无区分度) - RerankMinScore = 6 // 重排分绝对下限:低于该分的条目直接剔除(重排器对泛化条款会给宽松低分,需绝对下限兜底) + RerankMinScore = 7 // 重排分绝对下限:低于该分的条目直接剔除(重排器对泛化条款会给宽松低分,需绝对下限兜底) RerankMaxChars = 300 // 重排候选段截断字数(16GB 机器 prompt 预算护栏,见 MaxRerankTopK 注释) RerankMaxTokens = 1024 // 重排输出 token 上限:打分 JSON 实际 <100 token,余量防思考链失控长文(Qwen3.5 思考文本会混入 content) diff --git a/kb/service/chat_service.go b/kb/service/chat_service.go index cd650f8..8e1d12e 100644 --- a/kb/service/chat_service.go +++ b/kb/service/chat_service.go @@ -591,7 +591,9 @@ func (r *HybridRetriever) ftsRetrieve(ctx context.Context, query string) []retri // 任何失败(调用/解析/空结果)返回错误,由调用方回退 RRF 排序 func (r *HybridRetriever) rerankByLLM(ctx context.Context, query string, items []scoredChunk) (map[int64]float64, error) { var sb strings.Builder - sb.WriteString("你是检索重排器。请评估每个候选段落与用户问题的相关性,为每个候选输出 0-10 的相关性分数(10=高度相关,0=完全不相关)。\n\n用户问题:\n") + sb.WriteString("你是检索重排器。请评估每个候选段落与用户问题的相关性,为每个候选输出 0-10 的相关性分数(10=高度相关,0=完全不相关)。") + sb.WriteString("严格规则:仅当段落直接回答问题的具体情境(如涉及偷窃、归还、退赃等具体事实)时才给高分(7-10);") + sb.WriteString("段落只是泛化提及相关概念(如犯罪、处罚、共同犯罪等一般规定)而未涉及问题具体情境时,必须给低分(0-3)。\n\n用户问题:\n") sb.WriteString(query) sb.WriteString("\n\n候选段落:\n") for i, it := range items { @@ -788,6 +790,27 @@ func (s *chatService) askAgent(ctx context.Context, model *OpenAIChatModel, data var allDocs []*schema.Document var allTriples []string + // 首轮预检索:不依赖模型自觉调用 search——9B 模型思考链关闭后偶发跳过工具调用直接凭自身知识作答, + // 导致检索不执行、引用列表为空、回答 [N] 为模型自编。强制注入首轮结果,模型仍可后续补充检索。 + if docs, triples, err := s.executeSearchTool(ctx, datasetId, question); err != nil { + g.Log().Warningf(ctx, "pre-retrieve failed, fallback to agent search tool: %v", err) + } else if len(docs) > 0 { + allDocs = append(allDocs, docs...) + allTriples = append(allTriples, triples...) + var sb strings.Builder + sb.WriteString("已为你检索到以下资料,回答时请优先基于这些资料,并在引用处标注 [N](N 为该句依据的资料条数);若资料不足,可继续调用 search 工具补充检索:\n\n") + for i, c := range buildCitations(docs, question) { + sb.WriteString(fmt.Sprintf("[%d] %s\n", i+1, c.Content)) + } + if len(triples) > 0 { + sb.WriteString("\n【知识图谱】以下为与问题相关的实体关系,可辅助回答关系类问题:\n") + for _, t := range triples { + sb.WriteString(t + "\n") + } + } + msgs = append(msgs, &schema.Message{Role: schema.User, Content: sb.String()}) + } + loopRound := 0 for { loopRound++ @@ -928,7 +951,7 @@ func (s *chatService) executeSearchTool(ctx context.Context, datasetId int64, qu return out.docs, triples, nil } -// aggregateCitations 多轮工具结果聚合引用:按 chunk_id 去重后重排编号 +// aggregateCitations 多轮工具结果聚合引用:按 chunk_id 去重后按重排分降序排序再编号 func aggregateCitations(docs []*schema.Document, question string) []domain.Citation { seen := map[int64]bool{} out := make([]domain.Citation, 0, len(docs)) @@ -939,6 +962,7 @@ func aggregateCitations(docs []*schema.Document, question string) []domain.Citat seen[c.ChunkId] = true out = append(out, c) } + sort.Slice(out, func(i, j int) bool { return out[i].Score > out[j].Score }) for i := range out { out[i].Index = i + 1 } diff --git a/技术设计.md b/技术设计.md index 9506b1c..6673443 100644 --- a/技术设计.md +++ b/技术设计.md @@ -159,7 +159,7 @@ func (h *HybridRetriever) Retrieve(ctx context.Context, query string, opts ...re 1. **向量检索**:query → Embedding → `vec0` KNN(L2 距离)预取 `topK*4` 候选,再单表查 `kb_chunk` 按 dataset_id 过滤(单表约束:内存组装,候选已按距离排序故过滤后取前 topK 等价原 JOIN),取 20 2. **关键词检索**:query → `TokenizeQuery` 分词(引号词组 OR 语义、过滤 <2 rune 与 FTS 特殊字符)→ FTS5 MATCH(bm25 负分升序)取 20 3. **RRF 融合**:`score = Σ 1/(60 + rank)` 全局融合,截 `RerankTopK=10` -4. **LLM 重排**:`rerankByLLM` 一次调用为 10 个候选打 0-10 分(候选截 `RerankMaxChars=300` 字),**门槛 = max(最高分×`RerankKeepRatio=0.5`, `RerankMinScore=6`)**,低于门槛剔除;重排模型调用**关闭思考链**(`chat_template_kwargs: {"enable_thinking": false}`)+ 输出上限 `RerankMaxTokens=1024`——Qwen3.5 思考链默认开启且思考文本直接混入 content,无上限时输出千级 token"Thinking Process"长文(~5 tok/s 下拖至数分钟超时,2026-08-12 实测挂起 7 分钟即此因) +4. **LLM 重排**:`rerankByLLM` 一次调用为 10 个候选打 0-10 分(候选截 `RerankMaxChars=300` 字),**门槛 = max(最高分×`RerankKeepRatio=0.5`, `RerankMinScore=7`)**,低于门槛剔除;重排 prompt 含**严格相关性指令**:仅当段落直接回答问题的具体情境(如偷窃、归还、退赃)才给高分,泛化提及概念(如犯罪、处罚)的条款必须给低分 0-3——实测重排器对泛化条款会宽松打 9 分(2026-08-12 "偷了又放回"问答中共同犯罪条款无关却得 9 分进引用);重排模型调用**关闭思考链**(`chat_template_kwargs: {"enable_thinking": false}`)+ 输出上限 `RerankMaxTokens=1024`——Qwen3.5 思考链默认开启且思考文本直接混入 content,无上限时输出千级 token"Thinking Process"长文(~5 tok/s 下拖至数分钟超时,2026-08-12 实测挂起 7 分钟即此因) 5. 按重排分降序截 `HybridTopK=5`,回表 `kb_chunk` 取原文与元数据,组装 `schema.Document` 与引用信息 6. 支持 `retriever.WithTopK` / `WithScoreThreshold` 选项 @@ -196,6 +196,8 @@ score = Σ(1 / (60 + rank)) // RRF 分区间过窄无区分度,仅用于候 - SSE 事件顺序:`event: citations`(先推,含引用列表与 conversation_id)→ `event: delta`(`{content}` 增量文本)→ `event: done`;异常推 `event: error` - **15 秒心跳**:controller 空闲时周期发送 `: ping` 注释行,防止代理/浏览器断开长连接;前端 `fetch + ReadableStream` 按 `\n\n` 分块、按 `data: ` 行解析,按字段(citations/content/status/message)识别事件,非 JSON 行(心跳)静默跳过 - 引用列表编号 [1][2] 与提示词中资料编号一一对应,随助手消息以 JSON 落库(chat_message.citations) +- **引用排序**:单次流水线路径 `buildCitations` 按检索结果顺序(重排分降序)编号;多轮 ReAct 工具路径 `aggregateCitations` 按 chunk_id 去重后**按重排分 Score 降序**重新编号——2026-08-12 实测多轮路径曾按轮次累积顺序输出,导致高分引用排后面、顺序与提示词 [N] 不一致 +- **Agent 路径强制首轮预检索**:`askAgent` 循环开始前先无条件执行一次 `executeSearchTool`(retrieve + GraphEnhance 并行,失败仅告警不阻断),结果以 `[N] 内容` + 图谱三元组注入首轮 user 消息,引用列表随循环首轮推送——检索不依赖模型自觉调用 search:2026-08-12 实测 Qwen3.5-9B 思考链关闭后偶发跳过工具调用直接凭自身知识作答(oMLX 日志该次仅 1 次 chat completion、0 次 embedding 请求),引用列表落库 `[]`、回答中 [N] 为模型自编;模型后续仍可调用 search 补充检索,chunk_id 去重防重复引用 ### 7.5 中文分词