ENGINEERING

決定的なプログラムとは?AIに任せる前に知りたい「同じ入力なら同じ答え」

AIに任せた仕事を「たまたま動いた」で終わらせないために。プログラミングの「決定的(deterministic)」の意味、結果がブレる5つの原因、揺れるものを引数で外から渡すコツを短いTypeScript例で解説。temperature 0 でも揃わないAIの周りを、テストやhooksで決定的に固めます。

S2026-09-23
media

AIにコードを書いてもらっていると、同じお願いをしたのに昨日と今日で違うコードが返ってきたり、さっき通ったテストがなぜか落ちたりすることがありますよね。こうしたモヤモヤの正体を説明してくれるのが、プログラミングでよく使われる「決定的(deterministic)」という言葉です。この記事では、決定的の意味、結果がブレる原因、自分のコードを決定的にするコツ、そしてAIと一緒に開発するときの活かし方を、短いTypeScriptの例を交えて解説します。先に結論を言ってしまうと、​AIの中身は決定的にできないので、その外側を決定的な部品で固める​のが一番の近道です。

AIが確率的だからこそ、周りを決定的にする

決定的なプログラムとは、​同じ入力を渡せば、いつ何度実行しても同じ答えを返すプログラム​のことです。

一方で、AIの出力は確率で決まります。同じ質問でも答えが揺れるのは、AIの性質そのものです。ここを無理に揃えようとするより、AIが書いたコードを確かめる仕組みの方を決定的にしておく方がずっと確実です。

周りを決定的にしておくと、次のようなうれしいことがあります。

  • テストが毎回同じ基準で合否を出してくれるので、AIの仕事を機械的に確かめられる
  • 不具合が起きたとき、同じ条件で何度でも再現できる
  • コードを読んだだけで、何が起きるか予想できる

AIに任せた仕事が「たまたま動いた」から「毎回確かめられる」に変わる。これがこの記事で一番伝えたいことです。

「決定的」とは「同じ入力なら、いつも同じ答え」

身近な例でいうと、電卓は決定的です。「2 + 3」と打てば、今日でも10年後でも答えは5。自動販売機も、同じボタンを押せば同じジュースが出てきます。

反対に、サイコロや天気予報は非決定的です。同じように振っても出る目は違いますし、同じ空模様でも予報は日によって変わります。人に道を聞くのも同じで、聞く相手やタイミングによって教えてくれるルートが変わりますよね。

プログラムに置きかえると、答えを決める材料がすべて入力として渡されていて、それ以外のものに結果が左右されない状態を「決定的」と呼びます。この性質を持つ手順のことを、計算機科学では「決定的アルゴリズム」と呼びます。コードで見ると、違いは一目瞭然です。

const add = (left: number, right: number): number => left + right;

const addWithNoise = (left: number, right: number): number =>
  left + right + Math.random();

add(2, 3); // いつ実行しても 5
addWithNoise(2, 3); // 5.37…、5.81…と毎回変わる

add は渡された2つの数だけを見て答えを出します。addWithNoise は、引数とは関係なく Math.random() という「サイコロ」を中で振っているので、同じ入力でも答えが変わります。

ちなみに、add のように引数だけから答えを作り、外の世界に何も影響を与えない関数を​純粋関数​と呼びます。逆に、画面に表示する・ファイルに書き込むといった、関数の外の世界に影響を与える動きは​副作用​と呼ばれます。もうひとつ、現在の時刻や乱数のように、引数を通らずに関数の中へ入り込んでくる値もあります。この記事では、こちらを「外から入ってくるもの」と呼ぶことにします。純粋関数は、副作用もなく、外から入ってくるものも使わない関数というわけです。

結果がブレる5つの原因は「関数が外に質問すること」

非決定的になる原因は、ひと言でまとめられます。関数が、引数でもらっていない情報を自分から外に聞きに行くことです。聞くたびに答えが変わるので、関数の結果も変わってしまいます。よくある「質問」を5つ見てみましょう。

「いま何時?」

new Date() は、関数が時計に「いま何時?」と聞いている状態です。たとえば「今日がクーポンの有効期限内か」を判定する処理は、期限の日を過ぎた瞬間にテストが落ち始めます。日付をまたぐ夜中や、国ごとの時差がからむ場面も要注意です。

「向こうは何と言っている?」

天気予報のAPIや為替レートのように、ほかのサービスに問い合わせる処理です。相手のデータが更新されれば、同じ問い合わせでも返事は変わります。そして、AIに質問する処理もまさにここに入ります。

「いまの中身はどうなっている?」

プログラムのあちこちから触れるデータを読む処理です。グローバル変数(プログラムのどこからでも読み書きできる変数)や、前のテストがデータベースに残していったデータがその例です。読んだ瞬間の中身しだいで、答えが変わってしまいます。

「どれが先に終わった?」

2つのAPIを同時に呼んだとき、どちらが先に返ってくるかは通信の混み具合で毎回違います。先に返ってきた方を使うような処理は、実行するたびに結果が入れ替わりえます。

「サイコロの目は?」

Math.random() のような乱数です。おみくじやランダムな並べ替えには欠かせませんが、答えを揺らすことがそもそもの目的なので、テストの中に入り込むと結果が安定しません。

こうして見ると、5つとも​引数の外から入ってくるもの​です。関数が外に質問せず、引数だけを見ている限り、結果はブレません。

決定的にすると何がうれしいのか

テストが「毎回同じ結果」になる

決定的なコードのテストは、何度実行しても同じ結果になります。逆に、たまにしか落ちないテストは厄介です。「また落ちたけど、どうせたまたまでしょ」と無視されるようになり、本当に壊れたときにも気づけなくなってしまいます。テストは毎回同じ判定をしてくれるからこそ信用できる道具です。

これはAIに開発を任せるときにも効いてきます。たまに落ちるテストがあると、AIは「コードが壊れた」と受け取り、本当は問題のない部分を書き換えたり、テストの条件をゆるめて通そうとしたりしかねません。テストが決定的なら、落ちたときは本当に何かが壊れている、とAIも人間も安心して判断できます。

バグを再現できる

不具合を直すときに一番困るのは、「どうやったら起きるのか分からない」状態です。決定的なコードなら、同じ入力を渡すだけで同じ間違いをもう一度起こせます。起こせれば、入力を少しずつ変えながら原因を絞り込めますし、直したあとで本当に直ったかも確かめられます。AIに修正を頼むときも、「この入力でこうなる」と渡せれば、AIは推測ではなく事実から原因を探せます。

読んで予想できる

決定的な関数は、引数を見れば答えが予想できます。これは人間にとっても、AIにとってもありがたい性質です。AIにコードを直してもらうときも、外から入ってくるものが少ない関数ほど、見当違いの修正をされにくくなります。関数の中で時刻やデータベースを読んでいると、AIはその関数の外側まで調べないと正しく直せません。引数だけで完結していれば、見るべき範囲がその関数の中に収まります。

決定的にするコツは「揺れるものを外から渡す」

自分のコードを決定的にするコツはシンプルで、​揺れるものを関数の中で取りに行かず、引数として外から渡す​ことです。

営業時間内かどうかを判定する関数で考えてみます。

// 変更前:関数の中で「今」を取りに行く
const isBusinessHours = (): boolean => {
  const hour = new Date().getHours();
  return hour >= 9 && hour < 18;
};

// 変更後:「今」を外から受け取る
const isBusinessHoursAt = (now: Date): boolean => {
  const hour = now.getHours();
  return hour >= 9 && hour < 18;
};

isBusinessHoursAt(new Date('2026-01-05T10:00:00')); // いつ実行しても true

変更前は、テストを朝に動かすか夜に動かすかで結果が変わってしまいます。変更後は now を渡すだけで、夜中に実行しても「朝10時のとき」を確かめられます。変えたのは1行だけなのに、関数が決定的になりました。

料理にたとえると、レシピ(計算)はいつも同じにしておき、その日の食材(時刻や乱数)だけを外から渡すイメージです。もちろん、本番ではどこかで本物の「今」を取る必要があります。それは画面やAPIの入り口など、プログラムの端っこで isBusinessHoursAt(new Date()) と一度だけ書けば十分です。揺れるものは端っこに集め、真ん中は計算だけにしておくと、テストしやすい部分がどんどん増えていきます。

どうしても関数を書き換えられない場合は、テストの側で時刻を固定する方法もあります。Vitestのドキュメントでも、テストの一貫性を保つために日付をコントロールする方法が紹介されています。

import { afterEach, expect, it, vi } from 'vitest';

import { isBusinessHours } from './isBusinessHours';

afterEach(() => {
  vi.useRealTimers();
});

it('returns true at 10:00', () => {
  vi.useFakeTimers();
  vi.setSystemTime(new Date('2026-01-05T10:00:00'));

  expect(isBusinessHours()).toBe(true);
});

同じ考え方は、ほかの場面でも使えます。乱数はシード(種)を固定すれば毎回同じ並びにできますし、使っているライブラリのバージョンは package-lock.json のようなロックファイルで固定できます。npmの公式ドキュメントによると、ロックファイルがあれば、途中で依存ライブラリが更新されても同じ構成をインストールし直せます。

AIは決定的ではない。temperature 0 でも

ここからはAIの話です。AIの出力の「ばらつき具合」は、temperature という設定で調整できると聞いたことがあるかもしれません。値を0に近づけるほど答えが安定する、という説明をよく見かけます。

ところが、0にしても完全には揃いません。Claude(Anthropic)の公式ドキュメントの用語集には、temperature を0にしても結果は完全には決定的にならず、同じ入力でもAPIを呼ぶたびに違う出力になりうる、とはっきり書かれています。さらにAPIリファレンスでは、新しいモデルでは temperature を1.0以外に設定できなくなったことも示されています。

なぜ揃わないのかを調べた研究もあります。Thinking Machines Labの記事では、あるモデルに temperature 0 で同じ質問を1,000回投げたところ、80通りの異なる答えが返ってきたそうです。主な原因は、サーバーの混み具合によって一度にまとめて処理するリクエストの数(バッチサイズ)が変わり、計算結果がわずかにずれることでした。

OpenAIにも同じ答えを返しやすくする seed という設定がありますが、公式のCookbookでは「できる限り(best effort)」という扱いです。

つまり、使う側がAIの中身を決定的にするのは、現実的にはかなり難しいということです。

これは開発の場面では、次のような意味を持ちます。

  • 同じ指示を出し直しても、同じコードが返ってくるとは限らない
  • 「前回はうまくいった指示」が、次も同じように通るとは限らない
  • AIの答えを、もう一度AIに聞き直して確かめる方法には限界がある

AIが悪いわけではなく、そういう性質の道具だということです。人間の同僚に同じ仕事を2回頼んでも、まったく同じコードは書かないのと似ていますよね。だからこそ、発想を切り替える必要があります。

AIの周りを決定的な柵で囲む

AIの答えが揺れるなら、その答えを受け止める側を決定的にしておけばいいのです。牧場で動物を自由に歩かせつつ、周りの柵だけはしっかり固定しておくようなイメージです。

テスト・型チェック・リンターは「毎回同じ判定をくれる審判」

テスト、型チェック、リンター(書き方のルールを確かめる道具)は、同じコードに対して毎回同じ判定を返します。AIがどんなコードを書いてきても、同じ基準で合否を出してくれる審判です。

たとえば Claude Code の公式ドキュメントでは、AIが自分で実行できるチェック(テストやビルドなど)を渡すことが勧められています。チェックがないと、AIは「できたように見える」ところで作業を止めてしまい、人間がひとつずつ確かめる係になってしまう、という趣旨です。合否がはっきり出るチェックがあれば、AIは「書く → 確かめる → 直す」を自分で回せます。

ルールは「お願い」ではなく「仕組み」にする

AIエージェントには、プロジェクトのルールをまとめた指示ファイルを読ませることができます。ただし、ここに書いたことは、あくまで「お願い」です。同じ公式ドキュメントでは、指示ファイル(CLAUDE.md)は助言にとどまるのに対して、hooks(決まったタイミングで自動実行されるスクリプト)は決定的で、必ず実行されると説明されています。

