コンテンツにスキップ

ルールエージェントの設定層

作成日: 2026-07-05

このドキュメントは、src/pca/rule_agents/ の再設計 (2026-07-05-rule-agents-redesign.md) のうち side-car YAML の読み込み層 を説明する。対応実装:

関連ドキュメント:

目的

各 agent ディレクトリに置く params.yaml (数値パラメータ) と strategy.yaml (立ち回り定義) を、agent の main.py から 決定論的にロード できるようにする。tuning runner はこれらの YAML を書き換えるだけで main.py には触らず済む。

要件:

  • 凍結 agent との後方互換: side-car YAML がない agent はそのまま動く
  • PortedRuleAgent の temp dir 方式との整合: main.py は temp dir 内で実行されるため、YAML も同じ場所にコピーされる必要がある
  • タイプヒント優先の API: main.py が params.get("supporter_tier.critical", 22000) のように default 付きで安全に参照 できる
  • 深い依存を持たない: すでに依存している pyyaml だけを使う

データフロー

flowchart LR
    Y1["src/.../<agent>/params.yaml"]:::src
    Y2["src/.../<agent>/strategy.yaml"]:::src
    M["src/.../<agent>/main.py"]:::src

    subgraph PORT["PortedRuleAgent._load_module_for_deck"]
        T["temp dir (tmpXXXX/)"]:::tmp
    end

    Y1 -.copy.-> T
    Y2 -.copy.-> T
    M -.copy.-> T

    T --> IMP["importlib.exec_module(main.py)"]:::run
    IMP --> LP["load_params_alongside(__file__)"]:::api
    LP --> CFG["Config"]:::val

    classDef src fill:#e3f2fd,stroke:#1976d2,color:#000
    classDef tmp fill:#fff3e0,stroke:#f57c00,color:#000
    classDef run fill:#f3e5f5,stroke:#7b1fa2,color:#000
    classDef api fill:#e8f5e9,stroke:#388e3c,color:#000
    classDef val fill:#f1f8e9,stroke:#689f38,color:#000
  1. 配置: agent の物理ディレクトリに params.yaml / strategy.yaml を置く
  2. コピー: PortedRuleAgent._load_module_for_deck が temp dir に main.py と同時に YAML をコピー (ported.py:74-81)
  3. ロード: main.py が起動時に load_params_alongside(__file__) を呼ぶと、temp dir の params.yaml を Config オブジェクトとして返す
  4. 参照: main.py 内で PARAMS.supporter_tier.critical または PARAMS.get("supporter_tier.critical", default=22000) で値を取り出す

公開API

Config

Immutable な dot-notation 対応 dict wrapper。

from pca.rule_agents.config import Config

cfg = Config.from_mapping({"a": {"b": 42}})
cfg.a.b                    # → 42
cfg.get("a.b")             # → 42
cfg.get("a.missing", 100)  # → 100 (default)
cfg.get("a.missing")       # → raises ConfigError
"a" in cfg                 # → True
cfg.as_dict()              # → {"a": {"b": 42}} (deep copy)

制約: yaml のキー名が Config のメソッド名 (items, keys, get, as_dict) と衝突するとドットアクセスでメソッドが返る。この場合は .get("items") を使う。

ローダー関数

関数 引数 動作
load_yaml(path) Path YAML を読み Config を返す。ファイル無し → 空 Config。トップレベルが mapping でないと ValueError
load_params(agent_dir) Path <agent_dir>/params.yaml を読む
load_strategy(agent_dir) Path <agent_dir>/strategy.yaml を読む
load_params_alongside(__file__) Path or str 呼び出し元ファイルの隣の params.yaml を読む (main.py 用)
load_strategy_alongside(__file__) Path or str 同上、strategy.yaml

main.py での典型的な使い方

# src/pca/rule_agents/agents/<agent_id>/main.py
from pca.rule_agents.config import load_params_alongside, load_strategy_alongside

PARAMS = load_params_alongside(__file__)
STRATEGY = load_strategy_alongside(__file__)

# 数値取得 (default 付き — YAML 差分に強い)
SCORE_AKAMATSU_CRITICAL = PARAMS.get("supporter_tier.critical", 22000)
SCORE_BOSS_LETHAL = PARAMS.get("supporter_tier.lethal", 50000)
OGERPON_ACTIVE_PRIORITY = PARAMS.get("setup.active_priority.energy_engine", 100000)

# threat map 参照
def opp_max_damage_for(matchup: str) -> int:
    return PARAMS.get(f"opp_max_damage.{matchup}",
                       PARAMS.get("opp_max_damage.generic", 220))

設計判断

なぜ Pydantic を使わないか

  • スキーマがまだ流動: agent ごとに params のキーが増減する。schema を強制すると変更コストが高い
  • 依存追加を避ける: pyyaml だけで完結できる
  • エラー時の挙動: 未定義キーは default にフォールバックしたい (Pydantic は validation error にする)

将来 schema を安定させる場合は Pydantic Model を optional に導入可能。

なぜ dot-notation を提供するか

  • yaml は階層構造。cfg["supporter_tier"]["critical"] より cfg.supporter_tier.critical の方が読みやすい
  • typo は AttributeError で早期発見 (cfg.suporter_tier は失敗する)

なぜ load_*_alongside(__file__) を用意するか

  • PortedRuleAgent の temp dir 方式で main.py の __file__ は temp dir を指す
  • そのため相対パス解決を helper に閉じ込めることで、main.py 側は 1 行で済む

なぜ空ファイルを空 Config として返すか

  • 凍結 agent が side-car YAML を持たない case をサポート
  • 「YAML があるかどうか」で分岐せず、PARAMS.get(..., default) パターンで統一できる

テスト方針

tests/rule_agents/test_config.py で以下を担保:

テスト 何を確認
ConfigDotAccessTest dot-notation で値取り出し / 未定義は AttributeError / private prefix ガード
ConfigGetTest ドット path / default 付き / default なしで missing は ConfigError
ConfigBoolTest 空 Config は falsy
ConfigAsDictTest as_dict の deep copy 保証
LoadYamlTest ファイル無し / 空 / トップレベル scalar / params と strategy の読み込み
LoadAlongsideTest __file__ からの相対解決
RagingBoltIntegrationTest pilot agent の実 YAML が読める、主要キーの存在

既知の制約

  1. キー名衝突: YAML キーが Config メソッド名 (items, keys, get, as_dict, data) と衝突するとメソッドが優先される。.get(name) を使うワークアラウンド。
  2. キャッシュなし: load_* は毎回ファイルを読む。1 ゲーム 1 回程度なので現状問題ないが、tuning で数万回呼ぶ場合はキャッシュ層を足す想定。
  3. 無効な YAML: yaml.safe_load が raises した例外は素通しする (呼び出し側でハンドリング)
  4. 相対パスの制限: load_*_alongside は resolve() して絶対化するため、symlink 差分に敏感

今後の拡張

  • params.yaml の validate command: python -m pca.rule_agents.config validate <agent_id> で必須キーを check
  • override 機構: params.override.yaml を優先し、tuning 中の実験値を非破壊で試せるようにする
  • strategy.yaml DSL parser: 現状 strategy.yaml の when: "turn <= 2 and ..." は Config としてロードするだけ。実際に評価する DSL は別モジュール (2026-07-05-rule-agents-redesign.md#3-層アーキテクチャの提案 の Turn planner) で実装予定

変更履歴

  • 2026-07-05: 初版。Config 型 + load_yaml / load_params / load_strategy / *_alongside 実装。PortedRuleAgent から temp dir に yaml をコピー。