【Ragflow】9.问答为什么比搜索响应慢?从源码角度深入分析
概述
前几天看到有群友提到一个现象:Ragflow的搜索界面的问答速度很快,但聊天界面的问答速度很慢,究竟是什么原因?
在深入挖掘源码之后,逐渐发现了问题的答案。
调试准备
ragflow 默认推荐docker的部署方式,但不利于本地部署调试。
下面使用本地环境来运行,在docker中关闭ragflow-server
,仅保留其它相关组件容器保持运行状态。
运行前端环境,打开ragflow/web,安装依赖:
npm install
启动前端:
npm run dev
运行后端环境,需要先根据pyproject.toml或uv.lock配置当前环境,官方给了一个docker/launch_backend_service.sh
这样一个liunx的后端启动脚本,如果是windows系统,可直接执行:
python -m api.ragflow_server
前后端均启动后,访问前端控制台显示地址,可正常登录访问。
聊天后台响应逻辑
下面在聊天界面,发送问题,观察后台日志如下:
2025-04-01 23:00:15,761 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:00:15] "POST /v1/conversation/set HTTP/1.1" 200 -
2025-04-01 23:00:15,812 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:00:15] "GET /v1/conversation/list?dialog_id=ec69b3f4fbeb11ef862c0242ac120002 HTTP/1.1" 200 -
2025-04-01 23:00:16,217 ERROR 2724 LLMBundle.encode_queries can't update token usage for d3de1596fb6911efa0f40242ac120006/EMBEDDING used_tokens: 7
2025-04-01 23:00:16,470 INFO 2724 POST http://localhost:1200/ragflow_d3de1596fb6911efa0f40242ac120006/_search [status:200 duration:0.101s]
2025-04-01 23:00:17,526 INFO 2724 HTTP Request: POST http://10.195.140.47:11434/api/chat "HTTP/1.1 200 OK"
2025-04-01 23:00:17,673 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:00:17] "POST /v1/conversation/completion HTTP/1.1" 200 -
从中可以注意到,在聊天界面对话时,前端实际是通过/v1/conversation/completion
这个接口向后端请求数据。
进一步查看源码,后端在得到前端请求后,响应逻辑如下(下面的缩进表示函数调用的层次关系):
- conversation_app.py 中的 completion 函数处理请求:
- 调用api\db\services\dialog_service.py里的 chat 函数
- embd_mdl 从 LLMBundle 得到 embedding 模型
- 自动读取用户最后3条问题信息
- 如果存在历史对话信息,并开启多轮对话优化,调用 rag\prompts.py 的 full_question 进行问题优化,这一点之前文章分析过,不做赘述
- 如果开启关键词分析,调用 rag\prompts.py 的 keyword_extraction 进行一轮额外模型询问,提取关键词
- 如果开启推理,调用 agentic_reasoning\deep_research.py 进行对问题进行推理
- 调用 rag\nlp\search.py 的 retrieval 进行知识库检索
- 如果填了tavily_api_key密钥,调用 rag\utils\tavily_conn.py 的 search 进行外部知识检索
- 如果开启知识图谱,额外进行知识图谱检索
- 调用 rag\prompts.py 的 kb_prompt 将检索到的知识库内容格式化为模型的提示词,这里有一个小的限制,即当检索到的知识数量超过97%模型的max_tokens时,会将未添加的知识进行舍弃。
- 如果开启显示引文,会加入额外的引文提示词作为系统prompt,具体内容见 rag\prompts.py 的 citation_prompt 函数。
- 调用 rag\prompts.py 的 message_fit_in,进一步对超出 95% max_tokens 限制的问题进行精简,这里的问题不包括额外添加的引文提示信息,精简逻辑是尽可能保留系统信息和最后一条用户信息,对超出部分进行截断。
- 调用 api\db\services\llm_service.py 的 chat_streamly 进行LLM问答交互
- 回答之后,处理引用信息,添加引用标记
- 得到回答内容,格式化成json格式,返回给前端
- 调用api\db\services\dialog_service.py里的 chat 函数
搜索后台响应逻辑
下面在搜索界面,发送问题,观察后台日志如下:
2025-04-01 23:07:10,491 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:07:10] "POST /v1/conversation/ask HTTP/1.1" 200 -
2025-04-01 23:07:10,639 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:07:10] "POST /v1/chunk/retrieval_test HTTP/1.1" 200 -
2025-04-01 23:07:10,761 ERROR 2724 Image not found.NoneType: None
2025-04-01 23:07:10,764 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:07:10] "GET /v1/document/image/undefined HTTP/1.1" 200 -
2025-04-01 23:07:10,931 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:07:10] "GET /v1/document/thumbnails?doc_ids=eb47531afbea11ef8f2e0242ac120002-fe5c351b2c6ad031 HTTP/1.1" 200 -
2025-04-01 23:07:16,311 INFO 2724 127.0.0.1 - - [01/Apr/2025 23:07:16] "POST /v1/conversation/related_questions HTTP/1.1" 200 -
可以看到,搜索时,前端实际通过/v1/conversation/ask
这个接口向后端请求数据。
后端在得到前端请求后,响应逻辑如下:
- conversation_app.py 中的 ask_about 函数处理请求:
- 调用 api\db\services\dialog_service.py里的 ask 函数
- embd_mdl 从 LLMBundle 得到 embedding 模型
- 过滤知识库信息,比如有些知识库是共用,有些是私用,所有选定知识库选择唯一的一条记录信息
- 调用 rag\nlp\search.py 的 retrieval 进行知识库检索
- 调用 rag\prompts.py 的 kb_prompt 将检索到的知识库内容格式化为模型的提示词
- 采用默认提示词,用于作为模型系统提示词
- 调用 api\db\services\llm_service.py 的 chat_streamly 进行LLM问答交互
- 回答之后,处理引用信息,添加引用标记
- 得到回答内容,格式化成json格式,返回给前端
- 调用 api\db\services\dialog_service.py里的 ask 函数
处理完之后,前端实际还会通过/v1/conversation/related_questions
这个接口,再次向模型发一轮询问,以显示相关问题提示,不过这不影响搜索界面的回答速度,不在本文的讨论范围之内,这里不作展开。
聊天响应慢的真相
比较两者的具体处理方式,可以看出,实际上两者都共用了数据库检索、模型问答等接口,主要区别在于,聊天的响应中包含了大量的数据处理方式。
比如,开启关键词分析和多轮对话优化后,系统都会启动一轮额外的交互使速度变慢(两个同时开就是额外两轮交互),多轮对话在默认情况下是开启的。这是响应速度慢的主要原因。
其次,聊天界面的会自动将最近三轮的对话纳入提示词之中,如果开启显示引文后,系统会自动的塞入一大段系统提示词,这某种程度也略微增加了一点响应时间,不过只是次要原因。
下面通过一个简单的小实验来证实推断:
使用本地部署的DeepSeek-r1:70b模型,聊天界面关闭所有可选项,响应时间(从用户发起请求到模型开始输出第一个字)约3秒,时间和搜索界面差不多。
如果开启显示引文、关键词分析、多轮对话优化之后,相同问题,聊天界面响应时间约13秒,的确慢了不少。
搜索功能分析
搜索实际上的作用是通过自然语言,对指定知识库文件进行语义检索。为什么要在这里塞个不考虑聊天历史的一次性问答?
当我把这个功能给客户演示时,客户也产生了相同的疑问,不禁思考,这个功能是否有存在的必要?
在后续二次开发的ragflow-plus中,也许会考虑将该功能移除掉。