最近对rag的一些想法和实践
- 内容介绍
- 文章标签
- 相关推荐
去年这会儿,大家都在搭rag,从dify, coze到ragflow,但是经常会遇到一个问题,demo好整,但是效果嘛,不好说。现在已经有一些解决方案,比如用easydataset做qa对,用Hyde扩展回答的范围,用RAG去扩展问题的范围。但是还是有一个我认为不能理解的,那就是向量相似度不等于语义相关性。如果不做qa对,仅仅按chunk切分,会导致什么问题呢,三重语义断裂——代词指代丢失、跨句依赖断裂、术语定义分离。更恼火的还有,Embedding导致的多个不确定的方面:比如模型选择、分块策略、相似度阈值导致相同的查询返回不同的结果。现在其实也有很多项目已经认识到这点,抛弃embbeding,比如:pageindex,sirchmunk,tree-search等等等,我研究了这些agentic+no embbeding的方案,然后有了一些自己的想法和实践。
解决之道是Agentic RAG范式转换:把语义理解还给LLM,检索系统只负责提供确定性结构。
以下是具体的实现方法和路径:
用SQLite+FTS5替代向量数据库,保留文档原始层级(章节-条款-段落),通过BM25关键词匹配+迭代收敛算法(自动调整查询词直到命中2-3篇核心文档),让LLM像查阅目录般精准导航。同时保留检索路径,这样整个过程完全可审计,每次查询轨迹都有完整记录。
这套机制的工程价值在于:零外部依赖、单人可维护(确定性代码易调试)。很适合法规合规、企业SOP、医疗指南等需要"精确溯源"的场景。
核心思想是信任LLM的理解与推理能力——不需要系统替它猜答案,只需要系统可靠地帮它找到内容。
注意:这个技术方向不是通用的,对于实时对话要求很高的来说,不太适用。
实践项目在这里:yang478/auditable-knowledge-packs
使用方法,放在skills下,用LLM来进行知识库skills的构建,再用该skills问问题就可以了。
感谢多个项目:
pageindex,sirchmunk
ragflow的博客文章:从 RAG 到 Context - RAG 2025 年度回顾 | RAGFlow 引擎
等等等,不一一列举了。
附上一篇我知乎文章:https://zhuanlan.zhihu.com/p/2018830163186566801
欢迎讨论和分享,谢谢。
--【壹】--:
传统的检索有很多东西都可以再深入研究
--【贰】--:
不太一样,他还是用了embbeding模型的,不过也挺有意思
--【叁】--: alphaply:
很多时候update一下metadata(用于初筛),默认也会把content(非结构化内容)再过一遍embedding模型
现在我就遇到这种情况了,之前没有考虑metadata,后来发现如果想要在语义搜索时限制查询范围,需要全量跑一遍embedding。metadata变一次就得重新跑一次。
--【肆】--:
一个非常简单的想法和思路。
其实要看具体的使用场景,因为rag的检索结果是不稳定的,每次检索出来的分数,顺序基本上都是不一样的,叠加agentic针对用户的同一个问题,query甚至都可以不一样。
具体场景具体分析,检索的内容是从1000个当中检索50个,和从10000个chunk当中检索100个,难度是完全不一样的。
一个简单的做法就是,在检索之前先进行过滤,类似于搜广推当中的协同过滤概念一样,先进行初筛,然后精筛,最后重排(没错 看起来没用的rerank是来自于搜广推的思路,个人觉得对于强大的模型,chunk的前后顺序应该影响不会很大)
第二就是维护性的角度了,维护一个巨大的向量数据库比传统数据库麻烦很多,很多时候update一下metadata(用于初筛),默认也会把content(非结构化内容)再过一遍embedding模型。此外,就是构建这玩意的过程了,怎么切分,切分的配置参数,集合的划分,metadata的细节设计等等等。
哦,说到agentic的方式,如果llm是使用那些强大的模型,也就意味着速度是巨慢的,有一段thinking过程,包括高峰时期算力紧张,上下文长度太长等等,agentic rag的话可能更偏好那些小模型进行,也就意味着高度定制化是不可分割的。
希望我的回答能对你有所启发。
--【伍】--:
哈哈,我只能说demo上的500个chunk返回50个,和5000个chunk返回50个完全就是两码事,rag这种高度定制化的场景,也许就是未来小厂的主要业务之一了。
--【陆】--:
很多老板觉得向量数据库很神奇,但我觉得还不足以替代掉传统数据库,维护起来是灾难性的,现在仍缺乏一个很高效的方案,现在rag大多是agentic式的,要求agent在一定迭代次数内努力命中到想要搜索的东西,这些内容有可能是本身就会,有可能是可能猜得到,有可能是完全不会,这些都要交给llm根据context来进行判断。金钱燃烧的气息是香喷喷的,也许就像人类一样,大家都在一个互联网,条件背景不一样,信息差也是不一样的。agentic的方式,以及高度定制化(低代码也好,写代码也好),rag的未来如果没有一个神乎其神的妙计,可能就朝着这两个方面挖掘价值。个人更加偏向高度定制化,因为token的算力需求也好,能源需求也好,以及等待的时间,llm的分薪比人高。。。
--【柒】--: yyy2024:
对于实时对话要求很高的来说,不太适用
对于语义检索,有一个问题值得深思
这张显卡的价格30000
这张显卡的价格2999
我要查询,假设是纯语义进行检索。
“价格3000左右的显卡” 假设这就是query
哪一个chunk的分数比较高?
--【捌】--: yyy2024:
认识到这点,抛弃embbeding,比如:pageindex
挺好的方向,就是完成一次查询会非常慢。我记得之前graphrag里就引入了类似的机制,举个例子,在用户查询一个问题后,它会在后台用LLM重写5个用户可能问到的问题,然后进行语义检索。完成上面5个查询后,它会将这5个问题和最初问题放在上下文里,让LLM再生成5个问题,在进行一轮。然后还可以一轮一轮的运行。比如设置每次问5个问题,然后重复3轮。或者每次问20个问题,重复5轮。
但是非常慢就是了。
用SQLite+FTS5替代向量数据库,保留文档原始层级(章节-条款-段落),通过BM25关键词匹配+迭代收敛算法
这种方式感觉思路不错,特别是对于短文本,感觉短文本向量化代价比长文本大很多,存储是个问题,之前短文本向量化后体积比原文大了十多倍……
明天研究试试
--【玖】--: alphaply:
对于语义检索,有一个问题值得深思
这张显卡的价格30000
这张显卡的价格2999
我要查询,假设是纯语义进行检索。
“价格3000左右的显卡” 假设这就是query
哪一个chunk的分数比较高?
语义搜索,也许是可以理解2999的,2999的排在30000前面。
>>> for i, score in enumerate(scores):
... print(f"文本: {chunks[i]} | 得分: {score:.4f}")
...
文本: 这张显卡的价格30000 | 得分: 0.6643
文本: 这张显卡的价格2999 | 得分: 0.6706
佬友问的可能是这个问题:
chunk:
这张显卡的价格30003000
这张显卡的价格2999
query:
价格3000左右的显卡
最终是30003000的分数显著高于2999。但是我们理解的,应该是2999更高。
>>> for i, score in enumerate(scores):
... print(f"文本: {chunks[i]} | 得分: {score:.4f}")
...
文本: 这张显卡的价格30003000 | 得分: 0.7354
文本: 这张显卡的价格2999 | 得分: 0.6706
如果之后还接入reranker模型,可能还会进一步放大这种差距。
重排后的顺序和得分:
得分: 0.9255 | 文本: 这张显卡的价格30003000
得分: 0.2485 | 文本: 这张显卡的价格2999
但是如果只有少量的显卡价格相关数据,对上面的影响很小,可以将他们都交给LLM,让LLM进一步判断。
毕竟embed只是负责尽可能的召回数据,但是数据是不是准确,可以交给LLM去识别。
不过听说如果embed返回大量无关的信息,可能会造成LLM幻觉,还没有实际遇到这种场景,不太了解。
感觉这种问题无法直接在embedding阶段解决,只能交给后面的流程解决。
--【拾】--:
和字节的openviking 思路是不是有点类似
去年这会儿,大家都在搭rag,从dify, coze到ragflow,但是经常会遇到一个问题,demo好整,但是效果嘛,不好说。现在已经有一些解决方案,比如用easydataset做qa对,用Hyde扩展回答的范围,用RAG去扩展问题的范围。但是还是有一个我认为不能理解的,那就是向量相似度不等于语义相关性。如果不做qa对,仅仅按chunk切分,会导致什么问题呢,三重语义断裂——代词指代丢失、跨句依赖断裂、术语定义分离。更恼火的还有,Embedding导致的多个不确定的方面:比如模型选择、分块策略、相似度阈值导致相同的查询返回不同的结果。现在其实也有很多项目已经认识到这点,抛弃embbeding,比如:pageindex,sirchmunk,tree-search等等等,我研究了这些agentic+no embbeding的方案,然后有了一些自己的想法和实践。
解决之道是Agentic RAG范式转换:把语义理解还给LLM,检索系统只负责提供确定性结构。
以下是具体的实现方法和路径:
用SQLite+FTS5替代向量数据库,保留文档原始层级(章节-条款-段落),通过BM25关键词匹配+迭代收敛算法(自动调整查询词直到命中2-3篇核心文档),让LLM像查阅目录般精准导航。同时保留检索路径,这样整个过程完全可审计,每次查询轨迹都有完整记录。
这套机制的工程价值在于:零外部依赖、单人可维护(确定性代码易调试)。很适合法规合规、企业SOP、医疗指南等需要"精确溯源"的场景。
核心思想是信任LLM的理解与推理能力——不需要系统替它猜答案,只需要系统可靠地帮它找到内容。
注意:这个技术方向不是通用的,对于实时对话要求很高的来说,不太适用。
实践项目在这里:yang478/auditable-knowledge-packs
使用方法,放在skills下,用LLM来进行知识库skills的构建,再用该skills问问题就可以了。
感谢多个项目:
pageindex,sirchmunk
ragflow的博客文章:从 RAG 到 Context - RAG 2025 年度回顾 | RAGFlow 引擎
等等等,不一一列举了。
附上一篇我知乎文章:https://zhuanlan.zhihu.com/p/2018830163186566801
欢迎讨论和分享,谢谢。
--【壹】--:
传统的检索有很多东西都可以再深入研究
--【贰】--:
不太一样,他还是用了embbeding模型的,不过也挺有意思
--【叁】--: alphaply:
很多时候update一下metadata(用于初筛),默认也会把content(非结构化内容)再过一遍embedding模型
现在我就遇到这种情况了,之前没有考虑metadata,后来发现如果想要在语义搜索时限制查询范围,需要全量跑一遍embedding。metadata变一次就得重新跑一次。
--【肆】--:
一个非常简单的想法和思路。
其实要看具体的使用场景,因为rag的检索结果是不稳定的,每次检索出来的分数,顺序基本上都是不一样的,叠加agentic针对用户的同一个问题,query甚至都可以不一样。
具体场景具体分析,检索的内容是从1000个当中检索50个,和从10000个chunk当中检索100个,难度是完全不一样的。
一个简单的做法就是,在检索之前先进行过滤,类似于搜广推当中的协同过滤概念一样,先进行初筛,然后精筛,最后重排(没错 看起来没用的rerank是来自于搜广推的思路,个人觉得对于强大的模型,chunk的前后顺序应该影响不会很大)
第二就是维护性的角度了,维护一个巨大的向量数据库比传统数据库麻烦很多,很多时候update一下metadata(用于初筛),默认也会把content(非结构化内容)再过一遍embedding模型。此外,就是构建这玩意的过程了,怎么切分,切分的配置参数,集合的划分,metadata的细节设计等等等。
哦,说到agentic的方式,如果llm是使用那些强大的模型,也就意味着速度是巨慢的,有一段thinking过程,包括高峰时期算力紧张,上下文长度太长等等,agentic rag的话可能更偏好那些小模型进行,也就意味着高度定制化是不可分割的。
希望我的回答能对你有所启发。
--【伍】--:
哈哈,我只能说demo上的500个chunk返回50个,和5000个chunk返回50个完全就是两码事,rag这种高度定制化的场景,也许就是未来小厂的主要业务之一了。
--【陆】--:
很多老板觉得向量数据库很神奇,但我觉得还不足以替代掉传统数据库,维护起来是灾难性的,现在仍缺乏一个很高效的方案,现在rag大多是agentic式的,要求agent在一定迭代次数内努力命中到想要搜索的东西,这些内容有可能是本身就会,有可能是可能猜得到,有可能是完全不会,这些都要交给llm根据context来进行判断。金钱燃烧的气息是香喷喷的,也许就像人类一样,大家都在一个互联网,条件背景不一样,信息差也是不一样的。agentic的方式,以及高度定制化(低代码也好,写代码也好),rag的未来如果没有一个神乎其神的妙计,可能就朝着这两个方面挖掘价值。个人更加偏向高度定制化,因为token的算力需求也好,能源需求也好,以及等待的时间,llm的分薪比人高。。。
--【柒】--: yyy2024:
对于实时对话要求很高的来说,不太适用
对于语义检索,有一个问题值得深思
这张显卡的价格30000
这张显卡的价格2999
我要查询,假设是纯语义进行检索。
“价格3000左右的显卡” 假设这就是query
哪一个chunk的分数比较高?
--【捌】--: yyy2024:
认识到这点,抛弃embbeding,比如:pageindex
挺好的方向,就是完成一次查询会非常慢。我记得之前graphrag里就引入了类似的机制,举个例子,在用户查询一个问题后,它会在后台用LLM重写5个用户可能问到的问题,然后进行语义检索。完成上面5个查询后,它会将这5个问题和最初问题放在上下文里,让LLM再生成5个问题,在进行一轮。然后还可以一轮一轮的运行。比如设置每次问5个问题,然后重复3轮。或者每次问20个问题,重复5轮。
但是非常慢就是了。
用SQLite+FTS5替代向量数据库,保留文档原始层级(章节-条款-段落),通过BM25关键词匹配+迭代收敛算法
这种方式感觉思路不错,特别是对于短文本,感觉短文本向量化代价比长文本大很多,存储是个问题,之前短文本向量化后体积比原文大了十多倍……
明天研究试试
--【玖】--: alphaply:
对于语义检索,有一个问题值得深思
这张显卡的价格30000
这张显卡的价格2999
我要查询,假设是纯语义进行检索。
“价格3000左右的显卡” 假设这就是query
哪一个chunk的分数比较高?
语义搜索,也许是可以理解2999的,2999的排在30000前面。
>>> for i, score in enumerate(scores):
... print(f"文本: {chunks[i]} | 得分: {score:.4f}")
...
文本: 这张显卡的价格30000 | 得分: 0.6643
文本: 这张显卡的价格2999 | 得分: 0.6706
佬友问的可能是这个问题:
chunk:
这张显卡的价格30003000
这张显卡的价格2999
query:
价格3000左右的显卡
最终是30003000的分数显著高于2999。但是我们理解的,应该是2999更高。
>>> for i, score in enumerate(scores):
... print(f"文本: {chunks[i]} | 得分: {score:.4f}")
...
文本: 这张显卡的价格30003000 | 得分: 0.7354
文本: 这张显卡的价格2999 | 得分: 0.6706
如果之后还接入reranker模型,可能还会进一步放大这种差距。
重排后的顺序和得分:
得分: 0.9255 | 文本: 这张显卡的价格30003000
得分: 0.2485 | 文本: 这张显卡的价格2999
但是如果只有少量的显卡价格相关数据,对上面的影响很小,可以将他们都交给LLM,让LLM进一步判断。
毕竟embed只是负责尽可能的召回数据,但是数据是不是准确,可以交给LLM去识别。
不过听说如果embed返回大量无关的信息,可能会造成LLM幻觉,还没有实际遇到这种场景,不太了解。
感觉这种问题无法直接在embedding阶段解决,只能交给后面的流程解决。
--【拾】--:
和字节的openviking 思路是不是有点类似

