LayerX エンジニアブログ

LayerX の エンジニアブログです。

LLMが苦手な麻雀点数計算問題生成タスクの精度を33%から90%に上げたMulti Agentの力

こんにちは、Hiromu Nakamura (pon) です。
LayerXの機械学習チームでMLOpsをやっています。LayerXではAI エージェント事業を進めており、その一環として身近なタスクで日々AIエージェントを触りまくっています。今回はLLMが苦手な麻雀点数計算問題生成タスクの精度を33%から90%に上げた話をします。

この話から次のことが学べます。

  • 「無数の選択肢の組み合わせを考えるタスクだが、正解は検証できる」という特性を持つタスクの精度を上げるノウハウ。
  • 実際のMulti Agentの実装方法

目次

成果物

イメージを掴んでもらうために今回の成果物を簡単にスクリーンショットでお見せします。

できたやつ

3飜40符になる問題が生成できています。

簡単にやっているように見えますが、裏では数多くのSub Agentが協調して動いています。

導入

LLMは麻雀点数計算が苦手

過去に麻雀点数申告練習アプリケーションの実装をしました。実際にフリーで点数申告をする練習として、音声で回答します。

www.m3tech.blog

この際に試したChatGPTによる麻雀点数問題生成が全然上手くいきませんでした。 最近はGeminiやClaude、DeepSeekなど、強力なLLMが次々と登場しています。
僕はワクワクして「3飜50符が答えになる問題を作って」という指示を出しますが、、、

実際にClaude Sonnet 4登場時に試したプロンプト

僕のよくやるオンライン麻雀ゲームのルールでは、これは僕の指定した点数の10倍の点数です。実は麻雀の点数計算はルールが超複雑であり、LLMがそのルールを把握した上で問題生成をしなくてはいけないので難易度がかなり高いです。

麻雀をやったことない人向けに説明すると点数計算の難しさはこんな感じです。(麻雀Station 110符の手牌とはどんな形か?具体例や図でわかりやすく解説」より引用)

2飜110符の例

そもそも計算が苦手なLLMです。こんな複雑な点数計算問題を一発で作らせるのは非常に困難です。

書籍「現場で活用するためのAIエージェント実践入門」でも複雑なルールを全て満たすように推論を行うのは難しいことが言及されています。

GPT-4oは膨大なルールから必要なルールを選ぶ力はありますが、関係のあるルール全てを満たすように推論するのは難しいことが研究で示されています。

まさに麻雀点数計算問題生成は上のような問題です。

実験1: 麻雀のルール、考え方を叩き込む

まずはコストの低い方法として、Single Agentでの精度向上を考えます。最初の実験としてZero-Shotと麻雀のルールをプロンプトに与え、Chain-of-Thoughtで点数計算問題生成の考え方を与えたものを比較する実験を行いました。

実験設定

比較手法

  • Zero-Shot
  • Chain-of-thought + ルールをプロンプトに埋め込み

Chain-of-Thoughtに関しては有名なのでここでは深掘りませんが、詳しく知りたい方は次の書籍をご覧ください。

精度向上に大きく寄与するFew-ShotやChain-of-Thoughtですが、デメリットとして、LLMが出す回答のバリエーションが渡した例に寄ってしまうという弊害があります。今回はこの弊害については考慮せず、Future Workとして取り組みたいと考えています。

データセット

  • 私がPythonスクリプトで作成したランダム指示20個
    • 全て「m翻n符が答えになる麻雀点数計算問題を出してください」という指示文。これをプロンプトテンプレートの最後に埋め込みます。

評価方法

  • 評価は生成した問題が実際にユーザーが指定したものとマッチしたものを正解とします。
  • 評価基盤
    • LangChainで用意しました。
    • 麻雀点数計算チェックはPythonの麻雀点数計算モジュールであるmahjougを使います。

LLMは麻雀点数計算チェックの精度が低いため、ここはソフトウェアの世界の力を借りる必要があります。mahjougを使うとこのように麻雀点数計算ができるのでこれをtoolとして提供します。

# アガリ形(man=マンズ, pin=ピンズ, sou=ソーズ, honors=字牌)
tiles = TilesConverter.string_to_136_array(man='234555', pin='555', sou='22555')

# アガリ牌(ソーズの5)
win_tile = TilesConverter.string_to_136_array(sou='5')[0]

# 鳴き(なし)
melds = None

# ドラ(なし)
dora_indicators = None

# オプション(なし)
config = None

# 計算
result = calculator.estimate_hand_value(tiles, win_tile, melds, dora_indicators, config)

アーキテクチャはこのようになります。

LLM麻雀点数問題生成タスク評価基盤

ここで重要なのは、mahjougで点数計算を行うため、LLMは点数計算関数が要求するデータスキーマに合うように構造化出力を行う必要があります。今回はLLMにはJSON出力を要求しています。

評価指標

  • 正解率(Accuracy)
    • ユーザーの指示に従った答えを持つ問題であった割合。点数計算は出力されたJSONとmahjougで検証。
  • 計算可能率(Calculatable Rate)
    • 点数計算が可能な出力であった割合
    • この指標は点数計算問題よりも手前の中間指標として採用します。Calculatableでないもの(要求するスキーマに合わないJSON/そもそもJSON出力されていないなど)は麻雀点数計算問題生成能力とは別の構造化出力の能力であるため、このように正解率(Accuracy)とは別の指標で評価します。

結果

  • Geminiに麻雀のセンスを感じる結果に。それでもZero-shotの33%がChain-of-Thoughtで45%に。ただこれでは満足できない。

定性確認による考察

  • 麻雀点数計算ルール自体が超複雑であり、ルールや考え方を渡しても一発で正しい問題を出させるのは難しい。
  • 評価のために要求するJSON自体が複雑で、点数計算ができる状態にJSONを出力するのが難しい。
    • 手牌構成ルール(ex: カンをしているなら手牌のリスト長が増える)
    • 点数計算の必須ルール(ex: 鳴いているならリーチはTrueにできない)
    • 数多くのオプション(ex: 嶺上開花)
  • 生成/検証/修正/出力整形の4つを1つのLLMに任せるとやるべきことが多くなり言うことを聞いてくれない。

精度を上げる方針

実験1でSingle Agent構成では難しいそうであることが確認できました。そのため、一旦立ち止まって精度を上げる方針を考察していきます。

改めて考えるタスクの特性

今回のタスクは抽象化して考えると「パターンは無数にあるが、それが正しいかは検証できる」という特性を持ちます。麻雀点数計算問題生成においては、無数にある牌の組み合わせが存在するが、それが正しいかどうかは点数計算関数で検証できます。

実はこのような特性を持つタスクは多く、例えば次のようなものがあります。

  • 例1: コーディング-> 書き方はいくらでもあるが、結果はテストで検証できる。
  • 例2: 出張パターン別、予算など複雑な条件を考慮したホテル+移動方法提案 -> 最終的にそれらが条件に合っているかは検証可能。

「パターンは無数にあるが、それが正しいかは検証できる」例

このように考えると、今回の精度向上にチャレンジすることは「パターンは無数にあるが、それが正しいかは検証できる」という特性を持つタスクを解くうえでも重要であることがわかります。

書籍「LLMのプロンプトエンジニアリング」でもこのようなタスクについて非常にわかりやすく言及しています。

解決策を検証する方が、解決策を考え出すよりもはるかに簡単なタスクがあります。たとえば、リメリック(滑稽五行詩)をその場で作り出すのは難しいものですが、ある詩がリメリックの基準を満たしているかどうかを確認するのは簡単です。

特性を考慮した精度向上アイデア

「パターンは無数にあるが、それが正しいかは検証できる」という特性があるため、次のことが可能になります。

  • 指示に合う問題を生成できるまで検証/修正するようにする
    • 「パターンは無数にあるが、それが正しいかは検証できる」という特性のため有効
    • 出力形式の検証/修正にも有効

生成/検証/修正を繰り返す。検証が通ったら終わり。
このループを実現するためにMulti Agentで検証/修正が行うことを考えます。これによって上で挙げた「生成/検証/修正/出力整形の4つを1つのLLMに任せるとやるべきことが多くなり言うことを聞いてくれない」という課題も同時に解決できます。

一つのプロンプトで検証/修正をやるならReActという手法もあります。ブログでは紹介しませんが、ReActも実験し精度があまり向上しないことを確認しています。自分で過ちに気づく難しさは「現場で活用するためのAIエージェント実践入門」でも語られています。

また自分の過ちに外部のフィードバックなしで気づくことができないという実験結果も示されています。誤りを見つけられるかが実務上ポイントになるため、なるべく外部フィードバックを使いましょう。

実験2: Multi Agent

実験2では実験1の結果からGeminiに焦点を絞って、Multi Agent設計を評価します。今回私が評価比較のために選択したMulti Agent Patternから紹介していきます。

Supervisor Pattern

Supervisor AgentというSub Agentを管理するAgentを置きます。Supervisor Agentが次のSub Agentを管理し、協調を図ります。

  • Generator Agent
    • 麻雀点数計算問題生成を責務とするAgent。RuleやChain-of-Thoughtを引き続きプロンプトに持つ。
  • Validation Agent
    • 麻雀点数計算問題がユーザーの指示に沿っているかを確認するAgent。麻雀点数計算関数をtoolとして渡します。
  • Output Formatter Agent
    • 要求する出力形式に構造化する責務を持つAgent。ここでは麻雀点数計算関数をJSONから呼び出してエラーがないか確認するtoolを渡します。

Supervisor Patternで構築したMulti Agentアーキテクチャ

Iterative Refinement Pattern

上のパターンではGenerator Agentに問題生成と修正2つの責務が与えられています。そこでさらに責務を分け、Supervisorが検証を回避してしまう問題を回避するために、LoopAgentでAgentic workflow的にMulti Agentを組みます。

上で挙げたSub Agentに加え次のSub Agentを追加します。

  • Refinement Agent
    • 現在の麻雀点数計算問題候補と、Validation Agentからの指摘から問題を修正する責務を持ちます。
  • Output Refinement Agent
    • 現在のアウトプット候補と、Output Validation Agentからの指摘からアウトプットを修正する責務を持ちます。

さらにLoopAgentとして、RefinementとValidationを必ずループさせます。

Iterative Refinement Patternのアーキテクチャ

実装方法

上で挙げたようなMulti Agentを組む際に筆者が選択したSDKがGoogle製のGoogle Agent Developer Kit(ADK)です。

google.github.io

Multi Agentを組み立てられるだけでなく、State管理、Tool連携、ガードレールなど基本的な要素が全て揃っています。

例えばIterative Refinement PatternをADKで実装するときは次のようになります。

## Sub Agent実装は省略

candidate_loop_agent = LoopAgent(
    name="candidate_loop_agent",
    description="This agent is responsible for managing the collaboration between the refining agent and the validation agent.",
    max_iterations=5,
    sub_agents=[
        validation_agent,
        refining_agent,
    ],
)

output_candidate_loop_agent = LoopAgent(
    name="output_candidate_loop_agent",
    description="This agent is responsible for managing the collaboration between the output json formatter agent and the output json validation agent.",
    max_iterations=5,
    sub_agents=[output_json_validation_agent, output_json_refining_agent],
)

mahjong_sequential_agent = SequentialAgent(
    name="mahjong_sequential_agent",
    description="This agent is responsible for managing the collaboration between the candidate loop agent and the output candidate loop agent.",
    sub_agents=[
        mahjong_score_question_generator_agent,
        candidate_loop_agent,
        output_json_formatter_agent,
        output_candidate_loop_agent,
    ],
)

上のコードでは2つのLoopAgentを定義し、それぞれ問題の検証/修正、アウトプットの検証/修正を行います。最初の叩き台となる問題生成、出力生成は別のSub Agentとして実装し、最終的にSequentialAgentでAgentが順番に実行されるようにしています。LoopAgentではmax_iterationsも指定でき、何回までループを許すかを指定できます。これによりLLMが迷子になっても途中で止めてフォールバックや、人間の呼び出しなどが挟めます。今回の実装ではmax_iterationsに達した場合は、現在の状態をそのまま上に流す実装にしています。

さらにADKであれば実際の指示を試したり、stateやtraceを確認できるWEB画面が利用できます。adk webというコマンドですぐに使えます。LLMOpsツールを別途組み込む必要がなく、そのまま使えるのでめちゃくちゃ便利でした。

実際にプロンプトを流してStateを確認

どのAgentがいつ呼ばれてどのくらいの時間がかかっているかもすぐに確認できます。便利すぎる。

トレースの確認

さらにadkコマンドをMulti Agentサーバーを起動したり、そのままCloud RunなどのGoogle Cloudサービスにデプロイできます。すごい。

結果

結果は次のようになりました。

Single AgentとMulti Agentの各種手法比較

  • Iterative Refinement Patternで精度をZero-shotの33%から90%まで上げれた!
    • ただ実行時間が悪化。1問生成に平均100sかかる。
    • ループすることに加え、必ず検証が呼ばれるためレスポンスタイムは当然悪化。
    • ちなみに、点数計算問題生成部分の平均ループ回数は2.65回。出力整形の平均ループ回数は1.5回
  • Iterative Refinement Patternが勝っている理由
    • SupervisorパターンではValidatorを回避して出力を返そうとすることが多々あった。
    • 合っているかどうかが明確に確認できるのであれば、必ずループを繰り返すIterative Refinement Patternに軍杯が上がる。

この結果から「パターンが無数に存在するが、それが検証できる」かつ、長い実行時間がある程度許容される場合、LLM同士で検証/修正を回す機構を採用すると良さそうです。今回のように大量の点数問題生成をオフラインで実行したいという目的ではパフォーマンスは大きな問題にならないため、今回のユースケースにおいては満足できる結果となりました。

まだまだプロンプトエンジニアリングは必要

精度は上がりましたが、オンラインで提供する際にはパフォーマンスが大きな問題です。一番のボトルネックはループが実行されるためです。そのため、ループ回数を限りなく1にするためにSub Agentの精度は相変わらず高めていかなければなりません。そこでプロンプトエンジニアリングの問題に戻ってきます。ルールの渡し方を変えたり、推論がしやすい例を渡したりなどの工夫はマルチエージェントであっても必要な工数です。

おまけ:運用時のAgent取り外し

今回のJSON出力は主に評価のためだけのものです。そのためチャットUIでの本番運用の場合は、JSON整形部分を全て外して提供します。このように評価と実運用時のAgentつけ外しもMulti Agentなら簡単にできます。

運用時は評価用アウトプット整形Agentは外すことができる。

これにより評価時に回っていた検証やループを外すことができ、オンライン提供の際も実行時間を短くできます。

Future Work

ここからさらに精度を上げるためには次のようなことを試したいと考えています。

  • Context Engineering
    • 点数計算に必要な情報だけを渡すContext Pruningなど
  • 点数計算問題の多様性の担保
  • 「暗カンを含む問題」など条件を指定した指示への対応
  • Reasoning Modelを使った精度比較(論理的思考がどれくらい麻雀点数計算問題生成に寄与するか?)

私が考えている「ユーザーの苦手な問題を推薦してくれるアプリケーション」ではContextual-Banditなどでユーザーの苦手なパターンを学習して、LLMが指定条件を満たす問題を作れるようにしたいと考えています。そのため、条件を指定した指示への対応は今後取り組みたいタスクです。(Contextual-Banditなんか使わなくてもLLMにUser stateを渡す方法で行けるか?...)

まとめ

今回は麻雀点数計算問題という「パターンが無数に存在するが、それが検証できる」という特性を持つタスクをMulti Agentで精度を上げた話をしました。今回の実験のために色々Agent周りを調べましたが、AI界隈はまだまだ明確なベストプラクティスがなく、みんなで模索している段階です。これからも皆さんと議論や情報交換を行いながら、AI周りを楽しんでいけたらと思います。今回は麻雀の話でしたが、これらの技術を活用してプロダクトも改善していきます。

LayerXではAIで全ての経済活動をデジタル化する仲間を募集しています。LLMを使って終わりではない難しいチャレンジが溢れています。是非カジュアルにお話ししましょう。麻雀の話も!!

ここから僕につながります! jobs.layerx.co.jp