ARTICLE DETAIL

资讯详情

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

RAG知识库问答系统搭建指南:从零接入Deepseek API

RAG知识库问答系统搭建指南:从零接入Deepseek API 如果你最近在负责企业内部知识管理、客服问答或者文档检索类的项目大概率已经听过 RAG 这个词。它全称 Retrieval-Augmented Generation中文叫检索增强生成核心思路是让大模型在回答问题时先从私有知识库里检索相关内容再基于这些内容生成回答。我的判断是RAG 是大模型落地到真实业务场景最稳的一条路径没有之一。它不需要微调模型不需要高昂的 GPU 训练成本只需要把文档处理好、检索做对、提示词写清楚就能在几天内搭出一个可用的知识库问答系统。但这不意味着 RAG 没有难度。真正容易翻车的不是“调用大模型”这一步而是文档解析、文本切分、向量检索质量这些看起来不起眼的环节。这篇文章会从零开始带你完整搭建一个 RAG 知识库问答系统并接入 Deepseek 大模型 API。你会看到完整的工程代码理解 RAG 的索引、检索、生成三个阶段知道每一步为什么这么做以及遇到问题该怎么排查。建议先收藏按照文章顺序边读边动手。1. RAG 到底是什么为什么它比直接问大模型更靠谱先看一个真实场景。假设你手上有一份 200 页的产品操作手册你直接问大模型“第二章节的异常码 0x3102 怎么处理”大概率得不到准确回答。原因是大模型的训练数据里根本没有你这本手册它只能根据训练时见过的类似问题“猜”一个答案。这就是 RAG 要解决的问题把外部知识在回答前先注入给大模型。具体流程是把手册提前切分成小块、向量化、存入向量数据库用户提问时先从向量数据库里检索出最相关的几个片段最后把这些片段拼进 Prompt让大模型只依据这些片段回答。RAG 和模型微调经常被放在一起比较但两者的适用场景完全不同。微调适合改变模型的“能力”和“风格”比如让模型学会某种固定输出格式、模仿特定文风RAG 适合注入“实时信息”和“私有知识”比如公司制度、产品文档、售后记录。你不需要为了新增几十篇文档去重新训练模型只需要更新知识库。另一个常见误区是把 RAG 简单理解成“给大模型接个数据库”。如果只是把文档全部塞进 Prompt很快会遇到两个问题一是大模型上下文窗口有限塞不下全部文档二是无关内容会干扰模型反而降低回答准确率。RAG 的核心价值是“精准检索”而不是无脑堆料。所以当你准备搭建知识库问答系统时第一件事不是写代码而是想清楚检索环节怎么设计。这一步的质量直接决定最终回答的效果。2. RAG 完整原理拆解索引、检索、生成三阶段RAG 的全流程可以拆成三个清晰的阶段理解好这三个阶段后面的代码就只是具体实现。第一阶段是索引构建英文叫 Indexing。这一步把原始文档变成计算机能快速检索的形式说直白点就是“把书拆成卡片再给每张卡片做一个编号”。流程是读取文档、清理格式、按一定规则切分成小块然后为每一块生成向量表示。这里有一个容易忽视但极其重要的设计点切分策略。切得太碎语义不完整切得太长噪声太多。常见做法是设置 chunk_size 和 chunk_overlap让相邻文本块有部分重叠防止关键信息正好被切在边界上。第二阶段是检索英文叫 Retrieval。用户提问后系统先把问题转换成向量然后在向量数据库里做相似度搜索取出最相关的若干个文本块。这个阶段有两个核心指标召回率和准确率。召回率关心“该找到的有没有找到”准确率关心“找到的是不是用户想要的”。初学者很多时候只关注相似度分数实际上还是应该多看召回的结果是不是真的相关。第三阶段是生成英文叫 Generation。系统把用户问题、检索到的文本块、系统提示词组合起来发送给大模型。大模型根据这些材料生成回答。这个阶段的技巧主要在于提示词的写法。你需要明确告诉模型“只依据提供的材料回答不要编造”同时给出“如果材料中没有答案就拒绝回答”的边界。三个阶段的注意力分配也很讲究。很多团队把大量时间花在调 Prompt 上实际上如果你的检索阶段召回的都是无关内容Prompt 写得再好也没用。真实项目里检索质量对最终效果的影响往往大于生成阶段。3. 为什么选择接入 Deepseek 作为生成模型搭建 RAG 时生成模型的选择直接影响回答质量、成本和开发效率。当前可选的大模型 API 不少Deepseek 在这其中是一个很务实的选择原因有三点。第一是 API 兼容 OpenAI 协议。这意味着你不需要引入全新的 SDK只要把 OpenAI 客户端库的 base_url 指向 Deepseek再填上自己的 API Key就能完成接入改造成本非常低。这一点在工程上很重要因为你现有的代码可能已经用过 OpenAI 接口迁移成本几乎为零。第二是中文场景能力表现稳定。RAG 知识库问答在国内业务场景下绝大部分是中文文档Deepseek 在中文语义理解上有不错的表现生成结果更贴合中文表达习惯。第三是成本控制。RAG 场景下用户每次提问都会附带检索出来的文档片段Token 消耗量比直接对话要大。选择一个性价比较高的模型 API在规模化使用时会明显影响预算。需要特别说明的是Deepseek 的 API 服务会不定期更新模型版本和价格具体模型名称、计费方式请以 Deepseek 官方文档为最终依据。本文使用的 deepseek-chat 模型是它在对话场景中常用的模型标识代码里会采用统一配置的方式方便你随时替换。另外这里有一个容易误解的点一个完整的 RAG 系统里其实需要两种向量化能力。一种是文本 Embedding也就是把文档块变成向量另一种是 Chat Completion也就是生成最终答案。Deepseek 目前提供的核心能力是后者。前者可以使用开源 Embedding 模型例如智源的 BAAI/bge-small-zh-v1.5用本地方式跑避免额外调用外部接口。这种方式的好处是文档向量化不产生费用也方便离线构建知识库。4. 环境准备与前置条件在开始写代码之前先把环境准备好。以下配置建议在你的开发机或服务器上完成生产环境建议使用 Linux 服务器。操作系统Windows 10/11、macOS 或主流 Linux 发行版均可本文演示不依赖特定系统命令。Python 版本建议使用 3.9 及以上版本。如果本机同时有多个 Python 版本建议用 conda 或 venv 创建独立环境避免依赖冲突。需要安装的核心依赖包括langchain 生态组件用于文档加载和文本切分faiss-cpu向量检索库CPU 版本足以支撑学习和中小规模知识库sentence-transformers用于加载本地 Embedding 模型openai兼容 OpenAI 协议的调用方式用于访问 Deepseek APIpython-dotenv管理环境变量pypdf解析 PDF 文档创建一个新的项目目录并在项目下新建虚拟环境。mkdir rag-knowledge-base cd rag-knowledge-base python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate然后安装依赖。这里不写死具体版本因为 langchain 生态更新较快建议安装最新稳定版出现兼容性问题时再根据报错锁定版本。pip install langchain langchain-community langchain-text-splitters faiss-cpu sentence-transformers openai python-dotenv pypdf安装完成后检查关键依赖是否成功python -c import faiss; print(faiss.__version__) python -c import sentence_transformers; print(sentence_transformers.__version__)如果这两行都能正常输出版本号说明基础环境没有问题。接下来需要准备 Deepseek 的 API Key。前往 Deepseek 开放平台完成注册创建 API Key 后复制保存。注意 API Key 相当于密码不要提交到 Git 仓库也不要写在代码里应该统一放到环境变量或 .env 文件中。在项目根目录下创建 .env 文件内容是DEEPSEEK_API_KEY你的API Key DEEPSEEK_BASE_URLhttps://api.deepseek.com DEEPSEEK_MODELdeepseek-chat EMBEDDING_MODELBAAI/bge-small-zh-v1.5注意 .env 文件通常需要加入 .gitignore防止密钥泄露。5. 文档加载与文本切分RAG 容易被忽视的关键一步文档加载是整个 RAG 链路的第一步也是工程化时坑最多的环节。直接读文件、拼字符串谁都会但真实知识库里的文档往往格式混乱、内容重复、表格和排版信息丢失这些问题都会在下游检索中放大。先看一个最小可用的文档加载代码。这里假设你的项目里已经准备了一份 PDF 文档放在 data 目录下。# 文件路径ingest.py from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # 1. 加载 PDF 文档 loader PyPDFLoader(data/product_manual.pdf) documents loader.load() print(f加载到 {len(documents)} 页内容) # 2. 清洗与基础处理 for doc in documents: doc.page_content doc.page_content.replace(\u3000, ).strip() # 3. 文本切分 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, 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} ---) print(chunk.page_content[:200])这段代码里最值得关注的是切分参数。chunk_size 是每个文本块最大字符数chunk_overlap 是相邻文本块之间重叠的长度。为什么需要重叠因为如果一段完整的话正好被切成两半检索时就可能只命中一半生成阶段拿到的信息不完整。重叠部分能让边界处的语义尽量保留下来。实际项目中的最佳实践是中文文档把标点符号加入分隔符列表优先在句子或段落边界切分而不是硬按固定长度切。RecursiveCharacterTextSplitter 的作用就是先尝试用最长的分隔符切不行再逐级向下回退直到每个块大小满足要求。这种方式在大多数文档上表现都不错。一个常见问题是chunk_size 设成多少合适这不是一个固定的数字取决于你的文档类型和 Embedding 模型能力。经验上中文场景 400 到 800 字符是相对稳妥的区间。太小会让每个块语义不完整太大会让向量包含过多噪声降低检索精度。如果你做的是代码文档或者技术手册可以适当调大分隔符的优先级。建议先跑一版把切分结果打印出来人工检查几段确认语义完整后再继续后续流程。6. 向量化与向量数据库存储把文本变成可检索的数文档切分完成后下一步是把文本块转换成向量。这里需要加载本地 Embedding 模型并用它对每个文本块生成向量表示。向量化的核心思想是语义相近的文本在向量空间里的距离也相近。之后用户提问时系统把问题转换成向量就能通过向量相似度找到语义最相关的文本块。使用 sentence-transformers 加载本地 Embedding 模型# 文件路径embedding_utils.py from sentence_transformers import SentenceTransformer def get_embedding_model(model_name: str BAAI/bge-small-zh-v1.5): return SentenceTransformer(model_name) def embed_texts(model, texts): # normalize_embeddingsTrue 会将向量归一化便于用内积或余弦相似度做检索 embeddings model.encode(texts, normalize_embeddingsTrue) return embeddings这里有两个容易被忽视的细节。第一首次运行时会自动下载模型文件可能耗时较长这和网络环境有关如果你无法访问 HuggingFace可以配置镜像源或者使用已经下载好的本地模型目录。第二Embedding 模型有最大输入长度限制如果你的文本块超过模型长度需要先做截断或改用支持长文本的模型。向量存储方案的选择上入门阶段推荐 FAISS。它是一个轻量级的向量检索库不依赖独立服务构建索引后可以保存到本地文件非常适合中小规模知识库。如果你需要处理百万级甚至亿级向量再考虑部署 Milvus、Qdrant 这类专业向量数据库。构建向量索引并保存到本地的完整代码如下# 文件路径build_index.py import os from dotenv import load_dotenv from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from embedding_utils import get_embedding_model load_dotenv() EMBEDDING_MODEL os.getenv(EMBEDDING_MODEL, BAAI/bge-small-zh-v1.5) # 1. 加载文档 loader PyPDFLoader(data/product_manual.pdf) documents loader.load() # 2. 切分文档 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, separators[\n\n, \n, 。, , , ., !, ?, , ;, , ,, , ] ) chunks text_splitter.split_documents(documents) texts [chunk.page_content for chunk in chunks] metadatas [chunk.metadata for chunk in chunks] # 3. 计算向量 model get_embedding_model(EMBEDDING_MODEL) embeddings embed_texts(model, texts) # 4. 构建 FAISS 索引 import faiss import numpy as np dimension embeddings.shape[1] index faiss.IndexFlatIP(dimension) index.add(embeddings) # 5. 保存索引和文本内容 faiss.write_index(index, data/faiss_index.index) import json with open(data/text_chunks.json, w, encodingutf-8) as f: json.dump({texts: texts, metadatas: metadatas}, f, ensure_asciiFalse) print(f向量维度: {dimension}) print(f索引中向量数量: {index.ntotal}) print(f知识库索引构建完成)这段代码里使用的是 IndexFlatIP也就是内积检索。因为在前面的 embed_texts 中已经做了向量归一化内积和余弦相似度在数学上是等价的。FAISS 构建索引后必须同时保存索引文件与原始文本因为向量数据库里面存的是向量真正要给大模型“看”的是原始文本。这一步有很多人踩坑只保存了向量文件检索出向量索引编号后找不到对应的原始文本导致无法生成回答。另外要注意如果后续更新了文档不能只把新文档追加到旧索引上因为同一个文本块的切分方式可能已经变了。稳妥的做法是重新构建整个索引。在数据量不大、构建时间可接受的情况下全量重建反而是最简单可靠的策略。7. 接入 Deepseek 的完整问答链路代码知识库索引构建完成后就到了最核心的部分实现“用户提问 → 向量检索 → 拼接 Prompt → 调用 Deepseek → 返回答案”的完整链路。Deepseek API 兼容 OpenAI 协议因此可以直接使用 openai 库只需要修改 base_url 和 api_key。下面给出完整的问答主流程代码。# 文件路径rag_query.py import os import json import faiss import numpy as np from dotenv import load_dotenv from openai import OpenAI from embedding_utils import get_embedding_model load_dotenv() DEEPSEEK_API_KEY os.getenv(DEEPSEEK_API_KEY) DEEPSEEK_BASE_URL os.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com) DEEPSEEK_MODEL os.getenv(DEEPSEEK_MODEL, deepseek-chat) EMBEDDING_MODEL os.getenv(EMBEDDING_MODEL, BAAI/bge-small-zh-v1.5) TOP_K 4 # 检索返回的文本块数量 def load_knowledge_base(): 加载向量索引和原始文本块 index faiss.read_index(data/faiss_index.index) with open(data/text_chunks.json, r, encodingutf-8) as f: data json.load(f) return index, data[texts], data[metadatas] def search_knowledge_base(index, texts, metadatas, query_embedding, top_kTOP_K): 在知识库中检索最相关的文本块 scores, indices index.search(query_embedding.reshape(1, -1), top_k) results [] for score, idx in zip(scores[0], indices[0]): if idx 0: continue results.append({ score: float(score), text: texts[idx], metadata: metadatas[idx] if idx len(metadatas) else {} }) return results def build_prompt(query, retrieved_chunks): 构造发给大模型的提示词 context_parts [] for i, chunk in enumerate(retrieved_chunks): context_parts.append(f[片段{i 1}]\n{chunk[text]}) context \n\n.join(context_parts) prompt f你是一个知识库问答助手。请根据以下已知信息回答用户的问题。 已知信息 {context} 要求 1. 只依据上述已知信息回答不要编造内容。 2. 如果已知信息不足以回答问题请直接说明根据当前知识库无法回答该问题。 3. 回答尽量准确、简洁并使用中文。 用户问题{query} return prompt def chat_with_deepseek(prompt): 调用 Deepseek 大模型生成回答 client OpenAI( api_keyDEEPSEEK_API_KEY, base_urlDEEPSEEK_BASE_URL ) response client.chat.completions.create( modelDEEPSEEK_MODEL, messages[ {role: system, content: 你是一个严谨的知识库问答助手。}, {role: user, content: prompt} ], temperature0.3, ) return response.choices[0].message.content def main(): query input(请输入你的问题) # 1. 加载知识库 index, texts, metadatas load_knowledge_base() # 2. 生成查询向量 model get_embedding_model(EMBEDDING_MODEL) query_embedding model.encode([query], normalize_embeddingsTrue)[0] # 3. 检索相关文本块 retrieved_chunks search_knowledge_base(index, texts, metadatas, query_embedding) print(f\n检索到 {len(retrieved_chunks)} 个相关文本块\n) for i, chunk in enumerate(retrieved_chunks): print(f--- 片段 {i 1} (相似度: {chunk[score]:.4f}) ---) print(chunk[text][:150]) print() # 4. 构造 Prompt prompt build_prompt(query, retrieved_chunks) # 5. 调用 Deepseek 生成回答 answer chat_with_deepseek(prompt) print(f--- Deepseek 回答 ---\n{answer}) if __name__ __main__: main()这段代码把前面所有环节串起来了。运行后系统会等待你输入问题然后依次完成加载索引、生成查询向量、检索文本块、构造 Prompt、调用 Deepseek 的完整流程。细心的读者会发现代码里有一个重要设计TOP_K 参数控制检索返回多少个文本块。这个值并不是越大越好。设置为 3 到 5 是常见选择因为太多无关片段进入 Prompt 后大模型容易被噪声干扰反而影响回答质量。同时也增加了 Token 消耗。Prompt 的设计同样值得重视。我在系统提示词里强调“只依据已知信息回答”在用户提示词里明确“如果信息不足则拒绝回答”。这是为了防止大模型在知识库没有相关内容时强行编造答案也就是俗话说的“一本正经地胡说八道”。温度参数设成了 0.3目的是让回答更确定、更保守。对于知识库问答场景低温度通常优于高温度。8. 运行结果与效果验证完成代码编写之后你可以运行脚本验证整个流程是否能正常工作。python rag_query.py输入一个问题比如“产品在什么温度范围内可以正常工作”预期效果是脚本先检索出 4 个相关文本块每个块前面会显示相似度分数然后调用 Deepseek 生成回答回答内容应该基于检索到的文档片段而不是泛泛而谈。下面是一段示意输出实际内容取决于你的文档请输入你的问题产品在什么温度范围内可以正常工作 检索到 4 个相关文本块 --- 片段 1 (相似度: 0.8421) --- 工作环境要求设备应在 -10℃ 至 50℃ 的环境温度下运行湿度不超过 85%RH且无凝露... --- 片段 2 (相似度: 0.7934) --- 运输和存储条件设备在运输过程中应避免剧烈冲击和震动存储温度建议在 -20℃ 至 60℃... --- 片段 3 (相似度: 0.6542) --- 安装注意事项设备应安装在通风良好的位置避免阳光直射... --- 片段 4 (相似度: 0.5120) --- 保修政策整机保修一年人为损坏不在保修范围内... --- Deepseek 回答 --- 根据产品手册设备应在 -10℃ 至 50℃ 的环境温度下正常运行湿度不超过 85%RH且无凝露。若设备处于运输或存储状态其允许的温度范围会有所不同具体请参考运输和存储条件说明。如何判断系统是成功的我通常按三个标准评估。第一检索结果是否相关如果片段 1 和片段 2 与问题明显相关说明切分和向量化基本有效。第二回答是否忠实于文档如果 Deepseek 的回答能从片段中找到出处没有自行补充文档里不存在的信息说明 Prompt 起到了效果。第三回答是否完整是否覆盖了所有相关片段里的关键信息。如果检索结果不相关优先检查文档切分是否合理而不是急着调 Prompt。你可以在 build_prompt 之前直接打印检索出来的文本块用人工判断“如果是人能不能从这些片段里找到答案”。如果人看着都找不到那大模型自然也不可能找到。如果回答明显编造了文档里没有的内容要降低 temperature同时强化 Prompt 中的限制条件比如加上“如果你不确定请直接回答不知道”。9. 常见问题与排查思路RAG 系统调试起来确实有些特别因为它不是单点问题链路中的一个环节出问题结果可能表现在完全不同的地方。我把实际开发中最常见的问题整理成一张排查表。问题现象可能原因排查方式解决方案检索到的文本块完全不相关文档切分不合理文本块语义不完整打印切分结果人工检查文本块是否有完整语义调整 chunk_size、chunk_overlap优化分隔符顺序检索结果相关但回答质量差召回的片段中有噪声或并列内容过多检查 TOP_K 设置是否过大观察每个片段的相似度分布减小 TOP_K增加片段过滤阈值或加入重排序环节回答出现明显编造内容Prompt 限制不足或 temperature 过高检查回答原文与参考片段是否匹配强化 Prompt 约束将 temperature 降到 0.2 到 0.3 之间首次运行下载模型耗时过长网络访问 HuggingFace 不稳定观察下载进度和报错信息配置镜像源或手动下载模型到本地目录加载 PDF 时中文乱码PDF 为扫描版或字体无法解析检查 PDF 是文本型还是图片型扫描版需要先做 OCR再进入 RAG 链路API 调用报 401 错误API Key 错误或环境变量未加载检查 .env 文件内容和加载逻辑确认 API Key 正确且未包含空格重启终端构建索引时内存不足文本块数量过多或 Embedding 模型过大监控内存使用情况使用批量编码或换用更小的 embedding 模型更新文档后检索结果没变化未重新构建索引检查索引文件修改时间重新运行 build_index.py 完成全量重建在这些问题里最隐蔽的是第一个。文本切分看起来很简单但实际上对检索效果的影响巨大。如果切分出来的块只有半句话或者把两个不相关的话题硬拼在一起向量表示的语义就会很混乱。所以在做任何优化之前我建议先把切分结果完整打印出来认真读一遍。另一个值得重视的问题是向量相似度分数并不代表绝对的“正确程度”。FAISS 返回的相似度分数可以用于排序但不同模型、不同文档体系下分数分布差异很大。不要用一个固定阈值去过滤所有问题。你更需要关注的是相对排序也就是排在前面的片段是否比后面的更相关。更复杂的场景下可以在向量检索之后再加一个重排序模型用交叉编码器对候选片段做二次精排但这属于进阶优化入门阶段先不用着急。10. 工程化落地的最佳实践与后续学习方向跑通上面的流程后你已经掌握了 RAG 的基础实现。但如果要把这个系统真正用到生产环境还需要考虑更多工程化问题。第一文档解析要做分层处理。不同格式的文档要使用不同的解析方案PDF 要区分文字版和扫描版扫描版需要接入 OCRWord 文档要考虑表格和图片网页需要爬虫和清洗。没有一种解析器能通吃所有格式。建议在文档入库前先做格式统一尽量转换成干净的 Markdown 或纯文本。第二实现增量更新与版本管理。知识库不是一次建完就结束了文档会持续更新。简单场景下可以用全量重建但数据量大了以后需要设计增量更新机制。同时建议为知识库版本打标签记录每个版本的文档来源、切分参数和模型版本这样即使上线后效果变差也能快速回滚到上一个版本。第三评估要前置到开发过程中。RAG 项目的难点不是写通链路而是持续优化效果。建议整理一批典型测试问题构建一个小型的测评集每次修改切分参数、检索策略或 Prompt 后都用同一批问题做回归验证。不要凭“感觉这次回答变好了”来判断效果。如果你准备做得更专业可以调研 RAG 测评相关的工具和指标比如忠实性、答案相关性和召回率。第四注意数据安全与访问权限。私有知识库里的文档很可能包含敏感信息。在调用外部大模型 API 时需要确认文档内容是否允许发送给第三方服务。如果数据敏感度高可以考虑私有化部署大模型或者使用支持私有化部署的开源模型。这属于安全边界问题一定要提前和业务方确认。第五为知识库补充引用溯源能力。当用户得到回答后最好能同时展示这段回答依据了哪些文档片段这样用户能自行核对答案。实现方式也很简单在返回回答的同时把检索到的文本块和对应的文档名称、页码一并返回给前端展示。这不仅能提升用户的信任感也能帮你判断系统是否回答在了点上。接下来值得深入研究的方向有三个一是混合检索把向量检索和关键词检索结合使用常见的做法是结合 BM25 与向量检索最后统一重排二是重排序模型通过交叉编码器对候选项做精排能明显提升检索质量三是 Agentic RAG也就是让大模型在回答过程中自主决定“要不要查知识库”“查几次”“要不要看当前结果再换一种方式查”这种方式适合更复杂的问答场景但工程复杂度会高很多。RAG 并不神秘它的核心本质是“给大模型配一个可更新的外部记忆”。你不需要一次做完所有优化先跑通最小闭环再围绕文档切分、检索质量和 Prompt 边界三个点做持续迭代这个系统的效果会稳步提升。如果这篇文章对你有帮助建议收藏备用后续可以按文中提到的三个方向继续深入把 RAG 从“能跑”做到“好用”。
返回列表