ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Jupyter Notebook实战:从零搭建RAG检索增强生成全流程

Jupyter Notebook实战:从零搭建RAG检索增强生成全流程 RAGRetrieval-Augmented Generation检索增强生成这几年已经从概念演示走向了工程落地。很多团队在正式接入知识库之前都会先用一个 Jupyter Notebook 把链路跑通加载文档、切分块、向量化、检索、拼提示词、生成答案。这种以 Notebook 为载体的 RAG Refresher复习/复现方式最大的价值在于让每一步都独立可见、可调参、可量化而不是把一堆代码塞进一个无人能复查的脚本里。对于刚开始学习 RAG 的开发者来说最大的障碍往往不是大模型 API 怎么调而是“检索”和“生成”之间的连接规则。文档加载后怎么切切成多长向量库怎么存检索结果怎么进提示词回答里没有命中知识库时怎么处理这些问题靠口头解释很难讲清楚。用 Jupyter Notebook 逐 cell 跑一遍每一步都能看到中间结果就能把黑盒变成白盒。这篇 RAG Refresher Notebook 会覆盖从环境准备到文档加载、切分、向量化、检索、重排、提示词组装、生成与评估的完整链路。所有示例都面向“最小可运行”并单独说明哪些写法适合学习环境、哪些写法在进生产前必须替换。读者可以是第一次接触 RAG 的初学者也可以是已经有 RAG 项目但不确认链路细节的开发者还可以是负责团队内部培训或技术验证的人。1. 用一个 Notebook 跑通 RAG核心是让每个阶段都可调试1.1 为什么选择 Jupyter Notebook 做 RAG 实践RAG 链路涉及多个模块解析器、切分器、向量化模型、向量数据库、检索器、提示词模板、大模型。如果直接在命令行脚本里写完整的 RAG 流程中间任何一个环节出错都要通过日志或 debug 反复定位。Notebook 的最大优势是“按 cell 执行、按 cell 看结果”。学习环境中Notebook 特别适合做这几件事观察切分后的每个文本块长什么样是否有句子被截断。观察向量检索返回的相似度分数分布判断阈值该设多少。观察提示词模板最终拼出来的完整字符串确认上下文没有溢出。把不同参数chunk_size、top_k、temperature跑出来的结果并排对比。生产环境则完全不同。生产 RAG 服务需要处理并发、权限、日志、监控、回滚、数据更新Notebook 的可交互性反而会成为负担。正确的态度是用 Notebook 做实验和复盘拿到稳定参数后再用 Python 服务、HTTP 接口和任务调度器去承接。1.2 RAG 最小链路加载、切分、向量化、检索、注入、生成RAG 可以拆成“索引”和“查询”两个阶段。索引阶段在文档进入知识库时执行查询阶段在每次提问时执行。最小链路可以表示为索引阶段 文档加载 - 文本切分 - Embedding 向量化 - 写入向量库 查询阶段 用户问题 - 问题向量化 - 向量检索 Top-K - 可选重排 - 组装提示词 - 大模型生成 - 返回答案其中最容易出错的是查询阶段。“问题向量化”和“文档向量化”必须使用同一个 Embedding 模型否则向量空间不一致相似度计算就是错的。这一点在切换模型时尤其容易踩坑后面会专门展开。1.3 Notebook 结构规划一个 cell 只做一件事推荐把 RAG Refresher Notebook 规划成下面几大块00 环境检查确认依赖安装、密钥可用、设备类型 01 常量配置模型名、存储路径、切分参数、检索参数 02 文档加载读取测试文档并输出前 N 个字符 03 文本切分输出切分数量和每个块的长度 04 向量化与入库确认向量维度、写入数量 05 检索试验手动输入问题查看检索结果和分数 06 提示词组装打印模板和最终 prompt 07 生成与对比调用模型输出答案和来源 08 评估与复盘记录结果整理下一轮要调整的参数这样规划的好处是每个 cell 都只依赖前一个 cell 的输出排查问题时能直接定位到具体环节。不建议把所有逻辑压缩到两三个 cell 里否则和其他人的脚本没有本质区别。2. 环境准备先搭建一个可复现的 RAG 实验内核2.1 依赖清单和版本注意点RAG 的依赖工具链更新速度很快安装前要确认当前环境的 Python 版本。建议使用 Python 3.10 或 3.11这两个版本对 LangChain、Chroma、pydantic 生态的兼容性比较稳定。下面是一份常见的学习环境依赖依赖包作用学习环境说明jupyter / notebookNotebook 运行环境也可以使用 Jupyter LablangchainRAG 编排框架不同版本 API 差异较大langchain-community第三方加载器和存储适配部分加载器在这里langchain-openaiOpenAI 风格模型接口不限于实际厂商兼容 OpenAI 接口的都行chromadb本地向量库安装简单适合原型pypdf / pymupdfPDF 解析根据文档类型选择sentence-transformers本地 Embedding 模型需要下载模型权重openai调用大模型 API 的客户端库如果使用本地模型可不装安装命令pip install jupyter pip install langchain langchain-community langchain-openai chromadb pypdf pip install sentence-transformers[torch] --index-url https://pypi.org/simple/注意不同版本的 LangChain 在导入路径和方法名上有明显差异。例如Chroma.from_documents在早期版本和后期版本中参数不同。文中示例以当前常见 API 写法说明思路具体落地前先打印langchain.__version__确认并以官方文档为准。2.2 用 conda 或 venv 创建独立内核不推荐把 RAG 依赖直接装进系统 Python。项目依赖一旦冲突排查成本很高。推荐先用 conda 或 venv 建一个独立环境。conda 创建环境conda create -n rag_refresher python3.10 -y conda activate rag_refresher pip install ipykernel python -m ipykernel install --user --name rag_refresher --display-name Python (rag_refresher)venv 创建环境python -m venv .venv source .venv/bin/activate pip install ipykernel python -m ipykernel install --user --name rag_refresher --display-name Python (rag_refresher)启动 Notebookjupyter notebook打开后在 Kernel 菜单选择Python (rag_refresher)确保当前 cell 使用的解释器和依赖安装环境一致。这一步看起来简单却是大多数“明明装了包但提示找不到”的根因。2.3 环境自检脚本先确认依赖和密钥再开始在 Notebook 第一个 cell 里运行环境自检能节约大量排障时间。import importlib import os required_packages [ langchain, langchain_core, langchain_community, chromadb, pypdf, ] for package in required_packages: try: module importlib.import_module(package) print(f{package}: OK) except ImportError: print(f{package}: MISSING) # 模型服务密钥只做存在性检查不要打印完整密钥 api_key os.getenv(OPENAI_API_KEY, ) if api_key: print(OPENAI_API_KEY: SET) else: print(OPENAI_API_KEY: NOT SET)如果包缺失回到上一步安装如果密钥未设置在环境变量或 Notebook 常量配置里补充。这里不使用硬编码密钥避免代码提交后泄露。注意密钥检查只做“是否存在”判断不要打印密钥本身也不要截图分享带密钥的 Notebook 页面。3. 文档加载与切分检索质量的第一道关口3.1 文档加载从 PDF、Markdown、HTML 到纯文本RAG 的第一步是把不同格式的文档变成纯文本。PDF 是知识库中最常见的格式但 PDF 解析质量差异很大。文本型 PDF 可以直接提取文本扫描件则必须先走 OCR。以 PDF 为例from langchain_community.document_loaders import PyPDFLoader loader PyPDFLoader(data/manual.pdf) documents loader.load() print(f加载到 {len(documents)} 页) for i, doc in enumerate(documents[:2]): print(f--- 第 {i 1} 页前150字符 ---) print(doc.page_content[:150]) print(元数据:, doc.metadata)加载结果中的page_content是文本metadata中通常包含页码、来源路径等信息。这些元数据很重要后续检索时可以用来追踪答案来源。对于 Markdown、HTML、Word 文档LangChain 分别提供了MarkdownTextLoader、BSHTMLLoader、Docx2txtLoader。实际项目中文档格式越杂越应该在加载阶段统一清洗包括去页眉页脚、去表格噪音、统一换行符。3.2 切分策略chunk_size、overlap 和 separator加载后的文本不能直接全部扔进向量库。大模型对上下文的长度有限制而且超长片段会稀释检索相关性。切分的目标是让每个片段语义尽量完整。推荐使用RecursiveCharacterTextSplitter它会按分层分隔符递归切分优先保留段落和句子from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , . , , ], ) chunks text_splitter.split_documents(documents) print(f切分成 {len(chunks)} 个文本块) for i, chunk in enumerate(chunks[:3]): print(f--- chunk {i} 长度 {len(chunk.page_content)} ---) print(chunk.page_content) print()参数说明参数含义学习环境推荐值影响chunk_size每个块的最大字符数300-800太小语义断裂太大检索粒度粗糙chunk_overlap相邻块之间重叠字符数50-150重叠过低跨块信息丢失separators切分优先级按段落、句号、空格依次切优先级顺序影响块边界位置常见误区是只看 chunk_size不看 overlap。由于切分发生在“固定字符数”处一个完整句子可能被切成两半。适当的 overlap 能减少这种割裂但也不能无限加大否则会造成冗余入库和检索结果重复。3.3 常见坑切分时把语义边界切碎真正影响 RAG 效果的不是切分长度而是切分是否尊重语义边界。处理合同、技术规范、医疗文档时一条完整条款、一个步骤描述往往跨越几百字。如果无脑按 300 字符硬切后面的检索会频繁召回不完整内容。出现以下现象说明切分可能有问题检索到某个 chunk 后发现关键结论在 chunk 后面的段落里。同一个完整表格被拆到多个 chunk生成阶段找不到表头和单元格的对应关系。问答中频繁出现“根据上文提到的未完成描述”。解决方式不是盲目调大 chunk_size而是先看失败样本。对长文档可以考虑按标题层级切分对表格文档先做表格结构化对列表优先保证列表项不能被切碎。4. 向量化与入库向量空间的一致性决定召回上限4.1 Embedding 模型选择远程 API 还是本地权重Embedding 模型的作用是把一段文本映射成固定维度的向量。RAG 对 Embedding 的要求是语义相近的句子向量距离也要近。选择时主要看三件事是否支持中文中文场景下要验证行业词汇是否有效。向量维度是多少维度越高存储和计算成本越大。是否能在当前环境中稳定调用本地模型需要显存和内存远程 API 需要密钥和网络。从 Notebook 出发可以封装一个统一的 Embedding 工厂import os def create_embeddings(): provider os.getenv(EMBEDDING_PROVIDER, openai) if provider openai: from langchain_openai import OpenAIEmbeddings return OpenAIEmbeddings( modelos.getenv(EMBEDDING_MODEL, text-embedding-3-small) ) if provider huggingface: from langchain_community.embeddings import HuggingFaceEmbeddings return HuggingFaceEmbeddings( model_nameos.getenv(EMBEDDING_MODEL, BAAI/bge-small-zh-v1.5) ) raise ValueError(f未知的 Embedding 类型: {provider}) embeddings create_embeddings()这个工厂函数的作用是隔离模型来源。本地调试时用开源权重团队协作时切到统一 API不会污染其他代码。4.2 向量库选型Chroma、FAISS、Milvus 怎么取舍Notebook 实验阶段不需要上分布式向量库。先本地跑通再根据数据量迁移。向量库部署方式适用阶段优点注意事项Chroma本地嵌入式原型、Notebook安装简单支持持久化并发和规模有限FAISS本地内存/文件单机中等规模检索性能高元数据过滤需要自行管理Milvus独立服务生产大规模功能全、扩展性好部署运维成本高Chroma 写入示例from langchain_chroma import Chroma vectorstore Chroma( collection_namerag_refresher, embedding_functionembeddings, persist_directory./data/chroma_db, ) # 已有 chunks 时直接写入 ids [fchunk_{i} for i in range(len(chunks))] vectorstore.add_documents(documentschunks, idsids) print(f向量库当前数量: {vectorstore._collection.count()})如果希望实验可以复制可以在 Notebook 里为每次实验指定一个独立的 collection 名称例如rag_refresher_v1、rag_refresher_v2。这样参数调整后不会污染上一轮的索引。4.3 元数据设计检索之后靠它回答“来自哪里”向量库里保存的不只是向量还有每个 chunk 的元数据。元数据至少要包含来源文件名、页码、标题、时间戳。这样生成阶段可以把引用信息返回给用户。写入前检查元数据for i, chunk in enumerate(chunks[:3]): chunk.metadata[source] data/manual.pdf chunk.metadata[page] chunk.metadata.get(page, 0) chunk.metadata[chunk_id] i元数据的另一个价值是精细过滤。例如只看某章节、某日期后的内容都可以在检索前通过元数据过滤缩小范围。4.4 常见坑维度不一致、重复入库、索引丢失向量化阶段有三个高发问题。第一Embedding 模型切换后新旧向量维度不一致。旧向量是 384 维新模型是 1024 维向量库写入或检索时直接报错。解决方式是在实验开始前固定 Embedding 模型版本或者为不同模型建不同 collection。第二重复运行同一个 cell 导致相同文档反复入库。Chroma 的add_documents不检查内容是否重复重复运行会让“总数”翻倍检索结果同一句话出现多次。解决方式是使用稳定的 id并在写入前按 id 去重。第三索引没有持久化。只把 Chroma 放在内存里Notebook 重启后索引消失。使用persist_directory并确认目录下有真实的 SQLite 文件重启后通过相同 collection 名称读取。5. 检索与重排从 Top-K 到真正有用的上下文5.1 检索参数top_k、score_threshold 和 fetch_k检索是 RAG 的真正核心。生成环节的质量上限由检索结果决定如果检索结果里没有正确答案再强的 LLM 也只能编造。Notebook 里可以先做最直观的相似度检索query 这个系统应该如何配置权限 results vectorstore.similarity_search_with_score(query, k4) for i, (doc, score) in enumerate(results): print(f--- 结果 {i 1} 分数 {score:.4f} ---) print(doc.page_content[:200]) print(来源:, doc.metadata)常见参数参数含义推荐设置错误表现k返回条数3-6设置过大导致上下文挤满噪音score_threshold相似度过滤阈值视模型而定设置过高导致检索为空fetch_k重排前先取多少条是 MMR 等重排的前提设置过小让重排失去意义不同 Embedding 模型返回的分数含义不同不要直接跨模型比较分数。首次使用某个模型时先打印 10 个查询的分数分布再决定阈值。5.2 混合检索与重排向量相似度不是唯一标准向量检索擅长语义相似但不擅长关键词精确匹配。例如型号编号 “T-1000”向量检索可能返回一堆含义接近但编号不对的文本。生产级 RAG 一般会引入混合检索向量检索 关键词检索BM25再通过重排模型合并排序。Notebook 阶段可以先做轻量重排用交叉编码器cross-encoder对 Top-K 结果重新打分from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-base) query 这个系统应该如何配置权限 candidates vectorstore.similarity_search(query, k10) pairs [[query, doc.page_content] for doc in candidates] scores reranker.predict(pairs) ranked sorted(zip(candidates, scores), keylambda x: x[1], reverseTrue) for doc, score in ranked[:4]: print(f重排分数 {score:.4f}) print(doc.page_content[:150]) print()重排的价值是让生成阶段只接收“最相关的一小部分上下文”而不是让模型从前 10 条里自己找答案。代价是每次查询都要多一次模型推理延迟会上升。Notebook 实验时要注意对比“不重排”和“重排后”的答案差别再决定是否引入。5.3 如何判断检索结果是否够用判断检索质量不能只看代码是否返回了结果要看返回结果是否能支撑回答。建议逐个查询做人工检查结果中是否包含答案所在的原文。结果中最相关的那条排在第一位。无关结果占比较高时是切分问题还是模型问题。用精确实体编号、人名、日期查询结果是否稳定。如果在 Notebook 中发现“答案错误”先不要调提示词先看检索结果。检索结果正确但生成错误才是提示词问题检索结果本身不对调整提示词没有意义。6. 组装生成提示词把上下文变成可解释的回答6.1 提示词模板设计上下文、问题、规则三段式RAG 提示词的目标是让模型“看着材料回答”而不是“凭着知识编造”。一个最小可用的模板包含三段from langchain_core.prompts import ChatPromptTemplate prompt_template ChatPromptTemplate.from_messages( [ ( system, 你是一个基于知识库的问答助手。请只根据下面的上下文回答问题。\n 如果上下文中没有答案请明确说知识库中没有找到对应内容不要编造。\n\n 上下文\n{context}, ), (human, 问题{question}), ] ) def format_context(results): return \n\n.join( f[来源{idx 1}] 文件:{doc.metadata.get(source, unknown)} f第{doc.metadata.get(page, ?)}页\n{doc.page_content} for idx, doc in enumerate(results) )提示词中的{context}是检索结果拼接后的文本{question}是用户问题。注意在系统提示词里加入“拒绝回答无关问题”和“标出来源”的规则否则模型容易把知识库内容和自身知识混淆。6.2 空检索和低置信度处理Notebook 实验里常见的一个现象是检索分数很低但模型依然自信地回答。这是因为模型把“没有上下文”当成了“上下文不够”。处理方式是在组装提示词前判断检索结果质量MIN_SCORE 0.3 # 以实际模型的分数分布为准 if not results: print(知识库中没有检索到相关内容直接返回固定提示) context_text 知识库中没有与该问题相关的内容。 else: lowest_score results[-1][1] if lowest_score MIN_SCORE: print(f检索分数过低: {lowest_score:.4f}降低回答置信度) context_text format_context([r[0] for r in results])生产环境还可以在响应里返回“confident: low”之类的标记让调用方决定是否展示默认兜底话术。6.3 流式输出与对话记忆的取舍Notebook 里可以先不引入流式输出和对话记忆先验证“单轮问答”的效果。对话记忆会引入两个变量历史问题会占用上下文空间历史答案可能让模型偏离知识库。先把单轮链路调稳再考虑多轮。如果一定要做多轮推荐只把“历史问题”作为检索上下文而不是把“历史答案”塞进提示词。同时清理过期的历史记录避免上下文膨胀。7. 可视化评估Notebook 里如何量化 RAG 效果7.1 评估指标检索命中率、忠实度、答案相关性RAG 的效果评估分成两块检索质量和生成质量。Notebook 里可以建立人工评估表格逐步沉淀测试集。常用指标指标含义判断方式检索命中率Top-K 中是否包含答案相关片段人工查看检索结果上下文相关性检索出的上下文是否与问题语义相关人工打分 0-5答案忠实度答案是否严格基于上下文检查答案是否出现上下文之外的细节答案相关性答案是否有效解决问题结合业务目标判断7.2 用 Notebook 构建评测表格在 Notebook 里维护一个测试集每个问题都记录多条结果test_questions [ 如何安装这个系统, 默认密码是多少, 系统支持哪些登录方式, ] records [] for q in test_questions: # 检索 hits vectorstore.similarity_search_with_score(q, k4) retrieved [doc.page_content for doc, score in hits] # 生成 answer chain.invoke({question: q}).content records.append( { question: q, top1_score: float(hits[0][1]) if hits else None, first_chunk: hits[0][0].page_content[:80] if hits else , answer: answer[:120], } ) for record in records: print(record[question]) print( top1_score:, record[top1_score]) print( answer:, record[answer]) print()这种表格的价值不在于自动化而在于记录每次参数调整前后的差异。下一次改动前先跑一遍同样的测试集才能判断效果是变好还是变差。7.3 参数回归改一个参数跑一次对比推荐在 Notebook 中建立参数记录experiments [] def run_experiment(name, chunk_size, overlap, k, notes): # 这里用当前配置重建拆分器和向量库再跑测试集 # 把结果追加到 experiments 列表 pass run_experiment(v1_default, chunk_size500, overlap80, k4) run_experiment(v2_larger_chunk, chunk_size800, overlap100, k6)每次实验只有一个变量不要同时改 chunk_size 和 top_k否则无法判断哪一项真正影响了结果。记录实验名字、参数、检索命中情况、生成答案作为后续生产参数选择的依据。8. 常见问题排查Notebook 实践 RAG 的典型故障8.1 按“现象—原因—检查—处理”整理问题RAG 链路长问题往往被层层传导。下面是一份常用排查表问题现象常见原因检查方式处理建议导入库时 ModuleNotFoundError内核不对或依赖未安装查看sys.executable、pip list切换 Notebook 内核到目标环境检索返回空结果向量库没有数据或集合名错误打印 collection 数量先执行入库 cell再检索检索结果全是无关文本Embedding 模型不合适或切分过粗打印检索分数的分布换 Embedding 或调整切分生成答案偏离上下文提示词里没有约束来源查看最终 prompt加“只根据上下文回答”规则相同内容重复检索重复运行入库 cell用 id 去重写入前检查现有 id重启后向量库为空持久化目录未配置查看persist_directory使用固定目录并确认写入成功上下文太长超出模型限制k 太大或 chunk 太长统计 prompt token 数降低 k缩小 chunk_size8.2 由外到内排查链路遇到 RAG 表现异常按照固定顺序排查更容易定位输入是否明确测试问题是否包含专有名词、拼写是否正常。文档是否加载成功输出文档页数、前 N 个字符确认没有乱码。切分是否合理检查 chunk 内容是否被截断。检索是否召回打印 Top-K 片段和分数。提示词是否正确拼接打印最终传入模型的字符串。模型是否按要求回答关闭温度或调低 temperature。版本是否匹配确认 LangChain 和相关库版本。大多数问题在 2 到 4 步就能定位。如果检索结果正常但回答错误基本是提示词或模型参数问题。8.3 Notebook 实验前检查清单复制到 Notebook 开头每次重跑前先过一遍代码已保存、git 已提交 内核确认是 rag_refresher 依赖安装完成 测试文档存在且路径正确 Embedding 模型可调用且不打印完整密钥 向量库使用独立 collection 名 检索参数和分数阈值已记录 测试集和人工评估表已建立 修改参数前已记录上一轮结果这份清单也是团队培训时的入门模板能帮助新手养成“先验证环境再调参数”的习惯。9. 从 Notebook 到生产环境复习 RAG 后如何落地9.1 Notebook 适合什么不适合什么Notebook 适合做原型验证和参数实验但不是一个生产服务载体。类比来说Notebook 是风洞实验生产是正式航线。风洞里测出机翼参数不代表可以直接把实验台搬上飞机。阶段使用方式核心目标学习环境Notebook 逐 cell 调试理解链路观察中间结果开发阶段用 Python 包封装 RAG 模块沉淀可维护的代码测试环境构建测试集 自动化评估验证检索和生成质量生产环境服务化 监控 权限 日志稳定、安全、可回滚9.2 生产化改造点配置、日志、权限、数据更新从 Notebook 到生产至少要做这些改造配置外置化API 密钥、模型名、向量库地址全部走环境变量或配置中心而不是写在 Notebook 里。服务化用 FastAPI 或类似框架封装/rag/query接口接收问题返回答案和引用来源。日志和监控记录每次检索的分数、耗时、命中的文档来源、模型 token 消耗。权限控制知识库内容如果包含敏感信息必须做用户级权限过滤不能一个向量库所有人都能查。数据更新文档变化后需要增量更新向量库同时清理旧版本 chunk。回滚方案大模型和 Embedding 模型升级前先备份旧的向量索引和参数配置。金融、医疗、银行等场景还要引入更多约束答案必须带引用文件编号低置信度结果必须报“不确定”对用户的敏感问题不能走未审计的外部模型。9.3 进阶方向Agentic RAG、多模态 RAG、GraphRAG如果基本 RAG 链路已经跑通下一步可以按这几个方向扩展Agentic RAG把“是否需要检索”“检索哪类文档”“是否追问用户”交给 AI Agent 决策而不是每次固定走同一个检索流程。多模态 RAG文档中的图表、截图、流程图也需要处理既有文本向量也有图片向量联合检索。GraphRAG把文档中的实体和关系抽出来构造成知识图谱回答多跳关系问题时比向量检索更稳定。领域化切分针对协议、规范、法律条文等结构化文档设计专用的切分规则而不是通用字符切分。扩展过程中最需要养成的习惯是每次改动都回到 Notebook 测试集重新跑一遍。RAG 系统的退化往往是无声无息的今天换了一个 Embedding 模型可能一周后才发现一批历史问题开始答错。用 Notebook 做回归记录是成本最低的防止退化方案。最后给准备动手的人一个练习建议找一份你熟悉的 PDF 文档不需要太长按这篇笔记的路径跑通一次 RAG。先不做任何优化记录第一次的检索结果和答案再依次调整切分参数、检索 K 值、提示词规则和重排策略。每调一次就把结果写进评估表。当你亲手看到同一个问题因为检索上下文不同而得到完全不同的答案时对 RAG 的理解会比读十篇概念文章都深刻。
返回列表