Введение
Работа с текстом — это, пожалуй, одна из главных областей применения больших языковых моделей (LLM). Существует много способов редактирования текста. аналитики например часто работают с разметкой markdown — такой текст почти ничего не весит, с ним легко работать в любом текстовом редакторе и его легко можно сгенерировать при помощи скриптов. Но не секрет, что для подавляющего большинства пользователей редактор Word по‑прежнему остается основным инструментом. Мой личный опыт работы с текстом таков — свои тексты и научные отчеты я готовлю в редакторской системе Quarto, иногда в чистом markdown. Готовый текст рендерю в docx и уже затем выполняю чистовую доработку в MS Word. И вот здесь могут возникать трудности — если с чистым markdown можно без проблем работать при помощи встроенных в текстовый редактор (я использую Visual studio code) инструментов LLM, то в Word их нет. Вернее есть, но использовать их в России по целому ряду причин невозможно.
Я давно хотел решить эту проблему и сделать так, чтобы LLM был всегда под рукой, прямо в редакторе Word. В итоге родился небольшой пет‑проект — набор VBA‑макросов для MS Word, который добавляет функционал работы с любыми LLM через OpenAI‑совместимый API.
Идея и возможности
Основная идея проста: дать возможность взаимодействовать с LLM, не выходя из привычного интерфейса Microsoft Word. Инструмент должен быть гибким и работать с любым сервисом, у которого есть API, совместимый с OpenAI (это сейчас практически все популярные модели, включая те, что можно запустить локально).
Проект включает два ключевых сценария использования, реализованных в виде отдельных макросов:
Одиночный запрос (RunLLMQuery). Быстрый режим для работы с выделенным фрагментом. Вы выделяете текст в документе, запускаете макрос, вводите свой промпт (например, «исправь грамматические ошибки» или «перепиши в более деловом стиле»), и LLM обрабатывает текст, а результат заменяет исходный выделенный фрагмент. Это удобно, если нужно что‑то исправить в тексте — например, расставить запятые, перевести на английский язык. Или вот, только что написал почти целое предложение на заметив, что пишу в английской раскладке — вместо переписывания можно просто попросить LLM изменить раскладку текста с английской на русскую.
Чат‑интерфейс (RunLLMChat). Полноценный диалог с LLM в отдельном окне, прямо как в веб‑версиях. Вы можете задавать вопросы, уточнять, обсуждать свои идеи, а затем одним нажатием кнопки вставить последний ответ модели в документ. Это подходит для «мозгового штурма», генерации идей или когда нужно обсудить сложный текст по частям.

Этот запрос заменит текст набранный латиницей на русский текст «Этот текст написан по‑русски, но в английской раскладке»

А здесь мы уже при помощи чата с LLM просим перевести текст на немецкий язык.
Ключевые особенности
Помимо основной функциональности, я в проекте реализовал важные с технической точки зрения вещи:
● Совместимость с любым OpenAI‑совместимым API. Вы не привязаны к конкретному провайдеру. Можно указать любой API_URL, MODEL_ID и API_KEY — и работать с ChatGPT, DeepSeek, Mistral, локальными моделями через Ollama или любым другим совместимым сервисом.
● Хранение настроек в реестре Windows. Все конфиденциальные данные (API‑ключ) и настройки (URL, модель, системный промпт) сохраняются в ветке HKCU\Software\LLMWordMacro\. Это безопасно и не требует прав администратора. Для настройки есть специальный макрос ConfigureLLMSettings.
● Для еще большего удобства там же — в макросе ConfigureLLMSettings можно указать системный промпт.
● Инструмент реализован как шаблон Word (.dotm). Т.е. его без проблем можно подключить к любому документу
Лично я в качестве хаба для подключения к LLM использую сервис aitunnel.ru, но вы можете выбрать любой другой — оригинальные OpenAI или даже локальные модели.




Технические детали: Как это работает под капотом
В основе лежат классические технологии автоматизации Microsoft Office:
● VBA (Visual Basic for Applications) — язык программирования, встроенный в продукты Office. Он позволяет управлять документом, его содержимым и создавать пользовательские формы.
● HTTP‑запросы из VBA. Для общения с API я использовал объект WinHttp.WinHttpRequest.5.1 (или MSXML2.XMLHTTP), который отправляет POST‑запросы с данными в формате JSON и получает ответы от LLM.
● Парсинг JSON. Я реализовал довольно примитивную (но вполне подходящую для пет‑проекта) логику для извлечения текста ответа из стандартного JSON‑ответа OpenAI‑совместимых API.
● Пользовательская форма. Для чата создал форму (LLM_chat.frm), которая обеспечивает интерфейс для ввода сообщений и отображения истории диалога.
Не буду сильно утомлять конкретной реализацией на VBA. Посмотреть ее можно в репозитории https://github.com/Obsidian‑pb/llm_4_word_vba Остановлюсь на некоторых нюансах.

Проект состоит из модуля LLM_work, и пользовательской формы LLM_chat. В коде пожалуй стоит обратить внимание на основную функцию отправки и обработки запроса CallLLM и функции обработки JSON.
Функция CallLLM — Вызов LLM через HTTP
Public Function CallLLM(ByVal prompt As String, ByRef answer As String) As Boolean On Error GoTo ErrHandler Dim http As Object Dim payload As String Dim responseText As String Dim startTime As Single Dim waitMs As Long ' Формируем запрос в JSON к модели payload = BuildJsonPayload(prompt) ' ServerXMLHTTP избегает проблемы вложенного COM-цикла сообщений, ' которая есть у синхронного XMLHTTP — не заходит повторно в ' события Visio/LLM_chat. Set http = CreateObject("MSXML2.ServerXMLHTTP") http.Open "POST", LLM_API_URL, True http.setRequestHeader "Content-Type", "application/json; charset=utf-8" http.setRequestHeader "Authorization", "Bearer " & LLM_API_KEY ' Таймауты (мс): resolve, connect, send, receive http.setTimeouts 10000, 10000, 30000, 120000 ' Отправляем тело запроса http.send payload ' Опрашиваем readyState без заморозки UI + спиннер Dim pollCounter As Long startTime = Timer Do While http.readyState <> 4 DoEvents pollCounter = pollCounter + 1 UpdateSpinner pollCounter ' Абсолютный лимит — 3 минуты, на случай зависания сервера If Timer - startTime > 180 Then Debug.Print "CallLLM: timeout (180s)" CallLLM = False Exit Function End If Loop If http.Status <> 200 Then Log "CallLLM: HTTP " & http.Status & " " & http.StatusText CallLLM = False Exit Function End If ' Получаем ответ responseText = http.responseText answer = ExtractContentFromJson(responseText) CallLLM = (answer <> "") Exit Function ErrHandler: CallLLM = False End Function
Что делает код:
Собирает JSON‑тело через BuildJsonPayload с системным + пользовательским сообщением
Использует MSXML2.ServerXMLHTTP (асинхронный режим, чтобы не блокировать события VBA‑форм)
Устанавливает таймауты: 10c resolve, 10c connect, 30c send, 120c receive
Крутит цикл опроса readyState с DoEvents + спиннером на форме чата (чтобы было видно, что модель думает, а не зависла)
При HTTP 200 — парсит JSON через ExtractContentFromJson (ищет choices[0].message.content)
Возвращает True при успехе, False при ошибке/таймауте/не-200
Функция BuildJsonPayload — Формирование JSON‑тела запроса
' Формирование JSON-тела запроса Private Function BuildJsonPayload(ByVal prompt As String) As String Dim escUser As String Dim escSystem As String Dim messagesJson As String escUser = JsonEscape(prompt) escSystem = JsonEscape(LLM_SYSTEM_PROMPT) ' Формируем массв messages, опционально добавляя системный промпт If Trim(LLM_SYSTEM_PROMPT) <> "" Then messagesJson = _ "{""role"":""system"",""content"":""" & escSystem & """}," & _ "{""role"":""user"",""content"":""" & escUser & """}" Else messagesJson = _ "{""role"":""user"",""content"":""" & escUser & """}" End If ' Формат под ваш API (OpenAI-совместимый) BuildJsonPayload = _ "{" & _ """model"":""" & LLM_MODEL_ID & """," & _ """messages"":[" & messagesJson & "]" & _ "}" End Function
Функция JsonEscape — Простейший экранировщик для JSON‑строки
Private Function JsonEscape(ByVal s As String) As String s = Replace(s, "\", "\\") s = Replace(s, Chr(34), "\" & Chr(34)) s = Replace(s, vbBack, "\b") s = Replace(s, vbFormFeed, "\f") s = Replace(s, vbCr, "\r") s = Replace(s, vbLf, "\n") s = Replace(s, vbTab, "\t") ' Удаляем остальные управляющие символы (ASCII 0–31, кроме вышеперечисленных) Dim i As Long For i = 0 To 31 If InStr(s, Chr(i)) > 0 Then s = Replace(s, Chr(i), "\u" & Right$("0000" & Hex(AscW(Chr(i))), 4)) End If Next i JsonEscape = s End Function
Функция ExtractContentFromJson — Разбор JSON‑ответа
Private Function ExtractContentFromJson(ByVal json As String) As String Dim key As String Dim pos As Long Dim startPos As Long Dim endPos As Long Dim tmp As String ' 1. Находим блок "message":{"role":...,"content":"..."} key = """message"":{" pos = InStr(1, json, key, vbTextCompare) If pos = 0 Then ExtractContentFromJson = "" Exit Function End If ' 2. Отрезаем всё до "message":{, чтобы сократить строку tmp = Mid$(json, pos + Len(key)) ' 3. Внутри этого блока ищем "content":"..." key = """content"":""" pos = InStr(1, tmp, key, vbTextCompare) If pos = 0 Then ExtractContentFromJson = "" Exit Function End If startPos = pos + Len(key) endPos = startPos ' 4. Ищем завершающую кавычку, учитывая возможные экранированные \" Do While endPos <= Len(tmp) If Mid$(tmp, endPos, 1) = """" Then ' Проверяем, не экранирована ли кавычка If Mid$(tmp, endPos - 1, 1) <> "\" Then Exit Do End If End If endPos = endPos + 1 Loop If endPos > Len(tmp) Then ExtractContentFromJson = "" Exit Function End If ExtractContentFromJson = JsonUnescape(Mid$(tmp, startPos, endPos - startPos)) End Function
Функция JsonUnescape — Обратное преобразование для \n, \“ и \\
Private Function JsonUnescape(ByVal s As String) As String ' \\ -> \ s = Replace(s, "\\", Chr(92)) ' \" -> " s = Replace(s, "\" & Chr(34), Chr(34)) ' \n -> CRLF s = Replace(s, "\n", vbCrLf) JsonUnescape = s End Function
Как это запустить и настроить за 5 минут
Весь процесс предельно прост. Я описал его в репозитории. Вот два основных пути:
Первый. Самый быстрый способ (использовать готовый шаблон):
Скачайте файл doc.dotm из репозитория.
Откройте ваш документ Word.
Перейдите в Файл → Параметры → Надстройки.
Внизу в выпадающем списке Управление выберите Шаблоны и нажмите Перейти.
В открывшемся окне нажмите Добавить и укажите путь к скачанному файлу doc.dotm. Готово! Все макросы уже подключены.

Второй. Импорт в существующий документ:
Откройте свой документ и запустите редактор VBA (Alt+F11).
В меню редактора выберите Файл → Импорт и поочередно импортируйте файлы LLM_work.bas и LLM_chat.frm из папки vba репозитория.
Сохраните документ.
После подключения макросов следует сделать следующее:
Настроить макросы. Запустите макрос ConfigureLLMSettings (через Разработчик → Макросы или Alt+F8) и введите свои данные: API URL, ID модели и ваш секретный ключ.
Настроить ленту для удобства (по желанию). Чтобы не запускать макросы каждый раз через Alt+F8, их лучше вынести на ленту Word. Создайте новую вкладку LLM, в ней группу и добавьте нужные команды:
LLM_work.RunLLMQuery
LLM_work.RunLLMChat
LLM_work.ConfigureLLMSettings

Заключение
Не буду лукавить — при написании проекта активно пользовался тем же LLM, но в целом, учитывая, что просто хотелось попробовать «А получится ли сделать LLM‑чат в word», получилось вполне удобоваримо. Ну и польза от проекта для меня лично оказалась вполне ощутимой — насколько проще стало работать с простым текстом, когда не нужно перекидывать его из редактора в чат LLM и обратно!
Вместе с тем есть и ряд сложностей.
Самое главное — да, мы можем редактировать текст, но не можем его стилизовать, мы не можем установить заголовки, выделить жирным или курсивом, не можем сделать текст подстрочным и так далее
Мы не можем работать с таблицами — это тоже очень важно
Мы не можем передать LLM изображения из текста
Мы не можем загрузить сторонние файлы…
Эти функции реализованы в инструментах самого редактора Word и для их применения следует использовать tools, а это уже отдельная история.
Но как знать, если всерьез взяться за этот проект, может быть и получится создать из него полноценный интеллектуальный редактор для Word. Вол всяком случае по примерно такой же схеме можно реализовать подобный редактор для отечественных офисных пакетов, например для LibreOffice.
Полезные ссылки:
● Репозиторий проекта: https://github.com/Obsidian‑pb/llm_4_word_vba
● Скачать шаблон doc.dotm: https://cloud.mail.ru/public/3W4K/annsCvYGG
