Python商品RAG实战:Embedding+SQLite实现关键词与向量混合检索
前言Function Calling解决了大模型与Python工具之间的结构化通信问题但模型仍然需要先找到与用户需求相关的商品。只使用WHERE product_name LIKE %关键词%可以处理“查询足球”这样的明确问题却难以理解“我准备跑马拉松需要透气并且能支撑足弓的装备”这类自然语言需求。本文基于电商客服AI Agent的真实代码使用text-embedding-v3、JSON向量索引、余弦相似度和中文关键词匹配实现轻量混合RAG。语义检索负责理解需求关键词检索负责保留商品名和品牌等精确信号两种分数融合后返回Top-K候选商品。文章还会重点解释一个容易被忽略的边界RAG结果只是候选不是价格和库存的权威证据。候选商品必须继续通过SQLite按商品ID验证才能进入最终回答。文章目录前言项目效果与流程图一、为什么商品查询需要RAG二、RAG的三个阶段三、RAG相关目录结构四、Embedding配置embedding_modelembedding_dimensionstop_k五、调用Embedding接口输入返回值调试提示六、商品如何转换为检索文档七、从SQLite读取商品八、构建商品向量索引九、运行索引构建脚本十、余弦相似度十一、中文关键词评分十二、混合检索核心有向量索引时没有向量索引时top_k限制十三、为什么is_verified_fact是False十四、将检索器包装为工具十五、离线假向量测试十六、完整运行步骤十七、断点调试建议十八、常见问题与解决方法1. 找不到product_embeddings.json2. 没有索引时程序是否完全不可用3. Embedding接口连接失败4. 向量维度不是10245. 商品数量和向量数量不一致6. 精确商品名排序不靠前7. 语义需求召回错误商品8. top_k传入0或很大数值9. JSON索引文件过大10. 修改价格后是否需要重建索引11. RAG结果能否直接回答库存12. 测试通过是否代表真实模型准确十九、性能与工程优化二十、总结项目效果与流程图商品RAG检索流程。用户问题先转换成查询向量再与商品索引进行混合评分最终返回候选商品ID。混合检索分数构成。存在向量索引时语义分数占65%关键词分数占35%。商品向量索引结构。每条记录保存商品ID、检索文档和1024维向量。检索与验证的边界。RAG负责召回候选SQLite负责确认商品事实。一、为什么商品查询需要RAG传统名称查询通常是SELECT*FROMproductsWHEREproduct_nameLIKE%足球%;它适合用户明确说出标准商品名但存在三个限制用户可能只描述用途不说商品名。同一种需求可以有多种自然语言表达。商品描述、规格和使用场景中的信息没有被充分利用。例如用户提问我想参加长距离跑步需要透气并且能支撑足弓的产品。数据库中的标准名称是“跑步鞋”。如果只匹配用户原句中的商品名称查询可能没有结果。Embedding可以把用户需求和商品描述转换到相同的向量空间使语义相近的文本获得较高相似度。二、RAG的三个阶段RAG是Retrieval-Augmented Generation即检索增强生成。Retrieval检索相关商品 ↓ Augmentation把检索结果加入模型上下文 ↓ Generation模型基于上下文生成回答本项目进一步增加SQL验证用户问题 → 混合RAG召回候选商品ID → SQLite按ID查询权威记录 → 模型基于验证结果回答因此项目中的RAG并不直接承担最终事实输出。三、RAG相关目录结构function_calling_rag_agent/ │ ├── config.py ├── llm_client.py ├── database.py │ ├── data/ │ ├── products.db │ └── product_embeddings.json │ ├── rag/ │ ├── __init__.py │ ├── retriever.py │ └── build_index.py │ ├── tools/ │ └── product_tools.py │ └── tests/ └── test_retriever.py文件职责如下文件职责config.py保存数据库、索引路径和Embedding配置llm_client.py调用百炼Embedding接口retriever.py构建索引、计算相似度和混合评分build_index.py执行真实向量索引构建product_tools.py将检索器包装为Function Calling工具test_retriever.py使用假向量离线验证检索流程四、Embedding配置项目配置fromdataclassesimportdataclassfrompathlibimportPath PROJECT_ROOTPath(__file__).resolve().parent DATABASE_PATHPROJECT_ROOT/data/products.dbINDEX_PATHPROJECT_ROOT/data/product_embeddings.jsondataclass(frozenTrue)classSettings:base_url:strhttps://dashscope.aliyuncs.com/compatible-mode/v1chat_model:strqwen-plusembedding_model:strtext-embedding-v3embedding_dimensions:int1024max_iterations:int10top_k:int3SETTINGSSettings()embedding_modeltext-embedding-v3负责把文本转换成向量。embedding_dimensions1024每段商品文本会转换为长度1024的浮点数列表。向量维度不是商品数量也不是词语数量而是模型用于表达文本语义特征的数值空间。top_k默认返回3个候选商品。候选太少可能漏掉相关结果太多则会增加后续SQL查询和模型上下文内容。五、调用Embedding接口defembed_texts(client:OpenAI,texts:list[str])-list[list[float]]:responseclient.embeddings.create(modelSETTINGS.embedding_model,inputtexts,dimensionsSETTINGS.embedding_dimensions,)return[item.embeddingforiteminresponse.data]输入texts:list[str]可以一次传入多条商品文档减少逐条请求造成的网络往返。返回值list[list[float]]外层列表对应输入文本内层列表是每条文本的1024维向量。调试提示建议在返回前观察len(texts) len(response.data) len(response.data[0].embedding)正常情况下输入数量与返回数量一致单条向量长度为1024。不要打印全部向量否则控制台会出现大量浮点数。六、商品如何转换为检索文档product_document()把SQLite的一条商品记录拼成自然语言defproduct_document(product:dict)-str:return(f商品ID{product[product_id]}f商品名称{product[product_name]}f品牌{product[brand]}f描述{product[description]}f规格{product[specifications]}f适用场景{product[usage]})价格和库存没有放进检索文档。原因是价格和库存属于可能变化的业务事实最终回答必须重新查询SQLite而不是依赖旧向量索引中的文本。以跑步鞋为例文档类似商品ID004商品名称跑步鞋品牌阿迪达斯 描述适合长距离跑步舒适透气提供良好的足弓支撑 规格多种尺码透气网布适用场景长跑、日常训练文档字段越贴近用户真实表达语义检索越容易召回相关商品。七、从SQLite读取商品defload_products()-list[dict]:withconnect()asconnection:rowsconnection.execute(SELECT * FROM products ORDER BY product_id).fetchall()returnrows_to_dicts(rows)返回结构[{product_id:004,product_name:跑步鞋,description:适合长距离跑步...,specifications:多种尺码透气网布,usage:长跑、日常训练,brand:阿迪达斯,price:500.0,stock_quantity:20}]检索器读取全部商品用于构建轻量索引。当前只有10条数据这种方式足够直观商品数量很大时应考虑增量索引和专业检索服务。八、构建商品向量索引defsave_embedding_index(embedder,index_pathINDEX_PATH)-int:productsload_products()documents[product_document(product)forproductinproducts]vectorsembedder(documents)payload[{product_id:product[product_id],document:document,embedding:vector,}forproduct,document,vectorinzip(products,documents,vectors,strictTrue)]index_path.parent.mkdir(parentsTrue,exist_okTrue)index_path.write_text(json.dumps(payload,ensure_asciiFalse),encodingutf-8)returnlen(payload)zip(..., strictTrue)要求商品、文档和向量数量完全一致。如果Embedding接口少返回一条程序会及时报错避免商品ID与错误向量对应。索引最终保存为JSON[{product_id:004,document:商品ID004商品名称跑步鞋...,embedding:[0.012,-0.038,0.104]}]示例只展示少量数值真实向量包含1024个浮点数。九、运行索引构建脚本rag/build_index.pyfromllm_clientimportcreate_client,embed_textsfromrag.retrieverimportsave_embedding_indexif__name____main__:clientcreate_client()countsave_embedding_index(lambdatexts:embed_texts(client,texts))print(f向量索引构建完成共写入{count}条商品记录。)运行python rag/build_index.py项目实际构建结果为向量索引构建完成共写入10条商品记录。修改商品名称、描述、规格、品牌或用途后需要重新构建索引。只修改价格或库存时不需要因为这两个字段不在检索文档中。十、余弦相似度def_cosine_similarity(left:list[float],right:list[float])-float:numeratorsum(a*bfora,binzip(left,right,strictTrue))left_normmath.sqrt(sum(value*valueforvalueinleft))right_normmath.sqrt(sum(value*valueforvalueinright))ifleft_norm0orright_norm0:return0.0returnnumerator/(left_norm*right_norm)公式为cosine(A, B) A·B / (||A|| × ||B||)通俗理解它比较两个向量的方向是否接近。用户问题与某个商品文档语义越接近余弦相似度通常越高。当前项目使用Python标准库计算没有依赖NumPy。数据量只有10条时性能足够大规模商品库不适合逐条Python计算。十一、中文关键词评分def_lexical_score(query:str,document:str)-float:normalized_queryre.sub(r\s,,query.lower())normalized_documentre.sub(r\s,,document.lower())ifnotnormalized_query:return0.0tokensset(normalized_query)tokens.update(normalized_query[index:index2]forindexinrange(len(normalized_query)-1))matchessum(1fortokenintokensiftokenandtokeninnormalized_document)exact_bonus2ifnormalized_queryinnormalized_documentelse0returnmin(1.0,(matchesexact_bonus)/max(len(tokens),1))这段代码生成两类Token单个字符例如“跑”“步”“鞋”。连续两个字符例如“跑步”“步鞋”。二元词组比单字更能保留中文局部语义。若完整查询直接出现在商品文档中再增加精确匹配奖励。这不是专业中文分词器而是适合小型教学项目的轻量实现。十二、混合检索核心classHybridProductRetriever:def__init__(self,embedderNone,index_pathINDEX_PATH):self.embedderembedder self.index_pathindex_pathdefsearch(self,query:str,top_k:int3)-list[dict]:productsload_products()semantic_scores{}ifself.embedderandself.index_path.exists():indexjson.loads(self.index_path.read_text(encodingutf-8))query_vectorself.embedder([query])[0]semantic_scores{item[product_id]:_cosine_similarity(query_vector,item[embedding])foriteminindex}results[]forproductinproducts:documentproduct_document(product)lexical_lexical_score(query,document)semanticsemantic_scores.get(product[product_id],0.0)combined(0.65*semantic0.35*lexicalifsemantic_scoreselselexical)ifcombined0:results.append({product_id:product[product_id],product_name:product[product_name],retrieval_score:round(combined,4),matched_document:document,is_verified_fact:False,})results.sort(keylambdaitem:item[retrieval_score],reverseTrue)returnresults[:max(1,min(top_k,10))]有向量索引时combined 0.65 × semantic 0.35 × lexical语义相似度权重更高关键词分数用于保留精确名称和品牌信号。没有向量索引时combined lexical检索器自动降级为关键词模式基本查询仍然可用但自然语言需求理解能力会下降。top_k限制results[:max(1,min(top_k,10))]保证至少返回1条、最多返回10条防止模型传入异常值导致大量候选进入上下文。十三、为什么is_verified_fact是False候选结果包含is_verified_fact:False它明确提醒模型和程序这条记录只是检索候选 不等于价格、库存、品牌已经验证search_products工具还会返回警告{warning:(候选结果不是权威事实必须用get_product_details进行SQL验证。)}RAG负责找到相关商品下一步必须根据product_id查询SQLite。十四、将检索器包装为工具defsearch_products(retriever:HybridProductRetriever,query:str,top_k:int3)-dict:candidatesretriever.search(queryquery,top_ktop_k)return{status:(candidates_foundifcandidateselsenot_found),source:hybrid_rag_index,warning:(候选结果不是权威事实必须用get_product_details进行SQL验证。),candidates:candidates,}Function Calling模型只接触这个工具返回的JSON不需要了解余弦相似度和索引文件的内部实现。十五、离线假向量测试真实Embedding接口需要网络和API。测试使用确定性假向量deffake_embedder(texts:list[str])-list[list[float]]:vectors[]fortextintexts:vectors.append([float(长跑intextor跑步intext),float(足球intext),float(瑜伽intext),])returnvectors测试代码deftest_semantic_retrieval_returns_running_shoes(self):withtempfile.TemporaryDirectory()asdirectory:index_pathPath(directory)/index.jsonsave_embedding_index(fake_embedder,index_path)retrieverHybridProductRetriever(fake_embedder,index_path)resultsretriever.search(我想长跑需要透气和足弓支撑,top_k1)self.assertEqual(results[0][product_id],004)self.assertFalse(results[0][is_verified_fact])该测试验证自然语言需求能够召回跑步鞋。Top-1商品ID为004。检索结果仍被标记为未验证事实。它不证明真实Embedding模型的准确率也没有计算召回率等指标。十六、完整运行步骤进入新版项目目录。cdD:\Jupyter_Projects\PythonProject\大模型学习\AI Agent\function_calling_rag_agent初始化数据库。python scripts/initialize_database.py构建真实向量索引。python rag/build_index.py运行离线检索测试。python-munittest tests.test_retriever-v启动完整客服Agent。python main.py输入语义需求。我想进行长距离跑步需要透气并且能支撑足弓的商品。十七、断点调试建议推荐断点documents [...]查看商品检索文档。vectors embedder(documents)检查向量数量。query_vector self.embedder([query])[0]检查查询向量。_cosine_similarity(...)比较单个候选分数。combined ...观察两种分数融合。results.sort(...)查看排序前后结果。关键变量query documents query_vector semantic_scores lexical semantic combined results不要在调试器中展开全部1024维向量优先观察长度、前5个数值和最终相似度。十八、常见问题与解决方法1. 找不到product_embeddings.json原因尚未运行索引构建脚本或索引路径错误。解决运行python rag/build_index.py并检查INDEX_PATH。2. 没有索引时程序是否完全不可用原因误以为RAG只能依赖向量。解决当前代码会降级到关键词评分但语义召回能力会下降。3. Embedding接口连接失败原因网络权限、接口地址或系统环境变量存在问题。解决检查网络和DASHSCOPE_API_KEY不要打印密钥。4. 向量维度不是1024原因模型配置、dimensions参数或旧索引不一致。解决确认配置后重新构建完整索引。5. 商品数量和向量数量不一致原因Embedding返回不完整或构建过程中数据变化。解决zip(strictTrue)会报错应重新构建而不是忽略差异。6. 精确商品名排序不靠前原因语义分数占比较高关键词特征不足。解决检查lexical分数按真实测试调整权重不要凭感觉修改。7. 语义需求召回错误商品原因商品描述过短、字段缺失或需求表达模糊。解决完善描述、规格和适用场景并建立带标准答案的评测集合。8. top_k传入0或很大数值原因模型参数可能异常。解决当前代码将结果限制在110条Schema也定义相同范围。9. JSON索引文件过大原因商品数量和向量维度增加。解决当前方案适合小数据教学大规模场景需要专业向量存储。10. 修改价格后是否需要重建索引原因不清楚哪些字段进入检索文档。解决价格和库存未写入检索文档无需重建描述、规格等变化需要重建。11. RAG结果能否直接回答库存原因混淆候选召回和事实验证。解决不能。必须调用get_product_details查询SQLite。12. 测试通过是否代表真实模型准确原因离线测试使用了人为设计的三维假向量。解决它只验证程序流程真实效果需要单独构建查询集和召回指标。十九、性能与工程优化商品数据量增加时改为增量索引。批量调用Embedding减少网络往返。缓存高频查询向量。为索引增加版本和生成时间元数据。商品更新后只重建受影响记录。建立真实查询—商品ID评测集合。使用RecallK、MRR等检索指标评估权重。大规模数据迁移到专业向量数据库。对索引文件进行完整性校验。把检索耗时和候选分数写入调试日志。这些属于后续工程化方向当前项目没有实现相应性能指标或专业向量数据库。二十、总结本文完成了一个轻量商品混合RAG检索器。项目将商品名称、品牌、描述、规格和适用场景组成检索文档使用text-embedding-v3生成1024维向量并将向量保存到JSON索引。查询时用户问题同样转换为向量程序使用余弦相似度得到语义分数同时通过中文字符和二元词组计算关键词分数。存在向量索引时最终分数由65%语义分数和35%关键词分数组成索引不可用时则降级为关键词检索。最重要的设计边界是RAG只返回候选商品ID并明确标记is_verified_factFalse。价格、库存、品牌和优惠必须继续通过SQLite验证。下一篇将分析SQL复核、Decimal价格计算、EvidenceLedger和最终回答拦截如何共同降低模型幻觉。

