Задача — пользователь передает json-схему, нужно сгенерировать ответ строго по переданной схеме
1. Наивный вариант
Подложить схему в контекст:
...
Return a JSON object that strictly matches the following schema:
{
"type": "object",
"properties": {
"status": {
"enum": ["SUCCESS", "FAILED"]
}
},
"required": ["status"],
"additionalProperties": false
}
Будет ли работать? Да, в большинстве случаев. Но без каких либо гарантий, что схема в итоге будет корректная
2. Добавляется constrained decoding
LLM генерирует ответ токен за токеном, на каждом шаге строя распределение вероятностей:
"{" = 0.75
"status" = 0.20
":" = 0.04
"answer" = 0.01А дальше в процесс вмешивается constrained decoding
Из json-схемы строится контекстно-свободная грамматика (CFG), которая позволяет понять, какие продолжения ответа в текущий момент всё еще могут привести к валидному результату
Например, для схемы выше грамматика могла бы выглядеть как-то так:
root ::= "{" ws "\"status\"" ws ":" ws status ws "}"
status ::= "\"SUCCESS\"" | "\"FAILED\""
И, согласно грамматике, инференс-движок накладывает маску на распределение вероятностей следующего токена
"{" = 0.75
"status" = 0.20
":" = 0.04
"answer" = 0.01
Превращается в
"{" = 0.75
"status" = 0
":" = 0
"answer" = 0
Сгенерили {
—
"status" = 0.6
":" = 0.3
"answer" = 0.1
Превращается в
"status" = 0.6
":" = 0
"answer" = 0
Сгенерили {"status"
... и так далее
—
С относительно небольшим оверхедом это позволяет генерить ответ, строго соответствующий схеме