コンテンツにスキップ

プロジェクトシステムの利用 (Working with the Project System)

プロジェクトシステム (Project System) は、すべてのワークフローにおけるファイルの整理、命名、保存を一元管理する Griptape Nodes のファイル管理フレームワークです。ハードコードされたファイルパスを排除し、一貫性があり設定可能なファイル操作アプローチを提供します。このページでは、ノード開発者側の観点から、ノードがプロジェクトシステムを介してファイルを保存する方法について説明します。

主要な概念 (Concepts)

プロジェクトシステムの基盤となる概念 — ワークスペース、プロジェクトテンプレート、シチュエーション、マクロ、ディレクトリ、環境変数 — は、プロジェクトシステムガイド で詳しく解説されています:

ノード作成者向けに要約すると:シチュエーション (Situation) はファイル保存シナリオ(例: save_node_output)に名前を付け、その マクロ (Macro) テンプレート(例: {outputs}/{node_name?:_}{file_name_base}{_index?:03}.{file_extension})が具体的なファイルパスを生成します。ユーザーはノードのコードを変更することなく、これらすべてをカスタマイズできます。

ノード内でのプロジェクトシステムの利用パターン

ノード内でプロジェクトファイルを操作する主なパターンは 2 つあります:

ノードが、ユーザーがカスタマイズできる出力ファイルパラメータを持つ場合は、ProjectFileParameter を使用します。

from griptape_nodes.exe_types.param_components.project_file_parameter import ProjectFileParameter
from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
from griptape.artifacts.video_url_artifact import VideoUrlArtifact


class MyVideoNode(ControlNode):
    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

        # 通常の出力パラメータを追加
        self.add_parameter(
            Parameter(
                name="output_video",
                output_type="VideoUrlArtifact",
                tooltip="生成された動画",
                allowed_modes={ParameterMode.OUTPUT},
            )
        )

        # 出力ファイル設定用のプロジェクトファイルパラメータを追加
        # `situation` はこのノードがどのシチュエーションで保存するかを宣言します。
        # デフォルトは "save_node_output" ですが、明示的に指定して選択を可視化します。
        self._output_video_file = ProjectFileParameter(
            node=self,
            name="output_video_file",
            default_filename="output_video.mp4",
            situation="save_node_output",
        )
        self._output_video_file.add_parameter()

    def process(self) -> None:
        # ... video_bytes の生成処理 ...

        # build_file() を使用して ProjectFileDestination を取得
        dest = self._output_video_file.build_file()
        saved = dest.write_bytes(video_bytes)

        # 保存先の場所を用いて出力パラメータを設定
        self.parameter_output_values["output_video"] = VideoUrlArtifact(saved.location)

重要なポイント:

  • ProjectFileParameter はユーザーが設定可能な UI パラメータを作成します
  • build_file() を呼び出して ProjectFileDestination インスタンスを取得します
  • write_bytes() を使用してファイルを保存します
  • saved.location 経由で保存されたファイルの URL/パスにアクセスします

シチュエーションの決定方法:

situation 引数は、ノードがそのシチュエーションを宣言する唯一の場所です。これはコンストラクタ引数であり、ノード上のパラメータではありません。ユーザーがノードの前面でシチュエーションフィールドを見たり編集したりすることはありません。デフォルトは save_node_output であるため省略可能ですが、コードを読む人がどのシチュエーションが使用されているかを把握できるように明示的に渡してください。

これによって作成される UI パラメータ(慣例として output_file という名前)は、パスではなくファイル名を保持します。build_file() はそのファイル名を file_name_base と file_extension に分割し、シチュエーションのマクロを介して解決するため、パラメータの値は最終パスの一部にすぎません。save_node_output ノードでユーザーが render.png と入力すると、outputs/MyNode_render.png が生成されます。

シチュエーション名はパラメータの値自体には表示されませんが、add_parameter() が生成するツールチップ(Output filename (uses 'save_node_output' situation template))に表示されるため、ユーザーはここから確認できます。