相关新闻

Unity WebGL与HTML深度集成实战:从构建到通信的完整指南

Unity WebGL与HTML深度集成实战:从构建到通信的完整指南

1. 项目概述:为什么Unity WebGL与HTML的集成如此重要? 如果你和我一样,从Unity原生平台开发转向WebGL,最初可能会觉得这不过是换了个发布平台,把游戏“放上网”而已。但真正上手后才发现,Unity WebGL与HTML…

2026/10/17 2:05:12 阅读更多 →
Facepunch.Steamworks:C#游戏接入Steam平台的优雅解决方案

Facepunch.Steamworks:C#游戏接入Steam平台的优雅解决方案

1. 项目概述:为什么你需要Facepunch.Steamworks? 如果你正在用Unity或者任何.NET环境(比如Godot的C#脚本、.NET桌面应用)开发游戏,并且想让你的游戏上架Steam平台,那么集成Steamworks API就是你绕不开的一步…

2026/10/18 23:19:09 阅读更多 →
像素升维重建+八大自研引擎:港口港区视频孪生虚实同源,构筑口岸立体化纵深管控底座

像素升维重建+八大自研引擎:港口港区视频孪生虚实同源,构筑口岸立体化纵深管控底座

像素升维重建八大自研引擎:港口港区视频孪生虚实同源,构筑口岸立体化纵深管控底座一、 前言:港口数字孪生的实战化跃迁 随着全球贸易枢纽数字化转型步入深水区,港口作为水陆交通的集结点和跨国供应链的核心节点,其治理…

2026/10/18 14:41:48 阅读更多 →

最新新闻

XERJ Second Brain 实战指南:一条命令把笔记文件夹变成可查询、可重放、带证据的链接索引

XERJ Second Brain 实战指南:一条命令把笔记文件夹变成可查询、可重放、带证据的链接索引

人工智能搜索引擎本地部署后端MCP 服务 【免费下载链接】xerj XERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for sear…

2026/10/18 23:21:26 阅读更多 →
TradeX-TPS交易插件:可审计的实盘风控代理层设计

TradeX-TPS交易插件:可审计的实盘风控代理层设计

简介:TradeX-TPS交易插件系统v1.0是一款面向量化交易开发者与券商系统集成人员的轻量级股票自动化交易工具,专为适配大智慧、通达信等主流行情软件设计,解决第三方程序对接券商交易通道难、接口兼容性差、清仓/撤单逻辑封装不足等实际问题。资…

2026/10/18 23:21:26 阅读更多 →
CAPL_系统变量函数_数据类型_独立函数参数

CAPL_系统变量函数_数据类型_独立函数参数

🍅 我是蚂蚁小兵,专注于车载诊断领域,尤其擅长于对CANoe工具的使用🍅 寻找组织 ,答疑解惑,摸鱼聊天,博客源码,点击加入👉【相亲相爱一家人】🍅 玩转CANoe&…

2026/10/18 23:21:26 阅读更多 →
deepseek-r1落地指南:用Ollama搭web-ui并接入本地代码编辑器

deepseek-r1落地指南:用Ollama搭web-ui并接入本地代码编辑器

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 23:21:26 阅读更多 →
高速充电桩预约小程序:微信+Spring Boot全栈实践

高速充电桩预约小程序:微信+Spring Boot全栈实践

简介:本资源是一套完整的微信小程序毕业设计项目——高速公路服务区充电桩在线预订系统,面向计算机专业本科生及小程序开发初学者,解决新能源出行场景下充电服务信息不透明、预约流程缺失的现实问题。压缩包共1131个文件,25.72MB&…

2026/10/18 23:21:26 阅读更多 →
FX3U与得利捷扫码枪RS232无协议通信实战:从接线到解析

FX3U与得利捷扫码枪RS232无协议通信实战:从接线到解析

搞工业自动化的兄弟都懂,扫码枪和PLC联机这套活,说简单是真简单——串口线一接、参数一对,数据就进来了;说麻烦也是真麻烦,现场变频器一开、地线没接好、参数差一位,一个下午就交代进去了。上个月我在一条包…

2026/10/18 23:20:26 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →