Files
2026Technology-Competition/docs/superpowers/plans/2026-08-08-phase1-foundation.md
T

23 KiB
Raw Blame History

Phase1 里程碑1(骨架+数据模型+config)实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 建立可安装的 src 布局项目骨架,实现 design §3+§9.4 全部业务数据模型与 config-design §1/§7 的配置加载,pytest 全绿。

Architecture: 标准 src 布局(src/genesis/+ pyproject setuptools 打包 + pytest。数据模型一个模块统一集中(future.annotations 支持前向引用);config 用 pydantic 模型对应三 yamlfrom_dir 加载并实现「env(yaml) 环境变量 > yaml > 默认值」优先级与敏感字段脱敏。

Tech Stack: Python ≥3.11(实际 3.14.3)、pydantic 2.13 / pydantic-settings 2.15、pyyaml、pytest 8。

Global Constraints

  • 全部文件修改遵循 docs/superpowers/specs/2026-08-08-phase1-foundation-design.md
  • 数据模型文件 src/genesis/data_models.py 顶部必须有 from __future__ import annotationsdesign §9.4.6P0-1)。
  • 字段名/默认值必须与 design.md §3 / §9.4 一一对应,不得增删。
  • 配置优先级:环境变量(GENESIS_ 前缀 + __ 嵌套分隔)> yaml 文件 > pydantic 默认值。
  • 敏感键(含 key/secret/token 的字段)在 get_redacted() 输出为 "***"
  • 交流语言统一中文(代码注释、提交信息用中文;标识符/技术术语英文)。
  • 每个任务结束前运行 pytest 全绿,随后 commit。

Task 1: 项目骨架 + pyproject 打包

Files:

  • Create: pyproject.toml
  • Create: src/genesis/__init__.py
  • Create: tests/__init__.py
  • Create: tests/test_smoke.py

Interfaces:

  • Consumes: 无(首个任务)

  • Produces: 包 genesisimport genesis 可用,genesis.__version__ == "0.1.0");pytest 可从根目录运行

  • Step 1: 写失败测试(冒烟)

tests/test_smoke.py:

import genesis


def test_package_importable():
    assert genesis.__version__ == "0.1.0"
  • Step 2: 写骨架文件

src/genesis/__init__.py:

"""Genesis:概要设计书自动生成 Agent。"""
__version__ = "0.1.0"

tests/__init__.py: 空文件。

pyproject.toml:

[build-system]
requires = ["setuptools>=69"]
build-backend = "setuptools.build_meta"

[project]
name = "genesis"
version = "0.1.0"
description = "概要设计书自动生成 Agent"
requires-python = ">=3.11"
dependencies = [
  "pydantic>=2.13",
  "pydantic-settings>=2.15",
  "pyyaml>=6.0",
  "openpyxl>=3.1",
  "python-docx>=1.0",
]

[project.optional-dependencies]
dev = ["pytest>=8.0"]

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
  • Step 3: 运行测试,确认失败

Run: python -m pytest tests/test_smoke.py -v Expected: FAILModuleNotFoundError: No module named 'genesis'

  • Step 4: 可编辑安装 + 运行测试,确认通过

Run: python -m pip install -e ".[dev]" -i https://pypi.tuna.tsinghua.edu.cn/simple Run: python -m pytest -v Expected: PASS1 passed

  • Step 5: Commit
git add pyproject.toml src tests
git commit -m "chore: 项目骨架与 pyproject 打包(src 布局 + pytest 就绪)"

Task 2: 数据模型 data_models.py

Files:

  • Create: src/genesis/data_models.py
  • Test: tests/test_data_models.py

Interfaces:

  • Consumes: Genesis 包骨架(Task 1

  • Produces: 模块 genesis.data_models,导出:

    • 枚举:SheetType(8) / ElementType(6) / RelationType(6) / Confidence(3) / ExtractionMethod(2)
    • 类型:Provenance CellFormatting CellComment CellValue ExcelTable ChapterMarker ParsedTemplate RuleDocument ImageAnalysis ControllerInfo ServiceInfo EntityInfo EndpointInfo ExistingSystemInfo UnifiedDocument CodeStructure ImageDescription StructuredSourceTask 3 的 config 不需要,后续里程碑消费)
  • Step 1: 写失败测试

tests/test_data_models.py:

from dataclasses import asdict

from genesis.data_models import (
    CellComment, CellFormatting, CellValue, Confidence, ElementType,
    ExcelTable, ExtractionMethod, ImageAnalysis, ParsedTemplate, Provenance,
    RelationType, RuleDocument, SheetType, StructuredSource,
)


def test_sheettype_has_8_members():
    assert len(SheetType) == 8
    assert SheetType.FUNCTION.value == "FUNCTION"
    assert SheetType.GENERIC.value == "GENERIC"


def test_value_enum_members():
    assert ElementType.FUNCTION.value == "機能"
    assert RelationType.USE.value == "利用"
    assert Confidence.HIGH.value == "high"
    assert ExtractionMethod.OPENPYXL.value == "openpyxl"
    assert ExtractionMethod.LLM_FROM_FREE_TEXT.value == "llm_from_free_text"


def test_cellformatting_defaults():
    fmt = CellFormatting()
    assert fmt.strikethrough is False
    assert fmt.font_color is None
    assert fmt.bg_color is None


def test_cellvalue_forward_reference_works():
    """CellValue 引用后置定义的 CellFormatting/CellCommentfuture.annotations 落地)"""
    prov = Provenance(file_name="f.xlsx", sheet_name="S", row=1, column="A", column_header="h")
    cv = CellValue(
        value="x",
        provenance=prov,
        formatting=CellFormatting(strikethrough=True),
        comment=CellComment(author="reviewer", text="check", source_uri="f.xlsx#S!A1"),
    )
    assert cv.formatting.strikethrough is True
    assert cv.comment.author == "reviewer"


def test_excel_table_references_sheettype():
    table = ExcelTable(
        name="機能一覧",
        detected_type=SheetType.FUNCTION,
        extraction_method=ExtractionMethod.OPENPYXL.value,
        headers=["機能ID", "機能名"],
        rows=[],
    )
    assert table.detected_type is SheetType.FUNCTION
    assert table.extraction_method == "openpyxl"


def test_structured_source_assembles_all():
    source = StructuredSource(
        tables=[],
        template=ParsedTemplate(file_name="t.docx", sections=[], placeholders={}, styles={}),
        rule_docs=[RuleDocument(
            file_name="記入規則.docx", category="write", markdown_content="# 規則",
            source_path="samples/記入規則.docx", file_type="word", hash="abc",
        )],
        image_analyses=[ImageAnalysis(
            image_ref="img1", description="画面遷移図", confidence=0.9,
            source_uri="f.xlsx#S!A1", sheet_name="S", anchor_cell="A1", status="recognized",
        )],
        existing_system=None,
        comments=[],
    )
    assert source.rule_docs[0].category == "write"
    assert source.image_analyses[0].nearby_text == ""


def test_asdict_serializable():
    prov = Provenance(file_name="f.xlsx", sheet_name="S", row=1, column="A", column_header="h")
    d = asdict(CellValue(value=1, provenance=prov))
    assert d["provenance"]["row"] == 1
  • Step 2: 运行测试,确认失败

Run: python -m pytest tests/test_data_models.py -v Expected: FAILModuleNotFoundError: No module named 'genesis.data_models'

  • Step 3: 实现 data_models.py

src/genesis/data_models.py(完整内容):

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Any


class SheetType(Enum):
    """Excel Sheet 的类型(Parser SheetDetector 判定结果)"""
    FUNCTION = "FUNCTION"
    SCREEN = "SCREEN"
    REPORT = "REPORT"
    DATABASE = "DATABASE"
    INTERFACE = "INTERFACE"
    BATCH = "BATCH"
    MASTER = "MASTER"
    GENERIC = "GENERIC"


class ElementType(Enum):
    """Impact Agent 抽取的构成要素类型"""
    FUNCTION = "機能"
    SCREEN = "画面"
    REPORT = "帳票"
    DB = "DB"
    IF = "IF"
    BATCH = "バッチ"


class RelationType(Enum):
    """关联类型(Impact Agent 推理结果)"""
    USE = "利用"
    REFER = "参照"
    UPDATE = "更新"
    OUTPUT = "输出"
    INPUT = "输入"
    DEPEND = "依赖"


class Confidence(Enum):
    """置信度等级"""
    HIGH = "high"
    MEDIUM = "medium"
    LOW = "low"


class ExtractionMethod(Enum):
    """Excel 表的抽取方式"""
    OPENPYXL = "openpyxl"
    LLM_FROM_FREE_TEXT = "llm_from_free_text"


@dataclass
class Provenance:
    file_name: str
    sheet_name: str
    row: int
    column: str
    column_header: str


@dataclass
class CellFormatting:
    strikethrough: bool = False
    font_color: str | None = None
    bg_color: str | None = None


@dataclass
class CellComment:
    author: str
    text: str
    source_uri: str


@dataclass
class CellValue:
    value: Any
    provenance: Provenance
    formatting: CellFormatting | None = None
    comment: CellComment | None = None


@dataclass
class ExcelTable:
    name: str
    detected_type: SheetType
    extraction_method: str     # 取 ExtractionMethod 的 value(同一常量来源)
    headers: list[str]
    rows: list[dict[str, CellValue]]


@dataclass
class ChapterMarker:
    type: str                 # "heading" | "bookmark" | "placeholder"
    name: str
    level: int


@dataclass
class ParsedTemplate:
    file_name: str
    sections: list[ChapterMarker]
    placeholders: dict[str, str]
    styles: dict


@dataclass
class RuleDocument:
    file_name: str
    category: str             # "write" | "design" | "ref"
    markdown_content: str
    source_path: str
    file_type: str            # "word" | "excel" | "ppt"
    hash: str


@dataclass
class ImageAnalysis:
    """图片分析结果(Parser 组装,StructuredSource 消费)"""
    image_ref: str
    description: str
    confidence: float
    source_uri: str
    sheet_name: str
    anchor_cell: str
    status: str               # "recognized" | "recorded_only" | "failed"
    nearby_text: str = ""


@dataclass
class ControllerInfo:
    name: str
    class_name: str
    path: str
    base_path: str
    endpoints: list[str]
    source_uri: str


@dataclass
class ServiceInfo:
    name: str
    class_name: str
    path: str
    methods: list[str]
    source_uri: str


@dataclass
class EntityInfo:
    name: str
    class_name: str
    path: str
    table_name: str | None
    fields: list[str]
    source_uri: str


@dataclass
class EndpointInfo:
    method: str
    path: str
    controller: str | None
    description: str
    source_uri: str


@dataclass
class ExistingSystemInfo:
    controller_layer: list[ControllerInfo]
    service_layer: list[ServiceInfo]
    entity_layer: list[EntityInfo]
    api_endpoints: list[EndpointInfo]
    source_path: str


@dataclass
class UnifiedDocument:
    """FileReader 的统一输出(多格式归一化)"""
    file_name: str
    file_type: str            # "excel" | "word" | "ppt" | "text"
    source_path: str
    content_type: str
    tables: list[list[list[Any]]] | None = None
    sheet_names: list[str] | None = None
    paragraphs: list[dict] | None = None
    slides: list[dict] | None = None
    text: str | None = None
    encoding: str | None = None


@dataclass
class CodeStructure:
    """CodeParser 的解析输出"""
    root_path: str
    language: str
    modules: list[dict]
    classes: list[dict]
    controllers: list[ControllerInfo]
    services: list[ServiceInfo]
    entities: list[EntityInfo]
    endpoints: list[EndpointInfo]
    raw_imports: list[dict]


@dataclass
class ImageDescription:
    """ImageAnalyzer 的原始识别输出(工具层;业务侧用 ImageAnalysis"""
    image_ref: str
    description: str
    objects: list[str]
    ocr_text: str | None
    confidence: float
    model: str


@dataclass
class StructuredSource:
    tables: list[ExcelTable]
    template: ParsedTemplate
    rule_docs: list[RuleDocument]
    image_analyses: list[ImageAnalysis]
    existing_system: ExistingSystemInfo | None
    comments: list[CellComment]

(请确认文件包含 from dataclasses import dataclass —— 上例顶部 import 中已含,若编辑器省略请补全为 from dataclasses import dataclass。)

  • Step 4: 运行测试,确认通过

Run: python -m pytest -v Expected: PASS(全部 tests,含 smoke + data_models

  • Step 5: Commit
git add src/genesis/data_models.py tests/test_data_models.py
git commit -m "feat: 数据模型 data_modelsdesign §3+§9.4future.annotations"

Task 3: 配置加载 config.py

Files:

  • Create: src/genesis/config.py
  • Create: tests/fixtures/app_min.yaml, tests/fixtures/inference_min.yaml, tests/fixtures/rag_min.yaml
  • Test: tests/test_config.py

Interfaces:

  • Consumes: 包骨架(Task 1

  • Produces: 模块 genesis.config,导出:

    • 模型:ServerConfig / AppConfig / InferenceConfig / RagConfig / ModelSpec / Settings
    • Settings.from_dir(config_dir: Path) -> Settingsyaml 缺失→默认;${VAR} 展开)
    • Settings.get_redacted() -> dictkey/secret/token → "***"
    • 优先级:GENESIS_ 前缀环境变量(__ 嵌套)> yaml > 默认值
  • Step 1: 写 fixtures

tests/fixtures/app_min.yaml:

server:
  max_upload_mb: 10
session:
  sqlite_path: "C:/tmp/genesis.db"
task_queue:
  backend: memory
  timeout_sec: 300

tests/fixtures/inference_min.yaml:

models:
  primary:
    provider: deepseek
    name: deepseek-chat
llm_calls:
  max_context_tokens: 16000
structured_output:
  max_parse_retry: 3

tests/fixtures/rag_min.yaml:

embedding:
  model: BAAI/bge-small-zh-v1.5
vector_store:
  adapter: chroma
  qdrant:
    url: http://qdrant:6333
    api_key: ${QDRANT_API_KEY}
retrieval:
  rrf_k: 42
  • Step 2: 写失败测试

tests/test_config.py:

from pathlib import Path
from genesis.config import Settings

FIXTURES = Path(__file__).parent / "fixtures"


def test_from_dir_maps_yaml_fields():
    s = Settings.from_dir(FIXTURES)
    assert s.app.server.max_upload_mb == 100
    assert s.app.session["sqlite_path"] == "C:/tmp/genesis.db"
    assert s.app.task_queue["timeout_sec"] == 300
    assert s.inference.models.primary.name == "deepseek-chat"
    assert s.inference.llm_calls.max_context_tokens == 16000
    assert s.inference.structured_output.max_parse_retry == 3
    assert s.rag.embedding.model == "BAAI/bge-small-zh-v1.5"
    assert s.rag.retrieval.rrf_k == 42


def test_defaults_when_dir_empty(tmp_path):
    s = Settings.from_dir(tmp_path)
    assert s.app.name == "genesis"
    assert s.app.server.max_upload_mb == 100
    assert s.app.task_queue["backend"] == "memory"
    assert s.inference.models.primary.name == "deepseek-chat"
    assert s.rag.embedding.model == "BAAI/bge-small-zh-v1.5"
    assert s.rag.retrieval.rrf_k == 60


def test_env_override_yaml(monkeypatch):
    monkeypatch.setenv("GENESIS_APP__SERVER__MAX_UPLOAD_MB", "25")
    s = Settings.from_dir(FIXTURES)
    assert s.app.server.max_upload_mb == 25


def test_env_nested_creation(monkeypatch):
    monkeypatch.setenv("GENESIS_RAG__RETRIEVAL__DEFAULT_TOP_K", "7")
    s = Settings.from_dir(FIXTURES)
    assert s.rag.retrieval.default_top_k == 7


def test_env_placeholder_expansion(monkeypatch):
    monkeypatch.setenv("QDRANT_API_KEY", "sk-test-xyz")
    s = Settings.from_dir(FIXTURES)
    assert s.rag.vector_store.qdrant.api_key == "sk-test-xyz"


def test_redacted_hides_secrets():
    s = Settings.from_dir(FIXTURES)
    red = s.get_redacted()
    assert "sk-test-xyz" not in str(red)
    assert red["rag"]["vector_store"]["qdrant"]["api_key"] == "***"
  • Step 3: 运行测试,确认失败

Run: python -m pytest tests/test_config.py -v Expected: FAILModuleNotFoundError: No module named 'genesis.config'

  • Step 4: 实现 config.py

src/genesis/config.py(完整内容):

from __future__ import annotations

import os
from pathlib import Path
from typing import Any

import yaml
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict

SECRET_KEYWORDS = ("key", "secret", "token")
ENV_PREFIX = "GENESIS_"


# ---------- 各 yaml 对应的 pydantic 模型 ----------

class ServerConfig(BaseModel):
    max_upload_mb: int = 100
    allowed_extensions: list[str] = Field(
        default_factory=lambda: [".xlsx", ".xls", ".docx", ".pptx", ".java", ".xml", ".yml"]
    )


class AppConfig(BaseModel):
    name: str = "genesis"
    version: str = "0.1.0"
    timezone: str = "Asia/Tokyo"
    server: ServerConfig = Field(default_factory=ServerConfig)
    session: dict[str, Any] = Field(default_factory=lambda: {
        "sqlite_path": "/data/db/genesis.db",
        "snapshot_dir": "/data/db/snapshots",
    })
    paths: dict[str, Any] = Field(default_factory=lambda: {
        "user_root": "/data/users",
        "shared_root": "/data/shared",
    })
    task_queue: dict[str, Any] = Field(default_factory=lambda: {
        "backend": "memory",
        "redis_url": "",
        "timeout_sec": 600,
        "retry_default": 2,
    })


class ModelSpec(BaseModel):
    provider: str = "deepseek"
    name: str = "deepseek-chat"
    temperature: float = 0.2
    max_tokens: int = 4096
    timeout_sec: int = 60
    retry_backoff: list[float] = Field(default_factory=lambda: [1.0, 3.0, 7.0])


class InferenceModels(BaseModel):
    primary: ModelSpec = Field(default_factory=ModelSpec)
    fallback: ModelSpec = Field(default_factory=lambda: ModelSpec(provider="qwen", name="qwen-max"))
    vision: ModelSpec = Field(default_factory=lambda: ModelSpec(name="deepseek-vl", timeout_sec=90))


class LlmCallsConfig(BaseModel):
    token_estimation: str = "tiktoken"
    max_context_tokens: int = 32000
    truncation_policy: dict[str, Any] = Field(default_factory=lambda: {
        "priority": ["shrink_rule_chunks", "summarize_history", "truncate_data"],
    })


class StructuredOutputConfig(BaseModel):
    max_parse_retry: int = 2


class PromptRegistryConfig(BaseModel):
    prompts_dir: str = "./prompts"
    default_version: str = "latest"


class InferenceConfig(BaseModel):
    models: InferenceModels = Field(default_factory=InferenceModels)
    llm_calls: LlmCallsConfig = Field(default_factory=LlmCallsConfig)
    structured_output: StructuredOutputConfig = Field(default_factory=StructuredOutputConfig)
    prompt_registry: PromptRegistryConfig = Field(default_factory=PromptRegistryConfig)


class EmbeddingConfig(BaseModel):
    model: str = "BAAI/bge-small-zh-v1.5"
    device: str = "cpu"
    max_batch_size: int = 32
    cache_dir: str = "/data/shared/models"


class ChromaStoreConfig(BaseModel):
    persist_dir: str = "/data/shared/rules-handbook/chroma"


class QdrantStoreConfig(BaseModel):
    url: str = "http://qdrant:6333"
    api_key: str = ""


class VectorStoreConfig(BaseModel):
    adapter: str = "chroma"
    chroma: ChromaStoreConfig = Field(default_factory=ChromaStoreConfig)
    qdrant: QdrantStoreConfig = Field(default_factory=QdrantStoreConfig)


class ChunkingConfig(BaseModel):
    word_max_tokens: int = 512
    excel_rule_block_rows: int = 10
    ppt_pages_per_chunk: int = 2
    min_tokens: int = 30


class RetrievalConfig(BaseModel):
    channel_top_k: int = 10
    rrf_k: int = 60
    default_top_k: int = 5
    contextual_enrichment: bool = True


class RagConfig(BaseModel):
    embedding: EmbeddingConfig = Field(default_factory=EmbeddingConfig)
    vector_store: VectorStoreConfig = Field(default_factory=VectorStoreConfig)
    chunking: ChunkingConfig = Field(default_factory=ChunkingConfig)
    retrieval: RetrievalConfig = Field(default_factory=RetrievalConfig)


# ---------- 加载辅助 ----------

def _expand_env(data: Any) -> Any:
    """递归展开 ${VAR} 占位(读环境变量,缺失→空串)"""
    if isinstance(data, dict):
        return {k: _expand_env(v) for k, v in data.items()}
    if isinstance(data, list):
        return [_expand_env(v) for v in data]
    if isinstance(data, str) and data.startswith("${") and data.endswith("}"):
        return os.environ.get(data[2:-1], "")
    return data


def _deep_merge(base: dict, override: dict) -> dict:
    """递归合并:override 覆盖 base;非 dict 值直接取 override 存在者"""
    out = dict(base)
    for k, v in override.items():
        if isinstance(v, dict) and isinstance(out.get(k), dict):
            out[k] = _deep_merge(out[k], v)
        else:
            out[k] = v
    return out


def _env_overrides() -> dict:
    """收集 GENESIS_ 前缀的条目为嵌套 dict__ 为嵌套分隔"""
    result: dict[str, Any] = {}
    for key, value in os.environ.items():
        if key.startswith(ENV_PREFIX):
            parts = key[len(ENV_PREFIX):].split("__")
            node = result
            for part in parts[:-1]:
                node = node.setdefault(part, {})
            node[parts[-1]] = value
    return result


def _load_yaml(config_dir: Path, name: str) -> dict:
    path = config_dir / f"{name}.yaml"
    if not path.exists():
        return {}
    with path.open("r", encoding="utf-8") as f:
        return yaml.safe_load(f) or {}


def _redact(data: dict) -> dict:
    out = {}
    for k, v in data.items():
        if any(kw in str(k).lower() for kw in SECRET_KEYWORDS):
            out[k] = "***"
        elif isinstance(v, dict):
            out[k] = _redact(v)
        else:
            out[k] = v
    return out


# ---------- 根 Settings ----------

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix=ENV_PREFIX, env_file=".env", extra="ignore")

    app: AppConfig = Field(default_factory=AppConfig)
    inference: InferenceConfig = Field(default_factory=InferenceConfig)
    rag: RagConfig = Field(default_factory=RagConfig)

    @classmethod
    def from_dir(cls, config_dir: Path | str) -> "Settings":
        config_dir = Path(config_dir)
        raw = {
            "app": _load_yaml(config_dir, "app"),
            "inference": _load_yaml(config_dir, "inference"),
            "rag": _load_yaml(config_dir, "rag"),
        }
        env = _env_overrides()
        merged = {k: _deep_merge(raw[k], env.get(k, {})) for k in raw}
        return cls(**{k: _expand_env(v) for k, v in merged.items()})

    def get_redacted(self) -> dict:
        return _redact(self.model_dump(mode="json"))

顺序说明:本文件类定义已是自顶向下的依赖顺序(ModelSpecInferenceModelsInferenceConfigRetrievalConfigRagConfig),不要重排

  • Step 5: 运行测试,确认通过

Run: python -m pytest -v Expected: PASS(全部含 test_config

  • Step 6: 修复可能出现的 NameError

python -m pytestNameError: name 'X' is not defined,说明类顺序不符;按 Step 4 最后的顺序提示调整(前置类型先行),重跑直至全绿。

  • Step 7: Commit
git add src/genesis/config.py tests/test_config.py tests/fixtures
git commit -m "feat: 配置加载 config(三 yaml + env 优先级 + 脱敏)"

Self-Review 结论(写计划时已执行)

  • Spec 覆盖:设计文档 §2(结构)→ Task1;§3(数据模型)→ Task2;§4config)→ Task3;§5(测试策略)→ 内嵌于各 Task;§6(完成门槛)→ 最终 pytest 全绿即满足。
  • 占位符扫描:无 TBD/TODO;每个 Step 有完整代码与命令。
  • 类型一致性Settings.from_dir / get_redacted / 各模型名在 Task3 内统一;Task2 导出类型与 design 一致;Task3 测试中 s.app.task_queue 为 dict(与 yaml 结构一致)、s.app.session 为 dict、s.rag.vector_store.qdrant.api_key 为 str。