Python: от данных до своей сводки Урок 51 из 56
Модель на наших данных: число проходит сверку
Пятидесятый урок курса по Python. Даём модели только небольшую таблицу с идентификаторами источников, принимаем структурированные утверждения и сверяем каждое число с исходной записью. Модель предлагает формулировку, но число, единицу и год в итоговый текст подставляет наш код.
Зачем это нужно
В уроке 49 мы проверили форму JSON. Ответ {"value": 17.9} может идеально соответствовать схеме и всё равно быть выдумкой. Схема знает тип числа, но не знает нашу таблицу.
Поэтому у каждого факта появляется короткий source_id. Модель обязана вернуть его вместе с числом. Код находит исходную строку и сравнивает значение, год и единицу. Если хотя бы одно поле не совпало, утверждение не публикуется.
Ещё надёжнее не разрешать модели вписывать цифры в готовую фразу. Она возвращает только словесную подпись, а проверенные числа форматирует программа.
Сразу целиком
Файл grounded.py продолжает договор прошлого урока.
"""Урок 50: сверить ответ модели с исходными данными."""
import json
from decimal import Decimal
from jsonschema import Draft202012Validator
SOURCES = {
"inflation-kz-2025": {
"year": 2025,
"value": Decimal("11.4"),
"unit": "percent",
}
}
CLAIM_SCHEMA = {
"type": "object",
"properties": {
"source_id": {"type": "string"},
"year": {"type": "integer"},
"value": {"type": "number"},
"unit": {"type": "string"},
"label": {"type": "string", "minLength": 1},
},
"required": ["source_id", "year", "value", "unit", "label"],
"additionalProperties": False,
}
VALIDATOR = Draft202012Validator(CLAIM_SCHEMA)
def verify_claim(raw):
"""Проверить форму, ссылку на источник и каждое число."""
claim = json.loads(raw, parse_float=Decimal)
VALIDATOR.validate(claim)
source = SOURCES.get(claim["source_id"])
if source is None:
raise ValueError("неизвестный источник")
for field in ("year", "value", "unit"):
if claim[field] != source[field]:
raise ValueError(f"поле {field} не совпало с источником")
if any(char.isdigit() for char in claim["label"]):
raise ValueError("модель добавила число в подпись")
return claim, source
raw = json.dumps({
"source_id": "inflation-kz-2025",
"year": 2025,
"value": 11.4,
"unit": "percent",
"label": "Инфляция в Казахстане",
})
claim, source = verify_claim(raw)
text = f'{claim["label"]}: {source["value"]} % ({source["year"]}).'
print("источник:", claim["source_id"])
print("число сверено:", claim["value"] == source["value"])
print("единица сверена:", claim["unit"] == source["unit"])
print("итог:", text)
Вывод:
источник: inflation-kz-2025
число сверено: True
единица сверена: True
итог: Инфляция в Казахстане: 11.4 % (2025).
Данные получают адрес
source_id — не ссылка на весь сайт и не номер строки, который изменится после сортировки. Это устойчивый идентификатор конкретной записи. Он должен вести к версии набора данных, дате наблюдения и единице измерения.
В запрос модели передавайте только строки, нужные для задачи. Это уменьшает шум, расход памяти и поверхность для ошибочного выбора. Рядом сохраните версию набора данных или его отпечаток.
Почему Decimal
JSON обычно превращает дробь в float. Для денег и точной сверки двоичная дробь неудобна. json.loads(..., parse_float=Decimal) читает 11.4 как десятичное значение, которое можно сравнить с Decimal("11.4") без допуска «примерно равно».
Это не делает число истинным — лишь исключает случайную ошибку представления. Истинность даёт совпадение с выбранным источником.
Словесная часть и числовая часть
Если модель вернула label="Инфляция выросла на 15 процентов", она уже спрятала новое число в тексте. Простое правило isdigit() отклоняет такую подпись. Оно намеренно строгое: модель формулирует название показателя, код печатает значения.
Для настоящего редакционного текста одной проверки цифр мало: модель может написать неверное «снизилась», не добавляя числа. Поэтому сейчас разрешена нейтральная подпись, а вывод о росте программа позже сможет вычислить сама из двух проверенных значений.
Отказ вместо исправления
Не заменяйте ошибочное значение модели правильным молча. Сохраните причину отказа и попросите новый ответ или передайте случай человеку. Иначе журнал покажет одно, а опубликованный текст — другое.
Полезные причины отказа:
- неизвестный
source_id; - значение, год или единица не совпали;
- цифра появилась внутри словесной подписи;
- JSON не прошёл схему.
Карта урока
Восстановите опорный сигнал: источник → source_id → схема → сверка полей → код подставляет число.
Скажите своими словами
- Почему правильной JSON Schema недостаточно для проверки факта?
- Зачем нужен устойчивый
source_id, если значение уже есть в ответе? - Почему итоговое число печатает код, а не модель?
- Что нужно сделать с ответом, в котором значение не совпало?
Разминка
1. Предскажите. Равны ли значения после обычного сложения float?
print(0.1 + 0.2 == 0.3)
2. Заполните пропуск. Прочитайте дроби JSON как Decimal.
claim = json.loads(raw, ...=Decimal)
3. Почините. Код доверяет числу модели, не сверяя источник.
claim = json.loads(raw)
text = f'Инфляция: {claim["value"]} %'
Задание
Обязательное. Напишите accept_many(raw_items, sources). Примите только утверждения, у которых существуют source_id и полностью совпадают year, value, unit; подпись не должна содержать цифр. Верните принятые идентификаторы и количество отказов по причинам.
принято: ['inflation-kz-2025']
неизвестный источник: 1
не совпало значение: 1
число в подписи: 1
На своих данных. Выберите две строки сводки, присвойте им устойчивые идентификаторы и попросите модель дать нейтральные подписи. Покажите один намеренно неверный ответ и доказательство его отказа.
По желанию. Добавьте к источнику dataset_version и SHA-256 файла. Отклоняйте ответ, если он ссылается на другую версию.
Куда это встанет в проекте
Теперь модель может предложить подпись к числу, но не может незаметно заменить само число. В уроке 51 проверим более трудную границу: правдоподобные объяснения причин, которых в данных вообще нет.
Ответы
Показать ответы
- Схема проверяет форму и тип, но не сравнивает ответ с нашей таблицей.
- Он однозначно показывает, с какой записью выполнять сверку и какую версию хранить.
- Тогда в публикацию попадает значение из доверенного источника, а не копия, набранная моделью.
- Отклонить, записать конкретную причину и не передавать дальше.
False
- Аргумент —
parse_float.
import json
from decimal import Decimal
raw = '{"value": 11.4}'
claim = json.loads(raw, parse_float=Decimal)
print(claim["value"] == Decimal("11.4"))
True
- Найдите источник, сравните поля и печатайте значение источника.
claim = {"source_id": "cpi-2025", "value": 11.4}
sources = {"cpi-2025": {"value": 11.4}}
source = sources[claim["source_id"]]
assert claim["value"] == source["value"]
print(f'Инфляция: {source["value"]} %')
Инфляция: 11.4 %
Источники
- Python
json—parse_floatи разбор JSON. - Python
decimal— точная десятичная арифметика. - OWASP: validating LLM output — проверка ответа перед передачей другим компонентам.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.