機能が使いすぎないようにする
LLM 機能の予算とは、トークンをお金に換え、上限を超える呼び出しを断る仕組みです。コストは使われるほど増えます。よく使う一人のユーザーや一つのループが、一か月分の予算を午後だけで使い切ることもあります。
モデルはトークン単位で課金され、請求書は月末まで見えません。自分のコードに置いた上限は、その場で効く唯一のブレーキです。
このレッスンのゴール
メモ
私は、ユーザーごとの上限、一日の上限、分かりやすいメッセージで、機能の使いすぎを防げます。
キーワード
| ことば | いみ |
|---|---|
| トークン | モデルが読み書きし、課金の単位にもなるもの |
| 価格表 | 100 万トークンあたりの、入力と出力の値段 |
| ユーザーごとの上限 | 一つのアカウントが一日に使える最大額 |
| 一日の上限 | 機能全体が一日に使える最大額 |
| 台帳 | 使った額を足していく記録 |
| 拒否 | 理由を付けて呼び出しを断ること |
学生が入力するセルは、このプレビューでは空白です。
トークンをお金に換える
モデルには二つの値段があります。入力トークン用と出力トークン用で、たいてい 100 万トークンあたりです。呼び出しの費用は、入力トークンに最初の値段を掛け、出力トークンに二つ目の値段を掛けて足したものです。
お金に浮動小数点は使いません。小さな整数の単位で数えます。ここでは、100 万トークンあたりの円が、そのままトークン一つのマイクロ円になるので、計算が整数で済みます。このレッスンの価格は架空です。
例: 二つの呼び出し
| モデル | 入力 | 出力 | 計算 | 費用 |
|---|---|---|---|---|
| small、価格 1 と 4 | 900 | 300 | 900 x 1 + 300 x 4 | 2100 マイクロ円 |
| large、価格 10 と 40 | 200 | 100 | 200 x 10 + 100 x 40 | 6000 マイクロ円 |
学生が入力するセルは、このプレビューでは空白です。
課金の前に上限を調べる
台帳を付けます。ユーザーごとの合計と、一日の合計です。呼び出しに課金する前に、まず調べます。ユーザーが自分の上限を超えるか、一日の合計が上限を超えるなら、断ります。合計が上限ちょうどなら許します。
ユーザーの上限を先に調べます。そのほうがメッセージが役に立つからです。断った呼び出しには課金しません。断るときは、人が次に何をすればよいか分かる言葉で理由を伝えます。
よくある間違い
注意
「上限は悪用を防ぐためだけのものだ」と考えて、公開のあとで設定する人が多くいます。コストは、再試行のループや、全員に送った長いプロンプトなどの不具合でも増えます。最初のユーザーが来る前に、二つの上限を決めましょう。
モデル用の助っ人を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答に含まれるトークン数。なければ null。' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モデル用の助っ人を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '' '// 架空の価格表。100万トークンあたりの円。トークン数に価格を掛けるとマイクロ円になる。' 'const PRICE = { small: { in: 1, out: 4 }, large: { in: 10, out: 40 } };' 'const USER_LIMIT = 5000; // 一人が一日に使えるマイクロ円' 'const DAY_LIMIT = 8000; // 全員が一日に使えるマイクロ円' 'const spent = { user: new Map(), day: 0 };' 'function cost(model, inTokens, outTokens) {' ' return inTokens * PRICE[model].in + outTokens * PRICE[model].out;' '}' > budget3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function check(user, price) {' ' const mine = spent.user.get(user) || 0;' ' if (mine + price > USER_LIMIT) return \"user\";' ' if (spent.day + price > DAY_LIMIT) return \"day\";' ' return \"ok\";' '}' 'function charge(user, price) {' ' spent.user.set(user, (spent.user.get(user) || 0) + price);' ' spent.day = spent.day + price;' '}' >> budget3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function message(why, user) {' ' if (why === \"user\") return user + \" さん、この呼び出しを行うと、今日の利用枠を超えてしまいます。枠は真夜中に戻ります。\";' ' return \"アシスタントは、全員分の一日の上限に達しました。明日もう一度お試しください。\";' '}' 'async function demo() {' ' const prompt = \"五語であいさつしてください。\";' ' const reply = await ask([{ role: \"user\", content: prompt }], \"こんにちは、ご用件は何ですか。\", 20);' ' const inT = reply.inTokens === null ? Math.ceil(prompt.length / 2) : reply.inTokens;' ' const outT = reply.outTokens === null ? Math.ceil(reply.text.length / 2) : reply.outTokens;' ' const how = reply.inTokens === null ? \"推定\" : \"実測\";' ' console.log(\"あなたの呼び出し: 入力 \" + inT + \"、出力 \" + outT + \" (\" + how + \")、\" + cost(\"small\", inT, outT) + \" マイクロ円\");' '}' >> budget3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算を書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const calls = [' ' { user: \"yuki\", model: \"small\", in: 900, out: 300 },' ' { user: \"haruto\", model: \"small\", in: 1000, out: 500 },' ' { user: \"yuki\", model: \"large\", in: 200, out: 100 },' ' { user: \"sakura\", model: \"large\", in: 300, out: 50 },' ' { user: \"sakura\", model: \"small\", in: 500, out: 100 },' ' { user: \"ren\", model: \"large\", in: 200, out: 50 },' '];' >> budget3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算を書く(5)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' await demo();' ' calls.forEach(function (c, at) {' ' const price = cost(c.model, c.in, c.out);' ' const why = check(c.user, price);' ' const label = \"#\" + (at + 1) + \" \" + c.user + \" \" + c.model + \" \" + price + \" マイクロ円: \";' ' if (why === \"ok\") { charge(c.user, price); console.log(label + \"ok\"); }' ' else console.log(label + message(why, c.user));' ' });' ' console.log(\"今日の合計: \" + spent.day + \" マイクロ円 = \" + (spent.day / 1000000).toFixed(6) + \" 円\");' '}' 'main();' >> budget3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算を実行する
docker exec -w /home/node/project agent node budget3.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力の例
| 行 | 示していること |
|---|---|
| あなたの呼び出し: 入力 42、出力 17 (実測)... | あなた自身の呼び出し。応答に使用量があれば実測、なければ推定。実行のたびに変わる |
| #1 yuki small 2100 マイクロ円: ok | 課金された |
| #3 yuki large 6000 マイクロ円: yuki さん... | ユーザーの上限で断られ、メッセージが付く |
| #4 sakura large 5000 マイクロ円: アシスタントは... | 一日の上限で断られた |
| 今日の合計: 6000 マイクロ円 = 0.006000 円 | 課金された呼び出しだけが数えられる |
学生が入力するセルは、このプレビューでは空白です。