G6 问答编排
系列定位:G3 把文档灌进了库,G4 决定了怎么切,G5 决定了怎么找。 但「找得到」不等于「答得出」 —— G6 讲的是把这些零件接成一条链路: 缓存挡在最前面、Chain 把检索/精排/大模型串起来、上下文撑不下时再压一层。 版本基准:MindSDK 26.1.0(RAG SDK)
第一段 章前定位
这课不教你什么:
- 不教检索与精排的选型与参数(查询改写 / 近似检索 / reranker 具体怎么配)—— 那是 G5。 本课只讲怎么把检索器接进 Chain。
- 不教效果评估(怎么量化「答得准不准」)—— 那是 G7。
- 不教容量与成本规划(多少卡、多少并发、缓存能省多少算力)—— 那是 G8。
- 不教权限、审计、多租户 —— 那是 G9。 本课只负责把缓存那条安全警告原样交给你,怎么在系统里落实是 G9 的事。
- 不教文档解析与切分 —— G4 讲过;不教建库与灌数据 —— G3 讲过。
这课要教你什么:
前面几课拿到的是零件,这一课拿到的是链路。 零件都会用 ≠ 链路能对外服务。编排要回答三个问题:
# 问题 答在哪 ① 用户的问题进来,先经过谁? 缓存在最前面(4.4 / 5.3) ② 缓存没命中,谁按什么顺序干活? Chain 把检索 / 精排 / 大模型串起来(4.3 / 5.2) ③ 链路上哪些地方会让程序直接退出、让数据违规? 两条官方硬规范(3.3 / 3.4 / 4.8)
⚠️ 本课有两条官方硬规范,是前面几课都没专门讲的(G3 给的官方示例里恰好没有包):
- RAG SDK 的接口必须在 try/except 语句块内调用;
- 用 Cache 缓存问答对,生成的数据库未做加密存储 —— 涉及个人数据请勿存放。
第二段 学习目标
- 官方《开发流程》的在线问答五步分别是谁?为什么缓存排在最前面?
Chain到底是什么?官方给了哪几种 Chain,各自的必选参数是什么?- 缓存有两种(
memory cache/similarity cache),分别用在什么场景?两者怎么串联? - 为什么 RAG SDK 的接口必须包在 try/except 里?不包会看到什么现象?
- 缓存里绝对不能放什么?官方原话是怎么说的?
- 上下文太长塞不进模型时,官方给了哪三个压缩类?
第三段 全景:编排是把零件接成链路
3.1 一条链路长什么样
用户提问
│
▼
① 缓存查找(可选) ← cache_module
│ │
│ └──► 命中:直接返回(不检索、不推理)
▼
② Chain 编排 ← llm_chains
│ (串联下面三步;多轮对话、检索推理并行)
├──► ③ 检索器 Retriever ← retrieval(G5)
├──► ④ Reranker(可选) ← reranker(G5)
└──► ⑤ 组装 prompt → 大模型 ← llm_client
│
└──► 答案 → 回写缓存
⭐ 这张图里最容易被忽略的是最后那根箭头:
有配置缓存时,问答完成后会把问答对刷新到缓存中(官方原文), 再次命中时将缩短问答耗时。 —— 也就是说 缓存是"越用越热"的:第一周不省,第二周才开始省。
3.2 官方《开发流程》给的在线问答五步(原文摘录)
官方把在线问答拆成五步,顺序是官方定的,不要自己调:
| 步 | 官方原文 | 本课的零件 |
|---|---|---|
| ① | 「初始化缓存(可选),RAG SDK 支持配置缓存和近似搜索,当用户问答时优先从缓存中搜索答案,问题命中则直接返回缓存中的回答,未配置缓存或问题未命中缓存则继续以下推理过程。」 | cache_module |
| ② | 「初始化大模型 Chain,通过 Chain 串联大语言模型,检索和精排模块进行问答,用户可以选择文生文、文生图、图生图等 Chain,支持多轮对话、检索推理并行等方式。」 | llm_chains |
| ③ | 「初始化检索方式,用户可以定义近似检索、查询改写检索等方式。问题经过 embedding 模型向量化后,通过检索在知识库中找到上下文 context,进行下一步处理。」 | retrieval(G5)/ embedding |
| ④ | 「对检索到的上下文 context 通过 reranker 进行精排(可选),提高检索质量。」 | reranker(G5) |
| ⑤ | 「最后将用户问题和上下文 context 组装成 prompt,传入大语言模型进行推理并获得回答返回给用户。如果有配置缓存,问答完成后会将问答对刷新到缓存中,再次问答命中时将缩短问答耗时。」 | llm_client |
为什么这个顺序不能换:
- ① 在最前面:命中缓存的请求根本不进入 ②③④⑤ —— 挡在最前面才挡得最多;
- ② 在检索之前:Chain 是容器,检索器和精排是被放进去的东西 —— 先有容器再放零件,所以官方把"初始化 Chain"写在"初始化检索方式"前面;
- ⑤ 在最后:模型是唯一一个"贵且慢"的零件,前面所有步骤都是在为它准备输入。
3.3 ⭐ 硬规范一:RAG SDK 接口必须在 try/except 语句块内调用
官方原文(《RAG SDK · 接口参考 · 使用说明》):
RAG SDK接口遵循异常处理机制,故用户必须在try/except语句块内进行调用以及异常处理, 防止在使用的过程中出现异常抛出导致程序退出的情况。
这条为什么单列一段讲:
- 它是写在接口参考第一页的通用要求,管全部 16 个模块,不是某一个接口的注意事项;
- 而 G3 给的官方调用示例里没有包 try/except —— 照抄示例就违反了这条规范,这是最容易踩的一个坑;
- 后果不是"报个小错":官方原话是「异常抛出导致程序退出」。 一条链路上你写了十个 SDK 调用,任何一个没包,都可能让整个服务进程退出。
"程序退出"在企业场景里意味着什么:
不是"这次回答失败",是"这个常驻服务没了" —— 后面再来的请求全部连不上,直到有人发现并重启。
⚠️ 与 G5 的分工:G5 §5.4 已经从「检索/精排这一层为什么必须包」讲过这条规范; 本课不重复那一层,讲的是另外两件 G5 没讲的事:
| 本课补的 | 内容 |
|---|---|
| ① | G3 给的官方示例里没有包 try/except —— 而 G3 是「照抄就能跑通」的那一课,照着抄上线就不成立 |
| ② | 包法:一条链路上有缓存、模型客户端、构造 Chain、检索器、query 五类调用,怎么包才叫"全部罩住"(见 4.8) |
3.4 ⭐ 硬规范二:缓存未加密,个人数据请勿入库
官方原文(同上,NOTE 块):
RAG SDK如果使用Cache对问题-答案缓存,生成的数据库未做加密存储, 如果涉及银行卡号、身份证号、护照号、口令等个人数据,请勿存放到数据库中。
先把"为什么会这样"讲清楚:
| 缓存的落盘是什么 | 官方描述 |
|---|---|
memory cache |
内存 hash map,落盘文件(官方 NOTE:落盘文件不能超过 100MB) |
similarity cache |
sqlite(存问答对)+ 向量数据库(官方 NOTE:sqlite 落盘文件不超过 30GB,矢量库不超过 20GB) |
也就是说:缓存不是一个"临时内存",它是落在磁盘上的文件。 一旦问答对里带了身份证号、口令这类内容,它就以明文形式长期留在盘上 —— 清缓存文件、做备份、做迁移,都会把这份数据一起带走。
配套的第二条官方安全提醒(同样容易漏):
本接口内部使用了 pickle 模块,有被恶意构造的数据在 unpickle 期间攻击的风险。 需要保证在被加载的落盘数据
data_save_folder是安全存储,仅可加载可信的落盘数据。
两条合起来的判断:
缓存是"用安全换速度"的一个开关。 知识库问答(制度、手册、FAQ)可以放心开; 一旦问答内容涉及个人信息,就不是"要不要加密"的问题,而是"根本不该进缓存"。
3.5 编排里最反直觉的一点:Chain 的返回值有两种 key
官方给的两个文生文 Chain,返回字典的 key 不一样:
| Chain | 官方返回值说明 |
|---|---|
SingleText2TextChain |
带知识来源:{"query": query, "result": data, "source_documents": [...]} |
ParallelText2TextChain |
带知识来源:{"prompt": prompt, "result": data, "source_documents": [...]} |
一个是 query,一个是 prompt。
⚠️ 这是官方文档里就这么写的,不是我写错 —— 所以下游解析返回值的代码,不能假设两个 Chain 可以互换。 换 Chain 时先打印一次原始返回,比照文档核对 key。
第四段 【如果我来做】从「零件都在」到「一条链路能对外服务」
4.0 我会按这个顺序走(顺序错了会白干)
① 摸底:零件到底在不在(不装不改,先查事实)
⚠️ 别一上来就写编排代码 —— 零件缺一个,报错位置全在别处
② 先单独验大模型这条路(llm_client 一层,不接检索)
⚠️ 这一步最长被跳过:模型服务不通,后面所有报错都长得像"检索坏了"
③ 再单独验检索这条路(Retriever 一层,不接大模型)
看它返回的 source_documents 是不是你想要的片段
④ 两条路都通了,才装 Chain(把 llm + retriever 接起来)
⚠️ 一次只加一个零件,加完立刻验
⑤ 决定缓存要不要、要哪种(memory / similarity)
知识库问答重复率高 → 值得开;涉及个人数据 → 不能开
⑥ 把缓存套在 Chain 外面(CacheChainChat),跑两遍同样的问题
第一遍未命中(慢),第二遍命中(快)—— 这就是缓存的验收证据
⑦ 补上 try/except(官方硬规范)与缓存数据边界检查
⚠️ 官方示例没包,这一步必须自己加
⑧ 端到端跑一遍官方样例,留证据(命令 + 原始输出)
⭐ 这个顺序里最容易被跳过的是 ② 和 ③。
新手常犯的错:直接把三段代码拼在一起就跑 —— 一报错,你不知道是模型服务不通、还是检索没结果、还是 Chain 接错了。 先分开验,再合起来验,是把"排错"变成"不用排错"的唯一办法。
4.1 第 ① 步:先摸底,确认零件都在
命令 npu-smi info
含义:查昇腾设备当前状态 —— 确认卡在、驱动在 拆解:
npu-smi(昇腾设备管理工具)+info(子命令:显示设备信息) 用法:直接执行(⚠️ 官方说明:不支持多线程并发使用) 期望:输出一张表,列出每张卡的 NPU ID / Chip Count / 产品类型 / 温度 / 显存 / 进程 不过会怎样:命令不存在 → 驱动没装(回 A1); 有输出但列表里没有卡可用 → 大概率是容器没把设备透传进来(回 G3 的容器启动参数)
命令 docker images | grep ragsdk
含义:确认 RAG SDK 镜像拉下来了、Tag 对不对 拆解:
docker images(列出本地镜像)+| grep ragsdk(只看名字里带 ragsdk 的) 用法:直接执行 期望:一行形如ragsdk 26.0.0-910b-ubuntu22.04-py3.11 ...—— Tag 格式官方给的是{version}-{chip}-{os}-{python}不过会怎样:没有输出 → 镜像没拉或名字打错; 拉到了但架构不对(x86_64 / aarch64 混了)→ 容器起不来的典型原因(官方 NOTE 明确提醒要区分)
命令 ls /workspace/RAGSDK_Samples/rag_with_api
含义:确认容器里的官方样例代码在哪(官方 NOTE:容器内示例代码位于
/workspace/RAGSDK_Samples) 拆解:ls(列出目录)+ 完整路径 用法:先docker exec -it ragsdk_demo bash进容器,再执行 期望:列出rag_demo_knowledge.py、rag_demo_query.py等文件 不过会怎样:报No such file or directory→ 路径不对,或这是另一个镜像版本; 注意官方 NOTE 只给了"容器内示例代码位于/workspace/RAGSDK_Samples"这个大路径, 具体子目录名与脚本名请以你容器里的实际内容为准
命令 python3 -c "from mx_rag.chain import SingleText2TextChain, ParallelText2TextChain; from mx_rag.cache import MxRAGCache, CacheChainChat; from mx_rag.llm import Text2TextLLM; print('ok')"
含义:确认本课的四个核心模块都能 import 进来 拆解:
python3 -c(执行一段代码)+from 模块 import 类(官方给的导入路径) 用法:在容器内执行(类名与导入路径均来自官方调用示例) 期望:打印ok不过会怎样:报ModuleNotFoundError→ 该模块所属版本没装或 Python 环境不对(回 G3 的安装与PYTHONPATH); ⚠️ 这类 import 自检不是官方命令,是通用做法 —— 官方给的是各类的导入路径本身
4.2 第 ② 步:先单独验大模型这条路(llm_client)
为什么先验它:它是唯一一个要连外部服务(模型服务)的零件, 连接问题、证书问题、模型名问题都在这一层。这一层不通,后面全在报假错。
官方给的调用形态(照抄,改 URL / 模型名 / 证书路径):
from mx_rag.llm import Text2TextLLM, LLMParameterConfig
from mx_rag.utils import ClientParam
llm = Text2TextLLM(base_url="https://{ip}:{port}/v1/chat/completions",
model_name="qianwen-7b",
llm_config=LLMParameterConfig(max_tokens=512),
client_param=ClientParam(ca_file="/path/to/ca.crt")
)
res = llm.chat("请介绍下北京")
print(res)
for res in llm.chat_streamly("请介绍下北京"):
print(res)
这一段里有四个必须对上的东西:
| 你要给对的 | 官方说明 | 给错了会怎样 |
|---|---|---|
base_url |
大模型服务地址,长度 [1, 128];官方明说「当前只支持兼容 OpenAI 接口 /v1/chat/completions」 |
填成别的路径 → 一直连不上或返回格式错 |
model_name |
LLM 模型名称,长度 [1, 128] | 模型名不对 → 服务端拒绝 |
client_param |
https 客户端配置;官方 ClientParam 的 use_http 默认 False(即 HTTPS),HTTPS 下 ca_file 是必传参数 |
内网用 HTTPS 又没给 ca_file → 证书相关报错 |
llm_config |
只在通过 langchain 调用时生效;非 langchain 方式要通过 chat / chat_streamly 传入 |
在 llm_config 里配了参数却用 .chat() 调用 → 参数不生效 |
chat 与 chat_streamly 的返回值(官方):
chat(query, sys_messages, role, llm_config)→ 返回str:LLM 文本推理的结果;query字符串长度范围 [1, 4×1024×1024];role默认user;sys_messages默认None。chat_streamly(...)→ 返回Iterator[str]:流式结果(用for逐段取)。
⭐ 这一层验通了,再往下走 —— 记住你刚才看到的输出长什么样, 后面 Chain 报错时,先问一句"是这一层的输出没拿到,还是后面的检索没结果"。
4.3 第 ③ 步:再单独验检索这条路,然后才装 Chain
官方给的检索器构造形态(参数说明见《检索》接口参考,G5 已逐条展开):
retriever = Retriever(vector_store=vector_store,
document_store=chunk_store,
embed_func=emb.embed_documents,
k=1,
score_threshold=0.6)
Chain 的参数里,谁必选谁可选(官方对照):
| Chain | 函数原型(官方) | 必选 | 可选 |
|---|---|---|---|
SingleText2TextChain |
SingleText2TextChain(llm, retriever, reranker, prompt, sys_messages, source, user_content_builder) |
llm、retriever |
reranker(默认 None)、prompt、sys_messages、source(默认 True)、user_content_builder |
ParallelText2TextChain |
同上(class ParallelText2TextChain(SingleText2TextChain)) |
llm、retriever |
同左 |
⭐ 这就是"编排"最省事的地方:
Chain 的必选参数只有两个:一个模型对象、一个检索对象。 精排不接也能跑(默认
None)、prompt 不写也有默认值、系统消息不写就是None。 —— 所以"最小可用链路"就是三行:llm+retriever+Chain(...)。
官方调用示例(基本对话功能,照抄骨架):
# 前面已按 G3/G4 建好 vector_store、chunk_store、emb、加载器与切分器
client_param = ClientParam(ca_file="/path/to/ca.crt")
llm = Text2TextLLM(model_name="Meta-Llama-3-8B-Instruct",
base_url="https://x.x.x.x:port/v1/chat/completions",
client_param=client_param)
r = Retriever(vector_store=vector_store, document_store=chunk_store,
embed_func=emb.embed_documents, k=1, score_threshold=0.6)
rag = SingleText2TextChain(retriever=r, llm=llm)
response = rag.query("mxVision软件架构包含哪些模块?",
LLMParameterConfig(max_tokens=1024, temperature=1.0, top_p=0.1))
print(response)
几个容易漏的官方细节:
| 项 | 官方说明 | 记法 |
|---|---|---|
prompt 默认值 |
「根据上述已知信息,简洁和专业地回答用户的问题。如果无法从已知信息中得到答案,请根据自身经验做出回答」 | 不写 prompt 不等于"没提示词",官方有默认值 |
source 默认 |
True —— 即默认会返回检索到的文档(key 为 source_documents) |
不想要就把 source=False |
query 的 llm_config 默认值 |
官方:此处默认值 temperature 为 0.5、top_p 为 0.95 |
和 LLMParameterConfig 自身的默认值(1.0 / 1.0)不一样 |
query(text, ...) 的 text |
取值范围 (0, 1000×1000] | 超长问题要自己先截断 |
user_content_builder |
回调函数,默认函数把「原始问题 + 检索到的文档列表 + 系统提示词」拼成 user 消息的 content | 要改拼接格式(比如图文并茂)才需要自己写 |
⭐ 官方那个默认 user_content_builder 值得看一眼,它把"编排"这件事讲透了:
final_prompt = ""
document_separator: str = "\n\n"
if len(docs) != 0:
if prompt != "":
last_doc = docs[-1]
last_doc.page_content = last_doc.page_content + f"{document_separator}{prompt}"
docs[-1] = last_doc
final_prompt = document_separator.join(x.page_content for x in docs)
if final_prompt != "":
final_prompt += document_separator
final_prompt += query
return final_prompt
它做的事就是"第 ⑤ 步:组装 prompt":
检索到的 chunks 用空行拼成一整块 → 系统提示词挂在最后一片 chunk 后面 → 用户的原始问题放在最末尾。
4.4 第 ⑤ 步:决定缓存要不要、要哪种
先做决定,再写代码。官方的两种缓存,用途不一样:
| 类型 | 官方结构描述 | 什么时候用 |
|---|---|---|
memory cache |
「完全匹配的 cache,在内存采用的是 hash map 结构,hash key 是用户的 query hash 值,hash value 是用户的问题,用户问题需要满足完全匹配」 | 用户问法固定(点选式提问、按钮式提问) |
similarity cache |
「存储结构是 sqlite + 向量数据库(faiss, npu_faiss, milvus)」;查询时先 embedding → 向量库查 topK → sqlite 取答案与问题 → 再用 reranker 对"缓存的问题"和"用户的问题"精排 → 返回最相似的结果 | 用户用自己的话问(同一个意思、不同说法) |
官方还给了第三种做法:把两级串联起来:
| 方法 | 官方说明 |
|---|---|
join(next_cache) |
「将两个 cache 进行串联,达到多级缓存目的」;官方约束:下级必须是 MxRAGCache 类型、不能串联自己、不能成环、最大串联深度为 6 |
similarity cache 有一个让人意外的官方默认值:
similarity_threshold默认值 0.8,取值范围[0.0, 1.0]。
⚠️ 0.8 在"语义相似"这个任务上是偏严的 —— 用户换个说法问同一件事,相似度经常落在 0.7~0.9 之间。 这时候的表现是"看起来缓存没生效":你明明确信问过,但它就是不命中。 → 想让它命中得更积极,先动这个阈值,别急着怀疑代码写错了。
官方给出的可配参数(CacheConfig,用于 memory cache):
| 参数 | 必选/可选 | 官方默认值 | 范围 / 约束 |
|---|---|---|---|
cache_size |
必选 | — | (0, 100000],且不能小于等于 0 |
eviction_policy |
可选 | LRU |
见 EvictPolicy(LRU / LFU / FIFO / RR) |
auto_flush |
可选 | 20 | (0, cache_size],即"缓存多少条之后落一次盘" |
data_save_folder |
可选 | 当前用户家目录 + /Ascend/mxRag/cache_save_folder |
⚠️ 官方明说:若不存在需要用户创建;不能是软链接和相对路径;不能落在 ["/etc","/usr/bin","/usr/lib","/usr/lib64","/sys/","/dev/","/sbin","/tmp"] |
min_free_space |
可选 | 1GB | [20MB, 100GB],单位字节 |
similarity_threshold |
可选 | 0.8 | [0.0, 1.0] |
disable_report |
可选 | False |
True = 不支持维测数据 |
lock |
可选 | None |
⚠️ 官方:CacheConfig 不支持多线程或多进程处理;要用就自己申请锁(None=不支持并发/multiprocessing.Lock()=支持多进程/threading.Lock()=支持多线程) |
⭐ data_save_folder 是这一节里最会出事的一个参数,两个原因:
- 默认路径不存在时要你自己创建 —— 不建,缓存落盘就失败;
- 官方把
/tmp明确列进了禁止清单 —— 在容器里图省事写/tmp/cache是最常见的写法,也是明确违规的写法。
SimilarityCacheConfig 的必选参数(用于 similarity cache):
| 参数 | 官方说明 |
|---|---|
vector_config |
配置矢量数据库。注意:如果是 faiss 数据库,MindFAISS 的 load_local_index 参数会被 data_save_folder 落盘路径覆盖,auto_save 参数置为 False |
cache_config |
配置标量数据库,当前只支持配置为 "sqlite" |
emb_config |
配置 embedding 模型(参见 create_embedding) |
similarity_config |
配置相似度计算模型(参见 create_reranker) |
retrieval_top_k |
可选,默认 1,范围 (0, 1000] |
clean_size |
可选,默认 1 —— 每次缓存超过 cache_size 时老化的个数 |
官方约束(很重要):
vector_config和cache_config必须同时为None或同时不为None。 如果两者同时为None,则等同于 memory cache。
官方给的 CacheConfig + MxRAGCache 初始化片段(照抄):
from mx_rag.cache import CacheConfig
from mx_rag.cache import EvictPolicy
from mx_rag.cache import MxRAGCache
cache_config = CacheConfig(
cache_size=100,
eviction_policy=EvictPolicy.LRU,
data_save_folder="path_to_cache_save_folder"
)
mxrag_l1_cache = MxRAGCache("memory_cache", cache_config)
MxRAGCache(cache_name, config) 的 cache_name 有格式要求:
官方:字符串长度 (0, 64],取值范围
[0-9a-zA-Z_]—— 只能是字母、数字或下划线,且该命名会体现在落盘文件名中。
⚠️ 这意味着:中文缓存名、带横线的名字都会失败。
4.5 第 ⑥ 步:把缓存套在 Chain 外面(CacheChainChat)
这是"编排"这一步的成品 —— 官方的 CacheChainChat 把缓存和 Chain 接成一个对象:
| 项 | 官方说明 |
|---|---|
| 功能 | 「用于适配 RAG SDK 的文生文、图生图、文生图的各种 chain,同时也提供访问 MxRAGCache 的能力,当缓存未命中时,将进行大模型推理,然后将结果再刷新至缓存」 |
| 原型 | CacheChainChat(cache, chain, convert_data_to_cache, convert_data_to_user) |
cache |
必选,MxRAGCache |
chain |
必选,RAG SDK chain |
convert_data_to_cache |
可选,Callable[[Any], Dict],默认不做转换 |
convert_data_to_user |
可选,Callable[[Dict], Any],默认不做转换 |
query(text, ...) |
「提供给用户查询缓存的接口,当缓存无法查询时,会访问大模型」;text 字符数范围 (0, 128M] |
query 返回 |
Union[Dict, Iterator[Dict]],形如 {"query": query, "result": data, "source_documents": [...]} |
官方调用示例(照抄骨架,这就是本课的"最小链路"):
# Step2 在线问题答复,初始化检索器
retriever = vector_store.as_retriever(document_store=chunk_store,
embed_func=emb.embed_documents, k=3, score_threshold=0.3)
# 配置 text 生成 text 大模型 chain,具体 ip 端口请根据实际情况适配修改
llm = Text2TextLLM(base_url="https://<ip>:<port>",
model_name="Llama3-8B-Chinese-Chat",
client_param=ClientParam(ca_file="/path/to/ca.crt"))
text2text_chain = SingleText2TextChain(llm=llm, retriever=retriever)
cache_chain = CacheChainChat(chain=text2text_chain, cache=cache)
res = cache_chain.query("请描述2024年高考作文题目")
print(res)
res = cache_chain.query("2024年的高考题目是什么", )
print(res)
⭐ 这段官方示例本身就是一份"验收脚本":
它问了两次,
- 第一次:请描述2024年高考作文题目
- 第二次:2024年的高考题目是什么 —— 换了说法,问同一个意思
如果第二次命中了缓存,就证明 similarity cache 那条路是通的。
官方示例里还带计时(time.time() 前后包住 query)——
这是我推荐的验收证据格式:同一问题的两次耗时 + 两次返回。
CacheChainChat 与直接用 MxRAGCache 的分工:
| 你想干什么 | 用哪个 |
|---|---|
| 让问答链路自动享受缓存(未命中→推理→回写) | CacheChainChat |
| 手工塞一条问答对进去 | MxRAGCache.update(query, answer) |
| 查一下某句话缓存在不在 | MxRAGCache.search(query) |
| 强制落盘 / 清空 | MxRAGCache.flush() / MxRAGCache.clear() |
| 拿到底层 gptcache 对象(对接 LangChain 等) | MxRAGCache.get_obj() |
这几个方法各有官方约束,写代码前先看清:
| 方法 | 官方约束 |
|---|---|
update(query, answer) |
必须在初始化之后调用,否则会抛出异常;answer 字符数范围 (0, min(1000000, cache_limit)],超了就不会被缓存 |
search(query) |
必须在初始化之后调用;返回 Dict,是返回 QA 对的 Unicode 编码字符串,形如 {"\u66aa....?": "\u6ae6..."} |
flush() |
必须在初始化之后调用;把内存缓存强制刷新至磁盘,地址就是 data_save_folder |
clear() |
「删除 data_save_folder 下已落盘的缓存文件」;⚠️ 官方:对于 memory cache,由于关闭时会自动 flush 再次写入缓存,需要用户程序中再次清理 |
set_cache_limit(cache_limit) |
每一条被缓存答案的字符数限制,默认一百万字符,范围 (0, 1000000];超限的答案不会被缓存 |
set_verbose(verbose) |
是否记录 cache 过程的命中或未命中,默认 False |
⭐ search 的返回值格式是个真实的坑:
官方写的是「返回 QA 对的 Unicode 编码字符串」—— 打印出来是
\u66aa这种形式,不是中文。 看到这个不要以为是乱码或编码坏了,这是官方定义的格式; 要显示成中文得自己解码。
clear() 那条也值得单独记:
对 memory cache,
clear()之后关闭程序会自动 flush 再把缓存写回去 —— 所以"清缓存"这件事必须放在程序里做,且要在关闭前处理。
4.6 多轮对话怎么接(官方给的是一条 LangChain 路线)
官方《开发流程》写的是「支持多轮对话」,但它给的样例是用 LangChain 的 memory 实现的:
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from mx_rag.llm import Text2TextLLM
from mx_rag.utils import ClientParam
template = """You are a chatbot having a conversation with a human. Please answer as briefly as possible.
{chat_history}
Human: {human_input}"""
prompt = PromptTemplate(input_variables=["chat_history", "human_input"], template=template)
# k可以设置保存的历史会话轮数,还支持ConversationBufferMemory和ConversationTokenBufferMemory
memory = ConversationBufferWindowMemory(memory_key="chat_history", k=3)
client_param = ClientParam(ca_file="/path/to/ca.crt")
chat = Text2TextLLM(base_url="https://ip:port/v1/chat/completions",
model_name="Llama3-8B-Chinese-Chat",
client_param=client_param)
llm_chain = LLMChain(llm=chat, prompt=prompt, memory=memory, verbose=True)
questions = ["请记住小明的爸爸是小刚", "七大洲前四个是啥?", "后三个呢?"]
for question in questions:
llm_chain.predict(human_input=question)
completion = llm_chain.predict(human_input="请问小明的爸爸是谁?")
print(completion)
这条样例里的关键事实(都是官方的):
| 事实 | 官方说明 |
|---|---|
| 历史轮数怎么控 | 「k 可以设置保存的历史会话轮数」;还支持 ConversationBufferMemory 和 ConversationTokenBufferMemory |
| 多轮对话是谁实现的 | 官方这条样例里,记忆是由 LangChain 的 memory 提供的,Text2TextLLM 只当"模型客户端"被塞进 LLMChain |
| 历史怎么进模型的 | 官方给了拼接结果:chat_history 被展开成 Human: ... / AI: ... 交替的文本,放在本轮问题之前 |
⚠️ 一个需要现场确认的地方: 《开发流程》说 Chain「支持多轮对话」,但《开发流程》的「多轮对话」章节给的是 LangChain 的 memory 方案; "RAG SDK 自己的 Chain 怎么原生管理多轮历史",官方文档未给出成文说明 —— 用哪条路线,请以你所用版本的实际样例为准。
而"检索推理并行"这条则是有明确官方对象的:
| Chain | 官方功能描述 |
|---|---|
ParallelText2TextChain |
「支持检索并行推理文生文对话 Chain。继承了 SingleText2TextChain 基类,节省检索时延」 |
所以"并行"的含义是:检索和推理不再串着等 —— 这是降延迟的手段,用在检索慢(库大)的场景。代价是返回值 key 变了(见 3.5)。
4.7 上下文撑不下时的第 6 个零件:Prompt 压缩
现象:检索回来一堆 chunk,拼起来超过了模型的上下文长度(或 max_tokens),
要么报长度错,要么模型只看了前面一半。
官方的名字叫「Prompt 压缩」,给了三个类:
| 类 | 官方功能描述 | compress_texts 原型 |
|---|---|---|
PromptCompressor |
prompt 压缩抽象类(class PromptCompressor(ABC)) |
def compress_texts(self, context, question) |
RerankCompressor |
「通过排序模型计算 question(总结长文本的指令)和 context 切片之间的相关性得分,根据设定的压缩率阈值,优先保留相关性高的切片」 | def compress_texts(context, question, compress_rate, context_reorder) |
ClusterCompressor |
「通过聚类模型将嵌入后的文本进行聚类,划分为多个语义簇。随后计算 context 的切片与 question 的余弦相似度。根据设定的压缩率,在每个簇内删除相似度较低的切片」 | def compress_texts(context, question, compress_rate) |
两者的区别(一句话记住):
RerankCompressor是"全局排序后砍尾巴";ClusterCompressor是"先在簇内公平地挑,再砍"。簇内挑的意义在于:避免某个话题把名额全占光 —— 全局排序时,语义集中的那一段会排满前面,别的主题一个都留不下。
官方给的 RerankCompressor 调用片段(照抄):
from mx_rag.compress.rerank_compressor import RerankCompressor
from mx_rag.reranker.local import LocalReranker
from langchain.text_splitter import RecursiveCharacterTextSplitter
reranker = LocalReranker(model_path="reranker_path", dev_id=0)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=0,
separators=["\n", ""], keep_separator=True)
compressor = RerankCompressor(reranker=reranker, splitter=text_splitter)
res = compressor.compress_texts(context, question, 0.3)
print(res)
三个参数的含义与默认值(官方):
| 参数 | 官方说明 |
|---|---|
context |
必选,待总结的长文本,长度范围 [1, 16MB] |
question |
必选,总结长文本的指令,长度范围 [1, 1000×1000] |
compress_rate |
可选,默认 0.6,取值范围 (0, 1) |
context_reorder |
可选,默认 False;官方原文:「若为 True,计算完相关性之后,将根据压缩率优先保留相关性低的文本切片」 |
| 返回 | str:压缩后的文本 |
⚠️ 官方对
context_reorder的这句描述读起来是反的 —— 参数名叫"重排",说明里却写"优先保留相关性低的切片"。 这是官方文档的原文,我照原样引用,不做解读; 要用这个参数,请现场先用一小段文本对比True/False的结果再决定。
ClusterCompressor 的一个参数很实用:
dev_id:NPU id,通过npu-smi info查询可用 ID,取值范围 [0, 63],默认为卡 0。 ⚠️ 注意"通过npu-smi info查询可用 ID"是官方原话 —— 也就是说不要默认卡号一定是 0(在容器/租用环境里经常不是,见本体系容器环境的实测记录)。
压缩类的角色分工:RerankCompressor 的 splitter 默认值、ClusterCompressor 的 splitter 默认值不一样:
| 类 | splitter 官方默认值 |
|---|---|
RerankCompressor |
RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=0, separators=["\n", ""], keep_separator=True) |
ClusterCompressor |
RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0, separators=["。", "!", "?", "\n", ",", ";", " ", ""]) |
记法:
ClusterCompressor是按中文标点切的(它要算"切片"和指令的相似度,切得细才准);RerankCompressor是按换行切的(切得粗,靠排序砍)。
4.8 第 ⑦ 步:把两条官方硬规范落到代码里
官方五步 + try/except,重写一遍骨架(这才是"符合官方规范的编排"):
from mx_rag.cache import CacheConfig, EvictPolicy, MxRAGCache, CacheChainChat, SimilarityCacheConfig
from mx_rag.chain import SingleText2TextChain, ParallelText2TextChain
from mx_rag.llm import Text2TextLLM, LLMParameterConfig
from mx_rag.retrievers import Retriever
from mx_rag.utils import ClientParam
try:
# ① 初始化缓存(可选)
cache = MxRAGCache("kb_cache", CacheConfig(
cache_size=1000,
eviction_policy=EvictPolicy.LRU,
data_save_folder="/data/mxrag/cache", # 必须自己创建,且不能是 /tmp
))
# ② 初始化大模型 Chain 所需的模型客户端
llm = Text2TextLLM(
base_url="https://<llm-ip>:<port>/v1/chat/completions",
model_name="Qwen3-4B",
client_param=ClientParam(ca_file="/path/to/ca.crt"),
)
# ③ 初始化检索方式(vector_store / chunk_store / emb 来自 G3、G4 已建好的库)
retriever = Retriever(vector_store=vector_store, document_store=chunk_store,
embed_func=emb.embed_documents, k=3, score_threshold=0.6)
# ④ 精排(可选)—— 不接就是 None;接了就见 G5
# ⑤ 组装:Chain 负责串起"检索 → 精排 → 组装 prompt → 大模型"
rag = SingleText2TextChain(llm=llm, retriever=retriever)
# 缓存套在 Chain 外面:未命中则推理,再把结果回写缓存
cache_chain = CacheChainChat(chain=rag, cache=cache)
res = cache_chain.query("公司报销流程是怎样的?",
LLMParameterConfig(max_tokens=1024))
print(res)
except Exception as e:
# 官方要求:异常处理也要在这里做
print(f"[RAG SDK 调用失败] {type(e).__name__}: {e}")
这段骨架里刻意做了三件事,每件对应一条规范:
| # | 我做了什么 | 对应哪条 |
|---|---|---|
| ① | 所有 RAG SDK 调用都包在同一个 try 里 |
官方硬规范一 |
| ② | except 里真的处理了(打印类型 + 消息),不是 pass |
官方原文是「调用以及异常处理」,光包不处理不算 |
| ③ | 缓存路径写在数据盘、不写 /tmp、缓存名只用 [0-9a-zA-Z_] |
官方 data_save_folder 约束 + cache_name 格式约束 |
⭐ 关于"包多细"的取舍(这是我的做法,官方未规定):
粒度无所谓,覆盖面必须全。 官方要求的是"调用在 try/except 内", 包一个大
try把整条链路罩住,和每个调用各包一个try,都满足要求。 但每个except里都要真的做点什么 —— 包了不处理,异常被吞掉,你看到的现象从"程序退出"变成"回答永远是空的", 那更难查。
缓存数据边界(规范二落地):
| 做法 | 说明 |
|---|---|
| 入库前过滤 | 在 CacheChainChat 的 convert_data_to_cache 回调里做拦截 —— 官方给了这个回调(Callable[[Any], Dict],默认不做转换),它就是"进缓存前的最后一关" |
| 把缓存目录排除在备份之外 | 因为官方明说"未做加密存储" |
| 权限收紧 | 官方对 data_save_folder 的要求里已经写了属组与写权限的约束,照着配 |
| 不要用缓存兜底敏感问答 | 官方原话是「请勿存放到数据库中」—— 不是"加密后可以放" |
⚠️
convert_data_to_cache是我按官方参数功能给出的落点, 官方文档没有给出"用它做敏感信息过滤"的示例 —— 这一条属用法归纳,请现场验证。
4.9 第 ⑧ 步:端到端跑一遍,拿证据
官方给的端到端样例命令(就是"一条能对外服务的链路"的最小验证):
命令 python3 rag_demo_knowledge.py --embedding_url http://127.0.0.1:8080/v1/embeddings --white_path /workspace --file_path /workspace/testdata/gaokao.txt
含义:运行官方的知识库构建脚本 —— 这是跑问答链路的前置(库得先有东西) 拆解:
rag_demo_knowledge.py(官方样例脚本);--embedding_url(向量模型服务地址);--white_path(允许访问的白名单目录);--file_path(要入库的文件) 用法:先进容器、cd /workspace/RAGSDK_Samples/rag_with_api,再执行(官方步骤 5) 期望:官方给的判据是输出以下结果表示知识库构建成功:['gaokao.txt']不过会怎样:报路径不在白名单 →--white_path没覆盖到--file_path;http://127.0.0.1:8080是官方示例 URL,embedding服务没起就是这个地址连不上
命令 python3 rag_demo_query.py --embedding_url http://127.0.0.1:8080/v1/embeddings --llm_url http://127.0.0.1:1025/v1/chat/completions --model_name Qwen3-4B --query "请描述2024年高考作文题目"
含义:运行官方的在线问答脚本 —— 这就是本课要交付的"一条能对外服务的链路" 拆解:
--embedding_url(检索侧向量服务);--llm_url(大模型服务);--model_name(模型名,要和--llm_url后面的服务对上);--query(问的问题) 用法:同上,在容器内rag_with_api目录执行(官方步骤 7) 期望:官方判据是「若输出包含检索到的文档内容和生成的回答,表示问答流程运行正常」, 形如{'query': '请描述2024年高考作文题目', 'result': '...', 'source_documents': [...]}不过会怎样: 只有result没有source_documents→ 检索没召回(回 G5 调检索,别在编排层找原因); 报证书/连接错 →ca_file与协议没对上(见 4.2 那张表);result是空字符串 → 不算成功(这跟 D2 的「空补全不算推理成功」是同一个坑); ⚠️ 官方 NOTE:http://127.0.0.1:8080和http://127.0.0.1:1025都是示例 URL, 具体 URL 以你本地部署的实际参数为准
验收证据(我建议的格式,官方未规定):
| 证据 | 从哪来 |
|---|---|
① 一次完整问答的原始返回(含 source_documents) |
上面那条命令的输出 |
| ② 同一意思、换说法问第二次的耗时对比 | 4.5 的官方计时写法 |
| ③ 缓存落盘目录里的文件清单 | ls <data_save_folder> |
④ try/except 生效的证据 |
故意把 --llm_url 改错,看程序是"打印异常后正常结束"还是"整个退出" |
第 ④ 条是我特别推荐的 —— 官方硬规范一验证起来其实很简单: 把一条必然失败的东西传进去,看进程是"活着报错"还是"死掉"。 这是唯一能证明你的
try/except真的罩住了全部调用的方法。
4.10 我诚实的地方:这课有几处官方没给答案
| 我写了什么 | 官方状态 |
|---|---|
4.1 三条摸底命令里的 npu-smi info |
官方命令(A 层已讲),但官方 RAG 文档没把它列进编排流程 |
| 4.1 的 import 自检 | 通用做法,非官方命令(类名与导入路径来自官方示例) |
4.4 的 similarity_threshold=0.8 偏严 判断 |
我的经验判断,官方只给了默认值 |
4.8 用 convert_data_to_cache 过滤敏感信息 |
用法归纳,官方无此示例 |
| 4.8 的"包一个大 try 就够了" | 我的做法,官方未规定粒度 |
| 4.9 的证据清单 | 实践归纳,官方只给了"输出包含文档内容和回答即正常" |
| 各 Chain 的性能对比数字 | 官方未给出 |
| 缓存能省多少算力 / 具体倍数 | 官方 26.1.0 文档原文只写「缓存命中可以大幅度提升端到端性能」,未给出具体倍数 |
| RAG SDK 自己的 Chain 如何原生管理多轮历史 | 官方未给出成文说明(样例走的是 LangChain memory) |
第五段 逐件拆开:本课四个零件的官方接口
5.1 对接模型客户端 llm_client
| 类 | 官方功能描述 | 函数原型 |
|---|---|---|
Text2TextLLM |
建立客户端对接语言大模型服务;当前只支持兼容 OpenAI 接口 /v1/chat/completions;继承实现了 langchain.llms.base.LLM |
Text2TextLLM(base_url, model_name, llm_config, client_param),所有参数需通过关键字参数传递 |
Text2ImgMultiModel |
对接文生图大模型服务;当前只支持 stable-diffusion-v1-5 和 stable-diffusion-2-1-base |
Text2ImgMultiModel(url, model_name, client_param) |
Img2ImgMultiModel |
对接图生图大模型服务;当前只支持 IP-Adapter 搭建的 stable-diffusion-v1-5 |
Img2ImgMultiModel(url, model_name, client_param) |
Img2TextLLM |
建立客户端对接视觉大模型服务;当前只支持兼容 OpenAI 接口 /openai/v1/chat/completions |
Img2TextLLM(base_url, prompt, model_name, llm_config, client_param) |
⭐ 注意三个"当前只支持" —— 这是官方原话。 多模态 Chain(文生图 / 图生图)的可用性取决于模型侧支持哪些模型, 不是"接上就能用"。
Text2TextLLM 的两个方法(对照记忆):
| 方法 | 返回 | 用途 |
|---|---|---|
chat(query, sys_messages, role, llm_config) |
str |
一次性拿完整答案 |
chat_streamly(query, sys_messages, role, llm_config) |
Iterator[str] |
流式(边生成边出) |
LLMParameterConfig 的官方参数表(编排时最常要动的就是这张表):
| 参数 | 官方默认值 | 取值范围 | 说明 |
|---|---|---|---|
max_tokens |
512 | [1, 100000] | 允许推理生成的最大 token 数 |
temperature |
1.0 | [0.0, 2.0] | 控制生成的随机性,越大越发散 |
top_p |
1.0 | (0.0, 1.0] | 累积概率选候选词 |
presence_penalty |
0.0 | [-2.0, 2.0] | 惩罚已出现过的词,促使谈新主题 |
frequency_penalty |
0.0 | [-2.0, 2.0] | 按出现频率惩罚,降低重复用词 |
seed |
None |
[0, 2³¹-1] | 相同 seed 可保证结果可重现;不传则系统随机 |
stream |
False |
— | 是否流式回答;⚠️ 官方:该参数在 ["ParallelText2TextChain", "SingleText2TextChain", "GraphRagText2TextChain"] 场景生效 |
官方对 LLMParameterConfig 的一句提醒:
「每个参数具体有效值会根据不同模型配置有差别。」 → 同一组参数换个模型可能就不生效了,调参前先确认模型侧支持什么。
5.2 大模型 Chain(llm_chains)—— 本课的主角
官方开篇一句话:
Chain 定义实现了对接大模型客户端。
全部 Chain 形态(官方目录里列了这些):
| Chain | 官方功能描述 | 函数原型 | 必选参数 |
|---|---|---|---|
Chain(抽象基类) |
大模型 Chain 抽象基类,定义抽象接口 | from mx_rag.chain.base import Chain;Chain() |
— |
SingleText2TextChain |
单轮对话 Chain,实现基本的问答对话功能,继承 Chain |
SingleText2TextChain(llm, retriever, reranker, prompt, sys_messages, source, user_content_builder) |
llm、retriever |
ParallelText2TextChain |
支持检索并行推理文生文对话 Chain,继承 SingleText2TextChain,节省检索时延 |
同上 | llm、retriever |
GraphRagText2TextChain |
知识图谱 Chain,继承自 SingleText2TextChain |
GraphRagText2TextChain(llm, retriever, reranker) |
llm、retriever(retriever 是 GraphRetriever) |
Text2ImgChain |
构建文生图对接大模型对象,继承实现抽象 Chain |
Text2ImgChain(multi_model) |
multi_model |
Img2ImgChain |
构建图生图对接大模型对象,继承 Chain |
Img2ImgChain(multi_model, retriever) |
multi_model、retriever |
每次调用的入口都是 query:
| Chain | query 返回 |
|---|---|
SingleText2TextChain |
Union[Dict, Iterator[Dict]];{"query": query, "result": data, "source_documents": [{'metadata': xxx, 'page_content': xxx}]} |
ParallelText2TextChain |
Union[Dict, Iterator[Dict]];key 是 prompt:{"prompt": prompt, "result": data, ...};stream 设成 True 返回迭代器,否则返回字典 |
GraphRagText2TextChain |
dict,形如 {'query': "who is Teutberga's parents?", 'result': "..."} |
Text2ImgChain / Img2ImgChain |
Dict,{"prompt": prompt, "result": data},data 为图片 base64 编码后的数据 |
文生图的额外参数(官方):query 支持 kwargs["output_format"](["png","jpeg","jpg","webp"],默认 png)
与 kwargs["size"]("height*width" 格式,默认 512*512);
图生图的 kwargs["prompt"] 是必选,text 是「检索图片的描述文字」。
Chain.query 的通用签名(抽象类里定义的):
def query(text, llm_config, *args, **kwargs)
text(待查询文本信息,必选)、llm_config(LLMParameterConfig,可选)、args/kwargs(「传入的有效参数根据具体的 Chain 而定」 —— 官方原话)。
⭐ 所以"编排"的官方模式是统一的:
不管哪种 Chain,都是
Chain(必选零件...)建对象 →.query(text, llm_config)拿结果。 区别只在"要往构造里塞哪些零件" —— 文生文塞llm+retriever;文生图塞multi_model;图生图塞multi_model+retriever。
5.3 缓存模块(cache_module)—— 本课的第二主角
官方开篇:MxRAGCache「主要基于开源组件 GPTCache 进行二次开发」, 支持 Cache 初始化 / 更新 / 老化 / 查询 / 级联; 相比 GPTCache 扩展了四点:
- 语义近似 Cache 矢量检索过程支持 FAISS_NPU 检索(Index SDK);
- 语义近似 Cache embedding 支持 RAG 优化的 TEI Embedding;
- 语义近似 Cache 相似度计算过程支持 RAG 优化的 TEI Reranker;
- 支持 RAG SDK chain(图生图,文生文,文生图)的 Cache 功能。
⚠️ 官方这一句里第二个词是「文生文」(原文如此)—— 本课按 chain 的三种形态(文生文 / 文生图 / 图生图) 理解,原文照引不改。
模块的五个类(一览):
| 类 | 干什么 |
|---|---|
CacheConfig |
配置 memory cache 的数据结构 |
SimilarityCacheConfig |
配置 similarity cache 的数据结构 |
EvictPolicy |
缓存的替换策略枚举:class EvictPolicy(Enum) |
MxRAGCache |
问答对的存储、更新、落盘(本课用的就是它) |
CacheChainChat |
把缓存和 Chain 适配到一起(4.5) |
EvictPolicy 的四种策略(官方说明):
| 属性 | 官方说明 |
|---|---|
LRU |
替换最久没有访问的缓存(CacheConfig 的默认策略) |
LFU |
替换使用频率最低的缓存 |
FIFO |
按先进先出的规则替换 |
RR |
随机替换缓存 |
"缓存级联"的官方规则(join):
join(next_cache)—— 「将两个 cache 进行串联,达到多级缓存目的」。 约束:下级必须是MxRAGCache类型、缓存必须先初始化、不能串联自己、 串联时不能成环、最大串联深度为 6。
官方级联用法(memory cache 在前、similarity cache 在后):
# 设置缓存级联
cache.join(similarity_cache)
# 设置缓存每条的字符限制为4000个字符
cache.set_cache_limit(4000)
# 设置是否详细显示缓存过程
cache.set_verbose(False)
# 手动更新缓存
cache.update("小明的爸爸是谁?", json.dumps({"小明的爸爸是谁?": "小明的爸爸名字是大明"}))
# 精确匹配结果
res = cache.search("小明的爸爸是谁?")
print(f"memory match res: {res}")
# 语义近似匹配结果
res = cache.search("小明的爸爸叫什么名字")
print(f"similarity match res: {res}")
这段官方示例把级联的意图写得很清楚:
同一个问题问两次:一次原话(走
memory cache精确命中),一次换说法(走similarity cache语义命中)。cache.set_cache_limit(4000)也是官方示例里的一个真实用法 —— 答案超过 4000 字符就不缓存(防超长答案把盘撑起来)。
自动生成 QA 作为缓存(QAGenerate):
| 项 | 官方说明 |
|---|---|
| 功能 | 「问答生成类,输入标题和正文,调用大模型根据该正文生成问答对」 |
| 原型 | QAGenerate(config: QAGenerationConfig);generate_qa(llm_config) |
QAGenerationConfig |
QAGenerationConfig(titles, contents, tokenizer, llm, max_tokens, qas_num) |
max_tokens |
可选,默认 1000,范围 [500, 100000] —— 用于截断正文,超出部分丢弃 |
qas_num |
可选,默认 5,范围 [1, 10] |
| 返回 | Dict:生成的 QA 对列表,形如 {"从成都到重庆要多久? : 乘坐高铁1个小时", ...} |
| 跳过规则 | 官方:「大模型返回的 QA 如果不符合格式和个数要求,则会跳过」 |
QAGenerationConfig 里有一条明确的安全要求(官方):
tokenizer:「通过AutoTokenizer.from_pretrained加载。 加载外部模型有安全风险,local_files_only设置为True。」
配套的 MarkDownParser(官方:
「解析 Markdown,返回的标题和正文的类」; 原型
MarkDownParser(file_path, max_file_num);max_file_num可选,默认 1000,范围 [1, 10000]; 返回Tuple[List[str], List[str]](titles 列表 + contents 列表), 正好可以直接喂给QAGenerationConfig。
5.4 缓存的三条安全相关约束(汇总,别漏)
| # | 约束 | 官方原文要点 |
|---|---|---|
| ① | 未加密存储 | 「生成的数据库未做加密存储,如果涉及银行卡号、身份证号、护照号、口令等个人数据,请勿存放到数据库中」 |
| ② | pickle 反序列化风险 | 「本接口内部使用了 pickle 模块,有被恶意构造的数据在 unpickle 期间攻击的风险。需要保证在被加载的落盘数据 data_save_folder 是安全存储,仅可加载可信的落盘数据」 |
| ③ | 落盘体积上限 | memory cache 落盘文件 不能超过 100MB;similarity cache 的 sqlite 不能超过 30GB、矢量数据库不能超过 20GB |
⭐ ③ 是一条排障时想不到的约束:
业务量涨上来之后,缓存文件自己会撞上限 —— 撞上之后现象往往不是报一个明确的"超限"错,而是缓存开始不正常。 上线前把这三条上限写进容量规划(这正是 G8 的事)。
5.5 Prompt 压缩(prompt_compression)
已在 4.7 讲透,这里只补一条类的继承关系:
| 类 | 官方定义 |
|---|---|
PromptCompressor |
class PromptCompressor(ABC) —— 抽象类 |
RerankCompressor |
class RerankCompressor(reranker, splitter) —— reranker 必选,只能为 mx_rag.reranker 的 Reranker 对象 |
ClusterCompressor |
class ClusterCompressor(cluster_func, embed, splitter, dev_id) |
ClusterCompressor 的 cluster_func 是必选,且对返回值有严格约束(官方):
聚类函数,「返回的结果必须为
List[int]或ndarray, 长度不能超过 1000×1000,且长度要和文本切片数量一致」。
官方给的聚类实现用的是 HDBSCAN(sklearn.cluster),
并在里面用 min(min_cluster_size, node_num) 兜了一下小样本 —— 这段官方示例代码值得直接抄。
第六段 判据 / 验收
一条"能对外服务的问答链路"要满足 8 条,全过才算完工:
| # | 判据 | 怎么验 | 不过说明问题在 |
|---|---|---|---|
| ① | 模型服务这一层单独通 | llm.chat("...") 有非空返回 |
base_url / model_name / ca_file(4.2) |
| ② | 检索这一层单独通 | retriever.invoke(...) 返回非空文档列表 |
检索侧(回 G5),不要在编排层找原因 |
| ③ | Chain 能出一个带来源的答案 | 返回里有 result 且有 source_documents |
source 参数(默认 True);或检索没召回 |
| ④ | 答案不是空的 | result 非空字符串 |
⚠️ "没报错"不算过(同 D2 的"空补全不算推理成功") |
| ⑤ | 缓存配了就真的命中 | 同一意思问两遍,第二遍耗时明显下降 | similarity_threshold(默认 0.8 偏严);或库还没"热" |
| ⑥ | 缓存落盘文件真的生成 | ls <data_save_folder> 有文件 |
目录不存在(官方:若不存在需要用户创建);或路径违规 |
| ⑦ | try/except 真的罩住了 | 故意传错参数,进程报错后正常结束 | 有调用漏在 try 外面 |
| ⑧ | 缓存里没有个人数据 | 抽查落盘内容 / 代码里做过拦截 | 违反官方硬规范二 |
⭐ 判据 ④ 和 ⑦ 是这套验收里唯一"不报错但没做成"的两条 —— 只看"命令退出了 0"会把这两条全部漏掉。
第七段 出问题怎么查
排查顺序:先确认"是哪一层",再看"参数对不对",最后才怪编排。
(编排层报的错,根因通常不在编排层 —— 这条和 E1 里"报错的位置 ≠ 问题的位置"是同一个道理。)
| 你看到的现象 | 先归到哪一层 | 具体动作 |
|---|---|---|
| 报连接失败 / 超时 | 模型服务层 | 先单独跑 4.2 那一层;确认 base_url 与模型服务实际地址一致 |
| 报证书相关错误 | 模型服务层 | ClientParam 的 use_http 默认 False(HTTPS),HTTPS 下 ca_file 必传 |
result 是空的 |
模型服务层或提示词 | 先看 4.2 单独调用有没有输出;再看 max_tokens 默认只有 512,可能被截到空 |
返回里没有 source_documents |
检索层 | source 默认 True;若为 True 仍没有 → 检索没召回(回 G5) |
| 答案内容跟知识库无关 | 检索层 | 同上;编排只负责把检索结果拼进去,拼的源头不对编排改不了 |
| 缓存从不命中 | 缓存层 | ① similarity_threshold 默认 0.8,调低试;② 库还没热(缓存是问答完成后才回写,第一遍一定不命中) |
| 缓存初始化报错 | 缓存层 | data_save_folder 是否存在、是否软链接/相对路径、是否落在禁止清单(含 /tmp) |
MxRAGCache 建对象就失败 |
缓存层 | cache_name 必须是 [0-9a-zA-Z_]、长度 ≤ 64 |
update / search / flush 抛异常 |
调用顺序 | 官方:"必须在初始化之后进行调用";update 官方明说"否则会抛出异常" |
| 答案太长没被缓存 | 参数 | set_cache_limit 默认一百万字符;update 的 answer 必须 ≤ min(1000000, cache_limit) |
| 清空缓存后又回来了 | 机制 | 官方:memory cache 关闭时会自动 flush 再写回,要在程序里再清理一次 |
| 多线程/多进程下缓存出问题 | 并发 | 官方:CacheConfig 不支持多线程或多进程;要用得自己传 lock |
换成 ParallelText2TextChain 后解析报 KeyError |
返回值 | 它返回的 key 是 prompt 不是 query(见 3.5) |
| 上下文太长报长度错 / 模型像只读了一半 | 输入长度 | 走 Prompt 压缩(4.7);或调 chunk_size / k 减少喂进去的量 |
| 进程整个退出(不是报错) | 规范未落地 | 有 RAG SDK 调用没包在 try/except 里(官方硬规范一) |
| 缓存的盘越用越大 | 容量 | 三条官方上限:memory 落盘 100MB / sqlite 30GB / 矢量库 20GB;clean_size 默认只老 1 个 |
第八段 常见报错清单
⚠️ 官方 RAG SDK 文档没有单独的"报错码表" —— 本表按现象组织。 查其他报错用官方案例库按稳定特征词检索:
https://www.hiascend.com/document/caselibrary
| 现象(报错/表现) | 先查什么 |
|---|---|
ModuleNotFoundError(mx_rag.*) |
该模块所属版本没装 / Python 环境不对 / 环境变量没配(回 G3) |
| HTTPS 请求报证书或 SSL 相关错 | ca_file 必传(use_http 默认 False);路径不能软链接、≤1M、不许含 .. |
| 大模型调用超时 | ClientParam.timeout 默认 60 秒,取值 (0, 600] |
| 响应被截断 | ClientParam.response_limit_size 默认 1M,取值 (0, 10MB] |
| 模型返回但内容不全 | LLMParameterConfig.max_tokens 默认只有 512 |
| 参数配了不生效 | 官方:每个参数的具体有效值随模型不同;且在非 langchain 调用下应通过 chat/chat_streamly 传 |
stream=True 不起作用 |
官方:该参数只在 ParallelText2TextChain / SingleText2TextChain / GraphRagText2TextChain 场景生效 |
缓存查询返回 \u66aa 之类 |
这是官方定义的返回格式(QA 对的 Unicode 编码字符串),不是乱码 |
| 缓存目录报权限/属组错 | 官方对 data_save_folder 有属组与写权限约束;必须自己创建目录 |
| 缓存落盘文件超限 | memory cache 落盘 ≤100MB;sqlite ≤30GB;矢量库 ≤20GB |
QAGenerationConfig 的 tokenizer 加载报安全警告 |
官方要求 local_files_only=True |
| Prompt 压缩后内容少了一半 | compress_rate 默认 0.6 —— 想留更多就调大 |
| Prompt 压缩结果反直觉 | context_reorder 官方说明写的是"优先保留相关性低的切片"(原文如此)→ 现场对比 True/False |
| 整个服务进程退出 | RAG SDK 调用漏在 try/except 外面(官方硬规范一) |
| 数据合规问题 | 缓存未加密存储,个人数据请勿入库(官方硬规范二) |
第九段 小结 + 钩子 + 自测
三句话记住本课
- 编排就三件事:谁在前(缓存在最前)、谁串谁(Chain 串检索与精排)、谁最容易捅娄子(模型那一层)。 —— 顺序是官方定的,别自己调。
- 两条官方硬规范,前面几课都没专门讲:
① SDK 接口必须包在
try/except里(G3 的官方示例恰好没包); ② 缓存数据库未做加密存储,个人数据请勿存放。 - 缓存是"越用越热",而且要用安全换 —— 第一遍一定不命中;同时盘上落下的是明文问答对。
自测(不看上文)
- 官方在线问答的五步是什么?缓存为什么排第一?
SingleText2TextChain的两个必选参数是什么?精排不接行不行?memory cache和similarity cache分别适合什么提问方式?两者怎么串联、有什么限制?similarity_threshold的官方默认值是多少?"缓存看着不生效"时先动哪个参数?- 为什么 RAG SDK 的接口必须包
try/except?不包的表现是什么(不是"报错",是别的)? data_save_folder有哪三个最容易踩的坑?SingleText2TextChain和ParallelText2TextChain返回字典的 key 有什么不同?- 上下文塞不下时官方给了哪三个类?
RerankCompressor和ClusterCompressor的区别是什么?
钩子:下一课
链路通了、缓存也命中了 —— 但你怎么知道它答得对不对?
"跑通了"和"答得好"是两件事。 一条链路上有至少五个可调点(切分参数、
k、score_threshold、reranker、prompt), 你改一个,怎么知道是变好还是变坏?
G7《效果评估》会把官方的 evaluation_module 拆开讲 ——
用一套固定的问题和一批固定的文档,把"感觉好了"变成"数字好了"。
附录 A 官方出处
版本基准:MindSDK 26.1.0(RAG SDK)。文档路径前缀均为
zh/mindsdk/2610/rag/ragug/docs/zh/。
| # | 内容 | 官方出处 |
|---|---|---|
| 1 | 在线问答五步(初始化缓存(可选)→ 初始化大模型 Chain → 初始化检索方式 → reranker 精排(可选)→ 组装 prompt 传入大模型);「如果有配置缓存,问答完成后会将问答对刷新到缓存中」 | 《RAG SDK · 开发流程》→ user_guide.md |
| 2 | 运行阶段请使用 root 用户执行相关用例;知识库构建和在线问答支持并发,具体参见对应 Demo | 同上 |
| 3 | 硬规范一原文:「RAG SDK接口遵循异常处理机制,故用户必须在try/except语句块内进行调用以及异常处理,防止在使用的过程中出现异常抛出导致程序退出的情况。」 | 《RAG SDK · 接口参考 · 使用说明》→ api/README.md |
| 4 | 硬规范二原文:「RAG SDK如果使用Cache对问题-答案缓存,生成的数据库未做加密存储,如果涉及银行卡号、身份证号、护照号、口令等个人数据,请勿存放到数据库中。」 | 同上 |
| 5 | Chain 定义:「Chain定义实现了对接大模型客户端」;Chain 抽象基类 from mx_rag.chain.base import Chain |
《RAG SDK · 大模型Chain · 接口参考》→ api/llm_chains.md |
| 6 | Chain.query(text, llm_config, *args, **kwargs);text 必选、llm_config 为 LLMParameterConfig 可选;args/kwargs 「传入的有效参数根据具体的 Chain 而定」 |
同上 |
| 7 | SingleText2TextChain(llm, retriever, reranker, prompt, sys_messages, source, user_content_builder):llm/retriever 必选;reranker 默认 None;source 默认 True;prompt 默认「根据上述已知信息,简洁和专业地回答用户的问题。如果无法从已知信息中得到答案,请根据自身经验做出回答」 |
同上 |
| 8 | SingleText2TextChain.query 默认值:temperature 0.5、top_p 0.95;text 范围 (0, 1000×1000];返回 {"query":…, "result":…, "source_documents":[…]} |
同上 |
| 9 | ParallelText2TextChain:「支持检索并行推理文生文对话 Chain。继承了 SingleText2TextChain 基类,节省检索时延」;返回 key 为 prompt;stream=True 返回迭代器 |
同上 |
| 10 | GraphRagText2TextChain(llm, retriever, reranker)(继承自 SingleText2TextChain,retriever 为 GraphRetriever) |
同上 |
| 11 | Text2ImgChain(multi_model) / Img2ImgChain(multi_model, retriever);kwargs["output_format"] 支持 ["png","jpeg","jpg","webp"] 默认 png;size 默认 512*512;返回 {"prompt":…, "result": 图片base64} |
同上 |
| 12 | 默认 user_content_builder 源码(_user_content_builder(query, docs, prompt):\n\n 拼接 docs → prompt 挂到最后一片 chunk → 最后追加 query) |
同上 |
| 13 | Text2TextLLM(base_url, model_name, llm_config, client_param):当前只支持兼容 OpenAI 接口 /v1/chat/completions;继承实现了 langchain.llms.base.LLM;所有参数需通过关键字参数传递 |
《RAG SDK · 对接模型客户端 · 接口参考》→ api/llm_client.md |
| 14 | chat / chat_streamly:返回 str / Iterator[str];query 长度 [1, 4×1024×1024];role 默认 user;sys_messages 列表最大长度 16 |
同上 |
| 15 | LLMParameterConfig(max_tokens, presence_penalty, frequency_penalty, temperature, top_p, seed, stream):默认 512 / 0.0 / 0.0 / 1.0 / 1.0 / None / False;「每个参数具体有效值会根据不同模型配置有差别」;stream 只在 ["ParallelText2TextChain","SingleText2TextChain","GraphRagText2TextChain"] 生效 |
同上 |
| 16 | Text2ImgMultiModel / Img2ImgMultiModel / Img2TextLLM:各自「当前只支持」的模型范围(SD v1-5、SD 2-1-base、IP-Adapter 的 SD v1-5;VLM 走 /openai/v1/chat/completions) |
同上 |
| 17 | MxRAGCache 总体说明:基于 GPTCache 二次开发;支持初始化/更新/老化/查询/级联;扩展 FAISS_NPU、TEI Embedding、TEI Reranker、Chain 的 Cache;「在知识文档检索前,增加问答 Cache,如果查询命中了 Cache,就不会经过大模型推理过程」;26.1.0 原文为「可以大幅度提升端到端性能」,未给出具体倍数 | 《RAG SDK · 缓存模块 · 接口参考》→ api/cache_module.md |
| 18 | 缓存两种类型:memory cache(hash map + 精确匹配,hash key 是 query hash 值);similarity cache(sqlite + 向量数据库,查询→embedding→向量库 topK→sqlite 取答案与问题→reranker 精排→返回最相似结果);可以将 Cache 进行串联 |
同上 |
| 19 | CacheConfig(cache_size, eviction_policy, auto_flush, data_save_folder, min_free_space, similarity_threshold, disable_report, lock) 全部默认值与范围(含 cache_size 必选 (0,100000]、auto_flush 默认 20、similarity_threshold 默认 0.8、data_save_folder 默认「当前用户家目+/Ascend/mxRag/cache_save_folder」且若不存在需要用户创建、禁止路径列表含 /tmp、lock 说明「CacheConfig不支持多线程或者多进程进行处理」) |
同上 |
| 20 | CacheConfig NOTE:「本接口内部使用了pickle模块,有被恶意构造的数据在unpickle期间攻击的风险。需要保证在被加载的落盘数据 data_save_folder 是安全存储,仅可加载可信的落盘数据」;「对于 memory cache 来说,它的落盘文件不能超过 100MB 大小」 |
同上 |
| 21 | SimilarityCacheConfig(vector_config, cache_config, emb_config, similarity_config, retrieval_top_k, clean_size, **kwargs):四个配置必选,retrieval_top_k 默认 1,clean_size 默认 1;faiss 时 MindFAISS 的 load_local_index 会被 data_save_folder 覆盖、auto_save 置 False;cache_config 当前只支持 "sqlite";NOTE:vector_config 和 cache_config 必须同时为 None 或同时不为 None,同时为 None 等同于 memory cache;sqlite ≤30GB、矢量库 ≤20GB |
同上 |
| 22 | EvictPolicy:LRU(替换最久没有访问的缓存)/ LFU / FIFO / RR |
同上 |
| 23 | MxRAGCache(cache_name, config):cache_name 长度 (0, 64]、取值 [0-9a-zA-Z_]、会体现在落盘文件名中 |
同上 |
| 24 | MxRAGCache 的 8 个方法:clear() / flush() / get_obj() / join(next_cache)(最大串联深度为 6,不能成环、不能串联自己)/ search(query)(返回 QA 对的 Unicode 编码字符串)/ set_cache_limit(cache_limit)(默认一百万字符,范围 (0, 1000000])/ set_verbose(verbose)(默认 False)/ update(query, answer)(必须在初始化之后调用,否则会抛出异常;answer 范围 (0, min(1000000, cache_limit)]) |
同上 |
| 25 | clear() 官方说明:「删除 data_save_folder 下已落盘的缓存文件,对于 memory cache,由于关闭时会自动 flush 再次写入缓存,需要用户程序中再次清理」 |
同上 |
| 26 | CacheChainChat(cache, chain, convert_data_to_cache, convert_data_to_user):「用于适配 RAG SDK 的文生文、图生图、文生图的各种 chain,同时也提供访问 MxRAGCache 的能力,当缓存未命中时,将进行大模型推理,然后将结果再刷新至缓存」;query(text, ...) 「当缓存无法查询时,会访问大模型」,text 范围 (0, 128M] |
同上 |
| 27 | QAGenerate / QAGenerationConfig(titles, contents, tokenizer, llm, max_tokens, qas_num):max_tokens 默认 1000 范围 [500,100000](截断正文);qas_num 默认 5 范围 [1,10];「大模型返回的 QA 如果不符合格式和个数要求,则会跳过」;tokenizer 官方要求 local_files_only=True |
同上 |
| 28 | MarkDownParser(file_path, max_file_num):max_file_num 默认 1000 范围 [1,10000];file_path 不能软链接/相对路径、.md ≤10MB、禁止路径列表含 /tmp;返回 Tuple[List[str], List[str]] |
同上 |
| 29 | PromptCompressor(ABC):compress_texts(self, context, question) |
《RAG SDK · Prompt压缩 · 接口参考》→ api/prompt_compression.md |
| 30 | RerankCompressor(reranker, splitter):reranker 必选、只能为 mx_rag.reranker 的 Reranker 对象;splitter 默认 RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=0, separators=["\n",""], keep_separator=True);compress_texts(context, question, compress_rate, context_reorder):compress_rate 默认 0.6 范围 (0,1)、context_reorder 默认 False(官方原文「若为 True…将根据压缩率优先保留相关性低的文本切片」);返回 str |
同上 |
| 31 | ClusterCompressor(cluster_func, embed, splitter, dev_id):cluster_func 返回必须为 List[int]/ndarray 且长度要和文本切片数量一致;splitter 默认 RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0, separators=["。","!","?","\n",",",";"," ",""]);dev_id「NPU id,通过 npu-smi info 查询可用ID,取值范围 [0, 63],默认为卡 0」 |
同上 |
| 32 | 端到端样例:镜像 Tag 格式 {version}-{chip}-{os}-{python};容器启动参数;cd /workspace/RAGSDK_Samples/rag_with_api;rag_demo_knowledge.py --embedding_url … --white_path … --file_path …;判据输出 ['gaokao.txt'];rag_demo_query.py --embedding_url … --llm_url … --model_name Qwen3-4B --query …;判据「输出包含检索到的文档内容和生成的回答」 |
《RAG SDK · 开发流程》→ user_guide.md |
| 33 | 多轮对话样例(LangChain LLMChain + ConversationBufferWindowMemory(memory_key="chat_history", k=3);官方注:「k 可以设置保存的历史会话轮数」) |
同上 |
| 34 | 接口清单(16 个模块:universal_api / knowledge_management / databases / llm_client / embedding / reranker / model_inference_acceleration / embedding_model_fine_tuning / evaluation_module / cache_module / retrieval / document_summary / prompt_compression / llm_chains / knowledge_graph / corag_module) |
同上 api/README.md |
⚠️ 属于本文「归纳/实践」而非官方原文的部分,已明确标注:
- 4.0 的行动顺序、4.1 的三条摸底命令(
npu-smi info/docker images/ls)与 import 自检; - 4.2 的"先单独验大模型这一层"、4.3 的"再单独验检索"、4.9 的验收证据格式(尤其第 ④ 条自造失败);
- 4.4 的「
similarity_threshold=0.8在语义相似任务上偏严」; - 4.8 的「包一个大
try也满足官方要求」以及用convert_data_to_cache做敏感信息拦截; - 6 的 8 条判据表、7 的排查表、8 的报错清单;
- 5.5 的「
RerankCompressor全局砍尾巴 /ClusterCompressor簇内公平挑」的对比说法。
⚠️ 官方未给出、本课因此没有写的内容:
- 缓存带来的具体性能倍数(26.1.0 文档只写"可以大幅度提升端到端性能");
- 各 Chain 之间的性能/效果对比数字;
- RAG SDK 自己的 Chain 如何原生管理多轮历史(样例走的是 LangChain memory);
try/except的粒度要求(官方只说"必须在 try/except 语句块内进行调用");- RAG SDK 专属的报错码表(官方无此表);
Prompt 压缩三个类的效果对比数据。
附录 B 命令详解
G3 已解释过的命令(
chmod、pip3 install -r、source、docker exec、ls等)此处不重复解释基础含义, 只补本课特有的用法与判据。
命令 npu-smi info
含义:查昇腾设备当前状态 —— 本课用它确认"编排之前,卡是好的" 拆解:
npu-smi(昇腾设备管理工具)+info(子命令:显示设备信息) 用法:直接执行;⚠️ 官方:不支持多线程并发使用 期望:输出一张表,列出每张卡的 NPU ID / Chip Count / 产品类型 / 温度 / 显存 / 进程 不过会怎样:命令不存在 → 驱动没装(回 A1); 有输出但列不出可用卡 → 容器没透传设备(回 G3 的--device参数) ⭐Prompt 压缩的ClusterCompressor.dev_id官方就是让你用这条命令查可用的 NPU id —— 不要默认卡号一定是 0
命令 docker images | grep ragsdk
含义:确认 RAG SDK 镜像在不在、Tag 对不对 拆解:
docker images(列本地镜像)+|(管道)+grep ragsdk(只留名字含 ragsdk 的行) 用法:docker images | grep ragsdk期望:一行形如ragsdk 26.0.0-910b-ubuntu22.04-py3.11不过会怎样:无输出 → 镜像没拉或名字打错;grep不到但docker images有一堆 → Tag 里的{chip}/{os}和你以为的不一样 ⚠️ 官方 NOTE:注意区分 CPU 架构(x86_64 / aarch64)
命令 docker exec -it ragsdk_demo bash
含义:进入正在运行的 RAG SDK 容器 拆解:
docker exec(在运行中的容器里执行命令)+-i(保持标准输入)+-t(分配终端)+ragsdk_demo(容器名)+bash(要执行的命令) 用法:容器名换成你自己的(官方示例里docker run的--name ragsdk_demo) 期望:命令提示符变成容器内的 shell 不过会怎样:报No such container→ 容器没起或名字不对; 报the input device is not a TTY→ 少了-t(脚本里跑要注意)
命令 ls /workspace/RAGSDK_Samples/rag_with_api
含义:确认官方样例脚本的位置与文件名 拆解:
ls(列出目录)+ 完整路径 用法:进容器后执行 期望:列出rag_demo_knowledge.py、rag_demo_query.py等 不过会怎样:报No such file or directory→ 路径不对或镜像版本不同 ⚠️ 官方 NOTE 只给了「容器内示例代码位于/workspace/RAGSDK_Samples」这个大路径,rag_with_api这一层子目录名与脚本名,以你容器里的实际内容为准
命令 python3 rag_demo_knowledge.py --embedding_url http://127.0.0.1:8080/v1/embeddings --white_path /workspace --file_path /workspace/testdata/gaokao.txt
含义:跑官方知识库构建样例(问答链路的前置) 拆解:
--embedding_url(向量模型服务地址)+--white_path(允许访问的白名单目录)+--file_path(要入库的文件) 用法:先cd /workspace/RAGSDK_Samples/rag_with_api期望:官方判据 —— 输出['gaokao.txt']表示知识库构建成功 不过会怎样:白名单不含文件路径 → 报路径越权;127.0.0.1:8080是官方示例 URL,embedding 服务没起就是连不上(官方 NOTE 已注明)
命令 python3 rag_demo_query.py --embedding_url http://127.0.0.1:8080/v1/embeddings --llm_url http://127.0.0.1:1025/v1/chat/completions --model_name Qwen3-4B --query "请描述2024年高考作文题目"
含义:跑官方在线问答样例 —— 本课要交付的"一条能对外服务的链路" 拆解:
--embedding_url(检索侧)+--llm_url(大模型侧)+--model_name(模型名,要和--llm_url后面的服务对上)+--query(问题) 用法:在rag_with_api目录下执行 期望:形如{'query': '请描述2024年高考作文题目', 'result': '...', 'source_documents': [...]}不过会怎样:只有result没有source_documents→ 检索没召回(回 G5);result是空串 → 不算成功; 连接/证书错 →ca_file与协议没对上 ⚠️ 官方 NOTE:两个 URL 都是示例值,以本地实际部署为准
命令 python3 -c "from mx_rag.chain import SingleText2TextChain, ParallelText2TextChain; from mx_rag.cache import MxRAGCache, CacheChainChat; from mx_rag.llm import Text2TextLLM; print('ok')"
含义:确认本课的四个核心模块都能 import 拆解:
python3 -c(执行一段代码)+from 包 import 类(导入路径取自官方示例) 用法:容器内执行 期望:打印ok不过会怎样:ModuleNotFoundError→ 模块没装 / Python 环境不对 /PYTHONPATH没配(回 G3) ⚠️ 这是通用自检做法,不是官方命令 —— 官方给的是各类的导入路径本身
命令 ls <data_save_folder>
含义:确认缓存真的落盘了(判据 ⑥ 的证据) 拆解:
ls(列出目录)+<data_save_folder>换成你CacheConfig里配的那个路径 用法:ls /data/mxrag/cache期望:能看到缓存数据文件(文件名里会带上你配的cache_name) 不过会怎样:报目录不存在 → 官方明说「若不存在需要用户创建」; 目录在但是空的 → 还没触发落盘(auto_flush默认 20 条,或用flush()手动刷); 或这个目录落在了禁止清单里(含/tmp)
命令 du -sh <data_save_folder>
含义:看缓存目录已占多少空间(对着官方上限判断) 拆解:
du(disk usage,统计占用)+-s(只给总计)+-h(人类可读单位) 用法:du -sh /data/mxrag/cache期望:打印总大小,如180M不过会怎样:报路径不存在 → 同前一条 ⭐ 对着官方三条上限看:memory cache 落盘 ≤100MB、sqlite ≤30GB、矢量库 ≤20GB —— 接近上限就要调cache_size/clean_size,不要等它撞上再查