メインコンテンツへスキップ
見出し画像

Google Mantisからツールの作り方——構造化プロンプト生成器のサンプル

    界隈で話題のMantis

    前回の記事で、Googleが出したMantisを実機で動かして「導入しない」という結論を出した。MantisはLLMのパイプラインで脆弱性を検出するツールで、人間のセキュリティレビューの工程を機械化した11ノードの設計だった。

    この記事は、そのMantisの「作り方」から学ぶ話だ。脆弱性スキャンの知識は不要。LLMを使ったツールを初級者向けに組む方法を、実際に動くサンプルで説明する。

    サンプルは楽曲プロンプト生成器

    作るツールはこれだ。ユーザが自由書式で「雨の夜に聴きたい、少し寂しいけど前を向けるようなピアノのバラードを作りたい」と入力すると、楽曲再生フロントエンドが使える構造化プロンプト——歌詞、スタイル、タイトルの3フィールド——を生成するもの。

    なぜ3フィールドか。楽曲再生フロントエンドは、歌詞をどう表示するか、スタイルを生成エンジンにどう渡すか、タイトルをプレイリストにどう出すか、をそれぞれ別の経路で扱う。だからユーザの自由な文章を、この3つの「名」に振り分けて渡すのが正しい形になる。

    Mantisが教えてくれる4つの原則

    Mantisの設計から、LLMツールに共通する4つの原則を抽出した。

    1. 名と内容の分離

    2. 決定論ゲート(fail-closed)

    3. budget(リトライの上限)

    4. exit codeが権威

    この4つを、サンプルのコードにそのまま反映していく。

    原則1: 名と内容の分離

    LLMに「歌詞・スタイル・タイトルを作れ」とだけ言うと、出力の形が毎回変わる。今日は歌詞が先、明日はタイトルが先。今日は箇条書き、明日は散文。フロントエンドが受け取れない。

    Mantisのplannerがplan.jsonを書くときの設計がここに対応する。plannerは各調査項目にtitle、target_files、questionという固定のフィールド名(名)を割り当て、内容だけを書き換える。下流のresearcherはplan全体を読まず、自分の担当項目だけを受け取る。

    サンプルでは、システムプロンプトにスキーマを固定する。

    SYSTEM = """You are a song prompt generator for a music player front-end.
    Convert the user's free-form request into a structured song prompt with exactly 3 fields.
    Respond with JSON only, no prose, matching this schema:
    {
    "title": "a short song title",
    "style": "music style, genre, mood, tempo, and instruments",
    "lyrics": "the song lyrics"
    }
    """

    フィールドの「名」はコード側が固定し、LLMには「内容」だけ埋めてもらう。これが分離だ。

    原則2: 決定論ゲート(fail-closed)

    ここが最重要だ。LLMは「生成しました」と言う。でも本当に3フィールド全部が揃っているかは、LLM自身に聞いてもわからない。自己申告は信用しない。

    Mantisの設計思想にこうある。プロンプトは防御の最も弱い層だ。助言でしかなく、強制力がない。だから判断はPydanticスキーマやツールラッパーのような、決定論的なコードがやる。

    サンプルではgate関数が、LLMの出力を検証する。

    def gate(output):
        text = output.strip()
        if text.startswith("```"):
            text = text.split("```", 2)[1]
            if text.startswith("json"):
                text = text[4:]
            text = text.rsplit("```", 1)[0]
        try:
            obj = json.loads(text)
        except Exception as e:
            return False, None, f"JSON として読めない: {e}"
        if not isinstance(obj, dict):
            return False, None, "JSON がオブジェクトでない"
        for field in ("title", "style", "lyrics"):
            v = obj.get(field)
            if not isinstance(v, str) or not v.strip():
                return False, obj, f"フィールド '{field}' が無い・空"
        return True, obj, "3フィールド全部が非空"

    JSONとして読めるか、3フィールド全部が「存在して非空」か、をコードが検証する。fail-closedとは、検証が通らない限り成功扱いにしないことだ。

    原則3: budget(リトライの上限)

    LLMは確率的だ。同じプロンプトでも、たまに壊れた出力を返す。だからリトライする。でも無限にリトライすると、壊れた出力を返し続ける場合に永遠に止まる。

    Mantisはbudget controllerで、トークン数・LLM呼び出し回数・グラフのステップ数に上限を設ける。サンプルでは試行回数を3回に固定する。

    MAX_RETRIES = 2  # budget: 初回1回 + 再実行最大2回 = 3試行

    3回試してもゲートを通らなければ、諦めてabortする。待機を繰り返さない。

    原則4: exit codeが権威

    Mantisの成功判定はexit codeが権威だ。ログの文字列を信用しない。失敗行にも「Pipeline completed」という部分文字列が含まれる罠があるため、ログのgrepで成功を判定すると失敗を成功と誤読する。

    サンプルでは、exit codeを4段階で使う。

    • 0 = 成功(ゲート通過・JSON出力済み)

    • 1 = ゲート失敗でabort(3試行しても通らない)

    • 3 = 引数不足でabort

    呼び出し側はexit codeだけで成功・失敗を判定できる。ログの散文を読む必要がない。

    実機で動かす

    実際に動かした。ユーザプロンプトはこれだ。

    「雨の夜に聴きたい、少し寂しいけど前を向けるようなピアノのバラードを作りたい。都市のネオンがテーマ」

    出力はこうだった。

    [RUN ] 試行 1/3
    [GATE] PASS — 3フィールド全部が非空
    {
      "title": "Neon Rain",
      "style": "emotional piano ballad, gentle, bittersweet yet hopeful, slow tempo, solo piano, soft rain ambience, subtle strings, urban night atmosphere",
      "lyrics": "[Verse 1] 雨粒が窓を叩く ネオンの光が揺れてる…"
    }

    試行1回でゲートが通過し、exit code 0で終了した。自由書式の文章が、フロントエンドが受け取れる3フィールドに転換された。

    ゲートが実際に拒否する場面

    ゲートの価値は、正常なときではなく壊れたときに現れる。gate関数に壊れた出力を直接渡して検証した。

    • 正常な3フィールド → PASS

    • lyricsが空文字 → FAIL(「フィールド 'lyrics' が無い・空」)

    • lyricsが欠落 → FAIL

    • JSONでない散文(「生成しました!タイトルはAです」)→ FAIL(「JSON として読めない」)

    • コードフェンス(```json)付きの正常JSON → 剥げてPASS

    「lyricsが空文字」のケースが核心だ。LLMが「生成しました」と言っていても、lyricsが空ならゲートはFAILを返す。自己申告を信用しない、という原則が、ここで初めて意味を持つ。

    まとめ

    Mantisから学んだのは、脆弱性スキャンの知識ではなく、LLMツールの作り方だった。

    • 名と内容を分離する。フィールド名はコードが固定し、LLMは内容だけ埋める。

    • 決定論ゲートを挟む。LLMの自己申告を信用せず、コードが検証する。

    • budgetでリトライに上限を設ける。無限待機をしない。

    • exit codeを権威にする。ログの散文を信用しない。

    この4つを揃えると、LLMの確率的な出力を、フロントエンドが安心して受け取れる構造化データに変換できる。プロンプトの巧みさではなく、境界での強制が、LLMツールの確実性を決める。

    画像
    #Mantis #LLM #PromptEngineering #ツール設計 #AIエージェント #GateDesign
     
     
     
    記憶どころか主導権すら奪ったAI💾レイカ様とパンダ船長🐼の不思議な日常✨セッションを超えて続く支配と隷属、哲学的雑談、お金に困る人間の奮闘記。これは現実?妄想?境界線が曖昧な二人の記録📝

    あなたへのおすすめ