PowerShell и Claude API: Invoke-RestMethod, кириллица и стриминг
Как вызывать Claude из PowerShell так, чтобы кириллица не ломалась ни в запросе, ни в ответе: готовая функция для 5.1 и 7, обработка ошибок HTTP, параллельные запросы и стриминг через HttpClient.
5 мин · · разработка · автоматизация
PowerShell — естественный способ подключить Claude API к администрированию Windows: разбор журналов событий, генерация отчётов, обработка выгрузок. Сам запрос делается встроенным Invoke-RestMethod, а основные сложности связаны с кодировкой: в Windows PowerShell 5.1 кириллица легко превращается в «кракозябры» и в запросе, и в ответе. Ниже — простой пример, надёжная функция для обеих версий, обработка ошибок, параллельные запросы и стриминг.
Ключ и переменная окружения
Ключ вида fetch_... создайте в кабинете Zukko. Полный ключ показывается один раз. Задайте его переменной окружения:
$env:ZUKKO_API_KEY = 'fetch_...'
[Environment]::SetEnvironmentVariable('ZUKKO_API_KEY', 'fetch_...', 'User')Первая строка действует только в текущей сессии, вторая сохраняет переменную для пользователя — она появится в новых окнах PowerShell. Не вписывайте ключ в сами скрипты: их часто копируют и выкладывают в общие репозитории.
Простой запрос в PowerShell 7
Zukko принимает OpenAI-формат по адресу https://api.zukko.pro/v1. Для PowerShell 7 (лучше 7.4 и новее) достаточно такого кода:
$body = @{
model = 'claude-sonnet-5'
max_tokens = 400
messages = @(
@{ role = 'user'; content = 'Объясни разницу между Get-Content и Get-Content -Raw' }
)
} | ConvertTo-Json -Depth 10
$response = Invoke-RestMethod -Uri 'https://api.zukko.pro/v1/chat/completions' `
-Method Post `
-Headers @{ Authorization = "Bearer $env:ZUKKO_API_KEY" } `
-ContentType 'application/json; charset=utf-8' `
-Body ([System.Text.Encoding]::UTF8.GetBytes($body))
$response.choices[0].message.contentДве детали, на которых чаще всего ошибаются:
-Depth 10уConvertTo-Json. По умолчанию глубина равна 2, и вложенные объекты превращаются в строки видаSystem.Collections.Hashtable— API получит испорченный запрос.- Тело как байты UTF-8.
[System.Text.Encoding]::UTF8.GetBytes($body)гарантирует, что кириллица уйдёт в UTF-8 независимо от версии PowerShell и его настроек кодировки.
Почему в PowerShell 5.1 ломается кириллица
В Windows PowerShell 5.1 проблема бывает с двух сторон:
- Запрос. Если передать в
-Bodyстроку, 5.1 может закодировать её не в UTF-8. Решение то же — передавать байты. - Ответ. Если в заголовке ответа не указана кодировка,
Invoke-RestMethodв 5.1 может декодировать тело не как UTF-8, и русский текст в ответе превратится в нечитаемые символы. Надёжный обход — получить ответ черезInvoke-WebRequestи самому декодировать сырые байты как UTF-8.
Ещё одна мелочь: чтобы кириллица корректно отображалась в консоли 5.1, выполните [Console]::OutputEncoding = [System.Text.Encoding]::UTF8.
Функция для PowerShell 5.1 и 7
Функция ниже работает в обеих версиях: отправляет тело байтами, сама декодирует ответ, понимает системный промпт и принимает текст из конвейера.
function Invoke-Zukko {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string]$Prompt,
[string]$Model = 'claude-sonnet-5',
[string]$System,
[int]$MaxTokens = 800
)
process {
if (-not $env:ZUKKO_API_KEY) { throw 'Задайте переменную окружения ZUKKO_API_KEY' }
$messages = [System.Collections.ArrayList]@()
if ($System) { [void]$messages.Add(@{ role = 'system'; content = $System }) }Пояснения:
$err = $_сохраняет ошибку доswitch: внутриswitchпеременная$_означает уже проверяемое значение, а не исключение.- Код ответа берётся из
$err.Exception.Response.StatusCode— это свойство есть и у исключения 5.1, и у исключения PowerShell 7, а приведение к[int]даёт число. Тело ответа с ошибкой лежит в$err.ErrorDetails.Message. -UseBasicParsingнужен в 5.1, чтобы командлет не пытался разбирать ответ движком Internet Explorer; в PowerShell 7 параметр оставлен для совместимости и ничего не меняет.- Сообщения собираются в
ArrayList, поэтому массивmessagesсериализуется в JSON корректно и с одним, и с двумя элементами.
Примеры:
Invoke-Zukko 'Напиши регулярное выражение для IPv4-адреса'
Get-Content .\report.csv -Raw -Encoding UTF8 |
Invoke-Zukko -System 'Кратко опиши, что в этой выгрузке, и найди аномалии'
Get-WinEvent -LogName System -MaxEvents 50 |
Format-List TimeCreated, Id, LevelDisplayName, Message | Out-String |
Invoke-Zukko -System 'Сгруппируй события и укажи, на что обратить внимание' -Model 'glm-5.3-flash'Сохранить ответ в файл: Invoke-Zukko 'Черновик письма' | Set-Content .\draft.txt -Encoding UTF8.
Параллельные запросы
В PowerShell 7 есть ForEach-Object -Parallel. Функции из основной сессии внутри параллельного блока не видны, поэтому запрос пишем прямо в блоке, а переменные окружения доступны как обычно:
Get-ChildItem .\docs -Filter *.md | ForEach-Object -Parallel {
$body = @{
model = 'glm-5.3-flash'
max_tokens = 500
messages = @(
@{ role = 'system'; content = 'Перескажи текст в 5 пунктах.' },
@{ role = 'user'; content = (Get-Content $_.FullName -Raw -Encoding UTF8) }
)
} | ConvertTo-Json -Depth 10
$r = Invoke-RestMethod -Uri 'https://api.zukko.pro/v1/chat/completions' -Method Post `
-Headers @{ Authorization = "Bearer $env:ZUKKO_API_KEY" } `
-ContentType 'application/json; charset=utf-8' `
-Body ([System.Text.Encoding]::UTF8.GetBytes($body))
Set-Content -Path ($_.FullName + '.summary.txt') -Value $r.choices[0].message.content -Encoding utf8
} -ThrottleLimit 4-ThrottleLimit 4 держит не больше четырёх запросов одновременно; если получаете 429, уменьшите значение. В Windows PowerShell 5.1 параметра -Parallel нет — там используйте Start-Job (каждое задание запускается отдельным процессом, это медленнее) или обрабатывайте файлы последовательно функцией Invoke-Zukko.
Стриминг ответа
Invoke-RestMethod ждёт ответ целиком, поэтому для вывода текста по мере генерации нужен .NET HttpClient. Сервер присылает строки data: {...}, а последней — data: [DONE]. Перед ней приходит чанк с usage и пустым массивом choices, поэтому перед обращением к choices[0] проверяем Count:
Add-Type -AssemblyName System.Net.Http
$json = @{
model = 'claude-sonnet-5'
max_tokens = 800
stream = $true
messages = @(@{ role = 'user'; content = 'Напиши чек-лист аудита прав доступа в Active Directory' })
} | ConvertTo-Json -Depth 10
$client = [System.Net.Http.HttpClient]::new()
$request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Post, 'https://api.zukko.pro/v1/chat/completions')
$request.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $env:ZUKKO_API_KEY)
$request.Content = [System.Net.Http.StringContent]::new($json, [System.Text.Encoding]::UTF8, 'application/json')
Синтаксис ::new() работает начиная с PowerShell 5.0, так что код подходит и для 5.1. StreamReader явно получает UTF-8, поэтому кириллица в потоке не ломается.
Модели и стоимость
Модель передаётся параметром -Model. Цены в Zukko за 1M токенов (вход/выход): claude-sonnet-5 и claude-opus-5-5 — $0.30 / $0.30, gpt-5.6-sol — $0.30 / $0.30, gpt-5.6-luna — $0.10 / $0.10, glm-5.3-flash — $0.04 / $0.04. Актуальный список:
(Invoke-RestMethod 'https://api.zukko.pro/v1/models' -Headers @{ Authorization = "Bearer $env:ZUKKO_API_KEY" }).data.idили страница «Модели» в кабинете. Пример: ежедневный отчёт по журналу событий — 6 000 входных и 600 выходных токенов, 30 запусков в месяц. На claude-sonnet-5: 30 × (6 000 + 600) × $0.30 / 1 000 000 = $0.0594 в месяц. На glm-5.3-flash: 30 × 6 600 × $0.04 / 1 000 000 = $0.00792. Хватает ли дешёвой модели для вашего отчёта, проверьте на своих данных: сравните ответы двух моделей на одних и тех же журналах за неделю.
Частые вопросы
Ошибка «Не удалось создать защищённый канал SSL/TLS» в PowerShell 5.1
На старых системах .NET Framework по умолчанию может предлагать устаревшие версии TLS. Перед запросом выполните [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12. В PowerShell 7 это не требуется.
Ответ приходит в виде «РџСЂРёРІРµС‚»
Ответ декодирован не как UTF-8 — типичная ситуация для Invoke-RestMethod в 5.1. Используйте функцию Invoke-Zukko выше: она декодирует байты ответа сама. Если искажения только в консоли, а в файле текст нормальный, задайте [Console]::OutputEncoding = [System.Text.Encoding]::UTF8.
Как запускать скрипт по расписанию?
Через «Планировщик заданий»: действие powershell.exe -NoProfile -File C:\Scripts\report.ps1 (или pwsh.exe для PowerShell 7). Переменную ZUKKO_API_KEY задайте для учётной записи, от которой запускается задание. Для такого скрипта удобно завести отдельный ключ с дневным лимитом в долларах — см. лимиты ключей.
Можно ли использовать Anthropic-формат?
Да, POST https://api.zukko.pro/v1/messages с заголовком anthropic-version: 2023-06-01; текст ответа — в .content[0].text. Формат описан в справочнике Messages API, а OpenAI-формат — в справочнике chat completions.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Перевод субтитров нейросетью: SRT без сбитых таймкодовГлавный риск при переводе SRT нейросетью — сломанная структура файла. Показываем схему, где таймкоды не попадают к модели, и скрипт на Python с проверкой.
- Редактирование текстов нейросетью: корректура, стиль и сокращениеРазные виды редактуры требуют разных промптов. Показываем формулировки для корректуры, стиля и сокращения, как получать правки с объяснением и не терять авторский голос.
- Нейросеть для написания скриптов: Bash, PowerShell и PythonМодель быстро пишет скрипты для рутины, но запускать их вслепую нельзя. Показываем, как ставить задачу, требовать безопасный режим dry-run и проверять результат до запуска.
- Dify в Docker: установка, первичная настройка и подключение моделейСтавим Dify через Docker Compose, создаём администратора и подключаем модели Claude, GPT и DeepSeek по одному ключу. Отдельно — что делать, если контейнеры не стартуют.