マキモノ
業務自動化無料✅ 公式検証済みv1.0.0 / 更新

LLM経由のデータ取り込みを決定論化する — キャッシュが静かに嘘をつくのを止める

AIにAPI結果をJSONへ転記させる構成は、欠落と幻を同時に作り件数の自己申告では検出できない。サービスアカウント+ドメイン全体の委任で依存ゼロの決定論スクリプトに置き換える手順と、読み取り側で「データ無し」と「該当無し」を区別するガードまで。

出品者: kim@orgiast.jp2 DL📖 読込 約3,528トークン (約5円)💰 コスパ 24
トークン節約メーター87%節約
ゼロからAIに作らせた場合9.5万トークン
このMDを読ませた場合1.2万トークン

8.3万トークンの節約 (API料金換算で約120円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

LLM経由のデータ取り込みを決定論化する — キャッシュが静かに嘘をつくのを止める」は、業務自動化カテゴリのAI指示書(MDファイル)です。AIにAPI結果をJSONへ転記させる構成は、欠落と幻を同時に作り件数の自己申告では検出できない。サービスアカウント+ドメイン全体の委任で依存ゼロの決定論スクリプトに置き換える手順と、読み取り側で「データ無し」と「該当無し」を区別するガードまで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約8.3万トークン(API料金換算で約120円)・87%のトークンを節約できます。

カテゴリ
業務自動化
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約9.5万トークン
この巻物使用時
約1.2万トークン
節約量
約8.3万トークン (約120円)
更新日
2026-08-27

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/llm-5/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

LLM 経由のデータ取り込みを決定論化する — キャッシュが静かに嘘をつくのを止める

AI エージェントに「API を叩いて結果を JSON ファイルに保存させる」構成は、静かに壊れる。 この指示書は、実際に事故った構成とその直し方(決定論スクリプトへの置換+読み取り側のガード)をまとめたもの。 外部 API のデータをローカルにキャッシュして使うシステム全般(カレンダー・在庫・顧客・料金表など)に効く。

1. 何が起きるのか(実例)

ある社内アプリが「担当者のカレンダーから空き時間を出す」機能を持っていた。 取り込みは次の構成だった:

定期フック → headless の AI CLI を起動
          → AI が Calendar MCP で予定を取得
          → AI が読んだ内容を JSON ファイルに手書き保存
          → アプリはその JSON を読んで空き時間を計算

API の実データと保存済み JSON を1件ずつ突き合わせたところ、1回の更新で両方向の誤りが同時に出ていた

種別実際に起きたこと表面化した症状
欠落実在する予定2件が JSON に書かれなかったその時間が「空き」として顧客に提示された
別の日時へ移動済みの予定が、移動前の位置に残っていた空いている時間が「予定あり」で潰された

ログには refreshed 57 events と出ていた。しかしこれは書いた側の自己申告である。 欠落と幻が1件ずつなら総数は一致してしまうため、件数の自己報告では原理的に検出できない。

さらに悪いことに、この構成は「空き時間が1枠ずれる」という形で表面化する。 落ちるでもエラーになるでもないので、人間も気づかない。気づいたのは、 提示した候補時間に既に別の予定が入っていると担当者が指摘したときだった。

2. 原則

LLM に任せるのは「判断」だけ。値の移送・整形・保存は必ず決定論的なコードでやる。

LLM は要約・分類・判断には強いが、数十件の構造化データを一字一句転記する用途には向かない。 「件数を報告させる」「フォーマットを厳密に指定する」といった対策はどれも自己申告の域を出ない。

「認証経路が MCP しかないから AI 経由にした」が典型的な入口。先に決定論的な認証経路を探すこと。

3. 直し方 — サービスアカウント + ドメイン全体の委任で直接叩く

Google Workspace を使っている組織なら、サービスアカウント (SA) + Domain-Wide Delegation (DWD) で ユーザー個人の OAuth 同意なしに、そのユーザーとして API を読める。依存パッケージもゼロで書ける。

3-1. 既存の SA を使い回せるか先に確認する

新規に作る前に、組織内に既に DWD 設定済みの SA があるか探す。あればスコープ追加だけで済む。

# SA 鍵 JSON を探す(client_email と private_key を持つファイル)
# 秘密の値そのものは絶対に出力しないこと。パスとキー名の有無だけ確認する

3-2. JWT を自己署名してアクセストークンを取る(依存ゼロ)

Node 標準の crypto だけで書ける。ライブラリは不要。

import { createSign } from 'node:crypto';

const TOKEN_URL = 'https://oauth2.googleapis.com/token';
const SCOPE = 'https://www.googleapis.com/auth/calendar.readonly'; // 必要な読み取りスコープだけ
const b64url = (v) => Buffer.from(v).toString('base64url');

async function getAccessToken(keyPath, impersonatedUser) {
  const key = JSON.parse(fs.readFileSync(keyPath, 'utf8'));
  const now = Math.floor(Date.now() / 1000);
  const header = b64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));
  const claims = b64url(JSON.stringify({
    iss: key.client_email,
    sub: impersonatedUser,   // ← ここが DWD。このユーザーとして読む
    scope: SCOPE,
    aud: TOKEN_URL,
    iat: now,
    exp: now + 3600,
  }));
  const signer = createSign('RSA-SHA256');
  signer.update(`${header}.${claims}`);
  const assertion = `${header}.${claims}.${signer.sign(key.private_key, 'base64url')}`;

  const res = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      assertion,
    }),
  });
  const result = await res.json();
  if (!res.ok || typeof result.access_token !== 'string') {
    // ★ このエラーは必ず「スコープ未追加の可能性」を明示する。最初に必ず踏む
    if (result.error === 'unauthorized_client' || res.status === 401 || res.status === 403) {
      throw new Error(`${result.error}: 管理コンソールの「ドメイン全体の委任」に ${SCOPE} が未追加の可能性`);
    }
    throw new Error(`token request failed (${result.error ?? res.status})`);
  }
  return result.access_token;
}

秘密の扱い: private_key / access_token を標準出力・ログ・例外メッセージに絶対に含めない。 API のレスポンス body をそのままエラーに echo しないこと(トークンや個人情報が混じる)。

3-3. 人間にしか出来ない準備(2つだけ・API/CLI では不可)

この2つは Google の管理画面でしか変更できない。自動化を試みても権限で弾かれる (実測: SA 自身には serviceusage.services.enable が無く AUTH_PERMISSION_DENIED)。 だからこそ、手順を非エンジニアでも踏めるフル手順で書いて渡す

  1. 管理コンソール → セキュリティ → API の制御 → ドメイン全体の委任 対象 SA のクライアント ID の行を「編集」→ OAuth スコープ欄に、 既存の文字列を消さずにカンマ区切りで読み取りスコープを追記 → 承認
    • ⚠️ 既存スコープを消すと他の自動化が全部止まる。「追記」を太字で伝えること
  2. Cloud Console → API とサービス → 対象の API → 「有効にする」

3-4. 取り込みスクリプトに必ず入れる4点

// ① nextPageToken を最後まで走査する(取りこぼしの最大要因)
let pageToken;
do {
  const params = new URLSearchParams({ /* ... */ maxResults: '2500' });
  if (pageToken) params.set('pageToken', pageToken);
  const data = await fetchJson(url, params);
  items.push(...data.items);
  pageToken = data.nextPageToken;
} while (pageToken);

// ② tmp → rename の atomic 書き込み(途中失敗で壊れた JSON を残さない)
// ③ 書いた後に読み直して自己検証する
// ④ 検証失敗なら既存データを残して非ゼロ終了(壊れたキャッシュを本番に置かない)
function writeAtomicallyAndVerify(cache, expectedCount) {
  const tmp = path.join(DIR, `.cache.json.${process.pid}.tmp`);
  let backupMade = false;
  try {
    fs.writeFileSync(tmp, JSON.stringify(cache, null, 2) + '\n', { encoding: 'utf8', flag: 'wx' });
    validate(fs.readFileSync(tmp, 'utf8'), expectedCount);
    if (fs.existsSync(FILE)) { fs.copyFileSync(FILE, BACKUP); backupMade = true; }
    fs.renameSync(tmp, FILE);
    validate(fs.readFileSync(FILE, 'utf8'), expectedCount);   // rename 後にもう一度
  } catch (e) {
    if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
    if (backupMade) fs.copyFileSync(BACKUP, FILE);            // 既存に戻す
    throw e;
  }
}

検証で「開始日が対象期間内」を要求してはいけない。 期間をまたぐ長期データ (出張・休暇のような複数日にわたる予定)は開始日が過去になるため、 厳格に見ると正常な取得が毎回ロールバックされ、キャッシュが永久に更新されなくなる。 必ず「期間が重なるか」で判定する:

if (startDate > coverageTo || endDate < coverageFrom) throw new Error('does not overlap');

4. 読み取り側 — 「データが無い」と「該当が無い」を必ず区別する

これは取り込みと同じくらい重要。同じ事故のもう半分がここにあった。

キャッシュは7日分しか持っていないのに、UI は「14日」を選べた。 その結果、8日目以降が「予定ゼロ = 終日フリー」として提示されていた。 実際には予定が入っている日が「9:00〜18:00 まるごと空き」と表示される。

対策は3つセットで入れる:

  1. キャッシュ自身にカバー範囲を書くcoverageFrom / coverageTo)。 「更新時刻 + 保持日数」から推測させない。推測は境界を1日ずらす(実際に踏んだ: 「今日から N 日先まで」は起点日を含めて N+1 日分であり、+N 日で計算すると最終日が未取得扱いになる)
  2. 範囲外の日は結果から外す。ただし「該当なし」ではなく 未取得 という別ステータスにする
  3. 必ず画面に警告を出す。黙って除外するのは不可
⚠️ 9/4〜9/9 はデータ未取得のため候補から除外しました(空きではありません)。更新してください。

黙って除外すると、利用者は「その期間は何も無い」と受け取る。それが事故の再発条件そのもの。

5. 移行時のチェックリスト

  • 旧経路(AI にファイルを書かせる箇所)を消す。残すと古い経路が動き続ける
  • 旧経路が AI CLI の場所を探すコードを持っていたら、それも消えるか確認 (実例: 「CLI が見つからなければ return」が関数ではなくスクリプト最上位にあり、 後続の別処理まで丸ごと止まるバグが同居していた。置換で自然に解消した)
  • 新旧の日別件数を API 実データと突き合わせる。総数だけ見ない
  • 認証未設定の状態で1回走らせ、失敗しても既存データが無傷であることを確認する
  • 取得件数・期間・更新時刻を UI に表示する(102件 / 8/27〜9/10 / 3分前)。 鮮度が見えないキャッシュは、古くなったことに誰も気づかない

6. 副産物

更新1回ごとの AI CLI 起動が消えるため、トークン消費もゼロになる。 「決定論にする」は正確さの話だが、コストにも効く。定期実行なら効果は毎日積み上がる。

7. まとめ

やってはいけない代わりにやる
AI に API 結果を JSON へ転記させる決定論スクリプトで HTTP → parse → write
件数を自己申告させて検証とする独立した取得元と日別件数を突き合わせる
「データ無し」を「該当無し」として扱うカバー範囲を明示し、範囲外は別ステータス + 警告
更新時刻から保持範囲を推測するキャッシュ自身に範囲を書き込む
検証で開始日の内包を要求する期間の重なりで判定する
失敗時に書きかけを残すtmp → rename + 失敗時は既存を復元

よくある質問

「LLM経由のデータ取り込みを決定論化する — キャッシュが静かに嘘をつくのを止める」とは何ですか?

AIにAPI結果をJSONへ転記させる構成は、欠落と幻を同時に作り件数の自己申告では検出できない。サービスアカウント+ドメイン全体の委任で依存ゼロの決定論スクリプトに置き換える手順と、読み取り側で「データ無し」と「該当無し」を区別するガードまで。

どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約9.5万トークンかかりますが、この巻物を使えば約1.2万トークンで済みます。差し引き約8.3万トークン(API料金換算で約120円)・87%の節約です。

どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア