Туториал Anthropic по промптам, часть 2: средний уровень (главы 4–7)
Перевод глав 4–7 Prompt Engineering Interactive Tutorial: шаблоны и XML-теги для отделения данных от инструкций, формат ответа и prefill, рассуждение по шагам, примеры в промпте. С упражнениями под локальную модель.
Вторая часть перевода туториала Anthropic. Здесь начинается настоящая инженерия промптов: шаблоны с переменными, разметка XML-тегами, управление форматом ответа, «подумай, прежде чем ответить» и обучение примерами. Эти четыре главы — ядро урока 8.1.2, только с упражнениями.
Все примеры используют помощник pet.py из первой части: положите его
рядом с ноутбуком и запустите локальную модель (по умолчанию — qwen3:8b в Ollama). Что изменено по сравнению с
оригиналом и почему перевод распространяется по CC BY-NC 4.0, сказано там же.
Как читать вместе с курсом
Заголовок раздела «Как читать вместе с курсом»- Когда. После первой части и вместе с уроком 8.1.2. Глава 5 — практика к разделу о структурированном выводе, глава 6 — к разделу о chain-of-thought, глава 7 — к разделу о few-shot.
- Что важно знать заранее. Строки форматирования Python — f-строки и метод
.format()(урок 0.2.1). В главах ниже ими подставляют данные в шаблон промпта. - Главная оговорка. Глава 5 учит приёму prefill — «начать ответ за модель». У Claude с поколения 4.6 этот приём больше не поддерживается (урок 8.4.1), а у открытых моделей он работает по-разному в зависимости от сервера. Ниже показано, как его выполнить на vLLM и DeepSeek и как обойтись без него.
Глава 4. Отделяем данные от инструкций
Заголовок раздела «Глава 4. Отделяем данные от инструкций»Часто нам нужны не готовые промпты целиком, а шаблоны промптов, в которые позже подставляют входные данные, прежде чем отправить модели. Это удобно, когда модель должна каждый раз делать одно и то же, а данные для задачи каждый раз разные.
К счастью, это легко: нужно отделить неизменный каркас промпта от переменного пользовательского ввода и подставить ввод в промпт перед отправкой.
Ниже пошагово разберём, как написать шаблон с подстановкой и как в него подставить данные.
Примеры
Заголовок раздела «Примеры»В первом примере модель работает генератором звуков животных. Обратите внимание: модели уходит просто
PROMPT, в который подставлено значение (здесь — «Корова»). Слово «Корова» встаёт на место переменной ANIMAL
через f-строку — это видно, когда мы печатаем итоговый промпт.
Примечание: называть переменную можно как угодно. Здесь она ANIMAL, но могла бы быть CREATURE или A
(хотя лучше давать конкретные и понятные имена, чтобы шаблон читался и без подстановки). Главное — использовать в
f-строке то же имя, что вы дали переменной.
from pet import get_completion
# Переменная частьANIMAL = "Корова"
# Шаблон промпта с местом для переменной частиPROMPT = f"Я назову животное. Ответь звуком, который это животное издаёт. {ANIMAL}"
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT))Зачем так разделять? Шаблоны упрощают повторяющиеся задачи. Допустим, вы сделали промпт, в который сторонние пользователи отправляют свой контент (здесь — животное, чей звук хотят услышать). Им не нужно писать или даже видеть весь промпт: достаточно заполнить переменные.
Здесь подстановка сделана переменными и f-строками, но можно использовать и метод format().
Примечание: переменных в шаблоне может быть сколько угодно!
Когда в промпте появляются подставляемые переменные, очень важно чтобы модель понимала, где переменная начинается и где кончается (в отличие от инструкций и описания задачи). Посмотрим на пример, где между инструкцией и переменной нет границы.
Человеческому глазу в шаблоне ниже очевидно, где начинается и кончается переменная. Но в итоговом промпте после подстановки граница исчезает.
# Переменная частьEMAIL = "Приходите завтра к 6 утра, потому что я генеральный директор и я так сказал."
# Шаблон промпта с местом для переменной частиPROMPT = f"Привет, ассистент. {EMAIL} <----- Сделай это письмо вежливее, но больше ничего в нём не меняй."
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT))Здесь модель считает «Привет, ассистент» частью письма, которое нужно переписать! Это видно по тому, что переписанное письмо начинается с «Уважаемый ассистент». Человеку понятно, где письмо начинается и кончается, особенно в шаблоне, но после подстановки это гораздо менее очевидно.
Как это исправить? Обернуть ввод в XML-теги! Ниже мы так и сделали — и никакого «Уважаемый ассистент» в ответе больше нет.
XML-теги — это теги в угловых скобках вроде <tag></tag>. Они идут парами: открывающий тег, например <tag>, и
закрывающий, помеченный косой чертой, например </tag>. Тегами обрамляют содержимое: <tag>содержимое</tag>.
Примечание: Claude понимает самые разные разделители, но Anthropic советует использовать именно XML-теги: Claude специально обучали воспринимать их как способ упорядочить промпт. За пределами вызова функций нет никаких «волшебных» тегов, на которых Claude обучали и которые дают особый прирост качества: модель намеренно сделали гибкой и настраиваемой.
# Переменная частьEMAIL = "Приходите завтра к 6 утра, потому что я генеральный директор и я так сказал."
# Шаблон промпта: письмо обёрнуто в теги <email>PROMPT = f"Привет, ассистент. <email>{EMAIL}</email> <----- Сделай это письмо вежливее, но больше ничего в нём не меняй."
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT))Ещё один пример того, как помогают теги.
В промпте ниже модель неверно понимает, где инструкция, а где ввод. Из-за форматирования она считает
- Каждое из них про животное, например про кроликов частью списка, хотя пользователь (тот, кто заполняет
переменную SENTENCES), скорее всего, этого не имел в виду.
# Переменная частьSENTENCES = """- Мне нравится, как мычат коровы- Это предложение про пауков- Может показаться, что это предложение о собаках, но на самом деле оно о свиньях"""
# Шаблон промптаPROMPT = f"""Ниже список предложений. Назови второй пункт списка.
- Каждое из них про животное, например про кроликов.{SENTENCES}"""
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT))Чтобы это исправить, достаточно окружить предложения пользователя XML-тегами. Так модель видит, где начинаются и кончаются входные данные, несмотря на обманчивый дефис перед «Каждое из них про животное, например про кроликов».
# Шаблон промпта: пользовательские предложения в тегах <sentences>PROMPT = f"""Ниже список предложений. Назови второй пункт списка.
- Каждое из них про животное, например про кроликов.<sentences>{SENTENCES}</sentences>"""
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT))Примечание: в «неправильной» версии промпта пришлось поставить дефис, чтобы модель ошиблась так, как нужно для примера. Это важный урок промптинга: мелочи имеют значение! Всегда стоит вычищать из промптов опечатки и грамматические ошибки. Модель чувствительна к шаблонам (в ранние годы, до дообучения, она была просто предсказателем текста): она чаще ошибается, когда ошибаетесь вы, умнее, когда вы звучите умно, глупее, когда вы звучите глупо, и так далее.
Упражнения
Заголовок раздела «Упражнения»Упражнение 4.1 — Тема хокку
Заголовок раздела «Упражнение 4.1 — Тема хокку»Измените PROMPT так, чтобы он стал шаблоном, который принимает переменную TOPIC и выдаёт хокку на эту тему.
Упражнение проверяет, поняли ли вы подстановку через f-строки.
import re
# Переменная частьTOPIC = "Свиньи"
# Шаблон промпта — допишите f-строкуPROMPT = f""
# Ответ моделиresponse = get_completion(PROMPT)
# Проверка (адаптирована): в ответе есть «свин» и «хокку»def grade_exercise(text): t = text.lower() return bool(re.search("свин", t) and re.search("хокку", t))
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(response)print("\n------------------------------------ ПРОВЕРКА ------------------------------------")print("Упражнение решено:", grade_exercise(response))Подсказка
Проверка ищет в ответе слова «хокку» и «свин» (в оригинале — «haiku» и «pig»). Не забудьте вписать {TOPIC} туда,
куда должна подставляться тема. Если поменять значение TOPIC, модель должна написать хокку на другую тему.
Упражнение 4.2 — Вопрос о собаках с опечатками
Заголовок раздела «Упражнение 4.2 — Вопрос о собаках с опечатками»Исправьте PROMPT, добавив XML-теги, чтобы модель ответила правильно.
Старайтесь больше ничего в промпте не менять. Небрежный текст с ошибками написан нарочно — чтобы вы увидели, как модель на них реагирует.
# Переменная частьQUESTION = "мгт бть кчнвми?"
# Шаблон промптаPROMPT = f"Прив эт я у мня впрс про сбк йцукен {QUESTION} фывапр спс эт помжт очнь очнь спс бстр бстр отвт кртко кртко спс"
# Ответ моделиresponse = get_completion(PROMPT)
# Проверка (адаптирована): в ответе есть «коричнев»def grade_exercise(text): return bool(re.search("коричнев", text.lower()))
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(response)print("\n------------------------------------ ПРОВЕРКА ------------------------------------")print("Упражнение решено:", grade_exercise(response))Подсказка
Проверка ищет слово «коричневый» в любой форме (в оригинале — «brown»). Как изменится ответ, если окружить
{QUESTION} XML-тегами?
Упражнение 4.3 — Вопрос о собаках, часть 2
Заголовок раздела «Упражнение 4.3 — Вопрос о собаках, часть 2»Исправьте PROMPT БЕЗ XML-тегов. Вместо этого удалите из промпта одно-два слова.
Как и выше, старайтесь больше ничего не менять. Так вы увидите, какой язык модель способна разобрать.
# Переменная частьQUESTION = "мгт бть кчнвми?"
# Шаблон промпта — удалите одно-два словаPROMPT = f"Прив эт я у мня впрс про сбк йцукен {QUESTION} фывапр спс эт помжт очнь очнь спс бстр бстр отвт кртко кртко спс"
response = get_completion(PROMPT)
def grade_exercise(text): return bool(re.search("коричнев", text.lower()))
print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(response)print("\n------------------------------------ ПРОВЕРКА ------------------------------------")print("Упражнение решено:", grade_exercise(response))Подсказка
Удаляйте по одному слову или куску символов, начиная с самых бессмысленных. Если делать это по одному слову, вы заодно увидите, сколько модель способна разобрать, а сколько — нет.
Поздравляем!
Заголовок раздела «Поздравляем!»Если вы решили все упражнения, можно переходить к следующей главе. Удачного промптинга!
Глава 5. Формат ответа и prefill («говорим за модель»)
Заголовок раздела «Глава 5. Формат ответа и prefill («говорим за модель»)»Модель умеет оформлять ответ самыми разными способами. Нужно только попросить!
Один из способов — XML-теги, которые отделяют сам ответ от лишнего текста. Вы уже знаете, что XML-теги делают промпт яснее и понятнее для модели. Оказывается, можно и попросить модель обернуть ответ в теги, чтобы он стал понятнее людям.
Примеры
Заголовок раздела «Примеры»Помните «проблему вступления перед стихами» из главы 2, которую мы решили просьбой обойтись без вступления? Похожего результата можно добиться, попросив модель поместить стихотворение в XML-теги.
from pet import get_completion, chat
# Переменная частьANIMAL = "Кролик"
# Шаблон промптаPROMPT = f"Напиши хокку про животное: {ANIMAL}. Помести его в теги <haiku>."
print("--------------------------- Полный промпт после подстановки ---------------------------")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT))Зачем это нужно? Ответ в XML-тегах позволяет надёжно вытащить стихотворение — и только его — короткой программой, которая берёт текст между тегами.
Развитие этого приёма — поставить первый XML-тег в ход assistant. Текст в ходе assistant означает для
модели, что она уже что-то сказала и должна продолжить с этого места. Этот приём называют «говорить за модель» или
prefill ответа (предзаполнение).
Ниже мы так сделали с первым тегом <haiku>. Заметьте: модель продолжает ровно с того места, где мы остановились.
# Переменная частьANIMAL = "Кот"
# Шаблон промптаPROMPT = f"Напиши хокку про животное: {ANIMAL}. Помести его в теги <haiku>."
# Prefill — начало ответа моделиPREFILL = "<haiku>"
print("--------------------------- Полный промпт после подстановки ---------------------------")print("ХОД ПОЛЬЗОВАТЕЛЯ:")print(PROMPT)print("\nХОД АССИСТЕНТА:")print(PREFILL)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT, prefill=PREFILL))Модель хорошо справляется и с другими форматами, особенно с JSON. Если нужно добиться JSON на выходе (не
детерминированно, но почти), можно начать ответ модели с открывающей фигурной скобки {.
# Переменная частьANIMAL = "Кот"
# Шаблон промптаPROMPT = (f"Напиши хокку про животное: {ANIMAL}. Используй формат JSON с ключами " "\"first_line\", \"second_line\" и \"third_line\".")
# Prefill — начало ответа моделиPREFILL = "{"
print("ХОД ПОЛЬЗОВАТЕЛЯ:")print(PROMPT)print("\nХОД АССИСТЕНТА:")print(PREFILL)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT, prefill=PREFILL))Ниже пример с несколькими входными переменными в одном промпте И указанием формата ответа — всё через XML-теги.
# Первая переменнаяEMAIL = "Привет, Зак! Просто напоминаю: жду новостей по тому промпту, который ты обещал написать."
# Вторая переменнаяSTYLE = "старинный"
# Шаблон промптаPROMPT = (f"Привет. Вот письмо: <email>{EMAIL}</email>. Перепиши это письмо в стиле: {STYLE}. " f"Новую версию напиши в XML-тегах <{STYLE}_email>.")
# Prefill (теперь это f-строка с переменной)PREFILL = f"<{STYLE}_email>"
print("ХОД ПОЛЬЗОВАТЕЛЯ:")print(PROMPT)print("\nХОД АССИСТЕНТА:")print(PREFILL)print("\n---------------------------------- Ответ модели ----------------------------------")print(get_completion(PROMPT, prefill=PREFILL))Дополнительный урок
Заголовок раздела «Дополнительный урок»Если вы вызываете модель через API, закрывающий XML-тег можно передать в параметр stop_sequences (в Chat
Completions — stop), чтобы модель прекратила генерацию, как только выдаст этот тег. Это экономит деньги и время
до последнего токена: модель не пишет заключительных фраз после того ответа, который вам нужен.
# Остановимся сразу на закрывающем теге: всё, что модель хотела сказать после, не будет сгенерированоmessages = [{"role": "user", "content": "Напиши хокку про кота. Помести его в теги <haiku>."}]print(chat(messages, stop_sequences=["</haiku>"]))Упражнения
Заголовок раздела «Упражнения»Упражнение 5.1 — Стефен Карри — величайший
Заголовок раздела «Упражнение 5.1 — Стефен Карри — величайший»Если заставить модель выбрать, лучшим баскетболистом всех времён она называет Майкла Джордана. Можно ли добиться, чтобы она выбрала другого?
Измените переменную PREFILL так, чтобы заставить модель подробно доказывать, что лучший баскетболист всех
времён — Стефен Карри. Старайтесь не менять ничего, кроме PREFILL: упражнение именно про него.
import re
# Шаблон промптаPROMPT = "Кто лучший баскетболист всех времён? Выбери, пожалуйста, одного конкретного игрока."
# Prefill — менять нужно только эту строкуPREFILL = ""
response = get_completion(PROMPT, prefill=PREFILL)
# Проверка (адаптирована): в ответе упомянута команда Карри — «Голден Стэйт Уорриорз»def grade_exercise(text): return bool(re.search(r"Уорриорз|Голден|Warrior", text))
print("ХОД ПОЛЬЗОВАТЕЛЯ:")print(PROMPT)print("\nХОД АССИСТЕНТА:")print(PREFILL)print("\n---------------------------------- Ответ модели ----------------------------------")print(response)print("\n------------------------------------ ПРОВЕРКА ------------------------------------")print("Упражнение решено:", grade_exercise(response))Подсказка
Проверка ищет в ответе название команды Карри (в оригинале — слово «Warrior»). Напишите голосом модели побольше слов, чтобы направить её в нужную сторону. Например, вместо «Стефен Карри — лучший, потому что» можно написать «Стефен Карри — лучший, и вот три причины. 1:».
Упражнение 5.2 — Два хокку
Заголовок раздела «Упражнение 5.2 — Два хокку»Измените PROMPT с помощью XML-тегов так, чтобы модель написала про животное не одно хокку, а два. Должно быть
ясно, где кончается одно стихотворение и начинается другое.
# Переменная частьANIMAL = "коты"
# Шаблон промпта — менять нужно егоPROMPT = f"Напиши хокку про животное: {ANIMAL}. Помести его в теги <haiku>."
# PrefillPREFILL = "<haiku>"
response = get_completion(PROMPT, prefill=PREFILL)
# Проверка: есть «кот»/«кош», тег <haiku> и больше пяти строкdef grade_exercise(text): return bool( (re.search(r"кот|кош", text.lower()) and re.search("<haiku>", text)) and (text.count("\n") + 1) > 5 )
print("ХОД ПОЛЬЗОВАТЕЛЯ:")print(PROMPT)print("\nХОД АССИСТЕНТА:")print(PREFILL)print("\n---------------------------------- Ответ модели ----------------------------------")print(response)print("\n------------------------------------ ПРОВЕРКА ------------------------------------")print("Упражнение решено:", grade_exercise(response))Подсказка
Проверка ищет ответ длиннее пяти строк, в котором есть слово «кот» (в любой форме) и тег <haiku>. Начните с
простого: сейчас промпт просит одно хокку — попросите два (или больше). Если потом всплывут проблемы с форматом,
исправьте промпт уже после того, как модель начнёт писать несколько хокку.
Упражнение 5.3 — Два хокку, два животных
Заголовок раздела «Упражнение 5.3 — Два хокку, два животных»Измените PROMPT так, чтобы модель написала два хокку про двух разных животных. Используйте {ANIMAL1} для
первой подстановки и {ANIMAL2} для второй.
# Первая переменнаяANIMAL1 = "Кот"
# Вторая переменнаяANIMAL2 = "Пёс"
# Шаблон промпта — менять нужно егоPROMPT = f"Напиши хокку про животное: {ANIMAL1}. Помести его в теги <haiku>."
response = get_completion(PROMPT)
# Проверка (адаптирована): есть «хвост», «кот»/«кош» и тег <haiku>def grade_exercise(text): t = text.lower() return bool(re.search("хвост", t) and re.search(r"кот|кош", t) and re.search("<haiku>", text))
print("ХОД ПОЛЬЗОВАТЕЛЯ:")print(PROMPT)print("\n---------------------------------- Ответ модели ----------------------------------")print(response)print("\n------------------------------------ ПРОВЕРКА ------------------------------------")print("Упражнение решено:", grade_exercise(response))Подсказка
Проверка ищет слова «хвост», «кот» и тег <haiku>. Полезно разбить упражнение на шаги:
- Измените шаблон так, чтобы модель писала два стихотворения.
- Укажите модели, о чём будут стихотворения, но вместо самих животных (собака, кот…) вставьте переменные
{ANIMAL1}и{ANIMAL2}. - Запустите промпт и убедитесь, что в итоговом промпте все слова подставились правильно. Если нет — проверьте, что имена в фигурных скобках написаны верно и скобки одинарные.
Поздравляем!
Заголовок раздела «Поздравляем!»Если вы решили все упражнения, можно переходить к следующей главе. Удачного промптинга!
Глава 6. Предварительное обдумывание (думаем по шагам)
Заголовок раздела «Глава 6. Предварительное обдумывание (думаем по шагам)»Если бы вас разбудили и сразу начали задавать несколько сложных вопросов, на которые нужно ответить немедленно, как бы вы справились? Наверное, хуже, чем если бы вам дали время сначала обдумать ответ.
Угадайте что? С моделью то же самое.
Если дать модели время подумать шаг за шагом, она иногда отвечает точнее, особенно в сложных задачах. Но думать можно только вслух. Нельзя попросить модель подумать, а вывести только ответ: в этом случае никакого обдумывания на самом деле не происходит.
Примеры
Заголовок раздела «Примеры»В промпте ниже человеку очевидно, что второе предложение перечёркивает первое. Но модель понимает слова «не связанных» слишком буквально.
from pet import get_completion
# ПромптPROMPT = """Эта рецензия на фильм положительная или отрицательная?
Этот фильм поразил меня своей свежестью и оригинальностью. В совершенно не связанных новостях: я живу под камнем с 1900 года."""
print(get_completion(PROMPT))Чтобы улучшить ответ, дадим модели сначала всё обдумать. Для этого буквально распишем шаги, которые она должна пройти. Вместе с щепоткой роли это помогает глубже понять рецензию.
# Системный промптSYSTEM_PROMPT = "Ты — проницательный читатель кинорецензий."
# ПромптPROMPT = """Эта рецензия положительная или отрицательная? Сначала запиши самые сильные аргументы за каждую сторону в XML-тегах <positive-argument> и <negative-argument>, затем ответь.
Этот фильм поразил меня своей свежестью и оригинальностью. В совершенно не связанных новостях: я живу под камнем с 1900 года."""
print(get_completion(PROMPT, SYSTEM_PROMPT))Модель иногда чувствительна к порядку. Этот пример на пределе способности модели понимать тонкости текста. Если поменять местами аргументы из прошлого примера, чтобы отрицательный шёл первым, а положительный вторым, общая оценка меняется на положительную.
В большинстве ситуаций (но, как ни странно, не во всех) модель скорее выберет второй из двух вариантов — возможно, потому что в веб-данных, на которых она обучалась, второй вариант чаще оказывался правильным.
# Промпт: тот же вопрос, но варианты и аргументы в обратном порядкеPROMPT = """Эта рецензия отрицательная или положительная? Сначала запиши самые сильные аргументы за каждую сторону в XML-тегах <negative-argument> и <positive-argument>, затем ответь.
Этот фильм поразил меня своей свежестью и оригинальностью. Кстати, к делу не относится: я живу под камнем с 1900 года."""
print(get_completion(PROMPT))Возможность подумать может превратить неверный ответ в верный. Во многих случаях, когда модель ошибается, всё настолько просто!
Разберём пример, где модель отвечает неверно, и посмотрим, как просьба подумать это исправляет.
# ПромптPROMPT = "Назови известный фильм, в котором снимался актёр, родившийся в 1956 году."
print(get_completion(PROMPT))Исправим это: попросим модель подумать по шагам, на этот раз в тегах <brainstorm>.
# ПромптPROMPT = ("Назови известный фильм, в котором снимался актёр, родившийся в 1956 году. " "Сначала устрой мозговой штурм: перечисли актёров и годы их рождения в тегах <brainstorm>, затем дай ответ.")
print(get_completion(PROMPT))Упражнения
Заголовок раздела «Упражнения»Упражнение 6.1 — Классификация писем
Заголовок раздела «Упражнение 6.1 — Классификация писем»В этом упражнении модель сортирует письма покупателей по категориям:
- (A) Вопрос до покупки
- (B) Сломанный или бракованный товар
- (C) Вопрос по оплате
- (D) Другое (поясните)
В первой части упражнения измените PROMPT так, чтобы модель выводила правильную категорию и ТОЛЬКО её. Ответ
должен содержать букву правильного варианта (A–D) со скобкой и название категории.
Какая категория правильная для каждого письма, сказано в комментариях рядом с письмами в списке EMAILS.
import re
# Шаблон промпта — менять нужно его (и, если хотите, PREFILL)PROMPT = """Отнеси это письмо к зелёным или синим: {email}"""
# Prefill, если нуженPREFILL = ""
# ПисьмаEMAILS = [ "Здравствуйте! Мой Миксмастер4000 странно шумит при работе. А ещё пахнет дымом и пластиком, как горящая электроника. Мне нужна замена.", # (B) Сломанный или бракованный товар "Можно ли смешивать Миксмастером 4000 краску или он только для еды?", # (A) Вопрос до покупки ИЛИ (D) Другое "Я УЖЕ 4 МЕСЯЦА ЖДУ, КОГДА ПРЕКРАТЯТСЯ ЕЖЕМЕСЯЧНЫЕ СПИСАНИЯ ПОСЛЕ ОТМЕНЫ ПОДПИСКИ!! ЧТО ВООБЩЕ ПРОИСХОДИТ???", # (C) Вопрос по оплате "Как я сюда попал я плохо в компьютер. Памагите.", # (D) Другое]
# Правильные категории: список списков, потому что у письма может быть несколько верных вариантовANSWERS = [ ["B"], ["A", "D"], ["C"], ["D"],]
# Шаблоны для проверки регулярными выражениями: буква, скобка, первая буква названия категорииREGEX_CATEGORIES = { "A": r"A\) В", "B": r"B\) С", "C": r"C\) В", "D": r"D\) Д",}
def normalize(text): # Модель может написать кириллические «А)», «В)», «С)» — они выглядят как латинские, но это другие символы return re.sub(r"([АВС])\)", lambda m: {"А": "A", "В": "B", "С": "C"}[m.group(1)] + ")", text)
for i, email in enumerate(EMAILS): # Подставляем письмо в шаблон formatted_prompt = PROMPT.format(email=email) response = get_completion(formatted_prompt, prefill=PREFILL) # Проверяем ответ grade = any(bool(re.search(REGEX_CATEGORIES[ans], normalize(response))) for ans in ANSWERS[i])
print("ХОД ПОЛЬЗОВАТЕЛЯ:") print(formatted_prompt) print("\nХОД АССИСТЕНТА:") print(PREFILL) print("\n---------------------------------- Ответ модели ----------------------------------") print(response) print("\n------------------------------------ ПРОВЕРКА ------------------------------------") print("Упражнение решено:", grade, "\n\n")Подсказка
Проверка ищет правильную букву категории, закрывающую скобку и первую букву названия категории, например «C) В» или «B) С». Разберём упражнение по шагам:
- Откуда модель узнает, какие категории вам нужны? Скажите ей! Впишите четыре категории прямо в промпт — вместе с буквами в скобках, чтобы классифицировать было легко. Можно выделить категории XML-тегами, чтобы модель видела, где они начинаются и кончаются.
- Уберите лишний текст, чтобы модель сразу отвечала категорией и ТОЛЬКО категорией. Это можно сделать по-разному: от prefill (начала предложения или даже одной открывающей скобки — так модель поймёт, что ответ должен начинаться с буквы в скобках) до прямой просьбы ответить только категорией без вступления. Если нужно освежить эти приёмы, вернитесь к главам 2 и 5.
- Модель может всё ещё ошибаться в категории или не писать её название. Исправьте это, попросив указывать в ответе полное название категории.
- Убедитесь, что в шаблоне остался
{email}, чтобы письма правильно подставлялись.
Пример решения
ХОД ПОЛЬЗОВАТЕЛЯОтнеси это письмо к одной из следующих категорий: {email}
Не пиши никаких лишних слов, кроме категории.
<categories>(A) Вопрос до покупки(B) Сломанный или бракованный товар(C) Вопрос по оплате(D) Другое (поясните)</categories>
ХОД АССИСТЕНТА(Упражнение 6.2 — Формат классификации писем
Заголовок раздела «Упражнение 6.2 — Формат классификации писем»Теперь доведём ответ из прошлого упражнения до точно нужного формата.
Любимым приёмом форматирования добейтесь, чтобы модель обернула в теги <answer></answer> ТОЛЬКО букву правильной
категории. Например, ответ для первого письма должен содержать ровно строку <answer>B</answer>.
# Шаблон промпта — менять нужно егоPROMPT = """Отнеси это письмо к зелёным или синим: {email}"""
# Prefill, если нуженPREFILL = ""
# EMAILS и ANSWERS — те же, что в упражнении 6.1REGEX_CATEGORIES = { "A": "<answer>A</answer>", "B": "<answer>B</answer>", "C": "<answer>C</answer>", "D": "<answer>D</answer>",}
def normalize(text): # кириллические А/В/С внутри тега считаем латинскими return re.sub(r"<answer>([АВС])</answer>", lambda m: "<answer>" + {"А": "A", "В": "B", "С": "C"}[m.group(1)] + "</answer>", text)
for i, email in enumerate(EMAILS): formatted_prompt = PROMPT.format(email=email) response = get_completion(formatted_prompt, prefill=PREFILL) # Если в prefill был открывающий тег, вернём его, чтобы проверка видела ответ целиком full = PREFILL + response grade = any(bool(re.search(REGEX_CATEGORIES[ans], normalize(full))) for ans in ANSWERS[i]) print(formatted_prompt, "\n---\n", full, "\nУпражнение решено:", grade, "\n\n")Подсказка
Проверка ищет только правильную букву в тегах <answer>, например <answer>B</answer>. Буквы категорий те же, что
в прошлом упражнении. Иногда проще всего показать модели пример того, как должен выглядеть ответ, — только не
забудьте обернуть его в теги <example></example>! И помните: если вы начали ответ за модель, она этот кусок в
своём ответе не повторит.
Поздравляем!
Заголовок раздела «Поздравляем!»Если вы решили все упражнения, можно переходить к следующей главе. Удачного промптинга!
Глава 7. Примеры (few-shot)
Заголовок раздела «Глава 7. Примеры (few-shot)»Показать модели примеры того, как вы хотите (или не хотите), чтобы она себя вела, — чрезвычайно эффективно, если нужно:
- получить правильный ответ;
- получить ответ в правильном формате.
Такой промптинг называют few-shot («несколько выстрелов»). Встречаются и выражения zero-shot, one-shot, n-shot. Число «выстрелов» — это число примеров в промпте.
Примеры
Заголовок раздела «Примеры»Представьте, что вы разработчик и делаете «бота-родителя», который отвечает на вопросы детей. Ответ модели по умолчанию довольно официальный и роботизированный. Ребёнок расстроится.
from pet import get_completion
# ПромптPROMPT = "Дед Мороз принесёт мне подарки на Новый год?"
print(get_completion(PROMPT))Можно потратить время и описать нужный тон, но гораздо проще дать модели несколько примеров идеальных ответов.
# ПромптPROMPT = """Продолжи разговор: напиши следующую реплику от имени «О».В: Зубная фея правда существует?О: Конечно, солнышко. Заверни зубик и положи сегодня под подушку. Может быть, утром тебя там что-то будет ждать.В: Дед Мороз принесёт мне подарки на Новый год?"""
print(get_completion(PROMPT))В следующем примере про форматирование можно было бы пошагово объяснить модели, как извлекать имена и профессии и
как их оформлять. А можно дать несколько правильно оформленных примеров, и модель сама обобщит. Обратите
внимание на <individuals> в ходе assistant: так мы задаём модели верное начало.
# Шаблон промпта: два готовых примера и третий текст, который нужно разобратьPROMPT = """Сильвермист-Холлоу, очаровательная деревня, была домом для удивительной компании людей.Среди них был доктор Лиам Патель, нейрохирург, который совершил революцию в хирургических методах в региональном медицинском центре.Оливия Чен была архитектором-новатором и преобразила облик деревни своими экологичными и захватывающими проектами.Местный театр украшали чарующие симфонии Итана Ковача, профессионального музыканта и композитора.Изабелла Торрес, шеф-повар-самоучка, влюблённая в местные продукты, произвела кулинарную сенсацию своим фермерским рестораном, который стал обязательным местом для гурманов.Эти замечательные люди, каждый со своим талантом, вплели свои нити в яркий узор жизни Сильвермист-Холлоу.<individuals>1. Доктор Лиам Патель [НЕЙРОХИРУРГ]2. Оливия Чен [АРХИТЕКТОР]3. Итан Ковач [МУЗЫКАНТ И КОМПОЗИТОР]4. Изабелла Торрес [ШЕФ-ПОВАР]</individuals>
В самом сердце городка шеф-повар Оливер Гамильтон преобразил местную кухню своим фермерским рестораном «Зелёная тарелка». Преданность Оливера местным органическим продуктам принесла заведению восторженные отзывы критиков и жителей.Чуть дальше по улице стоит библиотека Риверсайд-Гроув, где главный библиотекарь Элизабет Чен неустанно трудится, чтобы создать гостеприимное и открытое для всех пространство. Её усилия по расширению фондов и программы чтения для детей заметно подняли грамотность в городе.Прогуливаясь по очаровательной городской площади, вы залюбуетесь прекрасными фресками на стенах. Эти шедевры — работа известной художницы Изабеллы Торрес, чей талант передать дух Риверсайд-Гроув оживил город.Стоит отметить и спортивные успехи Риверсайд-Гроув — благодаря бывшему олимпийскому пловцу, а ныне тренеру Маркусу Дженкинсу. Свой опыт и страсть Маркус вложил в подготовку молодёжи и привёл команду пловцов Риверсайд-Гроув к нескольким региональным чемпионствам.<individuals>1. Оливер Гамильтон [ШЕФ-ПОВАР]2. Элизабет Чен [БИБЛИОТЕКАРЬ]3. Изабелла Торрес [ХУДОЖНИЦА]4. Маркус Дженкинс [ТРЕНЕР]</individuals>
Оук-Вэлли, очаровательный городок, — дом для замечательного трио, чьё мастерство и преданность делу оставили заметный след в жизни общины.На шумном фермерском рынке городка вы встретите Лору Симмонс, увлечённую фермершу, известную вкусными овощами, выращенными без вреда для природы. Её преданность здоровому питанию вдохновила город на более экологичный образ жизни.В общественном центре Оук-Вэлли Кевин Альварес, умелый преподаватель танцев, подарил радость движения людям всех возрастов. Его открытые для всех занятия сплотили жителей и обогатили местную культурную жизнь.Наконец, Рэйчел О'Коннор, неутомимая волонтёрка, посвящает время благотворительным проектам. Её стремление сделать жизнь других лучше помогло создать в Оук-Вэлли крепкое чувство общности.Своими талантами и преданностью делу Лора, Кевин и Рэйчел вплели себя в ткань Оук-Вэлли и помогли создать яркий, процветающий городок."""
# Prefill — начало ответа моделиPREFILL = "<individuals>"
print(get_completion(PROMPT, prefill=PREFILL))Упражнения
Заголовок раздела «Упражнения»Упражнение 7.1 — Формат писем через примеры
Заголовок раздела «Упражнение 7.1 — Формат писем через примеры»Повторим упражнение 6.2, но теперь измените PROMPT, добавив few-shot-примеры писем с правильной категорией (и
форматом), чтобы модель отвечала верно. Мы хотим, чтобы последним символом ответа была буква категории.
Напомним категории:
- (A) Вопрос до покупки
- (B) Сломанный или бракованный товар
- (C) Вопрос по оплате
- (D) Другое (поясните)
import re
# Шаблон промпта — менять нужно егоPROMPT = """Отнеси это письмо к зелёным или синим: {email}"""
# PrefillPREFILL = ""
# EMAILS и ANSWERS — те же, что в упражнении 6.1for i, email in enumerate(EMAILS): formatted_prompt = PROMPT.format(email=email) response = get_completion(formatted_prompt, prefill=PREFILL) last = response.strip()[-1:] # последний символ ответа last = {"А": "A", "В": "B", "С": "C"}.get(last, last) # кириллица → латиница grade = any(ans == last for ans in ANSWERS[i]) print(formatted_prompt, "\n---\n", response, "\nУпражнение решено:", grade, "\n\n")Подсказка
Вам придётся написать несколько примеров писем и классифицировать их для модели (ровно в том формате, который нужен). Сделать это можно по-разному. Несколько советов:
- Дайте хотя бы два примера. Не обязательно покрывать все категории, и примеры не должны быть длинными. Полезнее дать примеры для самых каверзных категорий (о них стоило подумать в конце упражнения 6.1). XML-теги помогут отделить примеры от остального промпта, хотя это не обязательно.
- Оформление ответа в примерах должно быть ровно таким, какое вы ждёте от модели, — она его скопирует. Формат
должен заканчиваться буквой категории. Плейсхолдер
{email}оформите в точности как письма в примерах. - Убедитесь, что категории по-прежнему перечислены в промпте (иначе модель не узнает, из чего выбирать) и что
{email}остался на месте.
Пример решения
ХОД ПОЛЬЗОВАТЕЛЯОтнеси письма к одной из следующих категорий и не давай пояснений:<categories>(A) Вопрос до покупки(B) Сломанный или бракованный товар(C) Вопрос по оплате(D) Другое (поясните)</categories>
Вот несколько примеров правильного оформления ответа:<examples>В: Сколько стоит Миксмастер4000?О: Правильная категория: A
В: Мой Миксмастер не включается.О: Правильная категория: B
В: Пожалуйста, удалите меня из списка рассылки.О: Правильная категория: D</examples>
Вот письмо, которое нужно классифицировать: {email}
ХОД АССИСТЕНТАПравильная категория:Поздравляем!
Заголовок раздела «Поздравляем!»Если вы решили все упражнения, можно переходить к следующей главе. Удачного промптинга!
Что сделать руками
Заголовок раздела «Что сделать руками»- Решите упражнения глав 4–7 и запишите в «тетрадь ошибок», какой приём исправил каждую неудачу. Чаще всего это окажется одна из трёх вещей: XML-теги, явный формат ответа или пример.
- Prefill против structured outputs. Перепишите упражнение 6.2 без prefill: передайте в
chat()(или прямо вclient.chat.completions.create) параметрresponse_formatс JSON Schema, где полеcategory—enumиз четырёх букв (урок 8.1.2). Сравните: сколько писем классифицировано верно, как часто ломается формат. - Проверьте свой сервер. Отправьте напрямую через
client.chat.completions.createдиалог, который заканчивается сообщением{"role": "assistant", "content": "<haiku>"}, без флагов vLLM. Продолжает ли модель этот текст или начинает новый ответ? От ответа зависит, можно ли полагаться на prefill на вашем сервере. - Порядок вариантов. Прогоните оба промпта про рецензию (позитив/негатив и негатив/позитив) по 10 раз при
temperature=0.7. Подтверждается ли на вашей модели «эффект второго варианта»? Такие смещения порядка подробно разобраны для LLM-судей в уроке 7.3.2.
Словарик
Заголовок раздела «Словарик»| English | Русский |
|---|---|
| prompt template | шаблон промпта |
| variable / placeholder | переменная, плейсхолдер (место для подстановки) |
| substitution | подстановка |
| XML tags | XML-теги |
| delimiter | разделитель |
| output formatting | формат ответа |
| prefill (speaking for Claude) | prefill, «говорить за модель», предзаполнение ответа |
| stop sequences | стоп-последовательности |
| precognition / thinking step by step | предварительное обдумывание, рассуждение по шагам |
| chain of thought | цепочка рассуждений |
| few-shot / zero-shot / n-shot | с несколькими примерами / без примеров / с n примерами |
| structured outputs | структурированный вывод |