ユーザーは、パラメータの歯車アイコンボタンをクリックすることで、ノードごとにシチュエーションをオーバーライドできます。これにより、宣言されたシチュエーションがあらかじめ入力された FileOutputSettings ノードが生成されます。そのノードが接続されると、build_file() はそのノードの FileDestination を返し、元の situation 引数はバイパスされます。ノードの操作内容を正直に表すシチュエーションを選択し、ユーザーが必要に応じて変更できるように設計してください。

❌ よくある間違い: write_bytes() の戻り値を受け取らない

# 誤り - このように書いてはいけません:
dest = self._output_video_file.build_file()
dest.write_bytes(video_bytes)  # ❌ 戻り値がキャプチャされていない
artifact = VideoUrlArtifact(dest.location)  # 保存されたファイルではなく dest を使用している

# これは次のエラーで失敗します: "Failed because missing required variables: file_extension, file_name_base"

失敗する理由: {file_extension} や {file_name_base} などのマクロ変数は、write_bytes() がファイルを保存して保存済みファイルオブジェクトを返す際に解決されるものであり、build_file() の時点では解決されていません。書き込み前に dest.location を参照すると、マクロ解決エラーが発生します。

# 正しい実装:
dest = self._output_video_file.build_file()
saved = dest.write_bytes(video_bytes)  # ✅ 戻り値をキャプチャ
artifact = VideoUrlArtifact(saved.location)  # 保存済みファイルの解決された location を使用

saved オブジェクトには、すべてのマクロが展開・解決された完全なファイルパスが含まれています。

パターン 2: ProjectFileDestination の直接利用(ユーティリティ関数向け)

ユーティリティ関数の内部や、ユーザーによる設定が不要な場合は、ProjectFileDestination.from_situation() を直接使用します。

from griptape_nodes.files.project_file import ProjectFileDestination
from griptape.artifacts.video_url_artifact import VideoUrlArtifact


def frames_to_video_artifact(frames: list, fps: int = 30, video_format: str = "mp4") -> VideoUrlArtifact:
    """フレームのリストを VideoUrlArtifact に変換します。"""
    # ... フレームを video_bytes に処理 ...

    # プロジェクトファイルシステムを使用して保存
    dest = ProjectFileDestination.from_situation(filename=f"video.{video_format}", situation="save_node_output")
    saved = dest.write_bytes(video_bytes)

    return VideoUrlArtifact(saved.location)

重要なポイント:

  • 名前付きシチュエーションを指定して保存先を作成するには from_situation() を使用します
  • filename パラメータはベースとなるファイル名です(シチュエーションのマクロによって変換されます)
  • シチュエーション(例: "save_node_output")によって最終的なパスと衝突動作が決定されます

StaticFilesManager からの移行

旧パターン(非推奨)

from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
import uuid


def old_save_video(video_bytes: bytes) -> VideoUrlArtifact:
    filename = f"{uuid.uuid4()}.mp4"
    url = GriptapeNodes.StaticFilesManager().save_static_file(video_bytes, filename)
    return VideoUrlArtifact(url)

新パターン

from griptape_nodes.files.project_file import ProjectFileDestination


def new_save_video(video_bytes: bytes) -> VideoUrlArtifact:
    dest = ProjectFileDestination.from_situation(filename="video.mp4", situation="save_node_output")
    saved = dest.write_bytes(video_bytes)
    return VideoUrlArtifact(saved.location)

移行のメリット:

  • UUID を手動生成する必要がなくなります
  • すべてのノードで一貫したファイル整理が可能になります
  • プロジェクトテンプレートを介してユーザーがファイルパスを設定可能になります
  • ファイルの追跡と管理が向上します
  • ファイル名の重複(衝突)が自動的に処理されます

