1. 为什么我要自己搭一套知识库问答系统去年下半年我接手了一个内部资料检索的活儿。当时的情况是团队积累了大量的产品文档、会议纪要、技术方案和历史工单散落在好几个地方找东西全靠关键词搜索加人工翻页。新同事入职问得最多的一句话就是“那个XX文档在哪”老同事每天也要花不少时间在重复回答相似问题上。我粗略统计了一下一个二十来人的小团队每周花在“找信息”和“重复解释”上的时间加起来能超过十五个小时。这个数字看着不起眼但折算成人力成本相当可观。最开始我想得很简单直接上大模型不就行了把文档丢进去让它读然后回答问题。实际一跑就发现两个致命问题第一模型根本不知道我们内部的业务术语和产品代号回答起来一本正经地胡说八道第二上下文窗口再大也装不下全部资料而且每次全量塞进去成本高得离谱。于是思路转向了检索增强生成这条路——先从一个可控的知识库里把相关片段捞出来再让模型基于这些片段组织答案。这就是我后来花了一个多月折腾的基于知识库和 LLM 的问答系统。这套东西说白了就是给大模型配一个“随身资料库”用户提问系统先去资料库里找最相关的几段内容再把问题和这些内容一起交给模型让它照着材料回答。它解决的核心问题是让模型的回答有据可查、有源可溯而不是凭空编造。适合谁来参考我觉得三类人最对口一是手里有一堆非结构化文档、想做成智能检索的开发者二是想给内部工具加个问答入口的技术负责人三是对 RAG 流程好奇、想动手跑通一个完整 Demo 的学习者。下面我把自己踩过的路、做过的取舍、调过的参数尽量完整地摊开讲。2. 整体架构设计与技术选型思路2.1 系统分层的核心逻辑我把整套系统拆成了四个相对独立的层文档处理层、向量检索层、生成层、交互层。这么拆不是为了好看而是因为每一层的失败模式完全不同分开之后排查问题会快很多。文档处理层负责把各种格式的原始资料清洗成干净的文本块向量检索层负责把这些文本块变成向量、建索引、做相似度匹配生成层负责把检索结果和用户问题组装成提示词、调用模型交互层就是前端界面和会话管理。为什么强调分层我举个实际例子。系统刚上线时有用户反馈“问某个功能怎么配置回答总是答非所问”。我一开始以为是模型不行换了两个模型都没改善。后来逐层排查才发现问题出在文档处理层——那个功能的说明文档是表格形式解析时表格结构被压平成了连续文本关键参数和说明混在一起切块之后语义完全乱了。如果系统是铁板一块这种问题很难定位。分层之后我可以单独把那一批文档的切块结果打印出来看一眼就发现了症结。提示分层设计的一个隐性好处是每一层都可以单独做评估。检索层看召回率生成层看答案忠实度互不干扰。2.2 向量库与模型选型的取舍向量库这块我对比过几个常见方案。轻量场景下本地文件型的向量索引足够用部署简单、零依赖数据量上到几十万块以上就得考虑带持久化和增量更新能力的服务型方案。我最终选的是一个支持本地持久化、API 简洁的轻量库原因是我们的文档量在十万块这个量级单机完全扛得住而且团队里没人想维护额外的数据库服务。嵌入模型的选择上我试过通用型的中文嵌入模型和几个多语言模型。实测下来通用中文嵌入模型在业务术语上的表现明显更稳因为我们的文档里大量出现产品代号和内部缩写多语言模型反而容易把这些词映射到不相干的语义空间。这里有个经验嵌入模型不是越大越好关键看它在你所在领域的语料上有没有充分训练过。我当时的做法是抽了五十组“问题-正确片段”对手动算了一下各模型的召回命中率选命中率最高的那个而不是盲目追新。生成模型这块我建议先用一个中等规模的模型把流程跑通再根据效果决定要不要换更大的。很多人一上来就上最强模型结果发现瓶颈根本不在生成环节而在检索质量上白白烧了预算。我自己的配置是日常问答用中等模型遇到复杂推理类问题再路由到更强的模型这样成本和效果能兼顾。2.3 为什么不用微调而选 RAG这个问题我被问过好几次。微调当然也能让模型“记住”内部知识但它有几个绕不开的短板知识更新要重新训练、训练成本高、而且模型记错了你很难纠正。RAG 的优势在于知识是外挂的文档改了重新索引一下就行模型本身不用动。对于我们这种文档每周都在更新的场景RAG 的维护成本低了一个数量级。当然 RAG 也不是银弹。它的短板是检索环节可能漏掉关键信息导致模型“没看到”正确答案。所以我在设计时把重心放在了检索质量上后面会详细讲切块和召回优化的细节。一句话总结选型逻辑知识频繁变动、需要溯源、预算有限选 RAG知识相对固定、追求极致响应速度、有充足训练资源才考虑微调。3. 文档处理与切块最容易被低估的环节3.1 文档清洗的实操要点原始文档的脏乱程度远超想象。PDF 里有页眉页脚、有水印文字网页导出的 Markdown 里混着导航栏残留Word 文档里的自动编号在转换后变成一堆乱码字符。我的清洗流程是这样的先统一转成纯文本再用规则去掉重复出现的页眉页脚模式比如每页都出现的公司名加页码然后处理连续空行和特殊符号。这里有个细节值得说表格的处理要单独对待。我一开始把表格直接转成文本结果列与列之间没有分隔读起来像天书。后来改成把每一行转成“列名值”的形式比如“超时时间30秒重试次数3次”这样切块之后语义依然完整。这个改动让涉及配置参数的问答准确率提升了一大截。注意清洗阶段宁可多花时间也不要带着脏数据往下走。脏数据在检索阶段会被放大最后表现为“模型答非所问”而你还以为是模型的问题。3.2 切块策略的参数计算切块是 RAG 里最考验经验的一环。块太大检索出来的内容冗余模型容易被无关信息干扰块太小语义不完整检索到了也答不好。我用的策略是按语义边界切分再控制块大小。具体做法是先按段落和标题层级切然后对超长的段落做二次切分块大小控制在三百到五百字之间块与块之间保留五十字左右的重叠。为什么是这个范围我做过一组对比实验。块大小设成两百字时召回率不错但答案经常缺上下文设成八百字时上下文够了但检索精度下降因为一个块里混了多个主题。三百到五百字这个区间在我们的文档类型上表现最均衡。重叠部分的作用是防止一个完整的句子被切断在两块之间五十字大约是一到两句话的长度够用了。另外我加了一条规则标题和它下面的内容尽量放在同一个块里。因为很多问题的答案依赖于“这个内容属于哪个章节”这个上下文。比如“重试策略”这个词单独出现时你不知道它说的是网络请求还是任务调度但带上所属章节标题就清楚了。3.3 元数据的附加价值每个块除了文本内容我还附带了几个元数据字段来源文档名、章节路径、更新时间、文档类型。这些字段在检索时可以参与过滤比如用户问“最新的部署流程”我可以先按更新时间过滤掉过期文档再做向量匹配。元数据还解决了溯源问题——答案里可以标注“来自某文档某章节”用户点进去就能看原文信任度完全不一样。我踩过的一个坑是元数据字段设计得太随意早期只存了文档名结果同一个文档有多个版本检索出来根本分不清是新版还是旧版。后来强制要求每个块带上版本号和更新时间这个问题才彻底解决。4. 检索环节的调优与召回增强4.1 向量检索的基本流程与参数检索的基本流程不复杂用户问题进来用同一个嵌入模型转成向量然后在向量库里做相似度搜索取回 top-k 个最相似的块。关键在参数上。top-k 取多少取太少可能漏掉正确答案取太多会把噪声塞给模型。我实测下来k 取四到六之间比较合适再多的话生成阶段的提示词会变得很长而且后面的块相关性明显下降。相似度阈值也值得设一个。我设了一个下限低于这个分数的块直接丢弃哪怕它排在 top-k 里。原因是有些问题在知识库里根本没有对应内容这时候硬塞几个不相关的块给模型它反而会强行编一个答案。设了阈值之后系统在“不知道”的时候能老实说“资料里没有找到相关内容”这比胡编强得多。4.2 混合检索关键词与向量的互补纯向量检索有个软肋对精确匹配不敏感。比如用户问某个具体的错误码“E1024”向量检索可能召回一堆语义相近但错误码不同的内容。解决办法是加一路关键词检索把两路结果做融合。我的做法是向量检索取 top-6关键词检索取 top-6然后用倒数排名融合的方式合并排序取前五条送给生成层。这个改动效果立竿见影。涉及具体编号、参数名、函数名的问答准确率提升非常明显。原因很直白关键词检索擅长精确命中向量检索擅长语义泛化两者互补。融合时我给两路设了相近的权重没有刻意偏袒哪一方因为实测下来平衡权重在大多数问题上都够用。4.3 查询改写提升召回用户的提问往往很口语化直接拿去检索效果打折扣。比如“那个上传文件老失败咋整”向量化之后和文档里“文件上传失败排查步骤”的相似度未必高。我加了一个轻量的查询改写步骤用一个小模型把口语化问题改写成更接近文档表述的检索式同时生成两到三个变体分别检索后合并结果。改写不是让模型自由发挥而是给了明确的约束保留原问题的核心意图补充可能的同义词和术语。比如“咋整”改写成“排查方法”“老失败”改写成“失败原因”。这一步增加了一点延迟但召回率的提升值得这个代价。我实测过改写前后的召回命中率差距能到十个百分点以上。提示查询改写用的小模型不需要很强重点是快和稳定。改写结果要做长度限制防止模型把问题扩写成一大段话反而稀释了检索意图。4.4 重排序的取舍检索回来一批候选块之后还有一个可选步骤重排序。用一个专门的重排序模型对候选块和问题的相关性做精细打分重新排列。我试过加这一层效果确实有提升尤其是候选块之间相关性接近的时候。但代价是延迟增加明显因为重排序模型要对每个候选块单独推理。最后的决定是只在复杂问题上启用重排序。判断标准是检索结果的分数分布如果 top 几名的分数拉得很开说明检索很确信不需要重排如果分数挤在一起说明区分度不够这时候才启用重排序。这个按需启用的策略把平均延迟控制在了可接受范围内。5. 生成阶段的提示词设计与答案控制5.1 提示词的结构化组织生成阶段的提示词我改了很多版最后稳定下来的结构是这样的先给模型一个明确的角色设定和任务说明然后放检索到的资料片段每段标注来源编号最后放用户问题并明确要求“只依据提供的资料回答资料中没有的内容要明确说明”。这个“只依据资料”的约束非常关键它把模型的发挥空间压到了最小大幅降低了编造的概率。资料片段的排列顺序也有讲究。我把相关性最高的放在最前面和最后面中间放相关性稍低的。这是借鉴了模型对首尾位置更敏感的特性实测下来比按顺序平铺的效果好一些。每段资料前面加上编号方便模型在答案里引用也方便用户核对。5.2 答案格式与引用规范我要求模型在答案里用方括号标注引用的资料编号比如“根据资料[2]超时时间默认是三十秒”。这个要求一开始模型执行得不好经常忘记标注。后来我在提示词里加了几个示例明确展示标注的写法执行率就上来了。引用标注的价值在于可验证用户看到答案后能直接定位到原文发现不对可以立刻反馈这比一个黑盒答案可信得多。答案长度我也做了约束。太短可能没答全太长则啰嗦。我的做法是让模型先给一句直接结论再展开细节最后如果有必要补充注意事项。这个“结论先行”的结构对使用者很友好扫一眼就知道答案是什么。5.3 处理“资料不足”的场景这是很多人容易忽略的场景。用户问了一个知识库里没有的问题系统该怎么办我的处理是分两级如果检索阶段所有块的分数都低于阈值直接返回“资料库中未找到相关内容建议补充资料或换个问法”如果检索到了一些块但相关性一般就让模型基于现有资料给出“可能相关”的回答并明确标注“以下内容基于有限资料仅供参考”。这个设计避免了两个极端既不会在没资料时硬编也不会在资料不足时直接摆烂。实际使用中用户对这个诚实的反馈接受度很高反而比一个似是而非的答案更让人信任。6. 实操落地从零跑通的完整步骤6.1 环境准备与依赖安装整个系统的依赖不算多核心是嵌入模型、向量库和生成模型的客户端。我用 Python 搭的原型环境用虚拟环境隔离。安装依赖时有个小坑嵌入模型和向量库对某些底层库的版本有要求版本冲突会导致导入报错。我的做法是先装向量库再装嵌入模型相关的包让依赖解析器自己协调版本实在冲突再手动锁版本。python -m venv qa_env source qa_env/bin/activate pip install vector-store-client embedding-model-sdk llm-client模型文件我提前下载到本地避免每次启动都去拉取。嵌入模型选的是中等规模的中文模型生成模型通过接口调用。配置文件里把模型路径、向量库路径、检索参数都抽出来方便调参时不用改代码。6.2 文档入库的完整流程入库流程分四步读取、清洗、切块、向量化写入。我写了一个批处理脚本遍历文档目录按扩展名分派不同的解析器。解析出来的文本先过一遍清洗规则然后按前面说的策略切块每个块带上元数据批量调用嵌入模型转成向量最后写入向量库。批量写入时要注意控制批次大小。一次塞太多会导致内存飙升太少则速度慢。我实测每批五十到一百块比较合适。写入完成后做一个校验随机抽几个块用它的原文去检索看能不能把自己召回。这个自检能快速发现索引写入是否正常。6.3 问答接口的实现要点问答接口的逻辑是一条流水线接收问题、查询改写、双路检索、结果融合、组装提示词、调用生成模型、返回答案和引用。每个环节我都加了日志记录耗时和中间结果。这些日志在调优时是金矿能清楚看到是哪一步拖了后腿或者丢了信息。接口返回的结构里除了答案文本我还带了引用列表和检索耗时。前端可以据此展示“答案来源”和性能指标。会话管理上我保留了最近几轮对话的上下文但做了长度限制防止历史对话把提示词撑爆。6.4 效果评估的简易方法没有评估就没有优化。我建了一个小型的评估集五十个真实用户问题每个问题人工标注了正确答案所在的文档块。评估指标看两个检索命中率正确答案块有没有进 top-k和答案忠实度生成的答案有没有超出资料范围。前者靠脚本自动算后者抽样人工看。这个评估集不大但足够指导调优方向。每次改切块策略或检索参数跑一遍评估集看指标是升是降避免凭感觉瞎调。我建议一开始就建这个评估集哪怕只有二三十个问题也比没有强。7. 常见问题排查与避坑经验7.1 典型问题速查表现象可能原因排查方向解决思路答案答非所问切块语义断裂或检索召回错误打印检索到的块看内容调整切块大小加混合检索模型编造内容提示词约束不足或资料不足硬答检查提示词和相似度阈值强化“只依据资料”约束设阈值精确术语检索不到纯向量检索对精确匹配弱用错误码等词测试召回加关键词检索做融合回答冗长啰嗦提示词未约束长度和结构看提示词是否有格式要求要求结论先行限制展开响应慢重排序或生成模型过大看各环节耗时日志按需启用重排序模型分级新文档检索不到索引未更新检查入库流程是否执行建立文档更新触发重建索引7.2 我踩过的三个深坑第一个坑是切块时把代码块切断了。技术文档里有大量代码示例按字数硬切会把一段代码拦腰截断检索到半段代码根本没法用。后来我加了规则检测到代码块边界时优先在代码块结束后切分宁可块大一点也不要切断代码。第二个坑是嵌入模型和生成模型对术语的理解不一致。嵌入模型把某个产品代号理解成一个意思生成模型理解成另一个意思导致检索对了但回答跑偏。解决办法是在提示词里给关键术语加简短说明让生成模型和检索的语义对齐。第三个坑是忽略了多轮对话的指代问题。用户第一轮问“部署流程”第二轮问“它需要多久”这个“它”在检索时完全丢失了指代对象检索结果乱七八糟。后来我在查询改写环节把对话历史带上让改写模型把指代消解掉问题才解决。7.3 性能与成本的平衡技巧成本和性能的平衡贯穿始终。我的几个做法嵌入计算做缓存相同文本不重复算检索结果做短时缓存高频问题直接命中生成模型分级简单问题走小模型批量入库放在低峰时段跑。这些优化单独看都不起眼叠加起来能把整体成本压下来不少。还有一个容易被忽略的点向量库的索引类型选择。不同的索引类型在召回速度和精度上有差异数据量不大的时候用精确检索就行数据量上来了再换近似检索。我一开始就上了近似索引结果召回精度下降后来数据量没到那个程度换回精确检索反而又快又准。8. 上线后的迭代与个人体会系统上线后我养成了一个习惯每周看一遍用户的实际提问日志。这些日志比任何评估集都真实能暴露很多设计时想不到的问题。比如有用户问“这个功能和那个功能有什么区别”这种对比类问题一开始答得很差因为检索只召回了一个功能的内容。后来我针对对比类问题做了特殊处理强制两路检索分别召回两个对象的内容效果才上来。还有一个体会是知识库的质量决定了系统的上限。模型再强资料本身写得含糊答案也不可能清晰。我在迭代过程中花了不少时间反过来推动文档规范化——把过时的文档标记归档把关键参数集中到一处这些工作看着和系统无关但对问答质量的提升是根本性的。如果让我给准备动手的人一句建议那就是先把评估集建起来再开始调参。没有评估的调优就是盲人摸象改了半天不知道是变好还是变坏。评估集不用大但一定要真实来自实际用户的问题最有价值。这套系统没有什么高深的技术拼的是对细节的耐心——切块切得细一点检索多一路提示词多改几版答案就靠谱一分。
企业数字化 ERP 产品动态
相关推荐
基于FFmpeg进程与标准输入管道的C# RTMP推流实践 我最近在做一套桌面端的画面采集与转播工具,核心诉求只有一个:把本机采集到的画面和声音,稳稳当当地推到一台流媒体服务器上。听起来像是个被做烂的需求,可真落到 C# 这块地界上,能直接抄的完整方案并不多——大部分资料要么停在几行命令行 demo,要么是别… · 2026/11/6 2:36:55
C#基于FFmpeg实现RTMP直播推流:参数、进程封装与稳定性实践 1. 先搞清楚这条链路上到底谁在干活做直播推流这件事,绕不开一个现实:C# 生态里没有一块像 FFmpeg 这样成熟度、格式覆盖面、编码器支持都齐活的音视频全家桶。我见过不少项目一开始雄心勃勃,想用 C# 从头做采集、编码、RTMP 封装,… · 2026/11/6 2:36:55
第五套内网靶场复盘:ThinkPHP 5.0 RCE到域横向移动 1. 这套靶场到底在练什么:整条链路的骨架拆解第一次把某安全团队公开发布的第五套内网靶场环境解压、导入虚拟化平台,我盯着那台只对外暴露 Web 服务的入口机看了挺久。这套环境在圈子里流传很广,很多人把它当成"入门内网"的第一课… · 2026/11/6 2:36:55
朴素贝叶斯实战:小数据、低延迟、高可解释场景下的工程落地指南 1. 为什么今天还要学朴素贝叶斯?——一个被低估的“老派”算法的真实战场很多人看到“朴素贝叶斯”四个字,第一反应是:教科书里的古董、面试题里的背景板、调包时顺手点掉的sklearn选项。我第一次在某高校实验室带本科生做文本分类项目时&… · 2026/11/6 2:39:56
二阶锥松弛+改进禁忌克隆遗传算法:配电网故障重构MATLAB实现 简介:面向电力系统自动化与智能电网方向的技术人员及研究人员,这份PDF文档围绕二阶锥松弛(SOCP)在主动配电网故障重构中的应用展开,将非凸潮流方程转化为可解的凸优化形式,并结合改进的禁忌克隆遗传算法搜索… · 2026/11/6 2:39:56
ONTAP集群初始化:CLI建集群、NTP同步与SVM模板实战 简介:这份PDF面向联想凌拓ThinkSystem DM系列存储的运维与实施人员,聚焦命令行界面(CLI)初始化配置场景,解决GUI向导无法覆盖的集群搭建问题,如MetroCluster配置或仅限IPv6的环境。资源包仅含1个PDF文件&am… · 2026/11/6 2:39:56
MySQL连接数优化与高并发解决方案 1. 问题现象与本质分析上周五凌晨2点,我负责维护的电商平台数据库突然告警,前端页面大面积出现"504 Gateway Timeout"错误。登录服务器查看MySQL日志,满屏都是"ERROR 1040 (HY000): Too many connections"的报错信息。这… · 2026/11/6 2:39:56
ASP FileSystemObject 目录遍历与文件列表安全实践 做企业内网系统的人大概都遇到过这种需求:后台要挂一个"文档下载"页面,把服务器某个上传目录下的子文件夹和文件全列出来,让同事点着下。需求听起来比增删改查还简单,我第一次接的时候也是这么想的——调用Scripting.Fi… · 2026/11/6 2:39:56
原生应用选型、架构与性能优化实战指南 原生应用这四个字,放在五年前是个默认选项,放在今天反而成了一个需要反复论证的选择。我最近刚把一个团队从跨平台方案迁回原生,整个过程踩的坑比预想的多得多,也让我重新梳理了一遍:什么样的项目真的该做原生… · 2026/11/6 2:38:56
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
ASIC主模式RTL设计:状态机与三态驱动的流片级实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07