「ファイルを編集したら必ずリンターを通す」のように例外なく守ってほしいルールは、AIの気分に任せず、自動で動く仕組みにしてしまう方が確実です。

なお、AIの出力の形を揃えたいなら、決められたデータ形式どおりに出力させる「構造化出力」という機能もありますが、揃うのは形式までで、中身が正しいかどうかは別問題です。

判断だけAIに任せ、決めるのはコード:Jevの例

この考え方を製品の設計にまで落とし込んでいるのが、TypeSafe社の Jev というモデルです。Jev は文章を書く代わりに、「この問い合わせは返金の依頼か?」のような質問に対して、「はい」の確率を数値で返します。公式ドキュメントでも、処理の流れや決まったルールはコードに置き、AIには狭い判断だけを任せる設計が勧められています。

面白いのは、Jev自身も決定的ではないと公式が正直に書いている点です。検証記事では、同じ保険請求を15回評価したところ、ある質問の確率が0.43から0.53の間で揺れ、0.5の境目をまたいでしまいました。そこで、0.30〜0.70の「迷う帯」は人間の確認に回す、という方法が紹介されています。

type Decision = 'approve' | 'reject' | 'review';

// AIから受け取った確率を、毎回同じルールで振り分ける
const decideByProbability = (probability: number): Decision => {
  if (probability > 0.7) {
    return 'approve';
  }
  if (probability < 0.3) {
    return 'reject';
  }
  return 'review'; // 迷う帯は人に回す
};

decideByProbability(0.43); // 'review'
decideByProbability(0.53); // 'review'

AIの答えが0.43と0.53の間で揺れても、振り分けの結果はどちらも「人に回す」で変わりません。AIの揺れを、決定的なコードが受け止めているわけです。ただし、公式も書いているとおり、これでAIが決定的になるわけでも、自動で決まった判定が正しいと証明されるわけでもありません。揺れを前提にしたうえで、どこで人間が確かめるかをコードで決めておく。これが現実的な付き合い方だと思います。

まとめ

  • 決定的とは「同じ入力なら、いつも同じ答え」という性質のこと
  • 結果がブレる原因は、時刻・外部サービス・共有データ・終わる順番・乱数といった「引数の外から入ってくるもの」
  • AIの中身は temperature を下げても決定的にならないので、テストやhooksなどの外側を決定的にする

いきなり全部を変える必要はありません。まずは手元のコードで new Date() をひとつ見つけて、引数で受け取る形に書き換えてみてください。それだけで、AIに任せたコードを確かめられる範囲が少し広がるはずです。

参考

  • 決定的アルゴリズム - Wikipedia
  • Glossary - Claude Docs
  • Messages API - Claude Docs
  • Structured outputs - Claude Docs
  • Defeating Nondeterminism in LLM Inference - Thinking Machines Lab
  • Reproducible outputs with the seed parameter - OpenAI Cookbook
  • Best practices for Claude Code - Claude Code Docs
  • Vitest ドキュメント(日付のモック)
  • package-lock.json - npm Docs
  • System One - TypeSafe Docs
  • Self-consistency: nouls - TypeSafe Docs

プロフィール

Kkrd-knt

フリーランスエンジニアとして、Webアプリケーションの開発支援を行っています。フロントエンドを中心に、UI/UXの改善やデータ分析まで幅広く携わっています。

このブログでは、私が実際に役立ったIT知識/サービスの紹介や、誰でも試せるシンプルな自動化・ライフハックを発信しています。「これならできそう」と思えるヒントを、わかりやすく届けます。

Instagram

新規投稿はありません

SNSシェア

HomeK2BG Logo
  • Engineering
  • Design
  • Data Science
  • Life Style
  • Concept
  • Contact

Copyright © 2026 K2.B.G.Technology All rights reserved.

HomeK2BG Logo
  • Engineering
  • Design
  • Data Science
  • Life Style
ConceptContact