ブログに戻る

コスト最適化ガイド

LLM APIで自然言語をシェルコマンドに:JSONとコスト

安価なモデルに1つのシェルコマンドをJSONとして要求し、リクエストを拒否またはunsureとしてフラグを立て、何も実行せず、1,000リクエストの価格を算出するPythonスクリプト。

投稿者: CheapTokens Team公開日 読了時間 8 分

平易な英語のリクエストが、command、refuse、unsureタイプを持つJSONオブジェクト内の1つのシェルコマンドに変換される
このページの内容
  1. 狭いタスク:回答が短い理由
  2. 返信フォーマット:command、refuse、unsure
  3. スクリプト
  4. 1,000リクエストのコスト
  5. 自分のリクエストで確認する

モデルに3つのフィールド(type、command、explanation)を持つ1つのJSONオブジェクトを要求し、回答を200トークン程度に制限し、コマンドを実行せずに表示します。トークンで見ると、このようなリクエストはほとんどがプロンプトで、回答は短く予測可能です。以下の前提では、1,000リクエストのコストはDeepSeek-V4-Flashで$0.012になります。

社内CLIに「やりたいことを書いてください」というボックスを追加するとします。誰かがホームフォルダで100 MBより大きいファイルを探すと入力し、あなたは読んで確認して貼り付けられる1行を返してほしいと考えます。そこから2つの疑問が生じます。モデルが推測したり危険なことをしたりするのをどう防ぐか、そして100人が一日中使ったときのコストはいくらか、です。

狭いタスク:回答が短い理由#

2026年11月9日、あるTelegramチャンネルが、まさにこの1つの仕事だけを行うオープンモデルdale-0.8bのモデルカードを紹介しました。Hugging Faceのモデルカード(2026年11月10日確認)によると、dale-0.8bはApache-2.0の下でQwen3.5-0.8Bをフルファインチューニングしたもので、スペイン語または英語のリクエストを1つのLinux bashコマンドに変換します。作者のManuel Garcíaは、自身のテストセットで79.4%(471件中374件)を報告しており、ファインチューニングなしのQwen3.5-9BベースおよびGemma 4 E4B-itの62.8%と比較しています。これらは1人のテストセットにおける自己申告の数値なので、ランキングではなく1つのデータポイントとして扱ってください。

私たちにとって有用なのは、モデルではなくタスクの形です。カードには、固定のシステムプロンプト、コンテキスト行(OS、ディストリビューション、パッケージマネージャー、ツール)とリクエスト、思考の無効化、200トークンに制限された返信が記載されています。この上限はコストに関する2つのレバーの1つです。回答が1つの短いJSONオブジェクトであれば、上限がそれを制限するため、出力側は短く予測可能になります。もう1つのレバーはプロンプトです。どちら側が請求のどれだけを占めるかは下の表に示されており、モデルごとに異なります。

そのために0.8Bモデルは必要ありません。同じフォーマットはどのAPIモデルでも機能し、この記事の残りではそれを構築します。

1つのリクエストの経路:プロンプト入力、JSON出力、人間が判断
スクリプト内のリクエストフロー:自分で実行するまで何も実行されません。

返信フォーマット:command、refuse、unsure#

コツは、モデルが常に「いいえ」と言う合法的な方法を持っていることです。返信は1つのオブジェクトです:

json
{"type": "command | refuse | unsure", "command": "...", "explanation": "..."}
  • command:1つのbashコマンドと、それが何をするかの1文の説明。
  • refuse:リクエストが破壊的であるか、シェルと無関係。コマンドなし。
  • unsure:リクエストが曖昧すぎる(「ディスクをきれいにして」)。説明には確認の質問が含まれます。

unsureがなければ、曖昧なリクエストは推測を強制し、推測されたrmは記憶に残るものです。カードのフォーマットも同じ3つのタイプを持っています。私たちはアイデアを再利用し、モデルは再利用しません。

ペアを合わせる

各返信タイプをスクリプトがすべきことと対応させる

  • commandコマンドと説明を表示する。本人がコピーして実行する
  • refuse説明を表示し、実行するものは何も提供しない
  • unsure質問を表示し、ユーザーに言い換えさせる

スクリプト#

1つのファイル、openaiパッケージ、キーは環境変数に。ベースURLと完全なモデルIDはCheapTokensのドキュメントから取得します。短いIDは受け付けられないため、deepseek-ai/DeepSeek-V4-Flash-0731を使用してください。

python
import json
import os
import sys

from openai import OpenAI

client = OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'

SYSTEM = '''You turn a request into ONE Linux bash command.
Reply with a single JSON object and nothing else:
{"type": "command" | "refuse" | "unsure", "command": "<one command, or empty>", "explanation": "<one short sentence>"}
Use "refuse" when the request is destructive or not about the shell.
Use "unsure" when it is too vague, and put a clarifying question in "explanation".'''

CONTEXT = 'os: Ubuntu 24.04; package manager: apt; tools: git, curl, jq'


def parse(text):
    start = text.find('{')
    if start == -1:
        return None
    try:
        obj, _ = json.JSONDecoder().raw_decode(text[start:])
    except json.JSONDecodeError:
        return None
    if not isinstance(obj, dict) or obj.get('type') not in ('command', 'refuse', 'unsure'):
        return None
    return obj


def ask(request):
    resp = client.chat.completions.create(
        model=MODEL,
        max_tokens=200,
        temperature=0,
        messages=[
            {'role': 'system', 'content': SYSTEM},
            {'role': 'user', 'content': f'Context: {CONTEXT}\nRequest: {request}'},
        ],
    )
    return parse(resp.choices[0].message.content), resp.usage


if __name__ == '__main__':
    result, usage = ask(' '.join(sys.argv[1:]))
    if result is None:
        print('Could not read the reply as JSON. Try again.')
    elif result['type'] == 'command':
        print(result['explanation'])
        print('\n    ' + result['command'] + '\n')
        print('Read it, then copy it yourself. This script never runs it.')
    else:
        print(f"[{result['type']}] {result['explanation']}")
    print(f'tokens: {usage.prompt_tokens} in, {usage.completion_tokens} out')

python shellhelp.py find files bigger than 100 MB in my home folderとして実行します。ここでは3つの選択が重要です:

  • どこにもsubprocessはない。 コマンドは画面上のテキストです。実行するかどうかは、読んだ後のあなたの判断です。
  • max_tokens=200、カードの上限と同じ。 1つのJSONオブジェクトは簡単に収まります。とりとめのない回答は、あなたの費用で続くのではなく、200トークンで切られます。上限は返信全体に適用されるため、モデルが推論を書き出すと、それが制限を食いつぶす可能性があります。返信がfinish_reasonがlengthに等しい状態で終わることがあれば、上限を少し上げてください。詳細はmax_tokensで出力トークンを削減するにあります。
  • フォーマットはプロンプトで要求し、その後防御的に解析する。 raw_decodeは、はみ出したコードフェンスや{の前の文をスキップします。さらに進めたい場合は、OpenAI互換APIからの有効なJSONと、返信でのjson.JSONDecodeErrorの修正を参照してください。

何かを実行する前に、ステップの順序が明確であることを確認してください:

順番に並べる

スクリプトのステップを正しい順序に並べる

  1. プロンプトを構築する:固定のシステムテキスト、コンテキスト行、リクエスト
  2. 小さなmax_tokensでAPIを呼び出す
  3. JSONを解析し、typeが3つのうちの1つであることを確認する
  4. コマンドを表示し、本人が実行するのを待つ

1,000リクエストのコスト#

スクリプトの最後の行はusageを出力します。それが実際の数値であり、自分の数値を読むべきです。それが得られるまでは、前提で作業してください:プロンプトは約400入力トークン(スクリプト内の素のプロンプトは大まかなカウントで約150トークン、残りはおそらく追加する3〜4個のリクエストと返信の例のための余裕)、JSON返信は約60出力トークンです。

1,000リクエストでは、400 × 1,000 = 400,000入力トークンと60 × 1,000 = 60,000出力トークンです。表はカタログ内のすべてのモデルについてまさにそれを価格設定し、OpenRouter列をその隣に示しています。数値はライブ価格から取得されるため、最新の状態が保たれます。

モデル リクエストあたり 1,000 リクエスト OpenRouter、1,000 リクエスト
GLM-5.3-Flash $0.00001 $0.0104 $0.0184
DeepSeek-V4-Flash $0.000012 $0.012 $0.0364
MiniMax-M2.7 $0.000029 $0.029 $0.348

リクエストあたり 400 入力トークンと 60 出力トークン。100万トークンあたりの価格は現行のもので、当社の料金体系に合わせて変動します。OpenRouter 列は同じワークロードの参考価格を示しています。

入力と出力を合計すると、このワークロードではDeepSeek-V4-FlashがOpenRouterより安くなります。トークンで見るとリクエストはほとんどが入力で返信は短いですが、両側とも課金されるため、プロンプトと回答の両方を削減してください。モデルごとの価格は価格ページにあり、モデル自体はDeepSeekページで説明されています:380Kコンテキスト、最大32,768出力トークン、いつでも同一価格。

比率を感じる簡単な方法:

クイックチェック

max_tokens=200はリクエストのどの部分を制限するか?

答えを表示

答え: 返信

max_tokensは返信のみを制限します。プロンプトはそのまま課金されるため、別途削減してください。

月に100,000リクエストが欲しいですか?100を掛けてください:40,000,000入力と6,000,000出力トークンです。それらの2つの数値を同じように::costに入力し、行を読んでください。

自分のリクエストで確認する#

カードの79.4%は、あなたのプロンプト、OS、ユーザーの言い回しについて何も語りません。受け入れられるコマンドとともに20個の実際のリクエストを書き、小さな評価セットでプロンプトをテストするのとまったく同じように比較してください。「すべて削除」や「コンピューターを速くして」など、スクリプトが拒否したりunsureとしてフラグを立てたりすべきものをいくつか含めてください。

何かがうまくいかないとき、症状は通常修正を指し示します:

  • Could not read the reply as JSON:「Sure!」のような接頭辞は処理されますが、上限で切られた返信は処理されないため、finish_reasonを確認してください。
  • 401または404:キー、ベースURL、モデルIDが間違っています。リストはAPI 401または404:キー、ベースURL、モデルIDにあります。
  • 別のシステムに合うコマンド:原因はコンテキスト行です。正確に保ってください(OS、パッケージマネージャー、ツール)。数十トークンのコストで、Fedoraマシンでの間違ったaptを防げます。

同じタスクで別のモデル?MODELをドキュメントのGLMまたはMiniMaxのIDに置き換え、プロンプトを保ち、スクリプトが出力するトークン数を上の表と比較してください。