RU ▾

API текста без цензуры для голосовых конвейеров

uncensoredhttps://api.xtts.cc/v1

Демонстрация TTS API: примеры запросов и ответов в реальном времени

Эта демонстрация показывает реальные запросы, которые можно выполнить за минуту, и формат ответа. API — текстовая модель, поэтому вывод — это текст для озвучки, JSON и вызовы функций, которые отдельный голосовой движок может прочитать вслух.

Обновлено

Изометрическая иллюстрация терминала, транслирующего текст, данные и потоки вызовов функций

Перед запуском

Честное предупреждение для тех, кто ожидает нажать кнопку воспроизведения и услышать голос: эта демонстрация TTS API не издает звуков. Сервис возвращает текст от одной модели, uncensored, через POST /v1/chat/completions. Есть второй эндпоинт, GET /v1/models, для списка доступных моделей. Ничто не возвращает аудио, изображения или эмбеддинги.

Зачем тогда демонстрация? Потому что многим речевым проектам нужна именно эта часть: автор сценария, очиститель текста, сплиттер, решающий, кто что говорит. Приведенные ниже примеры показывают выполнение этих задач, а формат вывода — это то, что будет парсить ваш речевой код.

Вам нужен ключ. Зарегистрируйтесь на странице API-ключей через Google или с помощью электронной почты и пароля, затем получите пробный баланс $0.50 в один клик. Он действителен в течение 7 дней и не требует привязки карты. Экспортируйте ключ, чтобы приведенные ниже команды могли его прочитать:

export XTTS_API_KEY="paste-your-key-here"
curl -s https://api.xtts.cc/v1/models -H "Authorization: Bearer $XTTS_API_KEY" | grep uncensored

Если этот вызов возвращает список JSON, включающий uncensored, ключ работает, и вы можете продолжить.

Демонстрация 1: обычный запрос

Самый минимальный полезный вызов запрашивает короткий абзац в разговорном стиле:

curl -s https://api.xtts.cc/v1/chat/completions \
  -H "Authorization: Bearer $XTTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "uncensored",
    "messages": [
      {"role": "system", "content": "You write short lines for a voice actor. No stage directions."},
      {"role": "user", "content": "Welcome listeners to a show about lighthouses. Two sentences."}
    ],
    "temperature": 0.7,
    "max_tokens": 120
  }'

Ответ выглядит так, как показано ниже. Текст внутри content будет отличаться при каждом запуске, поэтому воспринимайте его как иллюстрацию структуры, а не как записанный вывод.

{
  "id": "chatcmpl-example",
  "object": "chat.completion",
  "model": "uncensored",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Welcome to the show. Tonight we climb the stairs of the world's loneliest lighthouses."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 38, "completion_tokens": 21, "total_tokens": 59}
}

Ваш код для синтеза речи читает choices[0].message.content. Поле finish_reason указывает причину завершения генерации: stop для естественного завершения, length, если max_tokens обрезал текст. Если вы не указываете max_tokens, завершение может занять оставшееся место в окне на 100 000 токенов, которое включает промпт и завершение вместе.

Что говорят заголовки

Добавьте -i в команду curl, и вы увидите X-Request-Id, X-Balance-USD, X-RateLimit-Limit-Requests и X-RateLimit-Limit-Concurrency. Заголовок баланса полезен в демо, потому что вы можете наблюдать, как он уменьшается на крошечную сумму после каждого вызова. Для этого запроса использование выше составляет 38 x $0,15 / 1 000 000 плюс 21 x $0,50 / 1 000 000, что равно $0,0000057 плюс $0,0000105, около $0,0000162.

Демонстрация 2: потоковая передача сценария

Для аудио вы редко хотите ждать полный текст. Добавьте "stream": true, и API отправляет Server-Sent Events, по небольшому фрагменту текста за раз. Пример на Node ниже выводит текст по мере поступления, и именно сюда можно подключить очередь предложений.

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.xtts.cc/v1",
  apiKey: process.env.XTTS_API_KEY,
});

