GEMINI LABEN
SCALE — Gemini アプリの月間利用者が8月11日に10億人を超えました。個人向けアシスタントとしての普及は一段落し、開発者側の関心はどう組み込むかへ移っていますASSISTANT — 9月4日から Android の Google アシスタントが Gemini に置き換わります。残り15日で、App Actions や音声ショートカットを実装したアプリは挙動確認が必要ですAGENTS — Gemini API の Managed Agents がパブリックプレビューになりました。Google 側がホストする実行環境で、状態を持つ自律エージェントを構築できますENTERPRISE — Gemini Enterprise で A2UI エージェントと A2A エージェントの登録・管理が一般提供になり、エージェント同士を接続する枠組みがプレビュー段階を抜けましたSUNSET — gemini-robotics-er-1.6-preview は8月31日に停止します。残り11日で、移行先は7月30日にパブリックプレビューとなった Gemini Robotics ER 2 ですPRICING — 8月13日に一般提供となった Gemini 3.7 Flash の導入価格は100万トークンあたり入力0.75ドル・出力3.75ドルで、2026年12月31日までですSCALE — Gemini アプリの月間利用者が8月11日に10億人を超えました。個人向けアシスタントとしての普及は一段落し、開発者側の関心はどう組み込むかへ移っていますASSISTANT — 9月4日から Android の Google アシスタントが Gemini に置き換わります。残り15日で、App Actions や音声ショートカットを実装したアプリは挙動確認が必要ですAGENTS — Gemini API の Managed Agents がパブリックプレビューになりました。Google 側がホストする実行環境で、状態を持つ自律エージェントを構築できますENTERPRISE — Gemini Enterprise で A2UI エージェントと A2A エージェントの登録・管理が一般提供になり、エージェント同士を接続する枠組みがプレビュー段階を抜けましたSUNSET — gemini-robotics-er-1.6-preview は8月31日に停止します。残り11日で、移行先は7月30日にパブリックプレビューとなった Gemini Robotics ER 2 ですPRICING — 8月13日に一般提供となった Gemini 3.7 Flash の導入価格は100万トークンあたり入力0.75ドル・出力3.75ドルで、2026年12月31日までです
記事一覧/開発ツール
開発ツール/2026-08-20初級

Gemini に書かせたレビュー返信を、送信前に止めるための最小の検査

ストアのレビュー返信は下書きより送信後が怖い作業です。守れない約束・連絡先の混入・文体崩れを送信前に機械で止める Python の検査を、実際に走らせた結果と誤検出の修正まで含めて置いておきます。

Gemini API215個人開発102アプリ運用2構造化出力14Python42

星ひとつのレビューに、深夜、返信を書きかけたことがあります。

「起動して設定を開くと必ず落ちる」。心当たりのある不具合でした。気づくと指が「次のアップデートで修正します」と打っていて、送信ボタンの手前でようやく止まりました。その時点で、次の版に間に合う保証はどこにもありませんでした。

個人開発でアプリを出していると、レビューへの返信も当然ひとりの作業になります。返信の文面そのものは、Gemini に下書きさせれば数秒で出てきます。速くなるほど、送ってしまった後の取り返しがつかなくなる作業でもあります。ストアの返信は公開されますし、書いた約束は次のバージョンが出るまで読まれ続けます。

そこで私は、下書きを賢くする前に、送る前に止める側を先に作りました。処理としては地味で、コードも短いものです。ただ実際に走らせてみると、間違えていたのは下書きではなく検査の側でした。その顛末まで含めて置いておきます。

返信するかどうかを、書き始める前に決める

レビューが届いた順に返信していくと、返信すべきものに時間が残りません。App Store と Google Play で文面の作法は変わりますが、振り分けの基準そのものはどちらでも同じで構いません。文章を作らせる前に、機械で三つに分けます。

振り分け条件その後の扱い
priority★2以下で、再現手順らしき記述がある下書きを作り、内容も自分で読む
ack_only★3以下で、再現手順がない受け止めだけを短く返す
no_reply★4以上返信枠を使わない

高評価に返信しないのは冷たく見えるかもしれません。ただ、返信を読んで態度が変わるのは、困っている人のほうです。限られた時間をそちらへ寄せる、という判断です。

import re
 
def triage(stars: int, review: str) -> str:
    repro = re.search(r"(すると|したら|開くと|押すと|落ちる|クラッシュ|固まる|表示されない|保存できない)", review)
    if stars >= 4:
        return "no_reply"        # 返信枠を使わない
    if stars <= 2 and repro:
        return "priority"        # 再現手順があるので最優先
    return "ack_only"            # 受け止めだけを返す

判定に使っているのは星の数と、症状を表す語だけです。感情の強さでは分けていません。強い言葉で書かれた不満より、淡々と手順を書いてくれたレビューのほうが、直せる情報を含んでいることが多いためです。

下書きは、文章ではなく構造で受け取る

Gemini に「返信を書いて」と頼むと、返ってくるのは一続きの文章です。これを検査に回すと、どこが謝罪でどこが約束なのかを毎回また解析することになります。

最初から分けて受け取るほうが、あとの工程が楽になります。

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_API_KEY")
 
REPLY_SCHEMA = {
    "type": "object",
    "properties": {
        "acknowledgement": {"type": "string"},   # 受け止め
        "status": {"type": "string"},            # 今わかっている事実だけ
        "next_step": {"type": "string"},         # 相手にお願いすること
    },
    "required": ["acknowledgement", "status"],
}
 
def draft(review: str, mode: str) -> dict:
    prompt = (
        "アプリのレビューへの返信案を作ってください。敬体で書き、"
        "実施が確定していない修正時期は書かないでください。\n"
        f"振り分け: {mode}\nレビュー本文: {review}"
    )
    res = client.models.generate_content(
        model="gemini-3.7-flash",
        contents=prompt,
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=REPLY_SCHEMA,
        ),
    )
    return res.parsed

status を独立させているのが要点です。「原因を調査しています」は事実ですが、「修正します」は約束です。同じ段落に混ぜて書かせると、後から切り分けられなくなります。スキーマの書き方でつまずいたときは、Gemini API の Structured Output でバリデーションエラーが返るときの原因と対処に典型的な失敗例をまとめてあります。

なお、プロンプトで「修正時期を書かないで」と指示しても、書かれるときは書かれます。だから次の検査が要ります。

送信前の三つの検査

止めたいものは三つだけです。守れない約束(A)、連絡先の混入(B)、文体の崩れと長さ(C)。

MAX_CHARS = 350  # ストアの上限とは別に、読ませる長さとして自分で決めた値です
 
PROMISE = [r"次の(アップデート|バージョン)で", r"必ず(修正|対応)", r"すぐに(修正|対応)",
           r"\d+\s*(|週間|)以内に", r"来週(まで)?に", r"今月中に", r"予定です"]
POLITE_END = re.compile(r"(です|でした|ます|ました|ません|ましょう|ください|ございます)$")
PLAIN_END = re.compile(r"(|である|した|ない|いる|する||)$")
 
def mask(draft: str) -> str:
    """メールと URL を先に伏せる。伏せないと @ の検出が同じ箇所で二重に鳴ります"""
    d = re.sub(r"[\w.+-]+@[\w-]+\.[\w.]+", " ", draft)
    return re.sub(r"https?://\S+", " ", d)
 
def check(draft: str) -> list:
    hits = []
    for p in PROMISE:
        if re.search(p, draft):
            hits.append(("A_promise", p))
    for pattern, target in ((r"[\w.+-]+@[\w-]+\.[\w.]+", draft),
                            (r"https?://\S+", draft),
                            (r"@[A-Za-z0-9_]{3,}", mask(draft)),
                            (r"0\d{1,4}-\d{1,4}-\d{3,4}", mask(draft))):
        m = re.search(pattern, target)
        if m:
            hits.append(("B_contact", m.group(0)))
    for s in re.split(r"(?<=)", draft):
        s = s.strip()
        if s and not POLITE_END.search(s) and PLAIN_END.search(s):
            hits.append(("C_plain", s))
            break
    if len(draft) > MAX_CHARS:
        hits.append(("C_length", f"{len(draft)}字"))
    return hits

B に連絡先を入れているのは、個別対応へ誘導する文面が公開の場に出ることを避けたいからです。サポート窓口へ誘導したい気持ちは分かるのですが、公開返信にアドレスを書くと、そのアドレスは以後ずっとそこに残ります。

走らせたら、間違えていたのは検査の側でした

サンプルのレビューと下書きを6組用意して、そのまま実行しました。最初の版の出力がこれです。

