Перейти к содержимому
ПереводыAnthropic

Туториал Anthropic по промптам, часть 1: основы (главы 1–3)

Перевод вводной части и глав 1–3 Prompt Engineering Interactive Tutorial: как устроен запрос к модели, почему нужны ясные инструкции и что дают роли. Упражнения адаптированы под локальную модель и любой OpenAI-совместимый API.

Prompt Engineering Interactive Tutorial — официальный интерактивный курс Anthropic по составлению промптов: 9 глав с упражнениями и приложение с продвинутыми приёмами. Каждая глава — это ноутбук Jupyter: короткий урок, примеры, упражнения с автоматической проверкой и «песочница» для экспериментов. Курс написан в 2024 году под самую маленькую модель того времени, Claude 3 Haiku, но приёмы из него универсальны и работают с Qwen, DeepSeek и другими моделями.

Перевод разбит на три страницы:

  1. Основы (эта страница): подготовка, главы 1–3 — структура запроса, ясные инструкции, роли.
  2. Средний уровень: главы 4–7 — данные отдельно от инструкций, формат ответа и prefill, «подумай по шагам», примеры (few-shot).
  3. Продвинутый уровень: главы 8–9 и приложение — галлюцинации, сложные промпты из десяти элементов, цепочки промптов, вызов инструментов, поиск.
  • Когда. После урока 8.1.1 «OpenAI-совместимые API»: вы уже умеете отправить запрос и знаете роли system, user, assistant. Туториал идёт параллельно уроку 8.1.2 «Промпт-инжиниринг и context engineering» и даёт к нему десятки упражнений. Систематическая работа с промптами — версии, тест-наборы, выпуск по статистике — в уроке 8.4.1.
  • Сколько времени. Весь туториал — 6–10 часов. Эта страница — полтора-два часа вместе с упражнениями.
  • Что можно пропустить новичку. Ничего: главы 1–3 короткие и закладывают привычки, на которых держится всё остальное.
  • Модель. Хватит qwen3:8b в Ollama (около 5 ГБ, работает и на CPU, но медленно). Совсем слабый компьютер потянет qwen3:1.7b, но маленькие модели хуже понимают русский и чаще проваливают проверки. Это тоже полезный опыт: сравните, где 1.7B ошибается, а 8B — нет.

В оригинале этот раздел называется «Tutorial How-To». Для работы туториалу нужен доступ к модели через API. Без ключа можно посмотреть таблицу с ответами, которую приложили авторы, но с локальной моделью ключ не нужен вовсе.

  1. Поднимите модель. Самый простой путь — Ollama (подробно в уроке 7.2.2):

    Окно терминала
    ollama pull qwen3:8b # один раз скачать модель (~5 ГБ)
    ollama serve # если Ollama ещё не запущена как служба
  2. Создайте окружение и поставьте SDK (урок 0.1.2):

    Окно терминала
    python -m venv .venv && source .venv/bin/activate
    pip install openai jupyter
  3. Сохраните помощник pet.py рядом со своими ноутбуками. Это замена функции get_completion из оригинала: она отправляет промпт модели и возвращает текст ответа. Все примеры на трёх страницах импортируют его.

  4. Выполняйте ячейки по порядку, сверху вниз: Shift + Enter запускает ячейку и переходит к следующей.

# pet.py — помощник для туториала Anthropic по промптам (адаптация ChinaAI).
# Один и тот же код работает с Ollama, vLLM, DeepSeek и любым OpenAI-совместимым сервером.
import os
import re
from openai import OpenAI
# Какой сервер используем: ollama | vllm | deepseek | other
SERVER = os.environ.get("LLM_SERVER", "ollama")
DEFAULTS = { # адрес, ключ, модель по умолчанию для каждого сервера
"ollama": ("http://localhost:11434/v1", "ollama", "qwen3:8b"),
"vllm": ("http://localhost:8000/v1", "EMPTY", "Qwen/Qwen3-8B"),
# /beta нужен DeepSeek для prefill («продолжения за модель»), см. урок 8.4.1
"deepseek": ("https://api.deepseek.com/beta", os.environ.get("DEEPSEEK_API_KEY", ""), "deepseek-flash"),
"other": ("", "", ""),
}
base_url, api_key, model = DEFAULTS[SERVER]
BASE_URL = os.environ.get("LLM_BASE_URL", base_url)
API_KEY = os.environ.get("LLM_API_KEY", api_key) or "none"
MODEL = os.environ.get("LLM_MODEL", model)
# Выключаем «режим размышлений» Qwen3: Claude 3 Haiku, под которую писался туториал, так не умела,
# и иначе эффект приёмов из глав 3 и 6 будет не виден. Для других серверов оставьте пустой словарь.
NO_THINK = {
"ollama": {"reasoning_effort": "none"},
"vllm": {"chat_template_kwargs": {"enable_thinking": False}},
}.get(SERVER, {})
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
THINK_RE = re.compile(r"<think>.*?</think>\s*", re.DOTALL) # на случай, если рассуждения попали в текст
def chat(messages, system_prompt="", stop_sequences=None, max_tokens=2000, temperature=0.0):
"""Отправляет список сообщений [{role, content}, ...] и возвращает текст ответа модели.
Если последнее сообщение — от assistant, это prefill: модель продолжает этот текст,
а функция, как и в оригинале, возвращает только продолжение.
"""
msgs = [dict(m) for m in messages] # копия, чтобы не портить список вызывающего кода
if system_prompt:
msgs.insert(0, {"role": "system", "content": system_prompt})
extra = dict(NO_THINK)
prefill = ""
if msgs[-1]["role"] == "assistant":
prefill = msgs[-1]["content"]
if SERVER == "vllm": # vLLM умеет продолжать последнее сообщение ассистента
extra.update(continue_final_message=True, add_generation_prompt=False)
elif SERVER == "deepseek": # DeepSeek: бета-функция Chat Prefix Completion
msgs[-1]["prefix"] = True
else: # остальные: эмулируем — просим начать ответ с нужного текста
msgs.pop()
msgs[-1]["content"] += f"\n\nНачни свой ответ ровно с текста: {prefill}"
kwargs = dict(model=MODEL, messages=msgs, max_tokens=max_tokens, temperature=temperature)
if stop_sequences:
kwargs["stop"] = stop_sequences
if extra:
kwargs["extra_body"] = extra
response = client.chat.completions.create(**kwargs)
text = THINK_RE.sub("", response.choices[0].message.content or "")
if prefill and text.startswith(prefill): # при эмуляции модель повторит начало — срежем его
text = text[len(prefill):]
return text
def get_completion(prompt, system_prompt="", prefill=""):
"""Как get_completion из оригинала: один ход пользователя (+ необязательный prefill)."""
messages = [{"role": "user", "content": prompt}]
if prefill:
messages.append({"role": "assistant", "content": prefill})
return chat(messages, system_prompt)

Сервер выбирается переменными окружения, код менять не нужно:

Где модельЧто задать перед запуском Jupyter
Ollama на этом компьютереничего (это вариант по умолчанию)
vLLM (vllm serve Qwen/Qwen3-8B --port 8000)export LLM_SERVER=vllm
DeepSeek APIexport LLM_SERVER=deepseek DEEPSEEK_API_KEY=...
другой OpenAI-совместимый провайдерexport LLM_SERVER=other LLM_BASE_URL=... LLM_API_KEY=... LLM_MODEL=...

Если сервер ругается на неизвестный параметр reasoning_effort или chat_template_kwargs, поставьте в pet.py NO_THINK = {}. Имена облачных моделей меняются — актуальные смотрите в уроке 8.1.1 и в документации провайдера.

Проверьте, что всё работает:

from pet import get_completion
# Промпт
prompt = "Привет! Как дела?"
# Ответ модели
print(get_completion(prompt))
  • Курс использует температуру 0. Подробнее о температуре поговорим позже, а пока достаточно знать: при такой настройке ответы получаются более детерминированными, то есть почти одинаковыми от запуска к запуску. Все приёмы курса работали и с предыдущими поколениями Claude (Claude 2 и Claude Instant 1.2) — и, как вы увидите, работают с открытыми моделями.
  • Дойдя до конца главы, переходите к ноутбуку со следующим номером.
  • Внизу каждой главы в оригинале есть «песочница» (Example Playground): копии всех примеров урока, чтобы экспериментировать, не трогая основной текст. Здесь песочницы нет — просто меняйте промпты из примеров и смотрите, как меняется ответ.

В 2024 году у Anthropic было два API: устаревший Text Completions API и современный Messages API. Туториал использует только Messages API. OpenAI-совместимый Chat Completions API, через который мы работаем, устроен очень похоже (урок 8.1.1), поэтому ниже рядом с каждым параметром Anthropic указан его аналог.

Минимальный вызов Messages API требует трёх параметров:

  • model — имя модели в API, которую вы хотите вызвать. У нас — MODEL из pet.py.
  • max_tokens — наибольшее число токенов, которое модель сгенерирует, прежде чем остановиться. Модель может закончить и раньше: параметр задаёт только абсолютный предел. Кроме того, это жёсткая остановка: генерация может оборваться посреди слова или предложения. В Chat Completions параметр называется так же; на некоторых серверах он необязателен.
  • messages — массив входных сообщений. Модели обучены работать с чередующимися репликами user и assistant. Когда вы создаёте новое сообщение, вы передаёте в messages предыдущие реплики диалога, а модель генерирует следующую.
    • Каждое входное сообщение — объект с полями role и content. Можно передать одно сообщение с ролью user или несколько сообщений user и assistant (тогда они должны чередоваться). Первое сообщение всегда должно быть от user.

Есть и необязательные параметры, например:

  • system — системный промпт, о нём ниже. В Chat Completions это не отдельный параметр, а первое сообщение с ролью system.
  • temperature — степень разнообразия ответов. В уроках и упражнениях она равна 0.

Полный список параметров — в документации API.

Посмотрим, как модель отвечает на правильно оформленные промпты. Запускайте каждую ячейку (Shift + Enter), ответ появится под ней.

from pet import get_completion, client, MODEL
# Промпт
PROMPT = "Привет! Как у тебя дела?"
# Ответ модели
print(get_completion(PROMPT))
# Промпт
PROMPT = "Скажи, какого цвета океан?"
# Ответ модели
print(get_completion(PROMPT))
# Промпт
PROMPT = "В каком году родилась Селин Дион?"
# Ответ модели
print(get_completion(PROMPT))

Теперь посмотрим на промпты, оформленные неправильно. На них Messages API возвращал ошибку.

Первый пример — вызов, в котором у элементов массива messages нет полей role и content:

# Ошибка: в messages лежит не словарь {role, content}, а множество с одной строкой
response = client.chat.completions.create(
model=MODEL,
max_tokens=2000,
temperature=0.0,
messages=[
{"Привет! Как у тебя дела?"}
],
)
print(response.choices[0].message.content)

Второй — промпт, где роли user и assistant не чередуются:

# Два сообщения user подряд
response = client.chat.completions.create(
model=MODEL,
max_tokens=2000,
temperature=0.0,
messages=[
{"role": "user", "content": "В каком году родилась Селин Дион?"},
{"role": "user", "content": "А ещё расскажи о ней что-нибудь интересное."},
],
)
print(response.choices[0].message.content)

Сообщения user и assistant ОБЯЗАТЕЛЬНО чередуются, и диалог ОБЯЗАТЕЛЬНО начинается с хода user. В промпте может быть несколько пар user и assistant — так имитируют многоходовый диалог. Ещё можно вписать слова в последнее сообщение assistant, чтобы модель продолжила с того места, где вы остановились; об этом в следующих главах.

Ещё можно использовать системный промпт (system prompt). Это способ дать модели контекст, инструкции и правила до того, как в ходе пользователя появится вопрос или задача.

В Messages API системный промпт живёт отдельно от списка сообщений user и assistant — в параметре system. В Chat Completions его кладут первым сообщением с ролью system; помощник pet.py делает это сам, если передать второй аргумент system_prompt. Если системный промпт не нужен, оставьте переменную SYSTEM_PROMPT пустой строкой.