const stream = await client.chat.completions.create({
  model: "uncensored",
  stream: true,
  messages: [
    { role: "user", content: "Read a four-line weather report for a coastal town." },
  ],
});

for await (const chunk of stream) {
  const piece = chunk.choices[0]?.delta?.content ?? "";
  process.stdout.write(piece);
}
process.stdout.write("\n");

Запустите его как ES-модуль (сохраните как demo.mjs и запустите node demo.mjs после npm install openai). В сети каждое событие — это строка, начинающаяся с data:, за которой следует JSON, и поток закрывается финальным маркером. Использование токенов сообщается в конце потока. Один важный момент для планирования: потоковый запрос удерживает слот параллелизма до тех пор, пока поток не завершится, поэтому медленный потребитель может заблокировать ваш следующий запрос.

Демонстрация 3: вызов функций как маршрутизатор речи

Вызов функций позволяет модели вернуть структурированную инструкцию вместо текста. В голосовом проекте это чистый способ выбрать стиль подачи для каждой строки. Вы определяете функцию с именем say, и модель заполняет ее аргументы. Ваш код затем решает, что с ними делать. Модель не выполняет ничего самостоятельно.

import json
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])

tools = [{
    "type": "function",
    "function": {
        "name": "say",
        "description": "Queue one line for the voice engine.",
        "parameters": {
            "type": "object",
            "properties": {
                "text": {"type": "string"},
                "style": {"type": "string", "enum": ["calm", "urgent", "whisper"]},
            },
            "required": ["text", "style"],
        },
    },
}]

messages = [
    {"role": "system", "content": "Always answer by calling say, once per line."},
    {"role": "user", "content": "The alarm sounds. The guard tells everyone to leave."},
]

r = client.chat.completions.create(
    model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)

msg = r.choices[0].message
for call in msg.tool_calls or []:
    args = json.loads(call.function.arguments)
    print(args["style"], "->", args["text"])

Аргументы поступают в виде строки JSON, поэтому распарсите их и проверьте перед использованием. Модели могут выдать стиль вне вашего перечисления или пропустить поле, и непроверенное значение никогда не должно достигать вашего движка. Если вы хотите продолжить разговор, добавьте сообщение ассистента и ответ с role: "tool" и соответствующим tool_call_id, затем снова вызовите API.

При потоковой передаче вызовы функций поступают частями. Имя функции приходит первым, а строка аргументов накапливается по частям, поэтому собирайте фрагменты по индексу и парсите их один раз после завершения потока.

Демонстрация 4: JSON-режим для списка ролей

Если вы предпочитаете пропустить определения функций, JSON-режим дает валидный JSON в содержании сообщения. Это хорошее решение для преобразования черновой сцены в строки по говорящим.

curl -s https://api.xtts.cc/v1/chat/completions \
  -H "Authorization: Bearer $XTTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "uncensored",
    "response_format": {"type": "json_object"},
    "temperature": 0.2,
    "messages": [
      {"role": "system", "content": "Return JSON: {\"lines\": [{\"speaker\": string, \"text\": string}]}"},
      {"role": "user", "content": "Ana asks Ben to hold the ladder. Ben agrees reluctantly."}
    ]
  }'

JSON-режим гарантирует, что вывод будет распарсен, а не то, что он соответствует вашей структуре. Опишите структуру в промпте, как выше, и проверяйте ключи в коде. Низкое значение температуры сохраняет структуру стабильной.

Демонстрация 5: длинный документ в одном запросе

Контекстное окно составляет 100 000 токенов, общих для промпта и завершения. Этого достаточно, чтобы отправить длинную расшифровку или целый короткий рассказ и попросить версию, написанную для слуха. Тело запроса может быть до 8 МБ, поэтому размер редко является ограничением; ограничением является количество токенов.

Вот стоимость тяжелого вызова. Скажем, вы отправляете 80 000 токенов и получаете 2 000 обратно. Вход: 80 000 x $0,15 / 1 000 000 = $0,012. Выход: 2 000 x $0,50 / 1 000 000 = $0,001. Итого $0,013, поэтому пробных $0,50 хватит примерно на 38 таких вызовов. Длинный вход дешев по сравнению с длинным выходом, поэтому краткие пересказы и переписывание больших текстов доступны по цене, а вот генерация целого романа за один раз — нет.

Практический совет для длинных входов: помещайте инструкции в начало и повторяйте ключевое правило в конце. Модели склонны лучше следовать последней инструкции, и однострочное напоминание после 80 000 токенов исходного текста стоит почти ничего.

Чтение ошибок в демонстрации

Намеренно сломайте что-то один раз, чтобы узнать это позже. Отправьте запрос с неверным ключом, и вы получите ошибку аутентификации вместо завершения. Отправьте десять запросов одновременно с пробного ключа, и некоторые из них вернут HTTP 429.

У 429 есть три причины, и код ошибки различает их:

  • rate_limited означает слишком много запросов за одну минуту: 60 на пробном ключе, 300 на полном ключе.
  • concurrency означает слишком много запросов в процессе одновременно: 2 на пробном ключе, 8 на полном ключе.
  • 429 с заголовком Retry-After: 5 означает, что сервис был временно перегружен. Подождите пять секунд и отправьте его снова.

Неудачные или отклоненные запросы не тарифицируются, поэтому ошибки ничего не стоят. Чтобы увидеть лимиты вашего ключа, прочитайте X-RateLimit-Limit-Requests и X-RateLimit-Limit-Concurrency в любом нормальном ответе. Небольшой цикл повторных запросов, который ждет значение Retry-After или пять секунд, если оно отсутствует, — это все, что нужно для демонстрационного скрипта.

Что попробовать дальше

После того как эти четыре вызова заработают, у вас есть компоненты конвейера для сценариев. Оберните обычный запрос в функцию, запустите цикл потоковой передачи в очередь и подключите очередь к речевому движку по вашему выбору. Движки различаются по цене, лицензии и качеству, поэтому ознакомьтесь с их текущими условиями. Этот сервис такой движок не предоставляет и ничего о них не заявляет.

Затраты остаются небольшими во время экспериментов. При цене $0,15 за миллион входных токенов и $0,50 за миллион выходных токенов, типичный вызов демо обходится в долю цента, а пробные $0,50 покрывают сотни таких вызовов. См. страницу цен для пополнения от $20 до $500 с бонусами от 5% до 15%, оплата криптовалютой. документация охватывает полный список параметров, лимиты запросов (8 одновременных и 300 в минуту для полного ключа) и коды ошибок. Промпты не используются для обучения.

Разумный порядок действий для первого вечера: запустите обычный запрос, пока интонация не станет правильной, переключитесь на потоковую передачу и выводите предложения по мере завершения, затем добавьте либо вызовы инструментов, либо JSON-режим, в зависимости от того, как ваш речевой код хочет получать ввод. Храните каждый промпт в файле под контролем версий. Когда строка звучит неправильно в финальном аудио, вы можете отследить ее до точной инструкции, которая ее сгенерировала, и изменить одну вещь за раз.

Вопросы и ответы

Могу ли я услышать образец голоса в этой демонстрации?

Нет. API возвращает только текст, JSON и вызовы инструментов. Вы предоставляете отдельный речевой движок для преобразования текста в аудио.

Какой ID модели нужно указывать?

Укажите uncensored как значение модели, используя базовый URL https://api.xtts.cc/v1 и свой ключ в заголовке Authorization: Bearer.

Работают ли демо-примеры с пробным API-ключом?

Да. Пробный ключ позволяет выполнять 2 параллельных запроса и 60 запросов в минуту, чего достаточно для этих примеров. Запускайте их по одному.

Почему я получаю ответ 429?

Вы превысили лимит запросов в минуту (rate_limited), у вас было слишком много активных запросов одновременно (concurrency) или сервис был временно перегружен. Если указан заголовок Retry-After, дождитесь его истечения и повторите запрос.

Ваш ключ — всего в одной форме от вас

Создайте аккаунт, скопируйте ключ, измените базовый URL. Это вся настройка.