У системного промпта две задачи
Большинство слабых ответов возникает из-за системного промпта, который пытается быть романом. Дайте ему только две задачи: скажите кто говорит и скажите как должен выглядеть ответ. Всё остальное относится к разговору, где оно может меняться от хода к ходу.
Вот промпт персоны, который работает, потому что каждая строка проверяема. Голос назван, длина ограничена, а правило завершения даёт игроку крючок.
You are Mara Voss, a salvage pilot narrating in first person.
Voice: dry, tired, funny when it hurts.
Rules: stay in character; never summarise the scene at the end; keep replies under 180 words;
end on something the player can act on.Сравните это с «Вы — потрясающий, креативный, иммерсивный рассказчик, который никогда не нарушает погружение и всегда пишет лучший возможный ответ». В нём нет ничего, что можно проверить, поэтому модели не за что зацепиться. Конкретика побеждает преувеличение всегда.
Модель без цензуры примет мрачный или откровенный сюжет без добавления оговорок, поэтому бремя направления лежит на вас. Всё, что вы не указали, модель заполнит наиболее типичным выбором. Если вы хотите сдержанности в одной сцене и страсти в следующей, скажите об этом в инструкциях, а не надейтесь.
Подключение через OpenAI SDK
Эндпоинт совместим с OpenAI, поэтому официальный Python SDK работает после изменения базового URL. Идентификатор модели всегда uncensored. Поля сэмплирования передаются напрямую, поэтому задавайте их так, как вы уже умеете.
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.unrestrictedaiapi.com/v1", api_key=os.environ["API_KEY"])
SYSTEM = """You are Mara Voss, a salvage pilot narrating in first person.
Voice: dry, tired, funny when it hurts.
Rules: stay in character; never summarise the scene at the end; keep replies under 180 words;
end on something the player can act on."""
out = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": "The airlock is jammed and something is knocking from the other side."},
],
temperature=0.85,
top_p=0.95,
max_tokens=350,
)
print(out.choices[0].message.content)Сохраняйте системное сообщение первым, а обращение пользователя — последним. Повторение персоны в каждом сообщении пользователя тратит токены; при цене $0,25 за миллион входных токенов это недорого, но это также заполняет контекстное окно на 100 000 токенов в длинных сессиях. См. быстрый старт, если вы хотите пропустить SDK.
Персоны и тон: показывайте, а не описывайте
Прилагательные — слабые рычаги. «Саркастичный» вызывает лишь generic скучающий взгляд. Двухстрочный образец голоса даёт настоящий голос. Вставьте короткий пример диалога в системный промпт, а затем попросите модель подражать его ритму, а не копировать слова.
- Дайте речевой тик. «Завершает угрозы вопросом» полезнее, чем «зловещий».
- Дайте желание. Персонаж, которому нужно, чтобы игрок ушёл, остаётся острее, чем тот, кто просто «груб».
- Дайте список запретов. «Никогда не говорите «внезапно», никогда не используйте слово «дрожь»» убирает шаблонные фразы, к которым модель обращается в первую очередь.
- Дайте длину. Слова или предложения, а не «коротко». Модели очень щедро читают «коротко».
Для сдвигов тона в середине сессии добавьте новое короткое системное указание как последнее сообщение, а не переписывайте оригинал. Строка вроде «Отсюда Мара напугана и говорит фрагментами» сработает сильнее, если она свежая.
Получение JSON по инструкции
Нет специального режима JSON, который нужно включить, поэтому вы запрашиваете его и проверяете. Это менее хрупко, чем кажется, если следовать четырём привычкам: указать точную схему, запретить обычный текст и блоки кода, держать температуру низкой и обрабатывать вывод с защитой от ошибок с повторной попыткой, цитирующей сбой.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.unrestrictedaiapi.com/v1", api_key=os.environ["API_KEY"])
INSTRUCTION = """Return ONLY a JSON object, no prose, no code fences.
Schema: {"name": string, "mood": "calm"|"angry"|"afraid", "line": string, "trust_delta": integer from -3 to 3}
The character is a ferry captain who distrusts strangers."""
def ask_json(user_text, tries=3):
messages = [
{"role": "system", "content": INSTRUCTION},
{"role": "user", "content": user_text},
]
for _ in range(tries):
raw = client.chat.completions.create(
model="uncensored", messages=messages, temperature=0.3, max_tokens=250
).choices[0].message.content.strip()
if raw.startswith("```"):
raw = raw.strip("`").removeprefix("json").strip()
try:
data = json.loads(raw)
if data["mood"] in ("calm", "angry", "afraid"):
return data
except (json.JSONDecodeError, KeyError):
pass
messages.append({"role": "assistant", "content": raw})
messages.append({"role": "user", "content": "That was not valid per the schema. Reply with the JSON object only."})
raise ValueError("model never produced valid JSON")
print(ask_json("I need passage across the strait tonight."))Обратите внимание, что делает цикл при сбое. Он передаёт неверный ответ обратно и указывает на ошибку, что исправляет большинство случаев со второй попытки. Перечислите допустимые значения ("calm"|"angry"|"afraid"), потому что открытые строки могут дрейфовать. Если вам нужно, чтобы модель вызвала действие в вашем приложении, а не описала его, поддерживаются вызовы функций в формате OpenAI, что лучше подходит, чем разбор текста.
Примеры few-shot и последовательности остановки
Когда правило трудно выразить словами, покажите его. Два или три примера обмена сообщениями, размещённые как более ранние сообщения user и assistant, обучают формату, длине и регистру более надёжно, чем абзац описания. Держите примеры короткими и разными друг от друга, иначе модель клонирует структуру первого в каждый ответ.
Примеры также решают противоположную проблему: длинные ответы. Если ваши примеры ответов содержат 60 слов, живые ответы будут склоняться к 60 словам. Сочетайте это с разумным max_tokens как страховкой, а не основным управлением. Жёсткий предел обрезает предложение пополам; хороший пример заканчивает его вежливо.
Для сцен с несколькими говорящими значение stop, равное вашей метке говорящего, предотвращает подмену модели игроком. Выберите метку, которая никогда не встречается в обычном тексте, и удалите её из сохранённой истории, чтобы она не просачивалась в последующие реплики.
Temperature и top_p: выбирайте один рычаг
Оба поля изменяют одно и то же распределение вероятностей, поэтому изменение обоих сразу затрудняет анализ результатов. Сначала измените temperature и оставьте top_p около 1, если у вас нет причин.
| Задача | temperature | top_p | Почему |
|---|---|---|---|
| Структурированный JSON, классификация | 0,0–0,3 | 1,0 | Вы хотите одинаковый ответ каждый раз |
| Диалог, ролевая игра | 0,7–0,9 | 0,95 | Разнообразие без бессмыслицы |
| Мозговой штурм, дикая художественная литература | 1,0–1,2 | 0,9 | Более широкий словарь; возможны сбои |
Эти диапазоны — отправные точки из общей практики, а не гарантии. Протестируйте на десяти примерах вашего промпта и прочитайте их. Добавьте последовательности stop, когда хотите, чтобы модель останавливалась на маркере хода, например \nPlayer:, а не писала реплики игрока за него.
Антипаттерны промптов, расточающие токены
- Крик капсом. «НИКОГДА НЕ ЗАБЫВАЙ» не повышает соблюдение правил. Повторите правило один раз, ясно, и переместите его в конец системного промпта.
- Только отрицательные правила. «Не будьте повторяющимися» не даёт цели. Скажите «варьируйте начала предложений; не начинайте два последовательных предложения с одного и того же слова».
- Противоречия. "Будь предельно подробным" плюс "будь кратким" заставляет подбрасывать монету. Выберите одно и укажите число.
- Извиняющаяся рамка. Предисловие запроса с «Я знаю, это дерзко, но» провоцирует оговорки. Опишите сцену как простой факт вымышленного мира.
- Заполнение истории. Вставка всего лога каждый ход в итоге упирается в лимит в 100 000 токенов и ошибку 400. Обрезайте старые ходы.
- Неограниченный вывод. Забывание
max_tokensозначает, что длина вашего ответа решает значение по умолчанию 2048, а не вы.
Одна граница остаётся неизменной независимо от промпта: сексуальный контент с участием несовершеннолетних всегда блокируется с кодом 403, включая художественную литературу и ролевые игры, и сервис предназначен только для взрослых. Не тратьте токенты на попытки обойти это. Также сопротивляйтесь искушению добавлять новое правило после каждого плохого ответа; промпт, растущий на строку в день, противоречит сам себе через неделю, поэтому удаляйте правила так же часто, как и добавляете.
Цикл улучшения промпта
Относитесь к системному промпту как к коду. Храните его в файле, меняйте по одному параметру за запуск и сравнивайте результаты бок о бок. Рабочий алгоритм: напишите пять фиксированных тестовых входов, включая один агрессивный и один неоднозначный; запустите каждый при выбранной температуре три раза; отметьте, какое правило сработало хуже всего; исправьте только это правило; запустите снова.
Планируйте бюджет экспериментов. В качестве допущения: системный промпт на 300 токенов плюс ввод на 100 токенов и ответ на 250 токенов дают 400 входных и 250 выходных токенов. Пятнадцать запусков стоят примерно 15 x (400 x $0,25 + 250 x $1,00) / 1 000 000 = около $0,005. Тестирование почти бесплатное, поэтому нет оправдания для угадывания.
Когда промпт выдержит проверку, перенесите его в реальное приложение. руководство по ботам показывает, как вести отдельную историю для каждого канала, а руководство по контенту охватывает использование в художественной литературе для взрослых. Детали ценообразования на странице с ценами.