{"slug":"lmstudio-api","title":"lmstudio-api","summary":"Справочник по HTTP-API LM Studio (сервер локальных моделей, обычно порт 1234). Используй когда надо программно управлять моделями на LM Studio - узнать что загружено, загрузить или выгрузить модель, задать длину контекста и параллельность, отключить размышления модели, - а также ","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-24T17:35:10.944966Z","repo":{"url":"https://github.com/Desko77/claude-code-skills-1c","stars":72,"forks":16,"license":"MIT","updatedAt":"2026-09-24T10:53:41Z"},"bodyHtml":"<hr>\n<h2>name: lmstudio-api\ndescription: \"Справочник по HTTP-API LM Studio (сервер локальных моделей, обычно порт 1234). Используй когда надо программно управлять моделями на LM Studio - узнать что загружено, загрузить или выгрузить модель, задать длину контекста и параллельность, отключить размышления модели, - а также при диагностике: модель thinking/reasoning не отключается, enable_thinking игнорируется, ответ приходит пустым а весь бюджет уходит в reasoning_content, запросы падают с HTTP 500 на холодном старте или при загрузке крупной модели, ошибка Context size has been exceeded, Model is unloaded, эндпоинт отвечает 404 или 200 с телом Unexpected endpoint, неверно определяется длина контекста. Содержит проверенные схемы эндпоинтов v1, параметр reasoning, особенности протокола и замеры на 30-35B.\"</h2>\n<h1>LM Studio API</h1>\n<p>Проверено вживую на LM Studio 0.4.x, 2026-08-07, локальный сервер на порту 1234\n(AMD Strix Halo, 128 ГБ единой памяти). Замеры в конце - на <code>qwen3-vl-30b-a3b-instruct</code>.</p>\n<h2>Три поверхности API</h2>\n<table>\n<thead>\n<tr>\n<th>Поверхность</th>\n<th>Путь</th>\n<th>Назначение</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>OpenAI-совместимая</td>\n<td><code>/v1/*</code></td>\n<td>инференс: <code>chat/completions</code>; <code>models</code> отдает только КАТАЛОГ скачанных</td>\n</tr>\n<tr>\n<td>Нативная v0 (устаревшая)</td>\n<td><code>/api/v0/*</code></td>\n<td><code>models</code> с полем <code>state</code>; управления моделями нет</td>\n</tr>\n<tr>\n<td>Нативная v1</td>\n<td><code>/api/v1/*</code></td>\n<td>с LM Studio 0.4.0, рекомендуемая: инференс + управление моделями</td>\n</tr>\n</tbody>\n</table>\n<p>Anthropic-совместимые эндпоинты тоже заявлены в доках, не проверялись.</p>\n<h2>Эндпоинты v1</h2>\n<pre><code>POST /api/v1/chat                      (НЕ /api/v1/chat/completions)\nGET  /api/v1/models\nPOST /api/v1/models/load\nPOST /api/v1/models/unload\nPOST /api/v1/models/download\nGET  /api/v1/models/download/status\n</code></pre>\n<p><strong><code>load</code></strong>: обязателен <code>model</code> - точный ключ модели. Опционально <code>context_length</code>,\n<code>eval_batch_size</code>, <code>flash_attention</code>, <code>num_experts</code>, <code>offload_kv_cache_to_gpu</code>,\n<code>echo_load_config</code>. Всегда ставить <code>echo_load_config: true</code> - ответ вернет фактически\nпримененный конфиг, и сразу видно, что сервер проигнорировал.</p>\n<p>Ответ: <code>{\"type\", \"instance_id\", \"load_time_seconds\", \"status\": \"loaded\", \"load_config\": {...}}</code>.\nВызов БЛОКИРУЮЩИЙ - возвращается по факту загрузки, отдельно опрашивать готовность не нужно.</p>\n<p><strong><code>unload</code></strong>: обязателен <code>instance_id</code>, НЕ имя модели. Берется из <code>loaded_instances[].id</code>\nответа <code>GET /api/v1/models</code>.</p>\n<p><strong><code>GET /api/v1/models</code></strong> отдает <code>{\"models\":[...]}</code> с полями <code>key</code>, <code>type</code>, <code>publisher</code>,\n<code>architecture</code>, <code>quantization</code>, <code>size_bytes</code>, <code>max_context_length</code>, <code>capabilities</code>,\n<code>loaded_instances[]</code>. У инстанса - <code>id</code> и <code>config</code> с фактическими <code>context_length</code>,\n<code>parallel</code>, <code>flash_attention</code>, <code>eval_batch_size</code>, <code>num_experts</code> и прочим.\nПустой <code>loaded_instances</code> = модель не загружена.</p>\n<h2>Инференс: два эндпоинта, и они НЕ равнозначны</h2>\n<table>\n<thead>\n<tr>\n<th></th>\n<th><code>/v1/chat/completions</code> (OpenAI)</th>\n<th><code>/api/v1/chat</code> (родной)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>поле ввода</td>\n<td><code>messages</code></td>\n<td><code>input</code></td>\n</tr>\n<tr>\n<td>схема</td>\n<td>лишние ключи проглатывает</td>\n<td>строгая, лишний ключ -&gt; 400 <code>unrecognized_keys</code></td>\n</tr>\n<tr>\n<td>управление reasoning</td>\n<td>НЕТ (см. ниже)</td>\n<td><code>reasoning</code> первым классом</td>\n</tr>\n<tr>\n<td>ответ</td>\n<td><code>choices[].message.content</code></td>\n<td><code>output[].content</code></td>\n</tr>\n<tr>\n<td>статистика</td>\n<td><code>usage</code></td>\n<td><code>stats</code> c <code>reasoning_output_tokens</code></td>\n</tr>\n</tbody>\n</table>\n<p>Тело родного вызова и ответ:</p>\n<pre><code>{\"model\": \"...\", \"input\": \"&lt;весь промпт строкой&gt;\", \"reasoning\": \"off\"}\n\n{\"model_instance_id\": \"...\",\n \"output\": [{\"type\": \"message\", \"content\": \"Париж\"}],\n \"stats\": {\"input_tokens\": 22, \"total_output_tokens\": 4, \"reasoning_output_tokens\": 0,\n           \"tokens_per_second\": 64.8, \"time_to_first_token_seconds\": 1.79}}\n</code></pre>\n<h2>Как ВЫКЛЮЧИТЬ размышления (thinking / reasoning)</h2>\n<p><strong><code>chat_template_kwargs: {\"enable_thinking\": false}</code> НЕ РАБОТАЕТ.</strong> Для GGUF линейки qwen3.x\nв LM Studio этот параметр не прокидывается - зарегистрированный баг\n(lmstudio-ai/lmstudio-bug-tracker issue #1990). Замер на <code>qwen/qwen3.6-35b-a3b</code>: с флагом и\nбез него результат идентичен - весь бюджет <code>max_tokens</code> уходит в размышления,\n<code>finish_reason=length</code>, <code>content</code> пуст, ответа нет вообще.</p>\n<p>Это опасно вдвойне: многие клиенты при пустом <code>content</code> подставляют <code>reasoning_content</code>.\nТогда в парсер уезжает текст размышлений вместо ответа, и задача со строгим форматом (JSON)\nне падает с ошибкой, а тихо возвращает мусор.</p>\n<p><strong>Рабочий способ - параметр <code>reasoning</code> на <code>/api/v1/chat</code>:</strong></p>\n<pre><code>{\"model\": \"qwen/qwen3.6-35b-a3b\", \"input\": \"...\", \"reasoning\": \"off\"}\n</code></pre>\n<p>Тот же запрос через родной эндпоинт: <code>reasoning_output_tokens: 0</code>, чистый ответ в <code>content</code>,\n5.8 с против полного провала на OpenAI-совместимом пути.</p>\n<p><strong>Допустимые значения зависят от МОДЕЛИ.</strong> Схема эндпоинта принимает\n<code>off | low | medium | high | on</code>, но конкретная модель может поддерживать лишь часть:\n<code>qwen3.6</code> отвечает 400 <code>Reasoning setting 'low' is not supported by model ... Supported settings: 'off', 'on'</code>. Источник истины - <code>capabilities.reasoning.allowed_options</code> в\n<code>GET /api/v1/models</code>. Модель без блока <code>reasoning</code> в capabilities (варианты Instruct,\nнапример <code>qwen3-vl-30b-a3b-instruct</code>) не думает в принципе и параметра не требует.</p>\n<p>Практический вывод: <strong>не отбраковывать reasoning-модели</strong> из-за «неотключаемого thinking» -\nчерез родной эндпоинт они полностью управляемы. Отбраковка оправдана, только если клиент\nнамертво привязан к OpenAI-совместимому пути.</p>\n<h2>Грабли протокола</h2>\n<p><strong>Неизвестный путь отдает HTTP 200, а не 404.</strong> Тело при этом\n<code>{\"error\":\"Unexpected endpoint or method. (GET /path)\"}</code>. Код ответа НЕ доказывает\nсуществование маршрута - читать тело обязательно.</p>\n<p><strong>Несоответствие метода отдает 404, а не 405.</strong> GET по POST-эндпоинту выглядит как\n\"маршрута нет\". Зондировать наличие маршрута GET-ом бесполезно и приводит к ложному выводу.\nПравильно: POST с пустым телом - валидатор вернет 400 со схемой\n(<code>Missing required field 'model'</code>), и это доказывает, что маршрут есть.</p>\n<p><strong><code>/api/v0/models</code> не отдает <code>loaded_context_length</code> у незагруженной модели</strong> - поля просто\nнет в объекте. Идиома <code>x.get(\"loaded_context_length\") or x.get(\"max_context_length\")</code> молча\nподставит архитектурный потолок (262144 вместо реальных 32768) и обрушит расчет бюджета.\nГейтить строго по <code>state == \"loaded\"</code> (v0) или по непустому <code>loaded_instances</code> (v1).</p>\n<p><strong><code>load</code> может вернуть 500 на крупной модели, хотя загрузка при этом состоится.</strong> Замер:\nмодель на 37 ГБ отдала HTTP 500 через 138 секунд (клиентский таймаут был 900 - обрывал не\nклиент), а через интерфейс та же модель загрузилась штатно. Похоже на внутренний предел\nсервера на длительность загрузки. Следствие для кода: <strong>после неуспешного <code>load</code> не объявлять\nмодель недоступной сразу</strong> - перечитать <code>GET /api/v1/models</code> и посмотреть <code>loaded_instances</code>,\nзагрузка могла завершиться уже после ответа с ошибкой. Иначе уйдешь в фолбэк на модели,\nкоторая через полминуты будет готова.</p>\n<p><strong>Идентификатор модели должен быть точным, включая префикс публикатора.</strong> Вызов\n<code>qwen3.6-35b-a3b</code> вместо <code>qwen/qwen3.6-35b-a3b</code> заставит LM Studio считать это другой моделью\nи загрузить дубликат.</p>\n<p><strong>WebSocket-namespace SDK доступны по сети</strong>: <code>/llm</code>, <code>/system</code>, <code>/embedding</code>, <code>/files</code>,\n<code>/repository</code>, <code>/diagnostics</code> - рукопожатие проходит с удаленной машины. На этом канале\nработают <code>lms</code> CLI и SDK (<code>lmstudio-python</code>, <code>lmstudio-js</code>). Для управления моделями он\nбольше не нужен - хватает REST v1.</p>\n<h2>Параллельность и контекст</h2>\n<p><strong>Unified KV cache: слоты параллелизма делят ОДНО окно контекста.</strong> Действует\n<code>параллельность * (промпт + max_tokens) &lt;= context</code>, а НЕ <code>промпт + max_tokens &lt;= context</code>.\nИгнорирование дает HTTP 400 <code>Context size has been exceeded</code>.</p>\n<p>Планировать бюджет только от ФАКТИЧЕСКОГО <code>context_length</code> загруженного инстанса. Считать по\nпаспортному <code>max_context_length</code> нельзя: разрыв бывает восьмикратным.</p>\n<p>Серверный потолок одновременных предсказаний - поле <code>parallel</code> в конфиге инстанса, оно же\nMax Concurrent Predictions в интерфейсе. <strong>Оно ЗАДАЕТСЯ через <code>load</code>, хотя в документации\nэндпоинта не указано</strong> - проверено: <code>{\"model\": ..., \"context_length\": 65536, \"parallel\": 8}</code>\nприменяется, эхо конфига и интерфейс показывают 8. То есть менять его руками в GUI не нужно,\nпрофиль загрузки полностью задается из кода.</p>\n<p>Больше слотов не значит быстрее. Замер на 30B (Strix Halo): при параллельности 4 и 8\nагрегированная пропускная способность одинакова (46.0 и 47.4 ток/с) - железо насыщается уже\nна четырех, лишние слоты только размазывают ту же полосу. Хуже того, при отправке всех\nзапросов разом стена равна САМОМУ ДОЛГОМУ ответу, тогда как очередь на меньшем числе слотов\nсглаживает разброс. Сравнивая режимы, нормируй на фактически сгенерированные токены: при\nненулевой температуре один и тот же вход дает разный объем вывода (наблюдалось расхождение\n27% между прогонами), и сравнение по «стене» без нормировки врет.</p>\n<h2>Холодный старт: параллельные запросы в незагруженную модель отбиваются</h2>\n<p>Замер на 30B, 4 одновременных запроса в выгруженную модель:</p>\n<pre><code>JIT (без явной загрузки)   успешно 1 из 4, три отказа HTTP 500 за 0.0-0.1 с\nявный load, затем те же 4  успешно 4 из 4, отказов нет\n</code></pre>\n<p>Первый запрос инициирует JIT-загрузку, остальные сервер отбивает, пока модель грузится.</p>\n<p><strong>Тело пятисотки - generic HTML Node, без JSON и без кода ошибки:</strong></p>\n<pre><code>&lt;!DOCTYPE html&gt;&lt;html lang=\"en\"&gt;&lt;head&gt;&lt;meta charset=\"utf-8\"&gt;&lt;title&gt;Error&lt;/title&gt;&lt;/head&gt;\n&lt;body&gt;&lt;pre&gt;Internal Server Error&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;\n</code></pre>\n<p>Отличить \"модель еще грузится\" от настоящего сбоя по тексту НЕВОЗМОЖНО. Единственный\nнадежный признак - время ответа: отказ неготовности приходит за доли секунды, тогда как\nнастоящий запрос к 30B идет десятки секунд. Правило: HTTP 500 быстрее секунды трактовать\nкак неготовность - убедиться в загрузке и повторить, в счетчик сбоев не засчитывать.</p>\n<p><strong>Правильный порядок работы с моделью:</strong></p>\n<pre><code>1. GET /api/v1/models            - есть ли в каталоге, есть ли loaded_instances\n2. загружена                     -&gt; взять context_length из конфига инстанса\n3. не загружена                  -&gt; POST /api/v1/models/load (блокирующий, ждет сам)\n4. загрузка не удалась           -&gt; вот теперь модель действительно недоступна\n5. бюджет считать от фактического context_length\n6. первый запрос отправить в одиночку, потом выходить на параллельность\n</code></pre>\n<h2>Жизненный цикл модели: грузить один раз на всю работу</h2>\n<p>Загрузка стоит десятки секунд, выгрузка - две. Любая перезагрузка между стадиями или между\nфайлами серии - чистая потеря времени.</p>\n<pre><code>Начало работы (всей серии, не одного файла):\n  модель уже загружена -&gt; ИСПОЛЬЗОВАТЬ КАК ЕСТЬ, бюджет считать от ЕЕ контекста\n  не загружена         -&gt; load один раз, запомнить instance_id как свой\nВо время обработки:\n  не выгружать и не перезагружать ничего - ни между стадиями, ни между файлами\nКонец:\n  РАБОЧУЮ модель конвейера ОСТАВИТЬ загруженной, TTL уберет сам\n  РАЗОВУЮ модель из бенчмарка ВЫГРУЗИТЬ СРАЗУ - см. ниже\n  чужие инстансы не трогать никогда\n</code></pre>\n<p><strong>Исключение из \"оставить загруженными\": модель, поднятая под разовый замер.</strong> Правило держать\nинстанс теплым написано для рабочих моделей конвейера, где перезагрузка дорога и повторится.\nК модели, которую подняли один раз ради сравнения и отвергли, оно не относится: она занимает\nпамять до истечения TTL и деградирует всех соседей.</p>\n<p>Цена ошибки измерена 07.08.2026 на общем боксе. <code>qwen3.6-35b-a3b</code> осталась после ночного теста\nв Q8_0 с <code>context_length 262144</code> и <code>parallel 4</code> - веса 35 ГБ плюс KV-кэш под четверть миллиона\nтокенов на четыре слота. Соседняя gemma, обслуживающая интерактивный голосовой ввод, отвечала\n<strong>34.5 с на 8 токенов</strong>; после выгрузки лишней модели - 12.3 с на холодном KV и <strong>0.2 с на\nтеплом</strong>. Деградация в сотню раз держалась часами и выглядела как \"сервер тормозит\", а не как\nчей-то забытый инстанс.</p>\n<p>Отсюда два следствия. Первое: закончил замер - выгрузи свой инстанс тем же ходом, не откладывая\nна TTL. Второе: <strong>грузя модель под замер, задавать <code>context_length</code> явно</strong>, по реальной нужде\nзамера. Дефолт берет паспортный максимум (у MoE это сотни тысяч токенов), и KV-кэш съедает\nбольше, чем сами веса.</p>\n<p><strong>Подстраивать себя под модель, а не модель под себя.</strong> Если модель уже загружена с \"неудобным\"\nконтекстом - считать параллельность от него, а не перезагружать ради круглого числа. Сохраненный\nконфиг мог быть выставлен осознанно (наблюдалось <code>context_length: 50176</code> у 32B - число не круглое\nи явно не дефолтное), и модель может обслуживать чужую задачу. Перезагрузка оправдана, только если\nсуществующего окна не хватает даже на ОДИН запрос, и делать ее молча нельзя.</p>\n<p>Несколько крупных моделей спокойно живут рядом при достаточной памяти: 30B + 26B + 32B заняли\nоколо 65 ГБ из 128 и работали одновременно. Значит \"свопов\" между стадиями конвейера может не\nбыть вовсе - проверять со-резидентность до того, как городить оптимизацию порядка стадий.</p>\n<h2>Правило на общем сервере</h2>\n<p>Сервер моделей может обслуживать не только текущую задачу (типовой случай - параллельно\nработающий голосовой ввод на своей модели).</p>\n<p><strong>Выгружать разрешено только те инстансы, которые загрузил ты сам в этом прогоне.</strong>\nВсе, что застали загруженным, не трогать. Перед <code>unload</code> сверять <code>instance_id</code> со своим\nсписком, а не искать модель по имени.</p>\n<p>Авто-вытеснение LM Studio этим правилом не управляется: загрузка своей модели в принципе\nможет выбить чужую без явного <code>unload</code>. На боксе с большой памятью почти не грозит - на\n128 ГБ 30B и 26B держались одновременно, вытеснения не было. На тесной машине проверять\nсостояние чужих моделей ПОСЛЕ своей загрузки и сообщать пользователю, если что-то выгрузилось.</p>\n<h2>Замеры (qwen3-vl-30b-a3b-instruct, Q6_K, Strix Halo 128 ГБ)</h2>\n<pre><code>загрузка 30B (ctx 32768)      29-39 с      выгрузка 1.8-3.3 с\nзагрузка 32B (ctx 50176)      16-19 с\nконтекст при загрузке        32768 (паспортный max 262144)\n</code></pre>\n<p>Зрение по кадрам-скриншотам, <code>prompt_tokens</code> ПОСТОЯНЕН для всех кадров одного видео (зависит\nот разрешения, не от содержимого - наблюдалось 1196 и на разреженном, и на плотном экране):</p>\n<pre><code>редкий кадр (список участников)   completion 63-216,   латентность 19-27 с\nплотный кадр 1С                   completion 560-4904, латентность 45-144 с\n4 параллельно, редкие кадры       6 с на кадр амортизированно\n4 параллельно, плотные кадры      25.5 с на кадр, ~46 ток/с агрегированно\n</code></pre>\n<p>Текстовая задача (маппинг спикеров, вход ~7200 токенов, выход ~80):</p>\n<pre><code>qwen2.5-32b-instruct (плотная)          78.5 с\nqwen3-vl-30b-a3b-instruct (MoE)         21.8 с\nqwen/qwen3.6-35b-a3b, reasoning=off      5.8 с\nqwen/qwen3.6-35b-a3b через OpenAI-путь   провал: весь бюджет в reasoning, ответа нет\n</code></pre>\n<p>Со-резидентность: три модели одновременно (30B ctx 32768 + 35B ctx 262144 + 26B ctx 32000),\nсуммарно порядка 80 ГБ на 128 ГБ единой памяти - работают без вытеснения. Footprint сильно\nзависит от ВЫБРАННОГО контекста, а не только от веса: 35B весом 35.2 ГБ при окне 262144\nзанимает 37.8 ГБ. Грузя модель сам, задавай окно под задачу, а не паспортный максимум.</p>\n<p>Выводы для планирования. Латентность одного запроса и пропускная способность расходятся\nвчетверо - оценивать стоимость стадии по латентности значит завысить в разы. И MoE-модели\nна текстовых задачах дают кратный выигрыш над плотными при сопоставимом размере.</p>\n","files":[{"path":"SKILL.md","sizeBytes":23120,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-28T19:24:40.84872Z","sha256":"6CCED3970203C158C19BA2A444B876042739ACE574244BD833BF10B019F97D3C","sizeBytes":8480},"review":null,"source":{"repositoryUrl":"https://github.com/Desko77/claude-code-skills-1c","path":"skills/lmstudio-api","license":"MIT","commit":"3accdd9a57aca1aabbe49a892a932df403ad5059","subtreeSha":"C800CC3D5B5C2BD03539B9A1C64E12D0BB6A8DA908FB5430B3FDCB9D3D937B62","lastSyncedAt":"2026-09-27T20:54:37.190964Z"},"reviewedAt":"2026-08-28T19:29:22.229789Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/Desko77/claude-code-skills-1c/tree/main/skills/lmstudio-api"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install desko77-claude-code-skills-1c@llmmart"},{"target":"git","command":"git clone https://github.com/Desko77/claude-code-skills-1c.git"}]}