首页
/ Quivr 实战指南:基于 quivr-core 与 Chainlit 构建文档问答聊天机器人

Quivr 实战指南:基于 quivr-core 与 Chainlit 构建文档问答聊天机器人

2026-09-05 11:57:31作者:何举烈Damon

本文以仓库中的示例文档 backend/core/examples/chatbot/README.md 为主体,完整讲解如何用 Quivr 的 Brain 类和 Chainlit 快速搭建一个"上传文本文件即可提问"的聊天机器人:从环境准备、依赖安装、启动命令,到上传、问答的完整操作流程,并结合 quivr-core 的源码剖析 Brain 从文件构建、向量库默认配置、RAG 流式问答的底层实现链路,帮助你既会跑通示例,又理解每一步背后的机制。

一、示例定位与整体架构

该示例演示的核心链路非常清晰(引自 README):

  • Quivr 负责核心智能:将用户上传的文本文件构建成一个 "brain"(大脑),并基于该 brain 回答关于文件内容的问题;
  • Chainlit 负责交互层:提供 Web 聊天界面,处理文件上传和消息流式渲染。

对应到实现文件 backend/core/examples/chatbot/main.py,整个示例只有两个异步回调函数:

  • @cl.on_chat_start:等待用户上传 .txt 文件,调用 Brain.from_files(...) 构建 brain,并把 brain 存入 Chainlit 会话(cl.user_session);
  • @cl.on_message:每次用户提问时,取出会话中的 brain,调用 brain.ask_streaming(message.content) 逐 token 流式输出答案。

也就是说,示例把"文档 → 知识"和"提问 → 答案"这两件事分别收敛到了 Quivr 的两个入口:Brain.from_filesBrain.ask_streaming,Chainlit 只是把它们串起来。

二、环境准备与依赖安装

2.1 前置条件

  • Python 3.8 或更高版本(README 的官方要求)。需要注意的是,backend/core/pyproject.toml 中 quivr-core 源码实际使用了 typing.Self 等较新语法(brain.py 第 5 行 from typing import ... Self),从源码结构看,实际运行建议采用 Python 3.11+,以兼容示例所依赖的 quivr-core 发行版。

2.2 安装依赖

示例目录自带依赖清单 backend/core/examples/chatbot/requirements.txt,内容固定为两行:

quivr-core[base]==0.0.8
chainlit==1.1.306

克隆仓库后进入示例目录执行安装即可:

cd backend/core/examples/chatbot
pip install -r requirements.txt