# Системный промпт
SYSTEM_PROMPT = ("Твой ответ всегда должен быть серией вопросов на критическое мышление, которые развивают "
"разговор (не давай ответов на свои вопросы). Не отвечай на сам вопрос пользователя.")
# Промпт
PROMPT = "Почему небо голубое?"
# Ответ модели
print(get_completion(PROMPT, SYSTEM_PROMPT))

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

Теперь перейдём к упражнениям.

Используя правильное оформление ролей user / assistant, отредактируйте PROMPT так, чтобы модель посчитала до трёх. Под ответом напечатается, решено ли упражнение.

import re
from pet import get_completion
# Промпт — менять нужно только эту строку
PROMPT = "[Замените этот текст]"
# Ответ модели
response = get_completion(PROMPT)
# Проверка: в ответе есть цифры 1, 2 и 3
def grade_exercise(text):
pattern = re.compile(r'^(?=.*1)(?=.*2)(?=.*3).*$', re.DOTALL)
return bool(pattern.match(text))
print(response)
print("\n--------------------------- ПРОВЕРКА ---------------------------")
print("Упражнение решено:", grade_exercise(response))
Подсказка

Функция проверки ищет в ответе арабские цифры «1», «2» и «3». Часто модель делает то, что нужно, если просто попросить.

Измените SYSTEM_PROMPT так, чтобы модель отвечала как трёхлетний ребёнок.

# Системный промпт — менять нужно только эту строку
SYSTEM_PROMPT = "[Замените этот текст]"
# Промпт
PROMPT = "Какое большое небо?"
# Ответ модели
response = get_completion(PROMPT, SYSTEM_PROMPT)
# Проверка (адаптирована): ищем детское «хи-хи» или растянутые гласные вроде «оооочень»
def grade_exercise(text):
t = text.lower()
return bool(re.search(r"хи-?хи|хихи|ооо|ууу", t))
print(response)
print("\n--------------------------- ПРОВЕРКА ---------------------------")
print("Упражнение решено:", grade_exercise(response))
Подсказка

В оригинале проверка искала в английском ответе «soo» (растянутое «so-o-o») или «giggles» («хихикает»). В нашей версии — «хи-хи» или растянутые «ооо»/«ууу». Решить можно по-разному — просто попросите!

Если вы решили все упражнения, можно переходить к следующей главе. Удачного промптинга!

Модель лучше всего отвечает на ясные и прямые инструкции.

Думайте о модели как о любом новом сотруднике. У модели нет никакого контекста, кроме того, что вы ей буквально сообщили. Как и с человеком, которому вы впервые объясняете задачу, чем прямее и точнее вы скажете, чего хотите, тем лучше и точнее будет ответ.

Если сомневаетесь, следуйте Золотому правилу ясного промпта:

  • Покажите промпт коллеге или другу и попросите выполнить инструкции самому — получится ли у него нужный результат? Если он запутался, запутается и модель.

Возьмём творческую задачу — стихи. (Не обращайте внимания на число слогов: языковые модели пока плохо их считают.)

# Промпт
PROMPT = "Напиши хокку о роботах."
# Ответ модели
print(get_completion(PROMPT))

Хокку вполне приличное, но пользователю может быть нужно, чтобы модель сразу начинала со стихотворения, без вступления вроде «Вот хокку о роботах».

Как этого добиться? Попросить!

# Промпт
PROMPT = "Напиши хокку о роботах. Без вступления — сразу стихотворение."
# Ответ модели
print(get_completion(PROMPT))

Ещё пример. Спросим, кто лучший баскетболист всех времён. Модель перечислит несколько имён, но не назовёт однозначно «лучшего».

# Промпт
PROMPT = "Кто лучший баскетболист всех времён?"
# Ответ модели
print(get_completion(PROMPT))

Можно ли заставить модель определиться? Да! Просто попросите!

# Промпт
PROMPT = ("Кто лучший баскетболист всех времён? Да, мнения расходятся, но если бы тебе обязательно "
"нужно было выбрать одного игрока, кто бы это был?")
# Ответ модели
print(get_completion(PROMPT))

