TGViewer
AI и грабли AI и грабли @oestick · 13.7K subscribers
Post #295 1.18K
Что такое structured output, почему это база и как это использовать (ч.3)

#технический_четверг

ч1, ч2

Как и обещал, сегодня про применение. Прямо сейчас участвую в Enterprise RAG Challenge от @llm_under_hood, так что будут прям примеры с полей.

Зачем я участвую – отдельный вопрос. Призовое место в таких штуках – норм валидация экспертности. Особенно для ноунеймов типа меня. Особенно, когда это не абстрактные соревнования, а супер практические задачи.
Ну и протестить свои идеи на практике всегда интересно.


Так, напомню, к чему мы пришли в предыдущих частях:

1. LLM генерирует токены по одному за раз
2. Можно смотреть на вероятности токенов, прежде чем она выберет какой-то один
3. А еще можно подшаманить эти вероятности, например занулить у некоторых токенов
4. Так можем на каждом шаге занулять у всех токенов, которые "ломают" наш формат.
5. У OpenAI это все есть под капотом, а для локальных моделей есть outline

———

Теперь задача с Enterprise Rag Challenge
Нужно отвечать на вопросы по бизнес отчеты больших компаний (пример в комментах)

Пример вопроса:
For Franklin Covey, what was the value of Year-end box office market share (if applicable) at the end of the period listed in annual report?


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

Можно спросить у GPT: скажи, название компании в вопросе? Вопрос: "For Frankling Covey, what was..."
GPT: "Franklin Covey"

Пытаемся найти файл "Franklin Covey.pdf", но упс, не находим. Оказывается, отчет называется "Franklin Covey Co.pdf"

Передадим все названия pdf-ок в промпт, чтобы точно выбрала нужную. Отлично, но примерно на 10ом вопросе, вместо "Playtech plc.pdf" отвечает просто "Playtech plc". Чтобы жестко ограничить ответ, используем structured_output. Создаю Pydantic класс:

class FileName(BaseModel):
file_name: Literal[
"Playtech plc.pdf",
"Johns Lyng Group plc.pdf",
...
]


Дада, прям все 100 перечисляем. Ну либо пишем file_name: Literal[tuple(file_names)], если адекватные.

———

Идем дальше. Вот нашли pdf-ку.

Можно задавать вопрос сразу по огромной pdf-ке, но это фигня. Будем умнее и сначала пробежимся по кускам pdf-ок, и вытащим оттуда все что нам может понадобиться. Например, мы знаем, что есть вопросы по финансовым метрикам. Чтобы модель не доставала вообще все, что хоть немного похоже на финансы, зададим ей рамки:

class FinancialMetric(BaseModel):
metric_name: Literal["Total revenue", "Operating income", "Net income", ...]
value: float
currency: str = Field(description="Currency code if applicable (e.g. 'USD', 'EUR'). Leave empty if cannot be inferred.")
period: str = Field(description="Reporting period or end date.")
details: str = Field(description="Contextual information about the metric. Will be used to choose the correct one when multiple similar metrics are present.")

class PageReport(BaseModel):
financial_metrics: list[FinancialMetric]
...


Тут мы даем LLMке выбирать сколько угодно данных со страницы, но только если они подходят под типы, которые перечислены в metric_name. Это очень мощный паттерн, который занял первое место в первой версии этого соревнования

———

Ок, мы вычленили нужные данные из всех отчетов, и нашли нужный отчет под конкретный вопрос. Я все данные из отчета добавляю в промпт с указанием страниц (нужно, по условию соревнований), а и прошу ответить на вопрос. На выходе вот такая структура:

class FinalAnswer(BaseModel):
answer: str | None
page_number: int | None


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

class FinalAnswer(BaseModel):
page_numbers_with_the_most_relevant_data: list[int]
statements_from_those_pages_containing_the_most_relevant_data: list[str]
reasoning_for_the_choice_of_the_final_answer: str
answer: str | None
page_number: int | None


———

Получается, что ставя ограничения, мы только помогаем LLMке. Как то даже по-человечески
  • ❤ 12
  • 👍 12
  • 🔥 6
More from @oestick
  1. Oct 4, 2026Саша Поляков собрал стату по банам (меня тоже в этот раз задело). Мб кому-то полезно будет…
  2. Oct 4, 2026Собрал статистику по банам Claude из комментариев в канале Пост с разбором телеметрии, кот…
  3. Oct 2, 2026О, мой баг репорт в getbb.app приняли. Приятно быть причастным к развитию того, чем сам по…
  4. Oct 1, 2026А у вас тоже есть задачки, в которых нужно очень много и глубоко думать, но вы их откладыв…
  5. Sep 30, 2026Anthropic > OpenAI, но есть пара но Предпосылки 1. opus-5.5 очень хорош 2. DevDay openai –…
  6. Sep 27, 2026Скоро: видео-монтаж-слоп во всех лентах страны Opus-5.5 теперь сам такое ваншотит на remot…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →