はじめに
こんにちは。とまと🍅好きエンジニアです。
先日、Google Cloud Japan 協賛の DevOps × AI Agent Hackathon 2026 で、
チームで開発した「こレッジ」という作品で特別賞をいただきました。
こレッジは、子どもの「なぜ?」を育てる、AIエージェントを使ったニュース発見アプリです。
毎朝ニュースを自動で取ってきて、6〜10歳の子どもが読める日本語に書き換え、
その子の興味に合わせてタイトルもサムネイルも作り変えて届けます。
つまり、AIが作ったものが、そのまま子どもの扱う画面に出るアプリです。
このアプリを作って運用し続けるなかで、いちばん時間をかけて考えたのが
「どこまでをAIに任せるか」でした。
そして出した結論が、
最終的な判断は、AIにさせない。機械にさせる。
決勝に残った他の10作品を見ても、うまく作られている作品ほどこれを徹底していました。
これからAIを組み込んだアプリを作る人には、たぶん必ず効いてくる考え方なので、
自分たちがどう線を引いたのかを記事にまとめました。
想定読者
- これからLLMやAIエージェントを組み込んだアプリを作ろうとしている方
- LLMの出力が毎回ブレて、テストの書き方に困っている方
- 「AIに任せたのに思ったとおりに動かない」とモヤモヤしている方
- AIを使ったプロダクトを本番で運用しようとしている方
逆に、AIモデルそのものの精度を上げる話(ファインチューニング等)はこの記事には出てきません。
すでにあるLLMを、どうアプリに組み込むかという話です。
まず「AIに判断を任せる」を明確にする
前提として、言葉を分解しておきます。
自分も最初は、AIがやっていることを一括りに「AIがやってくれること」だと思っていました。
「Geminiに投げれば、いい感じにやってくれる」と思っていた時期もありました。
でも実際に作ってみると、AIにやらせている仕事はまったく性質の違う2つに分かれていました。
| 種類 | 具体例 | AIに任せていいか |
|---|---|---|
| 生成 | 文章を書く/要約する/画像を作る/言い換える | ✅ 任せていい(というかAIにしかできない) |
| 判断 | 通すか落とすか/点数をつける/どっちが正しいか決める/次に何をするか決める | ⚠️ ここが今日の本題 |
生成は、答えがひとつに決まらないからこそAIの出番です。
「このニュースを小学生向けに書き換えて」に唯一の正解はありません。
一方で判断は、同じ入力なら同じ答えが返ってこないと困る性質の仕事です。
そして厄介なのは、LLMは判断も"それっぽく"やってくれてしまうことです。
「この記事を子どもに見せていいか判断して」と投げれば、ちゃんと答えが返ってきます。
返ってきてしまうから、任せてしまう。ここが落とし穴でした。
なぜ判断を任せてはいけないのか
理由は3つあります。
1. 再現性がない
同じ記事を2回投げて、1回目は「OK」、2回目は「NG」が返ってくることがあります。
temperature を0にしてもゼロにはなりません。
子ども向けアプリでこれが起きるとどうなるか。
「なぜこの記事が子どもに表示されたのか」を説明できなくなります。
2. テストできない
非決定的なものは、「バグが無いこと」を証明できません。
できるのは、ラベル付きのデータに対する一致率を測って、劣化したら気づくことだけです。
判断ロジックがLLMの中にあると、この「測る」ことすら難しくなります。
3. 検証器がブレると、測定値そのものが信用できなくなる
これが一番効きました。
「AIの出力が正しいかをAIに判定させる」(LLM-as-a-Judge)という手法があります。
便利なのですが、判定する側もブレます。
判定がブレると、「精度90%」という数字が、
実力が落ちたのか、判定器の気分なのか、区別できなくなります。
測るための道具がブレていたら、そもそも測れていません。
こレッジでどう線を引いたか
実際にこう分けました。
ここでいう機械とは機械的に判定させるアルゴリズムのことです。
| 処理 | AIにやらせたこと | 機械にやらせたこと |
|---|---|---|
| 記事の品質チェック | 1〜5点のスコアを出す | 通す/落とすの判断 |
| 子ども向けへの書き換え | 文章を書く | 書けているかの検証 |
| 興味の学習 | 興味の傾向をメモに書く | 興味スコアの計算 |
一言でいうと、
スコアと文章=AI、判断=機械。
以下、3つのケースを具体的に書きます。
ケース1: 「子どもに見せるか」をAIに決めさせない
このアプリでいちばん失敗が許されないのが、有害な記事が子どもに届くことです。
ここでAIにやらせているのは、採点だけです。
// AIに投げるのは「点数をつけて」だけ。「通していい?」とは聞かない
{
"educational_value": 1〜5, // 教育的価値
"thinking_hook": 1〜5, // 背景や影響が書かれているか
"reliability": 1〜5, // 事実と意見が区別されているか
"safety_flags": [] // 危険な種類があれば列挙
}
そして通す/落とすの判断は、条件式で行います。
/** 採点結果から除外理由を決める。null なら合格。 */
MIN_EDUCATIONAL_VALUE = 2
export function rejectReason(review: QualityReview | null): RejectReason | null {
if (review === null) return "scoring_failed"; // 採点自体が失敗 → 落とす
if (!review.safety.passed) return "safety"; // 危険フラグあり → 落とす
const ev = review.scores.educationalValue;
if (ev !== null && ev < MIN_EDUCATIONAL_VALUE) return "low_quality";
return null; // 合格
}
これには効果が3つあります。
① 閾値が「見える」
MIN_EDUCATIONAL_VALUE = 2 という数字がコードに書いてあります。
厳しすぎたら上げ、緩すぎたら下げることができるので、プロンプトを書き直すより確実で、差分がレビューできます。
② 単体テストが書ける
LLMを呼ばずに、この関数だけをテストできます。判断ロジックにバグが無いことを証明できます。
③ 説明できる
「なぜこの記事が落ちたのか」に、safety / low_quality / scoring_failed と必ず理由が付きます。
さらに、採点そのものが失敗したときは fail-closed(=落とす) にしています。
Geminiが落ちたらフィードは空になりますが、
検証できていない記事を子どもに見せるよりは安全だと判断しました。
この「迷ったら落とす」も、AIではなく if (review === null) という機械の判断です。
実測では、28日間で採点した299件のうち100件(33.4%)を、子どもに届く前に落としました。
そして落とした記事は消さずに、理由付きで全部残しています。
そうしないと、「良い記事を間違って落としていないか」を後から確認できなくなるからです。
ケース2: AIの出力の検証に、AIを使わない
「小学生向けに書き換えて」とお願いした結果が、ちゃんと書き換えられているか。
これをどう確かめるか、という話です。
最初は「もう一度Geminiに読ませて採点させればいいのでは」と考えました。
でも、やめました。理由は、チェックしたい項目のほとんどが機械的に判定できたからです。
| チェック項目 | 判定方法 |
|---|---|
| ルビの記法が壊れていないか | 〔漢字|よみ〕の対応をパースする |
| 漢字にルビが付いているか | 正規表現で漢字を検出して照合 |
| タイトルが長すぎないか | 文字数を数える |
| 本文が2〜4文になっているか | 文の数を数える |
| ひらがなだけになっていないか | 文字種を数える |
| 元の記事に無い数字が出ていないか | 本文中の数値を抜き出して元記事と突き合わせる |
最後の項目が、個人的にいちばん大事だと思っています。
LLMは、元記事に書かれていない数字を平気で作ります。
「約3万人が参加しました」——元記事にそんな数字は無い。
子ども向けのニュースでこれが起きると、事実と違うことを教えることになります。
これは「AIに『嘘ついてない?』と聞く」問題ではなく、
「本文の数値の集合が、元記事の数値の集合に含まれるか」という集合演算です。
だったら機械でやるべきです。
そして、この検証器には自己テストを書いてCIで毎回回しています。
わざと壊した出力を渡して、ちゃんと違反として検出できるかを確認します。
検証器が信用できなければ、そこから出てくる数字も信用できません。
この仕組みのおかげで、「数値ハルシネーション率5%以下」のような
目標値(SLO)を置いて、CIで落とすことができるようになりました。
AIの判定に頼っていたら、この数字は置けませんでした。
ケース3: 「何を好きになるべきか」をAIに決めさせない
こレッジは、子どもが記事を読んだ時間から興味を学習して、翌日の記事を作り変えます。
この「興味の学習」を、AIにやらせていません。数式です。
新しいスコア = 前のスコア × exp(-λ × 経過日数) + α × エンゲージメント値
指数減衰で古い興味を薄れさせて、読んだらそれを足す。
LLMがやるのは、「この子は宇宙の話に反応しやすいようです」という文章を書くことだけで、スコアには一切触れません。
これをする理由は、
子どもの興味の記録が、LLMの揺らぎで変わってしまうからです。
昨日は「科学 27点」だったのに、今日AIに聞いたら「科学 12点」になっている。
それでは、その子に合わせてパーソナライズしているということはできません。
この線引きが、たぶんこの記事でいちばん伝えたいところです。
AIエージェントは「自律的に学習する」と言われます。
でも自律的に学習していい部分と、そうでない部分がある。
数式にしておけば、スコアの変化は必ず説明できます。
「この日に30秒読んだから+3.2点」と、後から全部たどれます。
実際にどう作るのか
ここまでの話を、実装の指針としてまとめます。
1. LLMの出力は「答え」ではなく「入力」として扱う
いちばん効く発想の転換がこれでした。
❌ LLM → 判断 → 実行
✅ LLM → 材料 → 【自分で書いた判断ロジック】 → 実行
LLMが返してくるのは材料です。判断はその後ろに置きます。
2. 構造化出力で、返ってくる形を縛る
Gemini なら responseSchema で必須キーを指定できます。
形が保証されないと、判断ロジックの手前で崩れます。
自分たちの場合、必須キーを指定していなかったせいで
保護者向けの要約が返ってこないケースが半分以上あり、
元記事の文章がそのまま親に届いていました。
responseSchema で5つのキーを必須にしただけで、**構造の適合率が43%→100%**になりました。
3. 機械で判定できることに、LLMを使わない
文字数・形式・数値の照合・分類が既定のリストに含まれるか。
このあたりは全部、正規表現とif文の仕事です。
「AIを使っているほうがすごい」わけではありません。
4. 判断ロジックを、本番とテストで共有する
こレッジでは、プロンプトの組み立て・出力の正規化・除外判定を
ネットワーク通信を含まない1つのファイルに切り出して、
本番のCloud FunctionsとCIの評価スクリプトが同じものをimportしています。
テスト用に似たコードを書いてしまうと、
そのテストは本番を保証しなくなります。 これはAIに限らない話ですが、
LLMが絡むと「プロンプトが1文字違う」だけで結果が変わるので、より深刻です。
5. 迷ったらfail-closed。ただし「落としたもの」を残す
止まるのと、間違ったものを出すの、どちらがマシかを先に決めておきます。
そして落としたものは理由付きで必ず残します。
残していないと、「厳しすぎたかどうか」を検証できず、閾値を二度と動かせなくなります。
ハッカソンの他の作品も、同じことをしていた
決勝に残った作品を見ていて印象的だったのは、
AIをどれだけ使っているかではなく、AIをどこで止めているかがきれいな作品ほど強かったことです。
最優秀賞受賞をされていた、「Yasai Relay」は規格外野菜の無人販売を支援するエージェントアプリです。
Yasai Relayでは野菜の値付けを自動で行う機能がありますが、これもエージェントに丸投げしているわけではなく、安全な価格帯を用意したり、確信が持てない野菜は自動公開せず、農家へ確認を依頼するというガードレールの設定をしていました。
「AIエージェント」がテーマのハッカソンで、上位に来るのが「AIに任せきらない設計」だったのは、偶然ではないと思っています。
おわりに
「AIに判断を任せるな」と強めのタイトルにしましたが、
AIを使うなという話ではまったくありません。
こレッジは、記事の書き換えも、サムネイル画像も、クイズも、全部AIが作っています。
AIが無ければ成立しないアプリです。
そのうえで、最後にYES/NOを出すところだけは、自分が書いたコードに握らせておく。
それだけで、
- テストが書けるようになり
- 数字で目標を置けるようになり
- 「なぜこうなったのか」を説明できるようになります
これから作るものにAIを組み込むとき、
最初から完璧に設計できなくても大丈夫だと思います。
この判断は機械でいいのか、それともAIでいいのかという軸で仕分けてみると、思ったよりAIに判断させてしまっていることに気づくと思います。
自分がそうでした笑
参考
- 「こレッジ」作品ページ(ProtoPedia):
- DevOps × AI Agent Hackathon 2026:
- 「Yasai Relay」作品ページ(ProtoPedia):