RAG查询改写后数字、缩写和型号消失?Token清洗与双路检索完整排查

📅 发布时间:2026/8/10 11:14:05
RAG查询改写后数字、缩写和型号消失?Token清洗与双路检索完整排查
文章摘要技术文档、设备手册、API错误、商品目录和企业业务系统中真正决定召回结果的往往不是自然语言而是HTTP 429、Spring AI 2.0、ZX-4100B、SKU-00981、V2.3.1、EUDR、PUC等高信息密度Token。很多RAG系统为了“清洗输入”和“提升语义”会删除标点、停用词、连字符、大小写或数字最终导致BM25失去精确匹配Dense Embedding也丢失关键区分度。常见故障包括HTTP 429只剩“限流问题”Spring AI 2.0被改成Spring AI型号中的连字符被拆开C被清洗成CA/B测试被改写为“测试”日期和版本号被错误归一化。由于改写后的句子仍然流畅这类问题常被误判为向量数据库召回差或Embedding模型不适合中文。本文将查询处理拆成Raw、Normalized、Sparse和Dense四种视图建立受保护Token词法分析器、业务缩写词典、版本与错误码解析器、原始Query稀疏检索、受控Dense改写、Token保真校验和回归数据集并给出Spring Boot实现、指标和排查步骤。一、一个最典型的错误用户输入Spring AI 2.0升级后调用MCP Streamable HTTP返回429ttlMs配置是否有问题错误清洗spring ai 升级 调用 mcp streamable http 返回 限流 ttlms 配置 问题进一步改写Spring AI升级后MCP连接被限流如何调整缓存时间丢失信息2.0429ttlMs的大小写“是否有问题”这一诊断语义Streamable HTTP作为协议名称的整体性。后果BM25无法精确命中包含429和ttlMs的排障文档Dense检索会泛化到所有“限流”和“缓存”文章Reranker难以恢复已经被删除的Token最终回答可能讨论错误的限流策略。二、为什么技术Token特别脆弱1. 正则把非中文和字母数字当噪声错误代码query.replaceAll([^\\p{IsHan}a-zA-Z\\s], );这会删除数字-小数点-连字符-斜杠-加号-井号。2. 停用词表误删缩写IT、US、OR、IN在英文中可能是停用词但也可能是信息技术-国家代码-逻辑运算-SQL关键字。3. 分词器拆散型号ZX-4100B → ZX / 4100 / B拆分有时有利于召回但如果没有保留完整Token精确匹配能力会下降。4. 大小写归一化破坏业务含义mcp MCP Mcp自然语言可能等价但大小写敏感代码、字段名和型号可能不同。5. LLM“纠错”模型可能把低频Token改为常见TokenttlMs → TTL6. 翻译改写A/B Test → 对照实验自然语言等价但技术文档可能只出现A/B Test。三、不要只有一个Query字符串推荐建立四种视图。publicrecordQueryViews(Stringraw,Stringnormalized,StringsparseQuery,StringdenseQuery,SetProtectedLexemeprotectedLexemes,QueryNormalizationTracetrace){}Raw用户原始输入不允许覆盖。Normalized只做可逆或确定性规范化Unicode-空白-全角半角-不可见字符-常见引号。Sparse Query面向BM25或倒排索引最大程度保留精确Token。Dense Query面向Embedding可进行受控改写但必须保留关键Token和语义约束。四、第一原则Normalization必须可解释错误Stringcleanraw.toLowerCase().replaceAll([^a-z0-9\\u4e00-\\u9fa5 ], ).replaceAll(\\s, ).trim();问题不知道删除了什么无法恢复大小写全部丢失符号语义消失排障无法复现。正确做法publicrecordNormalizationChange(NormalizationRulerule,Stringbefore,Stringafter,intstartOffset,intendOffset){}每个规则有版本publicrecordQueryNormalizationTrace(StringnormalizerVersion,ListNormalizationChangechanges,SetStringremovedFragments,SetStringchangedFragments){}五、哪些字符不能随便删除技术Query常见符号符号示例可能含义.2.0.1版本-ZX-4100B型号或编号_tenant_id字段名/A/B、HTTP/2协议或比较C语言名#C#语言名:error:429键值或错误McpTool注解$${tenantId}模板变量%95%比例处理策略不应是“保留所有符号”或“删除所有符号”而是先识别Token类型。六、受保护Token类型publicenumLexemeType{VERSION,ERROR_CODE,HTTP_STATUS,MODEL_ID,SKU,DEVICE_MODEL,CONTRACT_ID,CLASS_NAME,METHOD_NAME,CONFIG_KEY,ANNOTATION,ACRONYM,DATE,PERCENTAGE,MONEY,FILE_PATH,URL_FRAGMENT,UNKNOWN_TECH_TOKEN}publicrecordProtectedLexeme(Stringraw,Stringcanonical,LexemeTypetype,intstart,intend,ProtectionModemode,doubleconfidence){}保护模式publicenumProtectionMode{EXACT,CASE_INSENSITIVE,CANONICAL_EQUIVALENT,TOKEN_SET_EQUIVALENT}七、技术Token词法分析器ComponentpublicclassTechnicalLexemeExtractor{privatefinalListLexemeRecognizerrecognizers;publicSetProtectedLexemeextract(Stringquery){MapString,ProtectedLexemeresultnewLinkedHashMap();for(LexemeRecognizerrecognizer:recognizers){for(ProtectedLexemelexeme:recognizer.recognize(query)){result.merge(lexeme.raw(),lexeme,this::preferHigherConfidence);}}returnSet.copyOf(result.values());}}Recognizer示例publicinterfaceLexemeRecognizer{ListProtectedLexemerecognize(Stringquery);}八、版本号识别privatestaticfinalPatternVERSION_PATTERNPattern.compile((?i)(?![a-z0-9])v?\\d(?:\\.\\d){1,4}(?:[-_][a-z0-9.])?(?![a-z0-9]));能够匹配2.0 V2.3.1 1.0.0-RC1 3.4.0build7版本比较不能转为普通小数2.10 并不小于 2.9需要语义版本模型publicrecordSemanticVersion(intmajor,intminor,intpatch,Stringprerelease,StringbuildMetadata){}九、错误码与HTTP状态privatestaticfinalPatternHTTP_STATUSPattern.compile((?i)\\b(?:HTTP[/\\s]?[12](?:\\.\\d)?\\s*)?([1-5]\\d{2})\\b);privatestaticfinalPatternERROR_CODEPattern.compile(\\b[A-Z][A-Z0-9]{1,15}[-_][A-Z0-9-]{1,24}\\b);429在普通文本中可能只是数字因此需要上下文HTTP 429 状态码429 返回429高置信识别后使用EXACT保护。十、字段名、类名和方法名ttlMs SearchRequest filterExpression similaritySearch spring.ai.vectorstore.qdrant规则CamelCasesnake_casedotted.key包名方法调用注解。privatestaticfinalPatternCAMEL_CASEPattern.compile(\\b[a-z](?:[A-Z][a-zA-Z0-9]*)\\b);privatestaticfinalPatternDOTTED_KEYPattern.compile(\\b[a-z][a-z0-9_-]*(?:\\.[a-z0-9_-]){1,8}\\b);十一、业务缩写词典publicrecordAcronymEntry(Stringacronym,SetStringexpansions,SetStringdomains,booleanpreserveExact,Stringversion){}例如EUDR MCP RAG SKU PUC SSE RRF DBSF不要直接把缩写替换为全称。推荐Sparse QueryMCP Streamable HTTPDense Query可以MCPModel Context ProtocolStreamable HTTP即扩展 但不删除原缩写十二、占位符保护法在把Query交给LLM前将高风险Token替换为不可修改占位符。publicrecordPlaceholderMap(StringprotectedText,MapString,Stringplaceholders){}原始ZX-4100B在V2.3后出现E1027保护后__LEXEME_001__在__LEXEME_002__后出现__LEXEME_003__模型改写后再恢复。ServicepublicclassQueryTokenProtector{publicPlaceholderMapprotect(Stringraw,SetProtectedLexemelexemes){Stringresultraw;MapString,StringmapnewLinkedHashMap();ListProtectedLexemeorderedlexemes.stream().sorted(Comparator.comparingInt(ProtectedLexeme::start).reversed()).toList();intsequence1;for(ProtectedLexemelexeme:ordered){Stringplaceholder__LEXEME_%03d__.formatted(sequence);resultresult.substring(0,lexeme.start())placeholderresult.substring(lexeme.end());map.put(placeholder,lexeme.raw());}returnnewPlaceholderMap(result,map);}}注意Offset替换必须从后向前执行。十三、占位符也可能被模型修改模型可能输出LEXEME_001或删除占位符。恢复前校验publicvoidassertAllPlaceholdersPresent(PlaceholderMapmap,Stringrewritten){SetStringmissingmap.placeholders().keySet().stream().filter(key-!rewritten.contains(key)).collect(Collectors.toSet());if(!missing.isEmpty()){thrownewQueryTokenLossException(missing);}}十四、Sparse Query如何构建Sparse检索强调词面保真。publicStringbuildSparseQuery(QueryViewsviews){returnString.join( ,views.raw(),views.protectedLexemes().stream().map(ProtectedLexeme::raw).distinct().collect(Collectors.joining( )));}也可以针对搜索引擎构建BoostZX-4100B^5 E1027^5 V2.3^3 设备 离线^1不要把整句话全部Exact Match否则召回过窄。十五、Dense Query如何构建Dense Query目标是提升语义但保留关键Token。publicStringbuildDenseQuery(Stringrewritten,SetProtectedLexemelexemes){Stringsuffixlexemes.stream().map(ProtectedLexeme::raw).distinct().collect(Collectors.joining( ));returnrewritten\n关键技术Tokensuffix;}是否追加Token需通过数据集评测避免过度影响Embedding。十六、双路检索ListDocumentsparseDocumentssparseRetriever.search(views.sparseQuery(),accessContext,40);ListDocumentdenseDocumentsvectorStoreRetriever.similaritySearch(SearchRequest.builder().query(views.denseQuery()).topK(40).filterExpression(tenantFilter).build());ListDocumentfusedfusionService.fuse(sparseDocuments,denseDocuments);技术Token查询通常不能只依赖Dense。十七、为什么Reranker不能修复Token丢失Reranker只能在已有候选中重新排序。如果正确文档因为ZX-4100B被删除而没有进入Top KReranker无文档可排因此Token保真是召回前问题不是重排问题。十八、查询改写后的保真校验publicrecordLexemePreservationReport(booleanpassed,SetStringmissingExact,SetStringcaseChanged,SetStringcanonicalMismatch,SetStringunexpectedTechnicalTokens){}publicLexemePreservationReportvalidate(SetProtectedLexemerequired,Stringrewritten){SetStringmissingrequired.stream().filter(lexeme-lexeme.mode()ProtectionMode.EXACT).map(ProtectedLexeme::raw).filter(token-!rewritten.contains(token)).collect(Collectors.toSet());returnnewLexemePreservationReport(missing.isEmpty(),missing,findCaseChanges(required,rewritten),findCanonicalMismatch(required,rewritten),findUnexpectedTokens(required,rewritten));}失败时Dense Query回退到Normalized或Raw Sparse Query继续使用Raw十九、大小写处理策略推荐同时保存raw_token token_lowercase canonical_token检索索引可以存多个字段content content_lowercase technical_tokens technical_tokens_keyword查询时technical_tokens_keyword精确content全文-向量字段语义。二十、中英文混合查询Spring AI的toolcallback.enabledfalse为什么仍然注册Tool不要把toolcallback.enabled翻译成中文。Translation Query策略应先保护技术Token2.只翻译自然语言片段3.恢复Token4.执行保真校验。二十一、分词调试排障时打印不同阶段TokenpublicrecordTokenizationDebug(ListStringrawTokens,ListStringnormalizedTokens,ListStringsparseTokens,ListStringdenseTokens,SetStringprotectedTokens){}但生产日志中不要直接输出敏感Query。可以在受控调试环境或对Token做Hash。二十二、指标rag_query_protected_lexeme_total{ type } rag_query_lexeme_loss_total{ type, stage } rag_query_normalization_change_total{ rule } rag_query_sparse_exact_hit_rate rag_query_dense_recall_rate rag_query_hybrid_required_document_recall rag_query_rewrite_fallback_total{ reason } rag_query_technical_token_query_total关键指标技术Token查询的正确文档RecallK不能只看所有Query平均召回。二十三、回归样本HTTP 429 HTTP/2 C C# A/B Test Spring AI 2.0 v1.2.10 ZX-4100B E1027 tenant_id filterExpression McpTool ttlMs 95% 2026-08-08 HT-2026-0081每条Case定义必须保留Token-允许的Canonical形式-期望文档-禁止文档-Sparse/Dense/Hybrid结果。二十四、自动化测试ParameterizedTestMethodSource(technicalTokenCases)voidprotectedTokensMustSurvive(Stringquery,SetStringexpected){QueryViewsviewsqueryViewService.build(query);assertThat(views.protectedLexemes().stream().map(ProtectedLexeme::raw)).containsAll(expected);assertThat(views.sparseQuery()).contains(expected.toArray(String[]::new));}检索集成测试TestvoidmodelNumberMustRetrieveExactManual(){RetrievalResultresultragRetriever.retrieve(ZX-4100B出现E1027怎么办);assertThat(result.documents()).extracting(Document::getId).contains(MANUAL-ZX-4100B-E1027);}二十五、完整排查顺序1. 检查query_raw 2. 检查Unicode和全角半角转换 3. 检查清洗正则 4. 检查停用词表 5. 检查分词结果 6. 检查技术Token提取 7. 检查占位符是否被删除 8. 检查改写输出 9. 检查Sparse实际Query 10. 检查Dense实际Query 11. 检查索引是否存储完整Token 12. 检查融合与Reranker候选如果索引阶段已经把连字符和大小写全部丢失仅修复查询侧仍不够需要重建相应字段。二十六、常见错误为了中文分词删除全部非中文字符 对所有Query统一转小写 将版本号当普通小数 停用词表直接应用于技术Query 只保存改写Query不保存Raw 只做Dense不做Sparse 依赖Reranker修复召回缺失 翻译Query时不保护代码和字段名二十七、上线前检查清单□ Raw Query不可变保存 □ Normalization规则可追踪且版本化 □ 技术Token在清洗前抽取 □ 版本、错误码、型号和字段名有专用Recognizer □ 业务缩写词典可版本化 □ 高风险Token使用占位符保护 □ 占位符恢复前检查完整性 □ Sparse Query保留原始Token □ Dense Query允许语义改写但通过保真校验 □ Hybrid检索使用稳定融合 □ 索引中存在技术Token精确字段 □ 技术Query有独立黄金数据集 □ Token损失指标进入质量门禁总结技术RAG的查询处理目标不是把句子变得更自然而是最大限度保留检索信息。推荐架构Raw Query → 技术Token抽取 → 可解释Normalization → Sparse Query保真 → Dense Query受控增强 → Token校验 → Hybrid Retrieval数字、缩写、型号和错误码一旦在检索前丢失后面的向量库、Reranker和大模型通常无法恢复。把这些Token视为一等数据而不是清洗噪声是技术知识库从Demo走向生产的基础。