一般的なシチュエーションと使い分け

  • save_node_output: ノードによって生成されたファイル(画像、動画、音声など)を保存する主要なシチュエーション
  • copy_external_file: 外部ソースからファイルをインポート/コピーする場合
  • download_url: URL からファイルをダウンロードして保存する場合
  • save_preview: サムネイルやプレビュー画像を生成して保存する場合
  • save_static_file: 実行間で変更されない静的アセット用

デフォルトのシチュエーション、マクロ、衝突ポリシーの全一覧については、シチュエーション (Situations) を参照してください。

ユーザーは、ノードのコードを変更することなく、これら(パス、マクロ、衝突ポリシー)を 2 つの方法でオーバーライドできます:

  • プロジェクト全体: griptape-nodes-project.yml でシチュエーションを再定義すると、そのシチュエーションを使用するすべてのノードに反映されます。カスタマイズガイド を参照してください。
  • ノード単位: ProjectFileParameter の歯車ボタンから FileOutputSettings ノードを接続すると、そのノード単体のシチュエーション、マクロ、衝突ポリシーをオーバーライドできます。

ベストプラクティス (Best Practices)

  1. ファイルの保存には常にプロジェクトシステムを使用する — ハードコードされたパスは絶対に使用しないでください。
  2. 適切なパターンを選択する: ユーザーが設定可能な出力には ProjectFileParameter を、ユーティリティ関数には ProjectFileDestination を使用します。
  3. 意味に合ったシチュエーションを使用する: 処理内容を最も的確に表すシチュエーションを選択してください。
  4. 命名はマクロに任せる: UUID やタイムスタンプを自前で生成せず、シチュエーションのマクロと衝突ポリシーに委ねてください。
  5. 一時ファイルを適切に処理する: 中間処理には Python の tempfile を使用し、最終結果のみをプロジェクトシステム経由で保存してください。
  6. 一時ファイルをクリーンアップする: プロジェクトシステムにコピーした後は、必ず一時ファイルを削除(クリーンアップ)してください。

完全な実装例: 動画処理ノード

import tempfile
from pathlib import Path
from typing import Any

from griptape.artifacts.video_url_artifact import VideoUrlArtifact
from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
from griptape_nodes.exe_types.node_types import ControlNode, AsyncResult
from griptape_nodes.exe_types.param_components.project_file_parameter import ProjectFileParameter
from griptape_nodes.files.file import File


class ProcessVideo(ControlNode):
    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

        self.add_parameter(
            Parameter(
                name="input_video",
                input_types=["VideoUrlArtifact"],
                type="VideoUrlArtifact",
                tooltip="処理対象の入力動画",
            )
        )

        self.add_parameter(
            Parameter(
                name="output_video",
                output_type="VideoUrlArtifact",
                tooltip="処理された動画",
                allowed_modes={ParameterMode.OUTPUT},
            )
        )

        # 出力用のプロジェクトファイルパラメータを追加
        self._output_video_file = ProjectFileParameter(
            node=self,
            name="output_video_file",
            default_filename="processed_video.mp4",
            situation="save_node_output",
        )
        self._output_video_file.add_parameter()

    def process(self) -> AsyncResult:
        yield lambda: self._process()

    def _process(self) -> None:
        # 入力動画を取得
        input_artifact = self.get_parameter_value("input_video")
        input_bytes = File(input_artifact.value).read_bytes()

        # 一時ディレクトリ/ファイルで処理を実行
        with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_file:
            temp_path = Path(temp_file.name)

        try:
            # 入力を一時ファイルに書き出し
            temp_path.write_bytes(input_bytes)

            # ... temp_path に対して実際の動画処理を実行 ...

            # 処理結果を読み込み
            output_bytes = temp_path.read_bytes()

            # プロジェクトシステムを使用して保存
            dest = self._output_video_file.build_file()
            saved = dest.write_bytes(output_bytes)

            # 出力パラメータに設定
            self.parameter_output_values["output_video"] = VideoUrlArtifact(saved.location)

        finally:
            # 一時ファイルのクリーンアップ
            if temp_path.exists():
                temp_path.unlink()