はじめに:論理テスト(Pytest)ALL GREENの先にある「ランタイムの罠」
Pythonをサーバー不要、維持費0円でブラウザ上にデプロイできる marimo × WASM (Pyodide) のスタックは、現代の個人開発における非常に強力な選択肢です。
データが外部に一切漏れないため、機密性の高い資産データを扱う金融シミュレーターなどとの相性は抜群に良いと感じています。
今回は第11弾プロジェクトとして、新NISAの「簿価復活枠」をFIFOで厳密管理し、過去の暴落相場(リーマンショック等)のデータをNumPyで適用するツール『NISA Simulator v4.0』の開発に挑戦しました。
計算エンジンのロジックは完璧に組み上がり、ローカルでのHeadless自動テスト(Pytest)も無事にすべて一発で通過(ALL GREEN)しました。
「これでいける!」と確信してブラウザ上でUIをロードしたのですが、なんと画面がクラッシュしてしまいました。
自動テストでは検知できなかった、marimoのランタイムバリデーションと、v0.20+から導入された強力な構文解析器(AST)の仕様に引っかかってしまったのです。
この記事では、AIに「なんとなく(Vibe)」でコードを書かせた結果ぶつかってしまった、marimo固有の特殊なスコープ規則やライフサイクルの罠と、その解決策について、泥臭い奮闘の記録を共有したいと思います。
1. 🛠️ ドロップダウン初期値の罠(ValueErrorとの遭遇)
Webアプリケーションにおいて、選択肢(ドロップダウン)の表示名と内部で扱う実体をマッピングするのは基本ですよね。
marimoでは mo.ui.dropdown に辞書型 {"表示ラベル": "内部データ実体"} を渡すことでこれを実現します。
今回、AI(Code Architect)は、初期選択状態を指定するために一般的なWebフレームワークの感覚で、引数 value に内部実体(例: "normal")を指定したコードを出力してくれました。
# 💣 クラッシュを引き起こしてしまったコード
inputs = mo.ui.dictionary({
"scenario": mo.ui.dropdown(
options={"通常運用 (年利 +5%)": "normal", "歴史的暴落": "stress"},
value="normal", # 👈 これを指定すると、ValueErrorでセルが停止します
label="市場リターン・シナリオ"
)
})
自動テストをパスしたはずのコードは、ブラウザにロードされた瞬間、ValueError: The option name 'normal' is not a valid option. という例外を出して止まってしまいました。
【原因と解決策】
marimoのバリデーションエンジンは、辞書型が渡された際、内部実体ではなく「辞書のキー(表示ラベル名)」を選択肢の正当性検証の基準として扱う仕様になっていました。解決策としては、初期値 value に実体ではなく「キーの文字列(ラベル名)」を指定します。選択された後に下流へ渡される .value は、正常に対応する実体("normal")へと内部で翻訳されるため、ロジック層を汚すことなく無事にバリデーションを通過できました。
2. 🛠️ 過剰な隠蔽とセル隔離の罠(連鎖するNameError)
marimoのDAG(有向非巡回グラフ)をクリーンに保つための大原則に、「セル内で完結する一時変数にはアンダースコア(_)を付与し、グローバルスコープへの漏洩を防ぐ」というルールがあります。
しかし、このルールを叩き込まれたAIは、少し過剰に守りに入ってしまったようです。他のセルから呼び出されるべき中核関数名やインポートオブジェクトの先頭にまで、盲目的に _ を付与してしまいました。
# ❌ AIが過剰に隠蔽してしまったコード(関数定義セル)
def _nisa_quota_revival_engine(current_book_value_total, ...):
# 内部ロジック
return { ... }
# グローバル空間にパブリックとして公開したいのに先頭に「_」を付けてしまっている
return _nisa_quota_revival_engine,
下流の計算セルは、この非公開にされたノードを呼び出そうとした瞬間に以下のエラーを出してしまいます。
# 💣 下流の計算セルで発生するエラー
# NameError: name '_nisa_quota_revival_engine' is not defined
_quota_res = _nisa_quota_revival_engine(...)
関数定義セルの最下部でどれだけ明示的にオブジェクトを return していたとしても、marimoの静的解析器は「先頭に _ がついたオブジェクトは、そのセル内部に完全隔離される」と解釈します。そのため、他セルへの参照が物理的に切断され、下流のセルが呼び出そうとした瞬間に NameError を起こしてアプリ全体が停止してしまいました。
さらに、 marimo ファイルの最上部にある定型文の import marimo に依存し、セル内部での import marimo as mo および return mo を怠ると、各セルの独立スコープの壁に阻まれ、下流の全セルで mo が未定義となりエラーになってしまいます。
【原因と解決策】
インターフェース(外部に流通させる関数やUIコンポーネント名)は、必ずアンダースコアなしのパブリックとしてエクスポートする必要があります。アンダースコアを使うのは、あくまで「関数内部」のローカル変数や、ループ変数などの使い捨てノードに限定します。この境界線を意識してコードを修正することで、各セルの連携は正常に動作するようになりました。
3. 🛠️ 早期Returnの罠(SyntaxErrorとUIが消える現象)
marimo スクリプトの実体は、単一の .py ファイル内に @app.cell デコレータでラップされたPython関数の並びです。AIはこの構造から「処理を途中でスキップさせたい」と考えたようで、セルの直下(トップレベル)に直接 return 文を記述したり、最終統合UIセルの最下部に return final_assembly_ui, を記述するという構文エラーを出力してしまいました。
# 💣 SyntaxError、あるいは画面が「真っ白」になるコード
if inputs.value is None:
return # 👈 SyntaxError: 'return' outside function を誘発
final_assembly_ui = mo.vstack([ ... ])
return final_assembly_ui, # 👈 画面のUIが1ピクセルも描画されなくなってしまいます
【原因と解決策】
marimoのセルは、記述上は「通常のPythonスクリプトのトップレベル」として書かなければなりません。そのため、単独で return を配置した瞬間にインタプリタから構文エラーを宣告されてしまいます。
さらに気をつけたいのが、最下部の最終統合セルでの明示的な return です。
ここでオブジェクトを関数のようにリターンしてしまうと、marimoはそれを「下流のセルへ引き渡すための静的な変数ノード」としてエクスポートするだけで、ブラウザへの視覚的なレンダリング(描画)をシステムレベルでスキップしてしまいます。
結果として、コンパイルエラーも出ずに「画面が完全に真っ白」になる現象が発生してしまいました。
これを解決するため、「単独のreturnを使わない」 ことと、 「暗黙の出力(Implicit Output)の徹底」 を行いました。早期終了を狙うのではなく、if/else 構文によって変数への代入を完全に条件分岐させ、セルの最終行にその変数をただ「置いて評価させる」という marimo 正規の出力方法に従います。
# ⭕ エラー時でも止まらないオブジェクト流通構造(関数の外なのでreturnは不使用)
if df_results is None:
# 不正入力時もプロセスを停止させず、警告UIオブジェクトを流通させる
chart_ui = mo.ui.callout(mo.md("⚠️ 正しい数値を入力してください"), kind="warn")
else:
# 正常系の描画
_chart = alt.Chart(df_results).mark_line().encode(x="経過年:Q", y="総資産:Q")
chart_ui = mo.ui.altair_chart(_chart)
# セルの最終行に配置し、暗黙の出力でレンダリングを強制させる
chart_ui
エラー時であっても mo.stop() で処理を切断せず、正常な None や警告UIオブジェクトとして下流に渡します。この形に移行することで、ユーザーが誤ったパラメータを入力してもクラッシュせず、入力パネルを残したまま警告を動的に表示できる、堅牢なUXを作ることができました。
🎯 まとめ:人間の役割は「境界線の設計と理解」
今回の開発で痛感したのは、AIエージェントに「雰囲気(Vibe)」だけでコードを書かせ、インターフェース設計まで委ねることの難しさです。AIの自律的なコーディング能力を最大限に活かすためには、人間側が「marimo固有の特殊なライフサイクル」をしっかり把握し、AIが脱線しないように適切な制約やルール(ハーネス)を設けてあげる必要があります。
WASMというサーバーレス環境で、正確な計算処理と安定した画面制御を両立させるために、ツールの仕様を深く理解してうまく付き合っていくこと。それこそが、AIと一緒に開発を進める上で、私たち人間に求められる役割だと感じました。
おまけ
今まで開発したプロダクト一覧については、こちらからどうぞ