这里有一个值得注意的细节:安装的是 quivr-core[base]base 额外依赖的版本。为什么必须带 base?查看默认实现 backend/core/quivr_core/brain/brain_defaults.py 可以发现,示例并没有显式传入向量库、嵌入器和 LLM,全靠默认实现兜底,而这三者的默认实现分别来自 LangChain 社区组件:

  • 向量库:build_default_vectordb 使用 langchain_community.vectorstores.FAISS 构建内存向量库(brain_defaults.py#L13-L30);
  • 嵌入器:default_embedder 使用 langchain_openai.OpenAIEmbeddings
  • LLM:default_llm 通过 LLMEndpoint.from_config(LLMEndpointConfig()) 构建,即 OpenAI 的 ChatOpenAI

如果导入失败,源码会抛出带明确指引的 ImportError,提示 "Please provide a valid vector store or install quivr-core['base'] package"。这就是 base 扩展依赖存在的意义。

另外,由于默认嵌入器和 LLM 都是 OpenAI 实现,实际运行前需要设置 OPENAI_API_KEY 环境变量,否则构建 brain 时会失败。

三、启动 Chainlit 服务

按照 README 的 "Running the Chatbot" 章节,启动方式只有一条命令:

chainlit run main.py

启动后在浏览器中打开终端提示的地址(通常是 http://localhost:8000)即可看到聊天界面。chainlit run 会加载 main.py 并注册其中的 @cl.on_chat_start@cl.on_message 回调,Chainlit 自身负责把回调中的 cl.Message 更新推送到前端 WebSocket,这也是后续"流式输出"能够生效的原因。

四、使用流程:上传文件到开始问答

README 给出的交互流程("Using the Chatbot" 章节)可以完整映射到 main.py 的源码行为:

  1. 界面加载后提示上传on_chat_start 中使用 cl.AskFileMessage 发送一个文件请求消息,参数直接限定了行为边界——

    files = await cl.AskFileMessage(
        content="Please upload a text .txt file to begin!",
        accept=["text/plain"],   # 仅接受纯文本
        max_size_mb=20,          # 大小上限 20MB
        timeout=180,             # 180 秒超时
    ).send()
    

    这与 README 中 "select a .txt file ... The file size should not exceed 20MB" 的说明一一对应。

  2. 文件处理:代码先以 UTF-8 读取上传文件,再写入一个 tempfile.NamedTemporaryFilesuffix=".txt"delete=False 以便 Quivr 稍后读取),然后把临时文件路径传给 Brain.from_files(name="user_brain", file_paths=[temp_file_path])。处理期间会先发一条 "Processing 文件名..." 的消息,构建完成后更新为 "Processing ... done. You can now ask questions!"。

  3. 开始提问:文件就绪后,用户直接在输入框提问。@cl.on_message 回调先检查会话中是否存在 brain(没有则提示 "Please upload a file first."),随后通过 ask_streaming 逐 chunk 调用 msg.stream_token(chunk.answer),实现答案边生成边显示。

    @cl.on_message
    async def main(message: cl.Message):
        brain = cl.user_session.get("brain")  # type: Brain
        if brain is None:
            await cl.Message(content="Please upload a file first.").send()
            return
        msg = cl.Message(content="")
        await msg.send()
        async for chunk in brain.ask_streaming(message.content):
            await msg.stream_token(chunk.answer)
        await msg.send()
    

五、How It Works 源码级解析:Brain 是怎么"读懂"文件的

README 的 "How It Works" 一句话概括为 "用 Quivr 库从上传的文本文件创建 brain,再用它回答问题"。下面沿着 Brain 的源码把这个过程展开。

5.1 构建 brain:Brain.from_files 的完整链路

from_files 是同步入口,内部通过事件循环直接运行异步版本 afrom_filesbrain.py#L169-L192)。afrom_files 做了四件事(brain.py#L118-L167):

  1. 补齐默认依赖llm 为 None 时调用 default_llm()embedder 为 None 时调用 default_embedder()
  2. 文件入库:对每个 file_paths 调用 load_qfile 加载为 Quivr 文件对象,再上传到 storage(默认是内存型的 TransparentStorage);
  3. 解析切块process_files 按文件扩展名从处理器注册表取出对应 processor,异步解析文件并产出 LangChain Document 列表;
  4. 构建向量库vector_db 为 None 时调用 build_default_vectordb(docs, embedder),即把所有文档用默认 FAISS 索引一次性建库。

关于第 3 步的处理器选择,注册表实现在 backend/core/quivr_core/processor/registry.py.txt 的默认处理器是 SimpleTxtProcessor(见 registry.py#L32-L47base_processorsFileExtension.txt 的映射)。这也解释了为什么示例只要求上传 .txt 文件——它是基础处理器注册表中原生支持的扩展名之一,无需额外依赖即可解析;而 PDF 等则依赖 Tika 等可选处理器。

5.2 回答问题的 RAG 流水线:ask_streaming

ask_streaming 是 brain 的异步流式问答入口(brain.py#L278-L309),内部流程为:

  1. 根据 rag_config 决定使用哪个 LLM(示例未传 rag_config,直接使用 brain 自带的默认 LLM);
  2. 构建 QuivrQARAG 流水线(backend/core/quivr_core/quivr_rag.py);
  3. 调用 rag_pipeline.answer_astream(question, chat_history, []) 逐块产出答案,并把用户问题与完整答案追加到默认聊天历史中,保证多轮上下文。

QuivrQARAG.build_chain 展示了典型的对话式 RAG 结构(quivr_rag.py#L94-L143):

  • 历史过滤filter_history 按 "1 token ≈ 4 字符" 的近似规则裁剪历史,受 max_tokensmax_history 双重限制(quivr_rag.py#L65-L92);
  • 问题改写:用 CONDENSE_QUESTION_PROMPT 把带上下文的问题改写为独立问题,再做向量检索;
  • 向量检索:通过 vector_store.as_retriever() 从 FAISS 索引召回文档;
  • 生成答案:结合 context 与 ANSWER_PROMPT 调用 LLM;若模型支持函数调用,则绑定 cited_answer 工具强制输出带引用的结构化答案(quivr_rag.py#L130-L136)。

5.3 默认参数一览:RAGConfigLLMEndpointConfig

示例没有显式配置任何参数,实际生效的是 backend/core/quivr_core/config.py 中的默认值:

参数 默认值 说明
LLMEndpointConfig.model gpt-3.5-turbo-0125 默认 LLM 型号,可通过 llm_base_url / llm_api_key 指向兼容端点
LLMEndpointConfig.max_input 2000 输入 token 上限
LLMEndpointConfig.max_tokens 2000 输出/历史预算上限,也用于 filter_history 的历史裁剪
LLMEndpointConfig.temperature 0.7 采样温度
LLMEndpointConfig.streaming True 启用流式输出,ask_streaming 依赖此特性
RAGConfig.max_history 10 保留的历史轮数(一问一答为一对)
RAGConfig.max_files 20 回答时引用的文件列表上限
RAGConfig.prompt None 可追加的自定义指令

在示例中可以通过给 Brain.from_files(...) 显式传入 llmembeddervector_db 来替换任意默认组件;brain.ask_streaming 也接受可选的 rag_config,允许在提问时临时切换模型(见 brain.py#L278-L290 中的覆盖逻辑)。

六、最小可运行参照:simple_question.py

如果想脱离 Web 界面验证同一套核心链路,仓库提供了更精简的脚本 backend/core/examples/simple_question.py

import tempfile

from quivr_core import Brain

if __name__ == "__main__":
    with tempfile.NamedTemporaryFile(mode="w", suffix=".txt") as temp_file:
        temp_file.write("Gold is metal.")
        temp_file.flush()

        brain = Brain.from_files(name="test_brain", file_paths=[temp_file.name])

        answer = brain.ask("Property of gold?")

        print("answer :", answer.answer)

        print("brain information: ", brain)

它只用了 Brain.from_files 和同步版 brain.ask(非流式),与聊天机器人示例形成对照:一个是"文件 → 向量库 → 检索 → 流式生成"的完整交互形态,另一个是同一 API 的最小调用形态。

七、运行前提与限制说明

  • OpenAI API Key 必需:默认 LLM 与嵌入器均基于 OpenAI(brain_defaults.py#L33-L55),运行示例前需配置 OPENAI_API_KEY
  • 仅支持 .txt 且 ≤ 20MB:由 AskFileMessageaccept=["text/plain"]max_size_mb=20 决定(main.py#L13-L18);
  • 内存向量库:默认 FAISS 索引保存在进程内存中,brain 生命周期与 Chainlit 会话一致,服务重启后需重新上传文件建 brain;
  • 上传超时:等待用户上传的超时时间为 180 秒,超时后需要刷新会话重新发起。

综上,这个示例用最少的代码展示了 Quivr 的核心用法:Brain.from_files 负责"把文件变成知识",ask / ask_streaming 负责"从知识中取答案",而 Chainlit 只承担界面职责——这正是 Quivr "Focus on your product rather than the RAG" 理念的体现:交互层可以随意替换,RAG 内核保持稳定且可配置。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384