
简介这是一份面向前端初学者与进阶开发者的JavaScript知识库整理包围绕JS语言核心与前端工程实践展开帮助读者系统梳理从语法基础到项目规范的完整知识脉络。包内共26个文件以19个Markdown笔记为主体辅以3个Excel表格、1个PNG与1个JPG图示、1个Visio流程图及1个JavaScript脚本压缩包约342KB体积轻便便于随时查阅。内容覆盖变量与数据类型、函数与箭头函数、对象与原型链、ES6类与模块、数组及Set/Map等核心概念并延伸至DOM操作、事件机制、AJAX异步请求以及Vue工程实践、前端缓存、Git团队协作、CSS BEM规范、NPM与脚手架搭建等工程化主题。已有132人学习适合作为日常开发速查手册与知识体系补全的参考材料。1. 知识库不是网盘为什么你搭的 RAG 总在胡说八道很多人第一次搭知识库脑子里想的是“把文档丢进去模型就能回答”。真跑起来才发现问它一句“我们内部报销标准是多少”它要么答得驴唇不对马嘴要么一本正经地编一个数字。问题不在模型在于你把知识库当成了网盘——只存不索引只堆不切。知识库knowledge base在工程语境里指的是一套“文档进得去、问题查得到、答案有出处”的检索增强生成系统。它解决的核心矛盾是大模型的参数里没有你的私有数据而你又不想每次把整本手册塞进提示词。适合谁适合手里有一批 PDF、Markdown、Excel 甚至聊天记录想让它们变成可问答资产的人。这一篇不讲玄学只讲从零搭一个能跑、能查、能溯源的知识库参数怎么设、坑在哪。2. 文档切分与向量化知识库的地基怎么打知识库的效果七成取决于文档处理三成才是模型。很多人一上来就调大模型参数结果检索回来的片段本身就是断的模型再强也拼不出完整答案。这一章把“文档进库”这条链路拆开讲。2.1 为什么固定长度切分是最容易翻车的做法最常见的做法是按字符数硬切比如每 500 字一段。这在小规模测试里看不出问题一旦文档里有表格、代码块、多级标题就会切出“上半句在讲配置项下半句跳到另一个章节”的碎片。检索时命中这种片段模型只能靠猜。我一般会按结构切Markdown 按标题层级切PDF 先转成带段落标记的文本再按段落切表格单独抽成结构化行。切分的目标不是“每段一样长”而是“每段语义自洽”。一个可用的经验值是中文段落 200400 字英文 100200 词重叠 10%15% 用来防止边界信息丢失。# 按 Markdown 标题层级切分保留上下文路径 import re def split_by_heading(text, max_len400, overlap50): # 按二级、三级标题切块标题作为元数据保留 blocks re.split(r(?^#{2,3}\s), text, flagsre.M) chunks [] for block in blocks: block block.strip() if not block: continue # 超长块再按段落二次切分 if len(block) max_len: paras block.split(\n\n) buf for p in paras: if len(buf) len(p) max_len and buf: chunks.append(buf.strip()) buf buf[-overlap:] \n\n p else: buf \n\n p if buf.strip(): chunks.append(buf.strip()) else: chunks.append(block) return chunks这段代码的关键在re.split(r(?^#{2,3}\s))它用零宽断言在标题前切一刀标题本身留在块首检索命中后能直接看到“这段属于哪一节”。max_len控制单块上限overlap是二次切分时的重叠字符数。注意重叠不要超过块长的 20%否则检索结果会大量重复挤占上下文窗口。2.2 向量化模型选型别只看排行榜切完块就要转向量。选型时三个参数最实际维度、最大输入长度、是否支持中文。维度决定存储成本和检索速度768 维和 1536 维在几万条数据下差异不大上百万条时存储差一倍。最大输入长度必须大于你的块长否则会被静默截断——这是最隐蔽的坑截断后向量只代表前半段检索自然不准。常见做法是先用一个中等规模的模型跑通全流程比如支持 512 token 输入、768 维的模型等召回率稳定后再换更大的。不要一上来就追最高维度检索质量更多取决于切分和查询改写不是维度数字。# 向量化并写入本地索引记录来源元数据 import numpy as np def embed_chunks(chunks, embed_fn, source_name): vectors [] metas [] for i, chunk in enumerate(chunks): vec embed_fn(chunk) # 返回归一化后的向量 vectors.append(vec) metas.append({ source: source_name, chunk_id: i, text: chunk, heading: chunk.split(\n)[0][:80] # 保留标题做溯源 }) return np.array(vectors, dtypenp.float32), metasembed_fn是外部传入的向量化函数这样换模型不用改主流程。heading字段存块首标题回答时能告诉用户“答案来自某文档的某一节”。向量统一转float32比默认float64省一半内存检索精度损失可以忽略。2.3 入库前必须做的两步清洗清洗不是可选项。第一步去页眉页脚和页码PDF 转文本后每页顶部常混入文档名和日期这些词会污染向量。第二步统一空白符把连续空行压成一个把全角空格转半角。这两步做完检索命中率通常能涨一截而且不需要动模型。注意清洗规则要按文档来源分别配置不要用一套正则打天下。技术手册和会议纪要的噪声模式完全不同。3. 检索策略从“能查到”到“查得准”向量库建好只是开始真正决定体验的是检索环节。用户问一句话系统要在一堆向量里找出最相关的几块这一步的召回质量直接决定最终答案。3.1 相似度阈值不设阈值的检索等于没检索很多教程只讲“取 Top-K”不讲阈值。结果是用户问一个知识库里根本没有的问题系统照样返回 5 条不相关的片段模型硬着头皮编。正确做法是设一个相似度下限低于阈值的直接丢弃并明确告诉用户“知识库中没有相关内容”。阈值怎么定用一批已知答案的问题做校准把正确答案所在块的相似度分布画出来取能覆盖 90% 正确块、同时排除大部分无关块的值。余弦相似度场景下归一化向量常用 0.350.55 之间具体看模型。这个值不是拍脑袋是测出来的。# 带阈值的检索返回命中的块和相似度 def search(query_vec, index_vectors, metas, top_k5, threshold0.4): # 向量已归一化点积即余弦相似度 scores index_vectors query_vec ranked np.argsort(scores)[::-1][:top_k] results [] for idx in ranked: if scores[idx] threshold: continue item dict(metas[idx]) item[score] float(scores[idx]) results.append(item) return resultsthreshold是硬门槛top_k是上限。两个参数配合使用先卡阈值再取前 K而不是先取 K 再过滤。顺序反了会把低分块带进来。scores用矩阵乘法一次算完几万条数据毫秒级返回。3.2 查询改写用户的问题往往不是检索该用的句子用户问“报销要多久”文档里写的是“费用审批周期为五个工作日”。字面重叠很低向量也可能拉不近。常见做法是加一步查询改写用一个小模型把口语问题扩写成包含同义词和关键实体的检索句比如“报销 费用审批 周期 工作日”。改写不是必须上大模型规则加同义词表也能覆盖大部分场景。关键是改写后的句子要保留原问题的意图不能扩着扩着跑偏。我一般会限制改写长度不超过原句三倍并保留原句一起检索两路结果合并去重。3.3 重排序用交叉编码器把 Top-K 再筛一遍向量检索是双编码器快但粗。对 Top-20 结果再用交叉编码器逐对打分能把真正相关的块顶到前面。这一步在数据量大时收益明显代价是延迟增加。实践里如果 Top-5 已经够用可以跳过如果发现正确答案总在第 6、第 7 位就该加重排序。策略延迟召回提升适用场景纯向量 Top-K低基线数据量小、问题直白向量 阈值低减少噪声必须防编造向量 查询改写中中等口语化提问多向量 重排序高明显答案分散在多块4. 生成与溯源让答案有出处、能验证检索回来的块怎么喂给模型决定了答案的可信度。直接把块拼起来丢进去模型可能混用不同来源的信息甚至把两块矛盾的内容揉成一句。4.1 提示词里必须写清“只用给定材料”系统提示词要明确三件事只依据提供的片段回答、每个结论标注来源编号、材料不足时直接说不知道。这三条写进去编造率会明显下降。片段按检索得分排序每块前面加编号方便模型引用。PROMPT_TEMPLATE 你是一个知识库问答助手。请严格依据以下材料回答问题。 要求 1. 只使用材料中的信息不要引入外部知识。 2. 每个结论后用 [编号] 标注来源。 3. 材料不足以回答时直接说“知识库中没有找到相关内容”。 材料 {context} 问题{question} 回答 def build_context(results): lines [] for i, r in enumerate(results, 1): lines.append(f[{i}] 来源{r[source]} / {r[heading]}\n{r[text]}) return \n\n.join(lines)build_context把来源和标题一起拼进去模型引用时能带上出处。编号从 1 开始和提示词里的要求对应。注意材料总长度要控制在模型上下文窗口的 60% 以内留出空间给问题和回答否则末尾片段会被截断。4.2 溯源展示把引用还原成可点击的原文位置回答里出现[2]界面上就要能展开第 2 块原文最好还能跳到原文档对应位置。这要求入库时存好source和chunk_id前端按编号反查。没有溯源的问答系统用户没法验证信任建立不起来。4.3 多轮对话里怎么防止上下文污染多轮场景下用户会追问“那第二条呢”。如果直接把历史对话拼进提示词模型可能把上一轮检索到的材料当成这一轮的依据。常见做法是每轮都重新检索只把历史对话作为“指代消解”的输入不把历史材料带进新上下文。追问里的“那第二条”要先改写成完整问题再检索。5. 避坑与排查知识库上线后最常见的五类问题这一章按“现象 → 原因 → 解决”写都是实际跑起来才会遇到的。现象一答案总是差一点关键数字对不上。原因多半是切分把数字和它的单位、条件切开了比如“五个工作日”被切到下一块。解决切分时把数字、单位、条件词作为不可分割单元或在块边界检测这类模式并向前合并。现象二问 A 文档的问题返回 B 文档的片段。原因是不同来源的文档用了同一套向量空间但内容领域差异大相似度区分度不够。解决入库时给每块加来源标签检索时先按来源过滤再算相似度或者对每个来源单独建索引。现象三相似度很高的块内容其实不相关。常见于文档里有大量重复模板句比如每页都有“本文档版权归某公司所有”。解决入库前做去重对高频重复句直接过滤不参与向量化。现象四检索延迟随数据量线性上涨。原因是用 NumPy 全量算点积几万条还行几十万条就慢。解决换近似最近邻索引比如基于图的 HNSW 或基于量化的 IVF用少量召回损失换数量级的速度提升。现象五模型回答里引用了不存在的编号。原因是提示词没约束编号范围或者材料拼接时编号和实际块数不一致。解决拼接后校验最大编号提示词里写明“编号不超过 N”并在后处理时过滤越界引用。注意这五类问题里前两类占实际故障的八成以上。先把切分和来源隔离做对再谈模型调优。6. 进阶技巧用混合检索和增量更新把知识库养起来纯向量检索对专有名词和代码标识符不敏感比如问“某个配置项叫什么”向量可能拉不近但关键词能精确命中。混合检索就是向量加关键词两路并行再融合排序。关键词路用 BM25 或简单的倒排索引即可成本低补足向量短板。融合排序常用倒数排名融合RRF两路各返回一个排名列表对每个文档算1/(krank)求和k一般取 60。这个方法不需要两路分数可比只依赖排名工程上很稳。# 倒数排名融合合并向量和关键词两路结果 def rrf_fusion(vec_results, kw_results, k60, top_n5): scores {} for rank, item in enumerate(vec_results): key (item[source], item[chunk_id]) scores[key] scores.get(key, 0) 1 / (k rank 1) for rank, item in enumerate(kw_results): key (item[source], item[chunk_id]) scores[key] scores.get(key, 0) 1 / (k rank 1) ranked sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return rankedk60是经验值调大让排名差异变平缓调小让头部结果更突出。两路结果的source和chunk_id必须能对齐否则融合会错位。增量更新是另一个绕不开的点。文档会改知识库不能每次全量重建。常见做法是给每块存内容哈希更新时只重新向量化哈希变化的块删除的块从索引里标记移除。这样一次更新只动变化部分几分钟就能完成。我自己的习惯是每次改完切分规则或换向量模型先拿二十个已知答案的问题跑一遍召回率低于上次就回滚不凭感觉上线。知识库这东西玄学少血泪经验多测过的数字比拍脑袋可靠。希望帮到你。本文还有配套的精品资源点击获取