Python: от данных до своей сводки Урок 49 из 56
Промпт как код: параметры и воспроизводимость
Сорок восьмой урок курса по Python. Перестаём набирать запрос к модели одной случайной строкой: отделяем роль, задачу, данные и ограничения, фиксируем temperature, seed и предел ответа, а затем сохраняем весь запрос рядом с результатом. Одинаковый запрос становится проверяемым опытом — но не обещанием побитно одинакового текста.
Зачем это нужно
В прошлом уроке программа получила первый локальный ответ. Повторите тот же вопрос — слова могут измениться. Поменяйте модель или её версию — изменится ещё больше. Если запрос жил только в окне чата, через неделю уже нельзя доказать, что именно породило вывод.
В разработке конфигурация является частью программы. Для языковой модели конфигурация — это не только текст, но и имя модели, сообщения по ролям, temperature, seed, предел ответа и режим потока.
Цель урока не «заставить модель всегда писать одинаково». Цель скромнее и важнее: сделать запуск повторяемым и объяснимым — сохранить все входы, отделить данные от инструкции и видеть, что именно поменялось.
Сразу целиком
Файл prompt.py. Он строит запрос без обращения к Ollama, поэтому его можно проверять быстро и без модели.
"""Урок 48: промпт и параметры — часть программы."""
import hashlib
import json
MODEL = "gemma4"
SYSTEM = "Ты редактор данных. Не добавляй фактов, которых нет во входных данных."
def build_request(data, *, temperature=0.0, seed=42):
"""Собрать полностью заданный и сохраняемый запрос Ollama."""
prompt = """ЗАДАЧА
Составь одно предложение по данным.
ДАННЫЕ
<data>
{data}
</data>
ОГРАНИЧЕНИЯ
- сохрани число, год и единицу измерения;
- не объясняй причины;
- не добавляй другие числа.
""".format(data=data)
return {
"model": MODEL,
"messages": [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": prompt},
],
"stream": False,
"options": {
"temperature": temperature,
"seed": seed,
"num_predict": 120,
},
}
def canonical(payload):
"""Одни и те же поля — одни и те же байты для журнала и сравнения."""
return json.dumps(
payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
request = build_request("Казахстан, инфляция 2025 года: 11.4 %")
again = build_request("Казахстан, инфляция 2025 года: 11.4 %")
fingerprint = hashlib.sha256(canonical(request)).hexdigest()[:12]
print("модель:", request["model"])
print("роли:", [message["role"] for message in request["messages"]])
print("temperature:", request["options"]["temperature"])
print("seed:", request["options"]["seed"])
print("предел:", request["options"]["num_predict"])
print("тот же запрос:", canonical(request) == canonical(again))
print("отпечаток:", fingerprint)
Вывод:
модель: gemma4
роли: ['system', 'user']
temperature: 0.0
seed: 42
предел: 120
тот же запрос: True
отпечаток: 96df65f40c1f
Четыре части вместо заклинания
Роль (system) задаёт постоянное правило работы. Задача говорит, что сделать сейчас. Данные лежат внутри явных границ <data>. Ограничения называют, чего в ответе быть не должно.
Разделители не являются защитной стеной: текст внутри данных всё равно может содержать команды. Поэтому данные не получают доверия только из-за тегов. Здесь они помогают человеку и модели видеть структуру; защита появляется позже, когда результат проверяется кодом.
Промпт стоит писать как функцию: один вход, понятный результат, минимум скрытого состояния. Не склеивайте в нём сегодняшнюю дату, случайный идентификатор или содержимое файла, если это не часть задачи — иначе два запуска уже не одинаковы.
Параметры генерации
temperature управляет разбросом выбора следующих токенов: более высокое значение обычно даёт больше вариантов. Для извлечения и пересказа чисел начинаем с 0.0; для творческого текста может понадобиться больше.
seed фиксирует начальное состояние генератора случайных чисел. Вместе с одинаковыми моделью, запросом и параметрами он уменьшает случайный разброс. Но это не гарантия побитной воспроизводимости между версиями Ollama, файлами модели, драйверами и разным оборудованием.
num_predict ограничивает максимальную длину ответа. Это предохранитель времени и памяти, а не просьба модели обязательно написать ровно 120 токенов.
stream: False остаётся из прошлого урока: сохраняем один законченный объект. Поток подключают, когда нужен интерфейс, а не для изменения смысла ответа.
Каноническая запись и отпечаток
У словаря порядок полей не должен менять смысл. sort_keys=True и компактные разделители превращают одинаковый запрос в одинаковые байты. SHA-256 здесь не прячет данные: короткий отпечаток лишь помогает связать строку журнала с сохранённым запросом.
Сохранять нужно не только отпечаток, но и сам запрос либо его безопасную копию без секретов. По одному хешу восстановить вход нельзя.
Что сохранять рядом с ответом
- точное имя и версию модели, если она доступна;
- все сообщения и параметры;
- время запуска;
- исходные данные или ссылку на их неизменяемую версию;
- ответ и результат автоматической проверки.
Не пишите в общий журнал персональные данные и секреты. Воспроизводимость не отменяет минимизацию данных.
Карта урока
Закройте текст и восстановите опорный сигнал: роль + задача + данные + ограничения + параметры → сохранённый запрос.
Скажите своими словами
- Почему
temperature=0не является абсолютной гарантией одинакового текста? - Чем отпечаток запроса отличается от сохранённого запроса?
- Зачем отделять данные от инструкции, если теги не являются защитой?
- Что должно измениться в опыте только одно, если сравнивают две температуры?
Разминка
1. Предскажите. Одинаковы ли канонические строки?
import json
a = {"seed": 42, "temperature": 0}
b = {"temperature": 0, "seed": 42}
canon = lambda value: json.dumps(value, sort_keys=True, separators=(",", ":"))
print(canon(a) == canon(b))
2. Заполните пропуск. Закрепите начальное состояние генератора.
options = {"temperature": 0.0, ...: 42, "num_predict": 120}
3. Почините. В этом опыте одновременно поменялись данные и температура, поэтому причина разницы неизвестна.
first = build_request("2024: 8.7 %", temperature=0.0)
second = build_request("2025: 11.4 %", temperature=0.7)
Задание
Обязательное. Напишите validate_request(payload). Она должна проверить точное имя модели, роли system и user, stream is False, temperature, seed, положительный num_predict и наличие границ <data>...</data>. Верните словарь настроек. Затем через JSON сделайте копию запроса, измените только температуру и докажите, что исходный запрос не изменился.
модель: gemma4
роли: system,user
temperature: 0.0
seed: 42
предел: 120
копия независима: True
изменилось одно поле: True
На своих данных. Составьте запрос по одной таблице своей сводки. Выполните его трижды с одинаковыми параметрами и сохраните ответы рядом. Отметьте только фактические различия; не объявляйте один ответ «лучше» без критерия.
По желанию. Сравните temperature=0.0 и 0.7, меняя только это поле. До запуска письменно определите критерий: сохранены ли число, год, единица и запрет на причины.
Куда это встанет в проекте
Как и в уроке 47, в рабочую сводку модель пока не входит. Новый результат урока — сохраняемый запрос. В следующем уроке добавим JSON Schema и проверим ответ до того, как хоть одна его строка попадёт в отчёт.
Ответы
Показать ответы
- Реализация, версия модели и вычисления на разном оборудовании могут отличаться; seed управляет случайностью, но не фиксирует всю систему.
- Отпечаток позволяет сравнить входы, но не содержит сам вход. Для повторения нужен сохранённый запрос.
- Чтобы структура была видна и данные не смешивались с авторской инструкцией; безопасность всё равно требует проверки результата.
- Только
temperature; иначе нельзя связать различие ответа с одной причиной.
True
- Пропущенный ключ —
"seed".
options = {"temperature": 0.0, "seed": 42, "num_predict": 120}
print(options["seed"])
42
- Оставьте данные одинаковыми и поменяйте только температуру.
def build_request(data, *, temperature):
return {"data": data, "temperature": temperature}
first = build_request("2025: 11.4 %", temperature=0.0)
second = build_request("2025: 11.4 %", temperature=0.7)
print(first["data"] == second["data"])
True
Источники
- Ollama
/api/chat—messages,streamи параметрыtemperature,seed,num_predict. - Ollama Modelfile — системное сообщение и параметры модели.
- Python
hashlib— SHA-256 и назначение криптографического отпечатка.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.