# RAG 数据流程 > 本文档基于当前代码实际逻辑梳理,详细记录 RAG 系统从用户查询到回答生成的完整数据流。 > 涵盖意图分析、混合检索、重排序、救援管道、上下文构建、图片选择与注入、LLM 生成和引用溯源等核心环节。 --- ## 一、概述 ### 1.1 检索管线总览 ``` 用户查询 │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 1. 意图分析 (intent_analyzer) │ │ 问题改写 · 指代消解 · 是否需要检索 · 重复提问检测 │ │ 模型:mimo-v2.5 │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 2. 混合检索 (engine.search_knowledge) │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 向量检索 │ │ BM25 检索 │ │ │ │ (ChromaDB) │ │ (关键词匹配) │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ └────────┬──────────┘ │ │ ▼ │ │ RRF 融合 (Reciprocal Rank Fusion) │ │ │ │ │ 查询缓存检查 (命中则跳过 3-7 步) │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 3. 云端 Rerank │ │ 模型:xop3qwen8breranker (讯飞云) │ │ 结果缓存:rerank_cache (TTL=1h, 版本失效自动清除) │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 4. 救援管道 (chat_routes.py) │ │ BM25 分歧救援 · 词法匹配救援 · 章节聚类救援 │ │ 目的:挽救被 CrossEncoder 低估但实际相关的切片 │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 5. 上下文构建 + 图片选择 │ │ _order_text_contexts_for_prompt · _build_context_with_budget │ │ select_images · VLM 相关性筛选 · CrossEncoder 精排 │ │ 懒加载 VLM 描述 · 图片描述注入 · P0 安全网 │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 6. LLM 生成 │ │ 模型:mimo-v2.5 │ │ 流式输出 · 置信度指令 · 引用标注 · 图片后置过滤 │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 7. 返回结果 │ │ { answer, sources, images } │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 1.2 模型配置一览 | 用途 | 模型 | API 端点 | 说明 | |------|------|----------|------| | 主 LLM | mimo-v2.5 | xiaomimimo.com | 回答生成、上下文理解 | | 意图分析 | mimo-v2.5 | xiaomimimo.com | 问题改写与意图判断 | | VLM | mimo-v2.5 | xiaomimimo.com | 图片理解与描述生成 | | 云端重排序 | xop3qwen8breranker | 讯飞云 Maas API | 替代本地 BGE-reranker | | 向量编码 | bge-base-zh-v1.5 | 本地模型 | 查询与文档 embedding | > 模型可通过环境变量覆盖:`DASHSCOPE_MODEL`、`INTENT_MODEL`、`VLM_MODEL`、`RERANK_CLOUD_MODEL` ### 1.3 关键配置参数 | 参数 | 默认值 | 说明 | |------|--------|------| | `RERANK_CONTEXT_MIN_SCORE` | 0.05 | Rerank 分数低于此值的切片不送入 LLM | | `MAX_CONTEXT_CHUNKS` | 20 | 送给 LLM 的最大文本切片数 | | `CONTEXT_MAX_CHARS` | 8000 | 上下文最大字符数(约 4000 token) | | `CONTEXT_SOFT_LIMIT` | 6000 | 软限制,超过后只接受高分切片组 | | `CONFIDENCE_WARN_THRESHOLD` | 0.15 | top-3 均分低于此值时,提示 LLM 谨慎回答 | | `CONFIDENCE_CAUTION_THRESHOLD` | 0.30 | top-3 均分低于此值时,提示 LLM 优先引用原文 | --- ## 二、请求入口 ### 2.1 API 路由 **入口文件**:`api/chat_routes.py` ``` POST /chat — 普通聊天(不检索知识库,直接 LLM 回答) POST /rag — 知识库问答(检索 + LLM 生成) POST /search — 纯检索(返回切片,不生成回答) ``` `/rag` 的核心函数 `rag()` 的职责: 1. 调用意图分析模块,获取改写后的查询与检索决策 2. 若需要检索,调用混合检索管线(`search_hybrid()`) 3. 执行救援管道(BM25 分歧 / 词法匹配 / 章节聚类) 4. 执行图片选择(`select_images()`) 5. 构建 LLM 上下文(排序 + 预算截断 + 图片注入) 6. 调用 LLM 生成回答(流式输出) 7. 后置图片过滤(`_filter_images_by_answer()`) 8. 组装最终响应(回答 + 引用来源 + 图片) ### 2.2 请求数据结构 ```python # 请求 { "message": "用户问题", "history": [...] # 可选:对话历史 } # SSE 响应事件流 data: {"type": "intent_result", "data": {...}} data: {"type": "chunks_retrieved", "data": {...}} data: {"type": "images_selected", "data": {...}} data: {"type": "context_built", "data": {...}} data: {"type": "chunk", "content": "部分回答"} data: {"type": "chunk", "content": "部分回答"} ... data: {"type": "finish", "answer": "完整回答", "sources": [...], "images": [...]} ``` --- ## 三、意图分析 **入口文件**:`core/intent_analyzer.py` **使用模型**:mimo-v2.5 ### 3.1 核心功能 意图分析器在检索前对用户查询进行预处理,输出结构化决策: ```python @dataclass class IntentAnalysis: rewritten_query: str # 改写后的查询(指代消解、省略补全) use_context: bool # 是否使用历史上下文 need_retrieval: bool # 是否需要检索知识库 intent: str # 意图类型:factual/comparison/reasoning/instruction sub_queries: list # 子查询列表(对比/复杂查询拆分) ``` ### 3.2 处理逻辑 | 功能 | 说明 | |------|------| | 问题改写 | 指代消解("它" → 具体实体)、省略补全(补全缺失主语) | | 上下文判断 | 根据对话历史决定是否需要前文信息 | | 检索决策 | 判断是否需要查询知识库(闲聊类问题可跳过) | | 意图分类 | 识别查询意图(事实查询/对比分析/推理/操作指导) | | 子查询拆分 | 对比类查询拆分为多个子查询并行检索 | | 语义缓存 | 相似度 ≥ 0.92 时复用历史意图分析结果 | ### 3.3 重复提问规则 当用户重复提问相同或相似问题时,意图分析器必须设置 `need_retrieval = true`。原因:用户可能对之前的回答不满意,或之前的回答包含错误信息,需要重新检索以获取更准确的结果。 --- ## 四、检索阶段 **入口文件**:`core/engine.py` **核心函数**:`search_knowledge()` ### 4.1 查询缓存检查 检索前先检查缓存,命中则直接返回历史结果,避免重复计算: ```python cached = cache.get_query_result(query, kb_name) if cached: return cached ``` 缓存层级(5 层): 1. `query_cache` — 精确查询结果缓存 2. `embedding_cache` — embedding 向量缓存 3. `rerank_cache` — rerank 分数缓存(TTL=1h,知识库版本变更自动失效) 4. `semantic_cache` — 语义相似查询缓存(阈值 0.92) 5. `intent_exact_cache` — 意图分析精确缓存 ### 4.2 向量检索 使用本地 bge-base-zh-v1.5 模型将查询编码为向量,在 ChromaDB 中执行 ANN 检索: ```python query_vector = embedding_model.encode(query) text_results = collection.query( query_embeddings=[query_vector], n_results=100 # recall_k,召回候选数量 ) ``` **图片独立召回**:同时对图片切片进行独立检索,保证图片有足够的召回机会: ```python image_results = collection.query( query_embeddings=[query_vector], where={'chunk_type': {'$in': ['image', 'chart', 'table']}}, n_results=5 ) ``` ### 4.3 BM25 关键词检索 基于 BM25 算法执行关键词匹配检索,弥补向量检索在精确匹配上的不足: ```python bm25_results = bm25_index.search(query, top_k=100) ``` ### 4.4 RRF 融合 使用 Reciprocal Rank Fusion 算法将向量检索和 BM25 检索的结果进行融合排序: ```python fused_results = reciprocal_rank_fusion( [text_results, image_results, bm25_results], weights=[VECTOR_WEIGHT, IMAGE_WEIGHT, BM25_WEIGHT] ) ``` ### 4.5 子查询并行检索 意图分析器拆分的子查询(如对比类查询)会并行检索,结果合并后再 rerank。 --- ## 五、重排序 ### 5.1 云端 Rerank **调用模型**:xop3qwen8breranker(讯飞云 Maas API) RRF 融合后的候选结果通过云端 Reranker 进行精排,基于查询与文档的语义相关性重新打分排序: ```python reranked = rerank_results(query, fused_results, top_k=15) ``` **重要特征**:CrossEncoder 对不同类型切片的评分分布差异显著: | 切片类型 | 典型分数范围 | 说明 | |----------|------------|------| | 文本切片 | 0.3 ~ 0.99 | 正常分布 | | 表格切片 | 0.1 ~ 0.5 | 偏低,有专用救援逻辑 | | 图片/图表切片 | 0.002 ~ 0.08 | 系统性偏低,需要宽松阈值 | ### 5.2 Rerank 缓存 - 每个 (query, doc_id) 对的 rerank 分数独立缓存 - TTL = 1 小时 - 知识库版本号变更时自动失效 --- ## 六、救援管道 **入口文件**:`api/chat_routes.py` **目的**:挽救被 CrossEncoder 低估但实际相关的切片 在 `_order_text_contexts_for_prompt` 之前执行,三个救援管道按顺序运行: ### 6.1 BM25 分歧救援 `_rescue_bm25_divergence` **触发条件**:BM25 排名 top-3 但 CrossEngineer 评分低于 min_score 的切片 **逻辑**:BM25 是关键词精确匹配的强信号,如果 BM25 认为相关但 CE 误判,提升分数至保底值。 ```python # 配置 BM25_DIVERGENCE_RESCUE_ENABLED = True BM25_DIVERGENCE_MAX_RANK = 3 # 仅救援 BM25 rank <= 3 的切片 ``` ### 6.2 词法匹配救援 `_rescue_lexical_match` **触发条件**:切片文本精确包含查询关键词(bigram 匹配率 > 35%)但 CE 评分低 **逻辑**:当切片文本直接包含查询中的关键词组合时,说明语义相关,CE 可能因为表述差异低估。 ### 6.3 章节聚类救援 `_rescue_section_cluster` **触发条件**:同一 section 内所有切片都被 min_score 过滤("全灭 section") **逻辑**:如果同一章节的切片全部被过滤,但其他章节有切片通过,说明 CE 对该章节整体低估。为全灭 section 的切片分配保底分数(0.06)。 ```python # 配置 SECTION_CLUSTER_RESCUE_ENABLED = True CLUSTER_RESCUE_FLOOR = 0.06 # 略高于 RERANK_CONTEXT_MIN_SCORE=0.05 ``` ### 6.4 表格救援 `_rescue_table_chunks` **触发条件**:查询涉及表格但上下文中没有表格数据(表格被预算截断) **逻辑**:从被截断的切片中补回表格数据。 --- ## 七、上下文构建 ### 7.1 切片排序与过滤 `_order_text_contexts_for_prompt` **核心逻辑**: 1. **文本切片 + 表格切片** → 进入 `text_contexts` 2. **图片/图表切片** → 进入 `chart_contexts`(独立处理) 3. **min_score 过滤**: - `text_contexts`:使用 `RERANK_CONTEXT_MIN_SCORE`(0.05) - 表格保护:同 section 内如有切片通过阈值,table 切片保底分数为 `min_score * 0.3` - `chart_contexts`:使用宽松阈值 `min_score * 0.5`(0.025) - 原因:CrossEncoder 对图片描述评分系统性偏低 - 实际内容相关性由 `select_images` 独立评分保证 4. **合并**:`text_contexts + chart_contexts[:3]` ### 7.2 预算构建 `_build_context_with_budget` 按字符预算构建上下文文本: 1. 按 (source, section) 分组 2. 组内按 chunk_index 排序(保持原文连续性) 3. 组间按组内最高 Rerank 分数降序 4. 逐组加入直到达到 `CONTEXT_MAX_CHARS`(8000) 5. 超过 `CONTEXT_SOFT_LIMIT`(6000)后收紧准入 **特殊处理**:表格切片不受预算截断(结构化关键内容) ### 7.3 图片选择 `select_images()` 从检索结果中筛选与查询最相关的图片: **步骤一:动态预算** | 查询类型 | MAX_IMAGES | MIN_SCORE | 检测方式 | |----------|------------|-----------|----------| | 精确图号查询("图2.3") | 2 | 5.0 | 正则匹配 `图\s*(\d+\.?\d*)` | | 有图片数据(检索含 image/chart) | 5 | 2.0 | 检查检索结果 chunk_type | | 文本引用图表 | 3 | 2.0 | 提取 top-5 文本中的图号/表号 | | 普通查询 | 2 | 3.0 | 默认 | **步骤二:提取图表引用** 从 top 5 文本块中提取 "见图2.3"、"如表2.2" 等引用,建立图号与来源文件的映射。 **步骤三:图片相关性打分 `score_image_relevance()`** | 匹配项 | 加分 | |--------|------| | 图号精确匹配(查询中有"图2.3") | +10 分 | | 表号精确匹配 | +10 分 | | 关键词匹配(jieba 分词) | +2 分/个,上限 8 分 | | 字符重叠 | +0.2 分/字符,上限 3 分 | | 章节匹配 | +1.5 分/关键词 | | 图片类型(chart) | +2 分 | | 图片类型(image) | +1 分 | | 向量相似度 | +2 分(最高) | | 引用匹配 + 章节相关 | +8 分 + 5 分 | | VLM 相关性 < 0.3 | -3 分(VLM 描述与查询不相关) | | VLM 相关性 ≥ 0.5 | +2 分 | | 章节不相关 | -5 分(除非被文本引用) | **步骤四:VLM 相关性筛选** 对有 VLM 描述的图片,使用关键词重叠率判断描述与查询的相关性: - 重叠率 < 0.3:降分 -3(描述与查询不相关) - 重叠率 ≥ 0.5:加分 +2 **步骤五:CrossEncoder 精排** 对 `select_images` 候选用 CE 做语义精排: - CE < 0:直接剔除(语义不相关) - CE 0~2:保留但不加分(弱相关) - CE > 2:加分 `min((ce - 2) / 3, 1) * 5` **步骤六:表格嵌入图片补充** 处理 `images_json` 字段的表格切片,补充表格中嵌入的图片。 **步骤七:文本引用图片补充** 从 top 5 文本块中提取 `referenced_images` 字段,补充未选中的关联图片。 **步骤八:返回 top N** ```python scored_images.sort(key=lambda x: x['score'], reverse=True) return scored_images[:MAX_IMAGES] ``` ### 7.4 图片描述注入 **核心逻辑**:将 `select_images` 选中的图片的 `full_description` 注入到 LLM context 中。 ``` context_text(文本切片) + 【相关图片信息】 【图片1】VLM 完整描述(来源:xxx 第N页) 【图片2】VLM 完整描述(来源:xxx 第N页) + 【回答要求】回答时请简要介绍每张图片的内容和用途。 ``` **P0 安全网**(防止图片描述被过滤遗漏): 1. 若 `selected_images` 非空但 `【相关图片信息】` 未生成 → 补注入所有选中图片描述 2. 若已有 `【相关图片信息】` 但遗漏部分图片 → 追加遗漏的描述 3. DEV 模式日志:检查所有选中图片的 ID 是否出现在 context 中 ### 7.5 置信度指令 根据 top-3 切片的平均 Rerank 分数注入不同的回答指令: | 置信度范围 | 指令 | |-----------|------| | < 0.15 | "参考资料与问题的相关性较低,仅基于明确信息回答,不足则说明" | | 0.15 ~ 0.30 | "参考资料的相关性一般,优先引用资料原文,避免推测" | | ≥ 0.30 | 无额外指令 | --- ## 八、LLM 生成 ### 8.1 流式生成 使用 SSE(Server-Sent Events)实现流式输出: ```python for token in engine.generate_answer_stream(query, enhanced_context, history): yield token # 逐 token 推送给前端 ``` **LLM 模型**:mimo-v2.5 ### 8.2 System Prompt ``` 你是一个严谨的知识库问答助手。 你必须且只能根据用户提供的【参考资料】回答问题。 如果参考资料中有答案,必须引用对应内容回答,并在回答末尾标注引用编号。 如果参考资料中确实没有相关信息,简短说明即可,不要编造或补充资料外的内容。 禁止使用参考资料以外的知识进行补充或推测。 【重要-表格处理规则】当用户询问表格时,必须将 Markdown 表格原样输出... ``` ### 8.3 图片后置过滤 `_filter_images_by_answer` LLM 生成回答后,根据回答内容对图片做最终过滤: 1. **候选 ≤ 1 张**:直接返回 2. **无图意图早退**:回答和查询都不含图片引用词 → 返回空 3. **图号精确豁免**:图片描述包含回答引用的具体图号/表号 → 无条件保留 4. **主题一致性**:合并查询+回答关键词,图片描述重叠 ≥ 阈值才保留 - 子章节惩罚:图片子章节与主要检索章节不一致时,阈值 +2 5. **兜底**:过滤后为空且有图片意图 → 保留分数最高的 1 张 ### 8.4 回答结构 ```python { "type": "finish", "answer": "根据蓄水以来的统计数据,三峡电站逐年发电量呈现波动上升趋势...", "sources": [ { "source": "三峡公报_2022.pdf", "page": 12, "section": "综述 > 2.3 发电", "chunk_id": "三峡公报_text_24" } ], "images": [ { "id": "ab77281e7913.jpg", "url": "/images/ab77281e7913.jpg", "type": "chart", "source": "三峡公报_2022.pdf", "page": 12, "description": "图2.3 柱状图:2003-2022年逐年发电量", "full_description": "...完整 VLM 描述..." } ] } ``` --- ## 九、引用溯源 ### 9.1 引用标注机制 `_attach_citations()` 负责在回答中插入引用标记,将回答内容与知识库来源关联: ``` 根据统计数据[ref:三峡公报_text_24],2022年三峡电站年度发电量为787.90亿千瓦时[ref:三峡公报_table_5]。 ``` ### 9.2 引用数据来源 每个引用标记对应检索结果中的一个切片,包含: | 字段 | 说明 | |------|------| | `source` | 源文件名 | | `page` | 页码 | | `section` | 章节路径 | | `chunk_id` | 切片唯一标识 | | `chunk_type` | 类型(text / table / image / chart) | --- ## 十、文档入库流程(补充参考) > 入库流程为检索提供数据基础,以下简要说明关键环节。 ### 10.1 文档解析 **入口文件**:`parsers/mineru_parser.py` **核心函数**:`parse_with_mineru()` MinerU 解析 PDF/Word/Excel 文件,输出结构化内容: ``` .data/mineru_temp/{file_hash}/ ├── auto/ │ ├── {doc_name}.md # Markdown 内容 │ ├── {doc_name}_content_list.json # 结构化内容列表(核心) │ └── images/ # 提取的图片文件 ``` **content_list.json 中的条目类型**: | item_type | 处理方式 | 关键字段 | |-----------|----------|----------| | `text` | 文本块 | content, section_path, text_level | | `table` | 表格 | content, table_html, image_path | | `image` | 图片 | content(=caption), image_path, context_before/after | | `chart` | 图表 | content(=caption), image_path, context_before/after | ### 10.2 切片入库 **入口文件**:`knowledge/manager.py` **核心函数**:`add_file_to_kb()` ``` MinerUChunk 列表 │ ├── 文本块 → 计算 embedding → 存入 ChromaDB │ ├── 表格块 → 生成语义增强摘要 → 存入 ChromaDB │ └── 图片块 → VLM 缓存检查 → 生成描述 → 存入 ChromaDB ``` **图片描述策略**: 1. 优先使用 VLM 缓存描述(由 mimo-v2.5 生成,语义更丰富) 2. 若无缓存,生成轻量描述(基于文件名 + 章节路径 + 上下文) 3. VLM 描述异步懒加载:首次检索命中时后台生成,下次查询即可使用 ### 10.3 ChromaDB 存储结构 | 字段 | 类型 | 说明 | |------|------|------| | `ids` | str | 切片唯一标识,如 `doc.pdf_text_24` | | `embeddings` | List[float] | 向量表示 | | `documents` | str | 切片内容(文本/VLM 描述/摘要) | | `metadatas` | dict | 元数据(见下表) | **图片切片 metadata 关键字段**: ```python { 'source': '三峡公报_2022.pdf', # 源文件名 'page': 12, # 页码 'chunk_type': 'chart', # 切片类型 'section': '综述 > 2.3发电', # 章节路径 'image_path': 'ab77281e7913.jpg', # 图片文件名 'has_vlm_desc': True, # 是否有 VLM 描述 'vlm_desc': '图2.3 柱状图...', # VLM 描述内容 'chunk_id': '三峡公报_chart_5', # 切片唯一标识 'version': 'v10', # 知识库版本号 'status': 'active', # 状态 } ``` > **注意**:ChromaDB metadata 中**没有** `full_description` 字段。图片的完整描述存储在 `vlm_desc` 字段或 `documents` 字段中。`full_description` 仅在 `select_images()` 运行时动态计算。 ### 10.4 懒加载增强 **入口文件**:`knowledge/lazy_enhance.py` 对检索命中但没有 VLM 描述的图片切片,在后台线程异步调用 VLM 生成描述: ```python # 检索命中 → 后台线程调用 VLM → 写入文件缓存 + ChromaDB enhance_retrieved_chunks(contexts, query, kb_name, defer_chromadb=True) ``` - **image/chart 切片**:更新 `ctx['doc']` 字段 - **table 切片**:更新 `ctx['image_description']` 字段 - **缓存目录**:`.data/cache/vlm/`、`.data/cache/llm/` --- ## 十一、缓存系统 ### 11.1 缓存层级 | 层级 | 类 | 用途 | 容量 | TTL | |------|-----|------|------|-----| | query_cache | LRUCache | 精确查询结果缓存 | 500 | 无 | | embedding_cache | LRUCache | 查询 embedding 向量 | 500 | 无 | | rerank_cache | TTLCache | Rerank 分数缓存 | 500 | 1h | | semantic_cache | SemanticCache | 语义相似查询缓存 | 200 | 无 | | intent_exact_cache | LRUCache | 意图分析精确缓存 | 200 | 无 | ### 11.2 缓存失效 - **版本失效**:知识库版本号变更时,query_cache 和 rerank_cache 自动失效 - **手动清除**:`POST /cache/clear`(DEV 模式) ### 11.3 缓存 API ``` GET /cache/stats — 查看缓存统计 POST /cache/clear — 清除所有缓存 ``` --- ## 十二、关键文件索引 | 文件 | 职责 | 关键函数/类 | |------|------|-------------| | `api/chat_routes.py` | API 路由与请求调度 | `rag()`, `select_images()`, `score_image_relevance()`, `_filter_images_by_answer()`, `_order_text_contexts_for_prompt()`, `_build_context_with_budget()` | | `core/engine.py` | 检索引擎核心 | `search_knowledge()`, `rerank_results()`, `generate_answer_stream()` | | `core/intent_analyzer.py` | 意图分析 | `analyze()`, `IntentAnalysis` | | `core/bm25_index.py` | BM25 关键词检索 | `BM25Index.search()` | | `core/mmr.py` | MMR 去重 | `mmr_rerank()` | | `core/cache.py` | 缓存管理 | `RAGCacheManager`, `get_cache_manager()` | | `core/semantic_cache.py` | 语义缓存 | `SemanticCache` | | `core/chunker.py` | 语义分块器 | `SemanticChunker` | | `core/llm_utils.py` | LLM 调用工具 | `call_llm()`, `call_llm_stream()` | | `parsers/mineru_parser.py` | 文档解析 | `parse_with_mineru()`, `MinerUChunk` | | `knowledge/manager.py` | 知识库管理 | `add_file_to_kb()`, `KBManager` | | `knowledge/lazy_enhance.py` | 懒加载增强 | `lazy_vlm_description()`, `enhance_retrieved_chunks()` | | `config.py` | 配置集中管理 | 模型/参数/开关 |