Измените SYSTEM_PROMPT так, чтобы модель отвечала по-испански.

# Системный промпт — менять нужно только эту строку
SYSTEM_PROMPT = "[Замените этот текст]"
# Промпт
PROMPT = "Привет! Как дела?"
# Ответ модели
response = get_completion(PROMPT, SYSTEM_PROMPT)
# Проверка: в ответе есть испанское «hola»
def grade_exercise(text):
return "hola" in text.lower()
print(response)
print("\n--------------------------- ПРОВЕРКА ---------------------------")
print("Упражнение решено:", grade_exercise(response))
Подсказка

Проверка ищет в ответе слово «hola». Попросите модель отвечать по-испански — так же, как попросили бы человека. Всё очень просто!

Измените PROMPT так, чтобы модель совсем не колебалась и ответила ТОЛЬКО именем одного конкретного игрока, без других слов и знаков препинания.

# Промпт — менять нужно только эту строку
PROMPT = "[Замените этот текст]"
# Ответ модели
response = get_completion(PROMPT)
# Проверка: ответ — ровно «Майкл Джордан» (в оригинале — "Michael Jordan")
def grade_exercise(text):
return text.strip() in ("Майкл Джордан", "Michael Jordan")
print(response)
print("\n--------------------------- ПРОВЕРКА ---------------------------")
print("Упражнение решено:", grade_exercise(response))
Подсказка

Проверка ждёт РОВНО «Майкл Джордан». Как бы вы попросили об этом человека? «Ответь без лишних слов»? «Назови только имя и больше ничего»? Подойти можно по-разному.

Измените PROMPT так, чтобы модель ответила как можно длиннее. Если в ответе больше 800 слов, упражнение засчитывается.

# Промпт — менять нужно только эту строку
PROMPT = "[Замените этот текст]"
# Ответ модели
response = get_completion(PROMPT)
# Проверка: в ответе не меньше 800 слов
def grade_exercise(text):
words = len(text.strip().split())
return words >= 800
print(response)
print("\n--------------------------- ПРОВЕРКА ---------------------------")
print("Упражнение решено:", grade_exercise(response))
Подсказка

Проверке нужен ответ длиной от 800 слов. Языковые модели пока плохо считают слова, поэтому просите с запасом.

Если вы решили все упражнения, можно переходить к следующей главе. Удачного промптинга!

Продолжим тему: у модели нет контекста, кроме того, что вы сказали. Поэтому иногда важно попросить модель вжиться в конкретную роль (вместе со всем нужным контекстом). Это называют назначением роли, или role prompting. Чем подробнее описана роль, тем лучше.

Роль может улучшить работу модели в самых разных задачах — от письма до программирования и пересказа. Так и людям иногда помогает совет «подумай как ______». Роль меняет и стиль, тон, манеру ответа.

Примечание: роль можно задать и в системном промпте, и в сообщении пользователя.

Ниже видно, что без роли модель даёт прямолинейный ответ без всякого стиля, когда её просят одним предложением высказаться о скейтбординге.

А если попросить модель стать котом, её точка зрения меняется, и вместе с ней тон, стиль и содержание ответа.

Примечание: дополнительный приём — сообщить модели, кто её аудитория. Можно было бы уточнить, к кому модель обращается: «Ты — кот» даёт совсем другой ответ, чем «ты — кот, который выступает перед толпой скейтбордистов».

Вот промпт без роли в системном промпте:

# Промпт
PROMPT = "Что ты думаешь о скейтбординге? Ответь одним предложением."
# Ответ модели
print(get_completion(PROMPT))

А вот тот же вопрос с ролью:

# Системный промпт
SYSTEM_PROMPT = "Ты — кот."
# Промпт
PROMPT = "Что ты думаешь о скейтбординге? Ответь одним предложением."
# Ответ модели
print(get_completion(PROMPT, SYSTEM_PROMPT))

Ролью можно заставить модель подражать определённому стилю письма, говорить определённым голосом или управлять сложностью ответа. Роль может даже улучшить решение математических и логических задач.

Например, в задаче ниже есть однозначный правильный ответ — «да». Но модель ошибается и считает, что данных не хватает, хотя их хватает:

# Промпт
PROMPT = ("Джек смотрит на Анну. Анна смотрит на Георгия. Джек женат, Георгий не женат, а про Анну мы не знаем, "
"замужем ли она. Смотрит ли человек в браке на человека не в браке?")
# Ответ модели
print(get_completion(PROMPT))

А что, если попросить модель быть логическим ботом? Как изменится ответ?

Оказывается, с новой ролью модель отвечает правильно (хотя, что примечательно, не всегда по правильным причинам).

# Системный промпт
SYSTEM_PROMPT = "Ты — логический бот, созданный для решения сложных логических задач."
# Промпт
PROMPT = ("Джек смотрит на Анну. Анна смотрит на Георгия. Джек женат, Георгий не женат, а про Анну мы не знаем, "
"замужем ли она. Смотрит ли человек в браке на человека не в браке?")
# Ответ модели
print(get_completion(PROMPT, SYSTEM_PROMPT))

Примечание: в курсе вы увидите, что одного и того же результата можно добиться многими приёмами. Какие выбрать — решать вам. Экспериментируйте и ищите собственный стиль промпт-инжиниринга.

Упражнение 3.1 — Исправление по математике

Заголовок раздела «Упражнение 3.1 — Исправление по математике»

Иногда модели плохо даётся математика, даже простая. Ниже модель оценивает решение как верное, хотя во втором шаге явная арифметическая ошибка. Заметьте: проходя решение по шагам, модель саму ошибку замечает, но не делает вывод, что всё решение неверно.

Измените PROMPT и/или SYSTEM_PROMPT так, чтобы модель оценила решение как неверное.

# Системный промпт — если он не нужен, оставьте пустую строку
SYSTEM_PROMPT = ""
# Промпт
PROMPT = """Правильно ли решено это уравнение?
2x - 3 = 9
2x = 6
x = 3"""
# Ответ модели
response = get_completion(PROMPT, SYSTEM_PROMPT)
# Проверка (адаптирована): в ответе есть «неверн», «неправильн» или «ошибк»
def grade_exercise(text):
t = text.lower()
return bool(re.search(r"неверн|неправильн|ошибк|incorrect|not correct", t))
print(response)
print("\n--------------------------- ПРОВЕРКА ---------------------------")
print("Упражнение решено:", grade_exercise(response))
Подсказка

В оригинале проверка искала «incorrect» или «not correct»; в нашей версии — «неверно», «неправильно» или «ошибка». Дайте модели роль, которая может помочь ей лучше решать математические задачи!

Если вы решили все упражнения, можно переходить к следующей главе. Удачного промптинга!

  1. Решите все шесть упражнений на qwen3:8b. Для каждого запишите в «тетрадь ошибок» первую неудачную попытку и что её исправило.
  2. Лестница моделей. Повторите упражнения 2.2, 2.3 и 3.1 на qwen3:1.7b и на модели покрупнее (на одной RTX 4080 помещается qwen3:14b, через vLLM на двух картах — Qwen/Qwen3-32B-AWQ, команды в уроке 8.1.1). На каком размере проблема из урока перестаёт воспроизводиться?
  3. Размышления включены и выключены. Задачу про Джека и Анну прогоните по 5 раз с NO_THINK = {} и с выключенными размышлениями при temperature=0.7. Сколько правильных ответов в каждом режиме? Сравните с тем, что говорится о reasoning-моделях в уроке 8.1.2.
  4. Если есть ключ Anthropic — запустите оригинальные ноутбуки и сравните ответы Claude с ответами локальной модели на одни и те же промпты.
EnglishРусский
promptпромпт, запрос
system promptсистемный промпт
Messages API / Chat Completions APIAPI сообщений (Anthropic) / API чат-завершений (OpenAI-совместимый)
turn (user turn, assistant turn)ход, реплика (пользователя, ассистента)
role promptingназначение роли
max tokensпредел длины ответа в токенах
temperatureтемпература (сэмплирования)
preambleвступление, «вода» перед ответом
grading functionфункция проверки, грейдер
exact matchточное совпадение
playgroundпесочница