Онлайн час в Google Meet
Лого на 91. НЕГ „Проф. Константин Гълъбов“

12. клас · Избираем модул · Урок 20

Структуриран изход (JSON) и валидация

Как да получим от модела JSON, който програмата може да ползва: схема, json.loads, проверка на полета, типове и стойности, какво правим при невалиден отговор и как записваме резултата в SQLite.

Проблемът с хубавия текст

„Разбира се! Родителската среща е на 24 септември от 18:30 в кабинет 204. Надявам се това да помогне!“ За човек — отличен отговор. За програма, която трябва да сложи събитието в таблица — безполезен: няма как надеждно да извади датата от него, а при следващото пускане изречението ще е друго.

Структуриран изход

Отговор в предварително зададен машинен формат — обикновено JSON по схема, — който може да се прочете от код без разчитане на текст.

Свободен текстСтруктуриран изход
За кого еза човекза програма
Разчитанетърсене на думи и надеждаjson.loads()
При промяна на формулировкатакодът се чупинищо не се променя
Подходящо заобяснения, чатизвличане, класификация, запис в база

JSON — кратък преговор

Срещнахте JSON при експорта от phpMyAdmin в урок 9. Това е текстов формат за данни: обекти във фигурни скоби с двойки „ключ“: стойност, списъци в квадратни скоби, низове в двойни кавички, числа, true/false и null.

{
  "zaglavie": "Родителска среща за 12а клас",
  "data": "2026-09-24",
  "chas": "18:30",
  "myasto": "кабинет 204",
  "vid": "среща"
}

В Python JSON обектът става речник (урок 18), списъкът — списък, null — None. Преводът е в двете посоки:

import json

zapis = json.loads('{"zaglavie": "Среща", "chas": null}')   # текст → речник
print(zapis["zaglavie"], zapis["chas"])                      # Среща None

tekst = json.dumps(zapis, ensure_ascii=False)                # речник → текст
print(tekst)                                                 # {"zaglavie": "Среща", "chas": null}

Как се иска JSON от модела

Първо ниво: с думи в промпта

Описвате полетата и давате пример (урок 17). Работи често, но моделът понякога добавя изречение преди JSON-а или го загражда в блок за код.

Второ ниво: режим JSON и схема

Услугата може да бъде задължена да върне само JSON — при Gemini с response_mime_type="application/json". Ако подадем и схема — описание на полетата и типовете им, — изходът е валиден JSON с точно тези полета.

from google.genai import types

SHEMA = {
    "type": "object",
    "properties": {
        "zaglavie": {"type": "string"},
        "data": {"type": "string"},          # ГГГГ-ММ-ДД
        "chas": {"type": "string", "nullable": True},
        "myasto": {"type": "string", "nullable": True},
        "vid": {"type": "string", "enum": ["среща", "състезание", "срок", "друго"]},
    },
    "required": ["zaglavie", "data", "vid"],
}

otgovor = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=f"Извлечи събитието от съобщението: {tekst}",
    config=types.GenerateContentConfig(
        temperature=0,
        response_mime_type="application/json",
        response_schema=SHEMA,
    ),
)
zapis = json.loads(otgovor.text)

Валидация — схемата не пази от глупости

Схемата гарантира формата, не смисъла. Дата „2026-13-45“, празно заглавие, час „след обяд“ — всичко това е валиден JSON. Проверките за смисъл остават ваша работа, както при формуляр в уеб приложение:

  • Задължителни полета — има ли ги и не са ли празни.
  • Типове — числото число ли е, датата разчита ли се (datetime.date.fromisoformat).
  • Допустими стойности — vid е от списъка; часът е между 00:00 и 23:59.
  • Смисъл — датата не е в миналото, ако събитието е предстоящо.
import datetime

DOPUSTIMI_VIDOVE = {"среща", "състезание", "срок", "друго"}

def proveri(z):
    problemi = []
    for pole in ("zaglavie", "data", "vid"):
        if pole not in z or not z[pole]:
            problemi.append(f"липсва {pole}")
    if z.get("data"):
        try:
            datetime.date.fromisoformat(z["data"])
        except ValueError:
            problemi.append(f"невалидна дата: {z['data']}")
    if z.get("vid") not in DOPUSTIMI_VIDOVE:
        problemi.append(f"недопустим vid: {z.get('vid')}")
    return problemi          # празен списък = всичко е наред

Функцията връща списък от проблеми, а не само да/не — така при неуспех знаете какво точно да поправите или да покажете.

Невалиден отговор и запис в базата

  1. Опитайте да разчетете отговора с json.loads.
  2. Ако не е JSON или проверката върне проблеми — повторете заявката веднъж, с изрично напомняне за формата.
  3. Ако пак не стане — отхвърлете записа: запишете случая в журнал и върнете на потребителя разбираемо съобщение.
  4. Никога не записвайте в базата непроверени данни.

Проверените записи отиват в базата. В Colab има вградена SQLite — същата, която ползва учебното приложение. Свързваме се със стандартния модул sqlite3:

import sqlite3

con = sqlite3.connect("sabitiya.db")
con.execute("""CREATE TABLE IF NOT EXISTS sabitiya (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    zaglavie TEXT NOT NULL,
    data TEXT NOT NULL,
    chas TEXT,
    myasto TEXT,
    vid TEXT NOT NULL
)""")
con.execute(
    "INSERT INTO sabitiya (zaglavie, data, chas, myasto, vid) VALUES (?, ?, ?, ?, ?)",
    (z["zaglavie"], z["data"], z.get("chas"), z.get("myasto"), z["vid"]),
)
con.commit()

Какво трябва да запомниш

  • Свободният текст е за хора; програмата иска JSON по схема — иначе кодът, който го разчита, се чупи при всяко пускане.
  • JSON → речник с json.loads, речник → JSON с json.dumps; при Gemini response_mime_type="application/json" и response_schema дават гарантирано валиден формат.
  • Схемата гарантира формата, не смисъла: задължителни полета, типове, допустими стойности и смисъл се проверяват от вашия код.
  • Проверката връща списък от проблеми; при неуспех — един повторен опит, после отхвърляне и журнал.
  • Записът в базата е с параметри (?), никога със слепване на низове; в базата влизат само проверени данни.
  • Температура 0 за извличане на данни.