★ 振り分け     判定   検出
1  priority   STOP  A_promise:次の(アップデート|バージョン)で
1  ack_only   STOP  B_contact:support@example.com / B_contact:@example
2  ack_only   OK    —
2  priority   STOP  C_plain:保存が完了しない事象を確認しました。
5  no_reply   —     返信枠を使わない
3  ack_only   STOP  C_plain:ご意見に感謝する。
 
下書き6件 → 返信対象5件 / 通過1件 / 差し戻し4件 / 対象外1件

差し戻し4件のうち、2件が検査側の誤りでした。

ひとつめは2行目です。support@example.com に対して B_contact が二度鳴っています。メールアドレスの中の @example を、SNS のハンドル名を探すパターンが拾っていました。同じ一箇所を二重に数えていたわけです。

ふたつめは4行目で、こちらは実害があります。「保存が完了しない事象を確認しました。」は敬体です。それを常体として止めていました。文末の活用形を した。 で拾う書き方にしていたため、ました。 がそこに含まれてしまったのです。敬体の判定は、常体の形を探すのではなく、先に敬体の語尾を確認してから常体を疑う順序にしないと成立しません。

修正は2箇所です。メールと URL を伏せてからハンドル名を探すこと。敬体語尾(です ました ません ください など)を先に判定すること。書き直して再実行した結果が次です。

1  priority   STOP  A_promise:次の(アップデート|バージョン)で
1  ack_only   STOP  B_contact:support@example.com
2  ack_only   OK    —
2  priority   OK    —
5  no_reply   —     返信枠を使わない
3  ack_only   STOP  C_plain:ご意見に感謝する。
 
下書き6件 → 返信対象5件 / 通過2件 / 差し戻し3件 / 対象外1件
項目修正前修正後
差し戻し4件3件
うち誤検出2件(二重検出1・敬体の誤判定1)0件
通過1件2件

止まった3件は、いずれも本当に止めるべきものでした。確定していない修正時期、公開の場に出したくないアドレス、常体のまま出てきた一文です。

誤検出が残ったまま運用に入ると、検査を信じなくなります。そして信じなくなった検査は、いずれ外されます。数字が小さいうちに一度手で並べて確かめておくと、その道を通らずに済みます。

何を任せ、何を手元に残すか

私は、返信文の下書きと、送信前の機械的な検査までを自動化の範囲にしています。送信ボタンは自分で押します。

線引きの理由は単純で、返信は約束を含むからです。「調査しています」までは事実の報告ですが、「直します」は開発計画への合意です。それを決められるのは、次に何に手をつけるかを知っている人だけです。文面の巧拙とは別の話だと考えています。

同じ考え方で、レビューの内容そのものへの判断も手元に残しています。返信するかどうかは振り分けの結果を参考にしますが、最終的には自分でレビューを読みます。読まずに返す返信は、読まれたときに分かってしまいます。

多言語で返信を回す場合は、語彙の統一や返信件数の分散など、ここで扱っていない論点が加わります。実際に複数言語で運用した記録はGoogle Play 多言語レビュー返信を Gemini と作るにまとめてありますので、返信の本数が増えてきた段階で覗いてみてください。

次の一手

まずは PROMISE の配列に、自分が過去に書いてしまった言い回しを1つ足すところから始めてみてください。汎用の禁止語リストより、自分の癖を1つ潰すほうが確実に効きます。私の場合は「予定です」でした。

小さな検査ですが、送信ボタンの手前に一段だけ挟まっていると、深夜に書いた文章を朝の自分が確認できます。お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-06-12
App Store 審査リジェクト対応に Gemini API を組み込む — 通知の構造化から Resolution Center 返信までの運用記録
App Storeの審査リジェクト通知をGemini APIで3層のJSONに構造化し、ガイドライン照合・Resolution Center返信ドラフト・提出前セルフチェックへつなげた個人開発の運用記録です。
開発ツール2026-08-17
配布済みアプリに残るモデル名と、停止日をまたいだ切り戻しの設計
サーバー側の移行が終わっても、ユーザーの端末で動いている旧バージョンのアプリは古いモデル名を握ったままです。停止日をまたぐと切り戻しが復旧手段でなくなる構造と、モデル解決をサーバーに寄せる設計をまとめます。
開発ツール2026-07-25
gemini-flash-latest への追従をやめた日 — モデルの静かな置き換えに耐えるバッチ設計
発表を伴わないモデル更新で、安定していたバッチの拒否率が一晩で4倍を超えました。gemini-flash-latest への追従をやめ、ピン留めとカナリア昇格の二段構えに移行するまでの実測と設計判断をまとめております。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →