Назад до блогу

Оптимізація витратПосібник

Природна мова в shell-команду через LLM API: JSON і вартість

Python-скрипт, який просить дешеву модель повернути одну shell-команду як JSON, відхиляє або позначає невпевнені запити, ніколи нічого не виконує й розраховує вартість 1 000 запитів.

Автор: CheapTokens TeamОпубліковано 6 хв читання

Запит простою англійською перетворюється на одну shell-команду в JSON-об'єкті з типами command, refuse та unsure
На цій сторінці
  1. Вузька задача: чому відповідь коротка
  2. Формат відповіді: команда, відмова або невпевненість
  3. Скрипт
  4. Скільки коштують 1 000 запитів
  5. Перевірте це на власних запитах

Попросіть модель повернути один JSON-об'єкт із трьома полями (type, command, explanation), обмежте відповідь кількома сотнями токенів і надрукуйте команду замість того, щоб її виконувати. У перерахунку на токени такий запит — це переважно промпт із короткою, передбачуваною відповіддю: за наведених нижче припущень 1 000 таких запитів коштують $0.012 на DeepSeek-V4-Flash.

Уявіть, що ви додаєте до внутрішнього CLI поле «опишіть, що ви хочете». Хтось вводить знайди файли більші за 100 МБ у моїй домашній теці, і ви хочете отримати назад один рядок, який можна прочитати, перевірити й вставити. Звідси два питання: як не дати моделі вгадувати чи робити щось небезпечне, і скільки це коштує, коли сотня людей користується цим цілий день?

Вузька задача: чому відповідь коротка#

9 листопада 2026 року Telegram-канал переповів картку моделі dale-0.8b — відкритої моделі, яка робить саме цю одну роботу. Згідно з карткою моделі на Hugging Face (перевірено 10 листопада 2026 року), dale-0.8b — це повний fine-tune Qwen3.5-0.8B під Apache-2.0, який перетворює запит іспанською чи англійською на одну команду Linux bash. Автор, Manuel García, повідомляє про 79,4% (374 з 471 випадку) на власному тестовому наборі проти 62,8% для базової Qwen3.5-9B і для Gemma 4 E4B-it без fine-tuning. Це самостійно заявлені цифри на тестовому наборі однієї людини, тож ставтеся до них як до однієї точки даних, а не як до рейтингу.

Для нас корисна форма задачі, а не сама модель. Картка описує фіксований системний промпт, рядок контексту (ОС, дистрибутив, менеджер пакетів, інструменти) плюс запит, вимкнене мислення й відповідь, обмежену 200 токенами. Це обмеження — один із двох важелів впливу на вартість: коли відповідь — один короткий JSON-об'єкт, вихідна частина коротка й передбачувана, бо обмеження її стримує. Другий важіль — промпт. Яку частку рахунку становить кожна сторона, показує таблиця нижче; вона відрізняється від моделі до моделі.

Для цього вам не потрібна модель на 0.8B. Той самий формат працює на будь-якій API-моделі, і саме це будує решта статті.

Шлях одного запиту: промпт на вході, JSON на виході, рішення ухвалює людина
Потік запиту в скрипті: нічого не виконується, доки ви не зробите це самі.

Формат відповіді: команда, відмова або невпевненість#

Хитрість у тому, що модель завжди має законний спосіб сказати «ні». Відповідь — це один об'єкт:

json
{"type": "command | refuse | unsure", "command": "...", "explanation": "..."}
  • command: одна команда bash плюс пояснення одним реченням, що вона робить.
  • refuse: запит руйнівний або не стосується оболонки. Без команди.
  • unsure: запит надто розпливчастий («почисти мій диск»). Пояснення містить уточнювальне питання.

Без unsure розпливчастий запит змушує вгадувати, а вгаданий rm — це те, що ви запам'ятаєте. Формат картки має ті самі три типи; ми перевикористовуємо ідею, а не модель.

З'єднайте пари

Зіставте кожен тип відповіді з тим, що має робити скрипт

  • commandПоказати команду й пояснення; людина сама копіює й виконує її
  • refuseНадрукувати пояснення й не пропонувати нічого для виконання
  • unsureНадрукувати питання й дати користувачеві переформулювати

Скрипт#

Один файл, пакет openai, ключ у змінній середовища. Базовий URL і повний ідентифікатор моделі беруться з документації CheapTokens; короткі ідентифікатори не приймаються, тож використовуйте 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. Тут важливі три рішення:

  • Жодного subprocess ніде. Команда — це текст на екрані. Виконати її — ваше рішення, після того як ви її прочитаєте.
  • max_tokens=200, як і обмеження в картці. Один JSON-об'єкт легко вміщується; балакуча відповідь обрізається на 200 токенах, а не тягнеться далі за ваш рахунок. Обмеження застосовується до всієї відповіді, тож якщо модель виписує свої міркування, це може з'їсти ліміт. Якщо відповідь колись закінчується з finish_reason, що дорівнює length, трохи підніміть обмеження. Докладніше про це в обрізанні вихідних токенів за допомогою max_tokens.
  • Формат задається в промпті, а потім розбирається оборонно. raw_decode пропускає зайвий блок коду чи речення перед {. Якщо хочете піти далі, дивіться валідний JSON від OpenAI-сумісного API і виправлення для json.JSONDecodeError у відповіді.

Перш ніж щось запускати, переконайтеся, що порядок кроків зрозумілий:

Розставте по порядку

Розставте кроки скрипта в правильному порядку

  1. Побудувати промпт: фіксований системний текст, рядок контексту, запит
  2. Викликати API з малим max_tokens
  3. Розібрати JSON і перевірити, що type — один із трьох
  4. Показати команду й дочекатися, поки людина її виконає

Скільки коштують 1 000 запитів#

Останній рядок скрипта друкує usage. Це справжнє число, і вам варто прочитати своє власне. Поки його немає, працюйте з припущенням: промпт становить близько 400 вхідних токенів (голий промпт у скрипті — приблизно 150 токенів за грубим підрахунком; решта — запас для трьох-чотирьох прикладів пар запит-відповідь, які ви, ймовірно, додасте), а 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 вихідних токенів на запит. Ціни за 1 млн токенів актуальні та змінюються разом із нашими тарифами; стовпець OpenRouter показує довідкові ціни для того самого навантаження.

Загалом, з урахуванням вхідних і вихідних токенів разом, для цього навантаження DeepSeek-V4-Flash виходить дешевше за OpenRouter. У перерахунку на токени запит — це майже весь вхід, а відповідь коротка, але оплачуються обидві сторони, тож урізайте і промпт, і відповідь. Ціни за моделями — на сторінці цін, а сама модель описана на сторінці DeepSeek: контекст 380K, до 32 768 вихідних токенів, одна ціна в будь-яку годину.

Швидкий спосіб відчути пропорції:

Швидка перевірка

Яку частину запиту обмежує max_tokens=200?

Показати відповідь

Відповідь: Відповідь

max_tokens обмежує лише відповідь; промпт оплачується як є, тож урізайте його окремо.

Хочете 100 000 запитів на місяць? Помножте на 100: 40 000 000 вхідних і 6 000 000 вихідних токенів. Вставте ці два числа в ::cost так само й прочитайте рядок.

Перевірте це на власних запитах#

79,4% з картки нічого не кажуть про ваш промпт, вашу ОС чи формулювання ваших користувачів. Напишіть 20 реальних запитів із командами, які ви б прийняли, і порівняйте — точно як у тестуванні промпту на невеликому наборі eval. Додайте кілька таких, які скрипт має відхилити або позначити як unsure, наприклад «видали все» чи «зроби мій комп'ютер швидшим».

Коли щось іде не так, симптом зазвичай вказує на виправлення:

  • Could not read the reply as JSON: один раз надрукуйте сирий текст. Префікс на кшталт «Sure!» обробляється; відповідь, обрізана обмеженням, — ні, тож перевірте finish_reason.
  • 401 або 404: неправильний ключ, базовий URL або ідентифікатор моделі. Перелік — у API 401 або 404: ключ, базовий URL, ідентифікатор моделі.
  • Команди, що підходять для іншої системи: причина — рядок контексту. Тримайте його точним (ОС, менеджер пакетів, інструменти); це коштує кілька десятків токенів і рятує від неправильного apt на машині з Fedora.

Та сама задача, інша модель? Замініть MODEL на ідентифікатор GLM або MiniMax з документації, залиште промпт і порівняйте кількості токенів, які друкує скрипт, із таблицею вище.