Создание масштабных технических документов с помощью LLM

Создание масштабных технических документов с помощью LLM

"Когда я думал: "Если я передам материалы LLM, документ появится быстрее, чем я ожидал", началась реальная проблема-solving.

Недавно, участвуя в проекте архитектурного консалтинга, мне нужно было подготовить масштабный документ описания услуги, упорядочив десятки бизнес-границ (Bounded Context) и сотни сервисов. Исходные материалы включали Excel-каталог услуг, состоящий из нескольких листов, и 24-страничный Word-документ, в котором были упорядочены существующие классификации. Цель заключалась в том, чтобы на основе этих данных создать новый документ с измененной осью классификации в едином формате Word.

Сначала я подошел к этому просто. Я думал, что могу просто передать материалы LLM и попросить: "Создай документ в этой структуре". Но результат оказался совершенно другим, чем ожидалось. Первый результат составил целых 187 страниц, и состояние не позволило использовать его ни по объему, ни по формату и структуре.

В этой статье описывается процесс решения проблемы резкого увеличения объема и разрушения формата, с которыми я столкнулся, создавая масштабные технические документы с помощью LLM, на четырех этапах: разбиение ввода, переработка структуры, переход к инструментам генерации и проверка согласованности. В результате, я рассказываю о процессе создания из 187-страничного невыполненного варианта, 43-страничного финального документа, и о том, как я связал и проверил выводы LLM с кодом.

1. Предпосылки выбора технологии — почему "передача всего LLM" провалилась?

Первый опыт был самым простым. Я передал весь ввод LLM и после объяснил желаемую структуру документа на естественном языке, а затем получил результат. Однако этот метод выявил ограничения в трех аспектах.

Первое — это резкое увеличение объема. Первый результат (v1) составил 187 страниц. Проанализировав причины, я обнаружил, что LLM написал отдельные разделы (заголовки) для сотен отдельных сервисов. Поскольку к каждому сервису прилагались заголовок и абзац описания, объем значительно увеличился. Люди ожидали "форму, сжатую до строк таблицы", но LLM имеет тенденцию максимально подробно раскрывать все элементы, если их не контролировать явно.

Второе — это несоответствие формата. Стиль таблиц различался в разных разделах, уровни заголовков были непоследовательными, а титульный лист и оглавление либо не создавались вовсе, либо не соответствовали формату. Не существовало единой правила форматирования, пронизывающего весь документ.

Третье — отсутствие воспроизводимости. Даже при повторном запросе с теми же данными состав и объем разделов каждый раз менялись. Это было фатально для масштабного документа. Даже после завершения рецензии, повторное создание приводило к разрыву структуры, делая процесс рецензирования бессмысленным.

В конечном итоге, я понял, что LLM является "технологией генерации содержания", а не "технологией структурной сборки масштабного документа". Особенно, когда ответственность за объем и формат полностью возложена на естественный вывод LLM, стабильных результатов не будет.

В дальнейшем я полностью изменил направление подхода. Я решил разделить и контролировать следующие два аспекта.

  • Структура и объем: человек явно определяет скелет документа (что сжать в таблицу и что оставить в разделе)

  • Формат и рендеринг: код несет единую ответственность за стили таблиц, цвета, заголовки, титульный лист и оглавление, такие визуальные элементы.

То есть LLM должен был брать на себя только то, "что писать (контент)," а структуру и форматирование" — контролировались людьми и кодом.

2. Процесс применения и реализации

Вся работа была разделена на четыре основных этапа: разбиение ввода, пересмотр структуры, переход к инструментам генерации и проверка согласованности. Каждый этап был разделен на независимые компоненты, чтобы в случае возникновения проблем можно было быстро отследить, на каком этапе они возникли.

2.0 Разбиение ввода — учёт ограничений контекстного окна при вызове BC

Перед реализацией необходимо было определить способ передачи ввода. Поскольку входные данные содержали десятки бизнес-границ и сотни услуг, возникали опасения, что, если мы сразу загрузим их в один запрос, появятся две проблемы: одна — это ограничение токенов (Context Limit), а другая — утрата информации в середине длинного ввода (Lost in the Middle).

Поэтому мы выбрали способ, при котором входные данные не загружаются сразу, а разбиваются на единицы бизнес-границ (BC), и LLM вызывается по отдельности в цикле. Каждый вызов получает список услуг для данного BC и общую классификационную систему (определения слоёв и т.д.) в качестве контекста, и генерирует только фрагмент документа, соответствующий этому BC. Созданные фрагменты в конечном итоге были объединены в один документ.

Преимущества этого подхода были очевидны. Входные данные для каждого вызова были короткими, что практически исключало потерю информации, и в случае возникновения проблем в определённом BC достаточно было просто заново сгенерировать этот BC, а не пересоздавать весь документ. Мы подтвердили, что для больших объёмов ввода более надёжно разбивать на «значимые единицы», а не обрабатывать «одним махом».

2.1 Пересмотр структуры — 'Услуга = секция' превращается в 'Услуга = строка таблицы'

Прямой причиной резкого увеличения объёма был вопрос структуры. Поэтому мы сначала явно пересмотрели скелет документа. Основной принцип состоял в том, что «не следует создавать отдельные секции для каждой услуги». Вместо этого услуги, относящиеся к одной бизнес-границе, были объединены в одну таблицу, а каждая услуга была сжата в строку таблицы.

Этим мы установили явные ограничения для LLM-промпта. Это было не просто «пиши кратко», а «не создавай каждую услугу как секцию H4. Сжимай их в строки таблицы. Не превышать в среднем 1,5 страницы на бизнес-границу» — то есть мы контролировали структуру количественно.

[구조 제약 — 프롬프트에 명시]
1. 개별 서비스를 H4 섹션으로 만들지 말 것 (그러면 180쪽 초과).
   → 서비스는 도메인 표의 '행'으로 압축.
2. 비즈니스 경계(BC)당 평균 1.5쪽, 총 40~45쪽 목표.
3. 문서 골격(헤딩 구조)은 아래 고정 템플릿을 그대로 따를 것.
   - H1 = Part, H2 = 비즈니스 경계, H3 = 하위 섹션

Только одно это изменение позволило резко сократить объём. Документ с 187 страниц сократился до 31 страницы (v2). Было подтверждено, что «просьба к LLM сократить объём» и «постановка самой структуры в такую форму, которую нельзя увеличить» приводят к совершенно различным результатам.

2.2 Переход к инструментам генерации — ограничения базового преобразования pandoc

Документ v2 (31 страница) устранял проблему объёма и имел подходящую структуру, но возникла другая проблема. Мы преобразовали генерированную LLM разметку в Word с помощью pandoc, но базовый стиль pandoc не обеспечивал визуальную завершенность, такую как обложка, содержание, цвет, дизайн таблиц. Содержимое было верным, но выглядело не так, чтобы его можно было передать клиенту.

Поэтому мы изменили способ рендеринга. Вместо прямого преобразования разметки мы решили использовать библиотеку для создания docx (docx-js), которая позволяет непосредственно собирать структуру документа в коде. Это дало возможность точнее контролировать элементы форматирования, такие как обложка, содержание, стили заголовков и дизайн таблиц, на уровне кода.

В этом контексте была важна так называемая 'данные мост' между выводом LLM и кодом. Поскольку LLM по сути является моделью для вывода естественного языка, если мы просто возьмём произвольный текст, код не сможет его надежно разобрать. Поэтому мы обязали окончательный формат вывода LLM соответствовать строгой JSON-схеме. Вместо того чтобы дать написать непосредственным образом естественный язык, мы давали ему заполнять заранее определенные поля схемы.

// LLM 출력은 자연어가 아니라 이 스키마를 따르는 JSON 으로 강제
{
  "bc": "환자·방문",
  "overview": "환자/encounter/동의 등 ...",
  "coreServices": [
    { "id": "S-001", "name": "환자", "layer": "Data", "owner": "원무" }
  ],
  "events": ["PatientCreated", "EncounterStarted"],
  "kpis": ["환자 등록 정합성", "encounter 완료율"]
}

Код просто принимал этот JSON и механически рендерил его в объект docx-js. То есть LLM заполнял JSON-поля, а код только читал эти поля и отвечал за рисование строк таблицы и параграфов. Таким образом, когда формат был упакован в схему, исчезла возможность произвольного изменения формата LLM, и неправильные форматы ответов сразу отсеивались на этапе парсинга.

// 서식의 단일 책임: 스타일 규칙을 코드 한 곳에서만 정의
const doc = new Document({
  styles: {
    default: { document: { run: { font: "Malgun Gothic", size: 20 } } },
    paragraphStyles: [
      { id: "Heading1", run: { size: 32, bold: true }, ... },
      { id: "Heading2", run: { size: 28, bold: true }, ... },
    ],
  },
  sections: [{ children: [/* 표지 → 목차 → 본문 */] }],
});

Ключ к переходу инструментов заключался в 'разделении контента и формата'. Мы оставили содержание, созданное LLM (данные строк таблиц), а за его вид (шрифт, цвет, границы) нес ответственность исключительно код. Поэтому даже если вывод LLM немного изменялся, форма итогового документа всегда оставалась неизменной.

2.3 Форматирование стиля — цветовое кодирование и дизайн таблицы

В заключение, визуальные правила были отражены в коде для улучшения читаемости. В документе были смешаны различные области, поэтому для каждой области были назначены разные цвета, чтобы их можно было легко различать. Также для заголовков таблицы был добавлен фоновый цвет и применена разметка шуга (zebra striping) для повышения читаемости таблицы.

[영역별 색상 코딩]
Core 영역    : 딥블루 (#1F4E79)
AI 지원 영역  : 퍼플   (#7030A0)
Analytics    : 그린   (#00875A)
공통(Shared) : 앰버   (#BF8F00)

Собрав все эти правила форматирования в одном месте, они были последовательно применены ко всему документу, и после этого достаточно было изменить только одно место для отражения изменений во всей работе. Таким образом, итоговая версия v3 составила 43 страницы. Объем остался в рамках целевого диапазона, и это стал документ на уровне, готовом к передаче, включая обложку, содержание, цвета и дизайн таблиц.

2.4 Проверка согласованности — предотвращение пропусков и галлюцинаций

Наиболее беспокоящим аспектом при массовом создании было обеспечение согласованности данных. Техническая документация критически зависит от отсутствия хотя бы одной упущенной или искаженной (галлюцинационной) информации из сотен служб. LLM может устраивать правдоподобные элементы, которых нет в вводимых данных, или, наоборот, тихо упускать некоторые элементы, поэтому ручное сопоставление 43 страниц было не надежным и не поддерживаемым.

Поэтому на последнем этапе пайплайна был размещён скрипт проверки согласованности. Суть была проста. Необходимо было механически перекрестно сопоставить список идентификаторов служб из оригинального Excel и список идентификаторов служб из сгенерированных LLM JSON-данных. Вычисляя разницу между двумя наборами, проверка не проходила, если отсутствовал хотя бы один идентификатор или если был идентификатор, отсутствующий в оригинале (галлюцинация), и пайплайн не переходил к следующему этапу.

source_ids = set(load_ids_from_excel())   # 원본 카탈로그
output_ids = set(s["id"] for bc in result for s in bc["coreServices"])

missing = source_ids - output_ids   # 누락된 서비스
halluc  = output_ids - source_ids   # 원본에 없는 서비스(환각)

assert not missing, f"누락: {missing}"
assert not halluc,  f"환각: {halluc}"

Благодаря этой проверке, случаи, когда некоторые службы пропадали из определенного вызова BC или когда создавались службы, отсутствующие в вводимых данных, могли быть автоматически выявлены до того, как их увидел человек. Это позволяло избежать лишних затрат, потому что, если возникала проблема, необходимо было лишь снова сгенерировать соответствующий BC. Я убедился, что этап, который гарантирует "создано именно так, как указано", а не просто "создано", необходим в автоматизации крупной документации.

3. Уроки, полученные через проб и ошибок

Эта работа не была завершена с первого раза, она была улучшена через три версии. Уроки, извлеченные из каждой версии, следующие:

버전   분량     상태       핵심 문제 / 개선
----   ----     ------     -------------------------------
v1     187쪽    실패       서비스마다 별도 섹션 → 분량 폭증
v2      31쪽    분량 OK    구조는 잡힘, 그러나 서식 미흡(pandoc 기본)
v3      43쪽    최종       docx-js로 표지·목차·색상·표 완성

Во-первых, контроль объема должен осуществляться через структуру, а не через просьбы. Запрос LLM "пожалуйста, напишите короче" не сократил 187 страниц. Когда я четко указал структурное ограничение "сжимать службы в строки таблицы", объем был установлен.

Во-вторых, "содержание верно" и "содержимое передается" — это разные вещи. v2 имела правильное содержание и объем, но из-за недостатка формата было сложно передать его как есть. Заменив инструмент с pandoc на docx-js, форматирование было контролируемым через код, что повысило качество работы.

В-третьих, форматирование должно контролироваться в одном месте. Я сосредоточил правила, такие как цвета, таблицы и заголовки, в одном месте рендерингового кода, что способствовало поддержанию согласованности и облегчению исправлений.

В-четвертых, вывод LLM нужно не доверять, а проверять. Если бы вывод не был принудительно в формате JSON и не осуществлялось автоматического контроля согласованности идентификаторов по сравнению с вводом, пропуски и галлюцинации остались бы в документе до момента, когда их бы обнаружил человек. Встроенная в пайплайн проверка кода определила уровень доверия.

4. Результаты применения

В результате удалось превратить ненужный черновик на 187 страниц в документ на 43 страницы, готовый к передаче, включающий обложку, содержание, цвета и дизайн таблиц. Просто по объему это составляет сокращение примерно на 77%, и, что наиболее важно, структура и формат, которые раньше постоянно колебались, были стабилизированы, что является наибольшим достижением.

В аспекте рабочего процесса также были изменения. Изначально выводы LLM требовали ручной доработки, но после фиксации структуры и контроля формата с помощью кода, вмешательство человека значительно сократилось при повторном создании документов одного типа. Поскольку обновленные данные можно было заново генерировать той же pipeline, затраты на обслуживание также снизились.

5. Ограничения и планы на будущее

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

Во-первых, в настоящее время структурные ограничения и правила формата определяются человеком. В будущем будет более эффективно, если будет этап, на котором предложится оптимальное количество и уровень сжатия на основе масштаба входных данных.

Во-вторых, когда входные данные представлены в табличной (Excel) форме, система работала хорошо, но в случае неструктурированных документов предварительная обработка для извлечения структуры необходима. Мы надеемся, что эта часть, в сочетании с предыдущим опытом анализа макета документов, позволит расширить весь процесс до "чтения неструктурированных документов для понимания структуры и реконструкции с помощью нового определения."

Из этого опыта я лично осознал, что просто "Искусственный интеллект хорошо пишет" не приведет к созданию крупных выходных данных, которые можно использовать в практике. В конечном итоге важно не "создавать содержание", а "чтобы структура контролировалась человеком, формат отвечал за код, а результаты проверялись" — это опыт, который я снова подтвердил.

Список литературы

Джунни

Site footer