WeKnora 答得不对,先别换模型:八个默认值、四个诊断端点与一条分诊链
本地 RAG 最常见的失败不是「装不起来」,而是「装起来了、文档也传了、答案却不对」。这时候换更大的模型通常没有用,因为问题多半出在生成之前。
本文以腾讯开源的 WeKnora v0.9.0 为例,给出一条从症状倒推到配置项的排查链:先用一次检索请求把 RAG 切成两半,再逐层确认解析、向量化、召回、重排、生成各自有没有坏。全部参数与端点来自官方仓库文件,未在本文环境实际部署,命令建议先在测试环境验证。
〇、结论先行
| 问题 | 结论 | 依据 |
|---|---|---|
| 答得不对,最该先做什么 | 绕开生成,直接调 hybrid-search 看召回结果 |
官方 swagger 提供独立检索端点 |
调 top_k / 阈值去哪儿改 |
.env 里没有,在 config/config.yaml |
.env.example 全文无这些变量 |
| 为什么「没找到资料」时它还在编 | 默认 fallback_strategy: "model" |
config/config.yaml |
| 换 Embedding 模型后召回变差 | 模型名不是环境变量;AUTO_MIGRATE=false 时不自动建新维度的 HNSW 索引 |
.env.example D1、A2 注释 |
| 扫描件 PDF 传上去没内容 | hybrid 默认 off 且附加参数默认 --no-ocr |
.env.example E2 |
| 改完配置不生效 | config.yaml 是 bind mount,改宿主机文件后需重建 app 容器 |
docker-compose.yml 第 57 行 |
一句话方法:一条检索请求定左右,四个端点定层次,八个默认值定改动点。
一、第 0 关:先选对形态,别在 40 个服务里迷路
docker-compose.yml 有 40 个顶层键(约 25 个服务 + 17 个 volumes/network)。这个数字容易让人误以为必须拉起一整套集群,但官方同时提供了两个轻量入口:【官方】
| 形态 | 依赖 | 注册 | 默认暴露 | 适用 |
|---|---|---|---|---|
| Lite 单二进制 | 零外部依赖(SQLite + 内存队列) | 无需 | 仅本机 | 个人、本机试用、低资源 |
| 标准版核心服务 | Docker Compose | 通常需要 | http://localhost |
多空间、完整解析链路 |
标准版 --profile full |
追加 Neo4j / MinIO / Qdrant / Langfuse 等 | 需要 | 多个端口 | 完整企业能力 |
标准版的最小启动只有两条命令,来自 README_CN.md:【官方】
# 标准版:先拉镜像再起服务
docker compose pull
docker compose up -d
# Web UI: http://localhost
# 后端 API: http://localhost:8080
需要可选组件时才加 profile,例如知识图谱与对象存储:【官方】
docker compose --profile neo4j --profile minio pull
docker compose --profile neo4j --profile minio up -d
Lite 的两个坑,官方文档写得很明确:【官方】
- Lite 只自动读取
.env.lite(先程序所在目录,再当前目录),不读.env。官方特意说明这是为了避免标准版模板里的REDIS_ADDR、Langfuse 凭据把 Lite 的内存队列切走或打开链路追踪。 - 只有 Lite 构建的二进制会自动加载 dotenv 文件;标准版二进制不会。需要指定别的文件时两者都可用
ENV_FILE。
# Lite 形态
cp .env.lite.example .env.lite
# 编辑 .env.lite 后直接运行 WeKnora-lite 二进制
【交叉】README 将「Lite 单二进制」与「桌面应用」并列为部署形态,桌面端标注「尚未提供安装包,需自行构建」。查 GitHub
v0.9.0Release 的 assets 列表为空,说明二进制分发走的是官网/其他渠道,不在 Release 附件里。需要 Lite 时应以官网下载页为准,不要假设git clone后能直接找到可执行文件。
二、核心分诊动作:一次检索把问题切成两半
多数人排查 RAG 的方式是「改提示词 → 再问一次 → 感觉好点没」。这会把五层问题混在一起,永远定位不到具体配置项。
正确的第一步是完全绕开生成模型,直接问检索层要结果。官方 swagger(345 个端点)里给了独立入口:【官方】
POST /knowledge-bases/{id}/hybrid-search
GET /knowledge-bases/{id}/hybrid-search
POST /knowledge-search
# 直接看检索层召回了什么,不经过任何 LLM
curl -s -X POST "http://localhost:8080/api/v1/knowledge-bases/${KB_ID}/hybrid-search" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-d '{"query": "差旅报销的住宿标准是多少", "top_k": 10}' | jq .
路径前缀与鉴权头以部署版本的 swagger 为准;
GIN_MODE=release时 Swagger UI 被禁用,需要接口清单可临时改为debug,或离线读仓库里的docs/swagger.json。【官方】
判据只有一条,但极其有效:
| 检索端点返回 | 结论 | 后续该查 |
|---|---|---|
| 召不回目标片段 | 问题在生成之前:解析 / 分块 / Embedding / 索引 / 阈值 | 第 3~5 节 |
| 能召回,但排序靠后或分数低 | 问题在召回阈值与重排 | 第 6 节 |
| 目标片段排第一,聊天仍答错 | 问题在生成层与兜底策略 | 第 7 节 |
这张表是全文的分诊刀。 它把「答不对」这个模糊症状,变成了「检索层有罪 / 无罪」的二值判断。换模型只在第三行才成立。
三、第 1 层|解析与分块:先用 preview 看文档被切成了什么
官方提供了两个不需要重新上传就能验证的端点:【官方】
POST /chunker/preview # 上传前预览分块结果
GET /chunks/{knowledge_id} # 查看已入库文档的实际分块
POST /knowledge-bases/{id}/rebuild-index
# 已入库文档:看真实分块,确认目标段落是否存在
curl -s "http://localhost:8080/api/v1/chunks/${KNOWLEDGE_ID}" \
-H "Authorization: Bearer ${TOKEN}" | jq '.data[:3]'
分块默认值在 config/config.yaml,不在 .env:【官方】
knowledge_base:
chunk_size: 512
chunk_overlap: 50
split_markers: ["\n\n", "\n", "。"]
document_process_timeout: 2h
| 参数 | 默认值 | 【推算】含义 |
|---|---|---|
chunk_size |
512 | 单块最大字符数 |
chunk_overlap |
50 | 重叠 50 字符,重叠率 50/512 ≈ 9.8% |
| 有效步长 | — | 512 − 50 = 462 字符 |
split_markers |
空行 → 换行 → 句号 | 优先按语义边界切,不按硬长度切 |
【推算】假设:字符数按中文字符计、切分不跨 marker 强制截断。若一段表格或条款恰好横跨两块边界,重叠 50 字符未必够覆盖,这就是「文档里明明有、检索却召回不到」的常见起点。
扫描件 PDF 是最典型的假故障。 官方 .env.example E2/E4 给了完整判定链:【官方】
# E4:扫描页自动判定
# DOCREADER_PDF_FORCE_SCANNED=false # 默认不强制全页 OCR
# DOCREADER_PDF_SCAN_IMAGE_RATIO=0.5 # 图片覆盖面积 ≥ 50% 视为扫描页
# DOCREADER_PDF_SCAN_MIN_CHARS=10 # 文本字符数 < 10 视为无可用文本层
# E2:hybrid 解析器
# DOCREADER_ODL_HYBRID=off # 默认关闭,用纯 fast 本地解析
# DOCREADER_ODL_HYBRID_FALLBACK=false # hybrid 失败不回退到 fast
# ODL_HYBRID_EXTRA_ARGS=--no-ocr # 默认不做 OCR,扫描件要改 --force-ocr
也就是说:一份纯图片 PDF 在默认配置下,很可能既不被判为需要 OCR、也没有 OCR 能力,最后入库的分块是空的。 表现是「文档显示处理完成,但问什么都答不上」。
处理顺序建议:先用 /chunker/preview 确认能切出文本,再决定是开 DOCREADER_PDF_FORCE_SCANNED=true,还是启用 --profile odl-hybrid 并把附加参数改为 --force-ocr(需 EasyOCR + libGL)。
四、第 2 层|向量化与索引:两个「改了没生效」的高发点
4.1 Embedding 模型名根本不是环境变量
.env.example D1 有一段容易被跳过的注释,原文要点:【官方】
向量模型名不是环境变量:在「设置 → 模型」、CLI 或初始化接口里把 Embedding 模型的 name 设为 Ollama 模型名(如
nomic-embed-text)。
所以 .env 里能配的是地址、批量大小、并发:
OLLAMA_BASE_URL=http://host.docker.internal:11434 # 本地 Ollama 唯一地址
OLLAMA_OPTIONAL=true # 未配 Ollama 只告警不阻断
# BATCH_EMBED_SIZE= # 留空走代码默认
CONCURRENCY_POOL_SIZE=5 # Embedding 并发,出现 429 时调小
换模型要去 UI / CLI / 初始化接口,不是改 .env。 而且换完必须重建索引——不同 Embedding 模型的向量空间不通用,旧索引与新查询向量不可比。
4.2 AUTO_MIGRATE 与 HNSW 索引的隐藏耦合
.env.example A2 注释里藏着一句关键说明:【官方】
启动时自动执行数据库迁移;设 false 禁用(默认启用)。为 false 时也不再为新的 embedding 维度自动创建 pgvector HNSW 索引。
AUTO_MIGRATE=true # 默认;关闭会连带禁用新维度的 HNSW 索引自动创建
AUTO_RECOVER_DIRTY=true
RETRIEVE_DRIVER=postgres # 默认走 Postgres/pgvector,无需额外起 Qdrant/Milvus
这条解释了另一类「召回突然变差」:换了 Embedding 维度、又关了自动迁移,pgvector 没有对应维度的 HNSW 索引,检索退化为暴力扫描或干脆失效。默认 RETRIEVE_DRIVER=postgres 意味着最小部署不需要额外向量数据库,多驱动时才涉及 MULTI_STORE_RETRIEVE_TIMEOUT_SEC。
重建索引的入口:【官方】
curl -s -X POST "http://localhost:8080/api/v1/knowledge-bases/${KB_ID}/rebuild-index" \
-H "Authorization: Bearer ${TOKEN}"
五、第 3 层|召回:三道阈值不是同一个量纲
config/config.yaml 的 conversation 段是全文最该抄下来的一块:【官方】
conversation:
max_rounds: 5
keyword_threshold: 0.3
embedding_top_k: 30
vector_threshold: 0.2
rerank_threshold: 0.3
rerank_top_k: 30
fallback_strategy: "model"
enable_rewrite: true
enable_query_expansion: true
enable_rerank: true
召回收窄由四个量共同决定:
| 参数 | 默认值 | 作用层 | 调大的代价 |
|---|---|---|---|
embedding_top_k |
30 | 向量召回候选数 | 候选变多、噪声上升、重排变慢 |
vector_threshold |
0.2 | 向量相似度下限 | 门槛变高会漏召回 |
keyword_threshold |
0.3 | 关键词匹配下限 | 同上,且量纲与向量相似度不同 |
enable_rewrite / enable_query_expansion |
true / true | 查询改写与扩展 | 实际检索词不等于用户输入 |
最容易犯的错:把 0.2、0.3、0.3 当成同一把尺子。 它们分别作用在向量相似度、关键词检索得分、重排模型得分上,三种分布、三种尺度,互相之间不能比大小,更不能「统一调成 0.5」。【推断】依据:三者分别位于召回与重排的不同阶段,且 rerank_threshold 与 rerank_top_k 同属 enable_rerank 开关控制的后处理链路。
另一处实用提醒:enable_rewrite 与 enable_query_expansion 默认都开着,拿用户原话去 /hybrid-search 复现线上请求,结果可能不一致。排查时要么临时关掉这两项,要么从 LLM_DEBUG_LOG 里取改写后的真实 query。
# 官方提供的上下文排查手段(.env.example A2)
LLM_DEBUG_LOG=true # 写 llm_debug.log,含完整请求/响应
WEKNORA_LLM_STREAM_RAW_DUMP=1 # 流式原始转储,默认 ~/.weknora/investigate/llm-stream
GIN_MODE=debug # 需要 Swagger UI 时临时切换
六、第 4 层|重排:两个 30 说明它只排序、不收窄
embedding_top_k: 30
rerank_top_k: 30
【推算】假设:两个值分别指重排前后的候选数量上限。二者相等意味着默认情况下重排不改变候选集合大小,只改变顺序。所以「答案在第 20 名」这类问题,重排能救;「答案压根没进前 30」这类问题,重排救不了,得回到 embedding_top_k 和 vector_threshold。
配套的重排模型也不在 .env 的必填区,而是 D2 的可选声明式配置:【官方】
# 需要先启用 config/builtin_models.yaml 才读取这些 ${...} 占位符
# RERANK_MODEL_NAME=
# RERANK_BASE_URL=
# RERANK_API_KEY=
# RERANK_PROVIDER=generic
如果 enable_rerank: true 但没有配 Rerank 模型,行为取决于实现(降级为不重排或报错),官方未在配置文件注释中说明,属于【未披露】,建议先用 /hybrid-search 返回的分数分布自行确认。
七、第 5 层|生成与兜底:幻觉是默认配置,不是模型毛病
fallback_strategy: "model"
fallback_response: "Sorry, I am unable to answer this question."
fallback_prompt_id: "default_fallback_prompt"
这一行是全文最该记住的默认值。 当检索没有命中时,默认策略不是直接返回 fallback_response,而是继续让模型生成回答。于是「知识库里没有这个规定」会被模型补成一段听起来合理的规定——很多 RAG「幻觉」投诉的根因在这里,而不在模型能力。
| 诉求 | 改动点 |
|---|---|
| 没命中就明确说不知道 | 把 fallback_strategy 从 model 改为直接使用 fallback_response 的策略,并同步调整 fallback_prompt_id 对应的模板 |
| 收窄上下文轮数 | max_rounds: 5(会话轮数,不是最大输出 token 数) |
| 改回答口径 | 提示词不在 config.yaml 正文,由 prompt_templates/ 下的 YAML 通过 *_prompt_id 引用 |
# config/config.yaml(示例改法,改前先备份)
conversation:
fallback_strategy: "model" # ← 需要"没命中就不编"时改这一项
fallback_prompt_id: "default_fallback_prompt"
max_rounds: 5
【未披露】
fallback_strategy的全部合法取值未在config.yaml注释里列出。改之前应在测试环境用「知识库里确定不存在的问题」验证一次,避免填了不被识别的值后静默回退。
八、八个默认值速查表
| # | 层 | 参数 | 默认值 | 所在文件 | 症状关联 |
|---|---|---|---|---|---|
| 1 | 分块 | chunk_size |
512 | config/config.yaml |
条款被切断、召回片段不完整 |
| 2 | 分块 | chunk_overlap |
50 | config/config.yaml |
跨块信息丢失 |
| 3 | 召回 | embedding_top_k |
30 | config/config.yaml |
答案根本没进候选 |
| 4 | 召回 | vector_threshold |
0.2 | config/config.yaml |
相似但无关片段混入 |
| 5 | 召回 | keyword_threshold |
0.3 | config/config.yaml |
专有名词/编号搜不到 |
| 6 | 重排 | rerank_top_k |
30 | config/config.yaml |
正确片段排名靠后 |
| 7 | 重排 | rerank_threshold |
0.3 | config/config.yaml |
重排后全被过滤 |
| 8 | 生成 | fallback_strategy |
model |
config/config.yaml |
没命中时开始编造 |
改哪个文件才生效:docker-compose.yml 第 57 行把宿主机配置以 bind mount 挂进容器:【官方】
volumes:
- ./config/config.yaml:/app/config/config.yaml
因此改宿主机 config/config.yaml 即可,但要重建 app 容器才生效;.env 里改不出这八个参数。部分环境变量型配置(如 WEKNORA_MINERU_MAX_IMAGE_BYTES)官方注释明确写了「修改后重新创建 app 容器」,可作为同一类操作的参照。
# 改完 config.yaml 后重建 app 容器
docker compose up -d --force-recreate app
九、可复现的分诊脚本
下面这段脚本按「检索 → 分块 → 重建索引」的顺序,用官方端点把一次「答不对」拆成可判定的三步。不依赖第三方 SDK,Key 与 ID 全为占位符。
#!/usr/bin/env python3
"""WeKnora 分诊脚本:判断「答不对」发生在检索之前还是之后。
依据官方 docs/swagger.json 端点整理,未在本文环境实际执行,请先测试。"""
import json, os, urllib.request
BASE = os.getenv("WEKNORA_BASE", "http://localhost:8080/api/v1")
TOKEN = os.getenv("WEKNORA_TOKEN", "YOUR_TOKEN") # 占位符
KB_ID = os.getenv("WEKNORA_KB_ID", "YOUR_KB_ID")
QUERY = "差旅报销的住宿标准是多少"
def call(method, path, payload=None):
req = urllib.request.Request(
f"{BASE}{path}",
data=json.dumps(payload).encode() if payload is not None else None,
headers={"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"},
method=method,
)
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read().decode())
def triage():
# 第 1 步:绕开生成,直接检索
hits = call("POST", f"/knowledge-bases/{KB_ID}/hybrid-search",
{"query": QUERY, "top_k": 10})
items = hits.get("data", hits) if isinstance(hits, dict) else hits
if isinstance(items, dict):
items = items.get("results", items.get("chunks", []))
print(f"[检索] 召回 {len(items) if isinstance(items, list) else '?'} 条")
if not isinstance(items, list) or len(items) == 0:
print("[判定] 零召回 → 查解析/分块/Embedding/索引(第 3~5 节)")
print("[动作] 调用 /chunker/preview 确认文档能否切出文本")
return
# 第 2 步:看目标片段是否进入候选、分数是否过低
for i, it in enumerate(items[:5]):
print(f" #{i+1} score={it.get('score', 'N/A')} "
f"content={str(it.get('content', ''))[:60]}...")
print("[判定] 有召回但聊天仍答错 → 查重排与 fallback_strategy(第 6~7 节)")
# 第 3 步:需要时重建索引(换过 Embedding 模型才做)
if os.getenv("REBUILD", "0") == "1":
call("POST", f"/knowledge-bases/{KB_ID}/rebuild-index", {})
print("[动作] 已触发 rebuild-index,等待任务完成后重测")
if __name__ == "__main__":
triage()
脚本的两个局限:① 响应体的实际字段名(是 data 还是 results/chunks)以部署版本 swagger 为准,脚本已做兼容但可能需要改一行;② 它只能证明「检索层有没有召回」,不能判断召回内容是否语义正确,最后一步仍需人工看文本。主动写出局限比给出看似万能的脚本更可靠。
十、排查清单
| 现象 | 最可能的层 | 一个验证动作 | 对应配置 |
|---|---|---|---|
| 文档显示完成,问什么都没有 | 解析 | POST /chunker/preview 看是否切出文本 |
DOCREADER_PDF_FORCE_SCANNED、ODL_HYBRID_EXTRA_ARGS |
| 扫描件 PDF 无内容 | 解析 | 检查扫描页判定阈值是否触发 | DOCREADER_PDF_SCAN_IMAGE_RATIO=0.5、SCAN_MIN_CHARS=10 |
| 目标段落被切成两半 | 分块 | GET /chunks/{knowledge_id} 看真实分块 |
chunk_size=512、chunk_overlap=50 |
| 换 Embedding 后召回骤降 | 索引 | 调 hybrid-search 看分数分布 |
Embedding 模型名(UI/CLI)、AUTO_MIGRATE |
| 专有名词、编号搜不到 | 召回 | 用改写后的 query 重测 | keyword_threshold、enable_rewrite |
| 相似但无关的片段混入 | 召回 | 看召回分数是否集中在 0.2 附近 | vector_threshold |
| 正确片段排在 10 名以后 | 重排 | 对比重排前后顺序 | rerank_top_k、enable_rerank |
| 知识库没这条,模型却答得头头是道 | 生成 | 问一个确定不存在的问题 | fallback_strategy: "model" |
| 改了参数没变化 | 部署 | 确认改的是宿主机 config/config.yaml |
bind mount + --force-recreate app |
| 导入时 429 | 并发 | 降低 embedding 并发 | CONCURRENCY_POOL_SIZE=5 |
| 升级后 Web UI 版本对不上 | 部署 | 确认是否执行过 pull |
官方要求先 docker compose pull 再 up -d |
十一、超时链:三层必须留余量
大文档解析失败时,多数人只调最外层超时,结果内层先断。.env.example E8 给了一条明确的三层约束:【官方】
WEKNORA_DOCUMENT_PROCESS_TIMEOUT=2h # 单文档任务总超时
WEKNORA_DOCREADER_CALL_TIMEOUT=30m # 单次 DocReader RPC(须小于上一项)
WEKNORA_MINERU_TIMEOUT=1000s # 自建 MinerU 单次解析
WEKNORA_PADDLEOCR_VL_TIMEOUT=1000s # 自建 PaddleOCR-VL 请求超时
官方注释原话:「单次 DocReader RPC 超时(默认 30m,须小于上一项)」「超大 PDF 同样需要外层留余量,例如本项 90m、DocReader 100m、单文档任务 2h」。三个值必须单调递增,否则外层还没等到,内层已经返回失败。
配套的一条官方实测数据可作为调并发的依据:【官方】874 页扫描件在渲染 worker 从 1 增到 4 进程时耗时 117s → 30s,8 进程 → 17s(对应 DOCREADER_PDF_RENDER_MAX_WORKERS)。注意这是官方在特定 CPU 条件下的实测,不能直接外推到自己的机器。
十二、未披露清单
| # | 项目 | 状态 |
|---|---|---|
| 1 | fallback_strategy 的全部合法取值 |
【未披露】,配置文件未列枚举 |
| 2 | enable_rerank: true 但未配 Rerank 模型时的降级行为 |
【未披露】 |
| 3 | config/config.yaml 是否支持热加载 |
【未披露】,本文按「重建容器」处理 |
| 4 | 三个 threshold 的分数分布与标定方法 | 【未披露】,只有默认值 |
| 5 | hybrid-search 响应体的确切字段名 |
【未披露】于本文,以部署版 swagger 为准 |
| 6 | Lite 单二进制的官方下载地址 | 【未披露】于 GitHub Release(v0.9.0 assets 为 0) |
| 7 | /chunker/preview 的完整请求体结构 |
【未披露】于本文,需查 swagger 定义 |
| 8 | 分块长度 512 的单位(字符 / token) | 【未披露】,注释未写单位 |
这些缺口不影响前文的排查链,但意味着任何具体数值调整都应该在自己的语料上测一遍,而不是照抄别人的「最佳配置」。
十三、一处合规提示:LICENSE 为什么显示 NOASSERTION
GitHub API 对该仓库的 license 判定为 NOASSERTION,二手文章据此提示「授权不明,别上生产」。查 LICENSE 原文可以确认真实情况:【官方】
WeKnora is licensed under the MIT except for the third-party components listed below.
文件共约 15 KB:开头是完整 MIT 条款,后面逐项列出第三方组件及其原始许可(含 Apache-2.0 全文等)。主体是 MIT,NOASSERTION 是混合许可文件导致自动识别失败,不是授权缺失。 生产使用前仍需按 THIRD_PARTY_NOTICES.md 核对依赖清单,但不必因为 API 字段恐慌。
顺带一个版本口径:仓库 VERSION 文件为 0.9.0,v0.9.0 Release 发布于 2026-10-29,仓库最后推送 2026-11-03。【官方】官方发布审查记录写明本轮范围 315 个提交、1929 个变更路径,并主动声明「这是完整的提交/文件差异清单,不等同于对所有业务代码逐行进行安全审计」——这类自陈边界比宣传语更值得引用。
十四、关键要点总结
- 先分诊,再调参:
hybrid-search一次请求把「答不对」切成「召不回」与「召回了答错」两类,换模型只在第二类末尾才成立。 - 八个默认值都不在
.env:config/config.yaml是唯一改动点,且由 compose bind mount 挂入,改完要--force-recreate app。 fallback_strategy: "model"是幻觉的默认开关:没命中时模型继续生成,不是「知识库说不知道」。- 两个 30 相等说明重排只排序不收窄:答案没进前 30 时,调重排参数无效。
- 三道阈值量纲不同:0.2 / 0.3 / 0.3 分属向量、关键词、重排三种分数,不可横向比较或统一取值。
- Embedding 模型名不是环境变量:在 UI/CLI/初始化接口配置;换模型要
rebuild-index,AUTO_MIGRATE=false会连带停掉新维度 HNSW 索引的自动创建。 - 扫描件默认既不判、也不 OCR:
FORCE_SCANNED=false+--no-ocr组合下,纯图片 PDF 可能入库为空分块。 - Lite 只读
.env.lite:拿标准版.env配 Lite 会意外切走内存队列、打开链路追踪。 - 三层超时必须单调递增:内层须小于外层,官方明确写了「须小于上一项」。
- NOASSERTION 不是授权缺失:主体 MIT + 第三方组件清单,自动识别失败而已。
- 本文所有数值来自仓库文件,未实际部署;响应字段名、fallback 枚举值等缺口列在第十二节,落地前按自己环境验证。
- 点赞
- 收藏
- 关注作者
评论(0)