← トップページ(目次)に戻る

8. ディレクトリ構造と開発要件 (ADR)

本システムのUIとロジックを完全に疎結合化し、各モジュールの関心事を分離するための「ソースコード構成」と、実行時に生成される「データ構成」を定義します。

8.1. ソースコードのディレクトリ構成 (src/)

アプリケーション本体の設計構造です。UI層、ビジネスロジック層(Core)、外部エンジン層を明確に分離しています。

ai-macro-system/
├── src/
│   ├── main.py                  // アプリケーションのエントリポイント
│   ├── ui/                      // 1画面構造・ウィジェットを管理するUI層
│   │   ├── views/               // MainWindow, RecordDialogなどのビュークラス
│   │   ├── viewmodels/          // UIの状態とビジネスロジックの橋渡し
│   │   └── resources/           // .uiファイルや.cssファイルを格納
│   ├── core/                    // ビジネスロジック層
│   │   ├── recorder/            // 操作記録(OS Hook)および入力操作データの取得
│   │   ├── generator/           // 統合データからのマクロコードAI生成
│   │   ├── executor/            // 動的待機・即時ポーリングによる実行エンジン
│   │   └── healer/              // 4段階自己修復(Image Diff検証・動的再生成)エンジン
│   ├── engines/                 // 外部認識エンジン層 (APIクライアント&サーバー管理)
│   │   ├── yolo/                // UI要素認識エンジン通信・ホスティング部
│   │   ├── ocr/                 // テキスト認識エンジン通信・ホスティング部
│   │   └── manager.py           // ローカルAIサーバーのライフサイクル管理
│   ├── models/                  // workflow_IDやデータのスキーマ構造定義
│   └── utils/                   // 環境変数管理、ロガーなどの共通ユーティリティ
├── tests/                       // 振る舞い・エラーテスト群
├── config.json                  // ユーザーが設定画面から変更した接続情報を永続化するファイル
├── .env.example                 // 環境変数のテンプレート
├── requirements.txt             // 依存パッケージ
└── README.md                    // 実例込みのドキュメント

8.2. 実行時データと生成マクロのディレクトリ構成 (macros/)

ユーザーがマクロを記録・生成する際に動的に作られるデータ領域です。特に一時データ(temp)のライフサイクル管理を厳格に行います。

ai-macro-system/
└── macros/
    └── wf_12345/                 // 記録時に発行される一意なマクロIDごとのルートディレクトリ
        ├── workflow.json         // 実行用マクロシナリオ (Executorが読み込む)
        ├── integrated.json       // コンテキスト統合データ (Healerが修復時に参照)
        ├── images/               // 永続化される画像リソース
        │   ├── evt_001_pre.png   // 操作前スクショ
        │   └── evt_001_crop.png  // 対象UIクロップ画像
        │
        └── temp/                 // ⚠️ 一時データ領域(生成完了後に自動破棄)
            ├── input_logs.json   // OS Hookからの入力生ログ
            ├── evt_001_yolo.json // YOLOエンジンの解析生データ
            └── evt_001_ocr.json  // OCRエンジンの解析生データ

📁 マクロ関連ディレクトリとファイルの役割・ライフサイクル

8.3. ローカルAIサーバーの起動・管理設計 (ADR)

YOLOやLLMなどのAIモデルは、プロセスの立ち上げからVRAMへのモデル展開(重みのロード)までに数十秒の時間を要します。UXを維持しタイムアウトを防ぐため、マクロ生成時ではなく「デスクトップアプリ起動時」にバックグラウンドで事前起動(ウォームアップ)させておく設計を採用します。

🖥️ 独立プロセスによるサーバー管理 (LocalServerManager)

UIスレッド(PySide6)のフリーズを完全に防止するため、Pythonの subprocess モジュールを利用して完全に独立したプロセスとして推論サーバーを立ち上げます。アプリ終了時には atexit を用いて確実にプロセスをクリーンアップします。config.jsonを読み込み、LLMとCV(YOLO/OCR)それぞれのホスト指定がローカルである場合のみ、該当するサーバープロセスを起動します。

# engines/manager.py
# @role: Manages the lifecycle of local AI API servers as independent background processes.

import subprocess
import os
import atexit
import json
from typing import List

class LocalServerManager:
    def __init__(self) -> None:
        self._processes: List[subprocess.Popen] = []

    def start_servers(self) -> None:
        # Load custom connection settings to determine if local servers are needed.
        config_path = 'config.json'
        ai_mode = 'local'
        llm_host = '127.0.0.1'
        llm_port = '8844'
        cv_host = '127.0.0.1'
        cv_port = '8843'

        if os.path.exists(config_path):
            try:
                with open(config_path, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    ai_mode = config.get('ai_mode', 'local')
                    llm_host = config.get('llm_host', '127.0.0.1')
                    # Fallback to default port if explicitly empty
                    llm_port = config.get('llm_port', '8844') or '8844'
                    cv_host = config.get('cv_host', '127.0.0.1')
                    cv_port = config.get('cv_port', '8843') or '8843'
            except Exception:
                pass

        env = os.environ.copy()

        # Start LLM server if local mode is selected and host points to local machine
        if ai_mode == 'local' and llm_host in ['127.0.0.1', 'localhost']:
            llm_cmd = ['python', '-m', 'uvicorn', 'engines.llm.server:app', '--port', llm_port]
            llm_proc = subprocess.Popen(llm_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            self._processes.append(llm_proc)

        # Start CV server independently if host points to local machine
        if cv_host in ['127.0.0.1', 'localhost']:
            yolo_cmd = ['python', '-m', 'uvicorn', 'engines.yolo.server:app', '--port', cv_port]
            yolo_proc = subprocess.Popen(yolo_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            self._processes.append(yolo_proc)

        atexit.register(self.stop_servers)

    def stop_servers(self) -> None:
        for p in self._processes:
            if p.poll() is None:
                p.terminate()
        self._processes.clear()

8.4. ネットワーク設計とアプリケーション開発要件

🔌 ポート設計と役割分担

🌍 柔軟なエンドポイント設計 (設定画面駆動)

AIの実体をどこで動かすかは、ホーム画面の設定エリアから変更されたIPアドレスやドメイン名(config.jsonに永続化)を基に、ソースコードを書き換えることなく透過的に切り替えられる設計とします。

【セキュリティ・堅牢性・設計要件】