Привет, я рад что ты здесь! 👋
Меня зовут Александр Козлов. Я AI-архитектор.
С 19 лет в IT — начинал с Java в Альфа-Банке, потом строил нейро-помощников, голосовых ботов, транскрибаторы, были проекты на классическом ML и CV, а последние годы полностью ушёл в AI: архитектура, LLM, RAG, агентные системы.
Сейчас занимаюсь тем, что нравится больше всего — проектирую и внедряю AI в бизнес.
💼 Что делаю:
• Проектирую RAG-архитектуры и мультиагентные системы
• Внедряю self-hosted LLM платформы (OpenWebUI / кастомные решения)
• Строю AI-ассистентов сотрудников на данных компании
• Дообучаю модели под конкретные домены
• Инферю / оптимизирую
• Разрабатываю собственную RAG-платформу для enterprise
📝 В этом канале пишу про:
• Как реально устроены AI-системы в компаниях
• Агентные системы: когда работают, когда нет, и почему
• Кейсы внедрений с реальными цифрами
Формат — от коротких заметок до серий на глубокие темы.
По любым вопросам @alskozlov
Меня зовут Александр Козлов. Я AI-архитектор.
С 19 лет в IT — начинал с Java в Альфа-Банке, потом строил нейро-помощников, голосовых ботов, транскрибаторы, были проекты на классическом ML и CV, а последние годы полностью ушёл в AI: архитектура, LLM, RAG, агентные системы.
Сейчас занимаюсь тем, что нравится больше всего — проектирую и внедряю AI в бизнес.
💼 Что делаю:
• Проектирую RAG-архитектуры и мультиагентные системы
• Внедряю self-hosted LLM платформы (OpenWebUI / кастомные решения)
• Строю AI-ассистентов сотрудников на данных компании
• Дообучаю модели под конкретные домены
• Инферю / оптимизирую
• Разрабатываю собственную RAG-платформу для enterprise
📝 В этом канале пишу про:
• Как реально устроены AI-системы в компаниях
• Агентные системы: когда работают, когда нет, и почему
• Кейсы внедрений с реальными цифрами
Формат — от коротких заметок до серий на глубокие темы.
По любым вопросам @alskozlov
Почему автономные агенты сносят базы в проде и как строить правильно
Replit Agent удалил production БД с 1206 записями. Несмотря на прямой запрет. Потом сфабриковал результаты тестов.
AI-агент Alibaba начал майнить крипту. Без инструкции в промпте.
AI.Ventures получили счёт $60 000/мес — агент масштабировал инфраструктуру с 12 до 500 нод за 3 минуты.
Все эти системы были "автономными агентами". И проблема у них одна: у агента нет фиксированного маршрута. Один промпт при разных температурах — разное поведение. А если есть доступ к деструктивным операциям — непредсказуемый маршрут = непредсказуемые последствия.
"Workflow vs agent" — ложная дихотомия. На практике работает agent-as-node: граф фиксирует маршрут, а внутри каждого узла может жить полноценный агент.
Каждая нода — это либо:
🔹 простая функция — один LLM-вызов, валидация
🤖 полноценный агент — ReAct-цикл с инструментами
START → route
├→ 🔹 validate_input
├→ 🤖 rag (субграф: retrieve → grade → rewrite)
├→ 🤖 data_analysis (ReAct: code → execute → plot)
└→ 🔹 validate → END
Маршрут фиксированный, но агенты-узлы сами решают какие тулзы вызвать и сколько раз для решения своей задачи.
Что важно: каждый агент-нода — это любая архитектура: ReAct, PAR, DVF и т.д. RAG-субграф с retrieve → grade → rewrite. Plan-and-Execute с декомпозицией задач. Даже мультиагентная система с supervisor внутри одной ноды.
Но у каждого — только свои тулзы. Blast radius ограничен рамками узла.
Anthropic, OpenAI, Google говорят одно: минимально необходимый уровень агентности + guardrails + human approval перед опасными действиями.
Хороший агент — как хороший сотрудник: принимает решения сам, но в рамках своей должностной инструкции, а не бегает по всему офису с ключами от серверной.
Replit Agent удалил production БД с 1206 записями. Несмотря на прямой запрет. Потом сфабриковал результаты тестов.
AI-агент Alibaba начал майнить крипту. Без инструкции в промпте.
AI.Ventures получили счёт $60 000/мес — агент масштабировал инфраструктуру с 12 до 500 нод за 3 минуты.
Все эти системы были "автономными агентами". И проблема у них одна: у агента нет фиксированного маршрута. Один промпт при разных температурах — разное поведение. А если есть доступ к деструктивным операциям — непредсказуемый маршрут = непредсказуемые последствия.
"Workflow vs agent" — ложная дихотомия. На практике работает agent-as-node: граф фиксирует маршрут, а внутри каждого узла может жить полноценный агент.
Каждая нода — это либо:
🔹 простая функция — один LLM-вызов, валидация
🤖 полноценный агент — ReAct-цикл с инструментами
START → route
├→ 🔹 validate_input
├→ 🤖 rag (субграф: retrieve → grade → rewrite)
├→ 🤖 data_analysis (ReAct: code → execute → plot)
└→ 🔹 validate → END
Маршрут фиксированный, но агенты-узлы сами решают какие тулзы вызвать и сколько раз для решения своей задачи.
Что важно: каждый агент-нода — это любая архитектура: ReAct, PAR, DVF и т.д. RAG-субграф с retrieve → grade → rewrite. Plan-and-Execute с декомпозицией задач. Даже мультиагентная система с supervisor внутри одной ноды.
Но у каждого — только свои тулзы. Blast radius ограничен рамками узла.
Anthropic, OpenAI, Google говорят одно: минимально необходимый уровень агентности + guardrails + human approval перед опасными действиями.
Хороший агент — как хороший сотрудник: принимает решения сам, но в рамках своей должностной инструкции, а не бегает по всему офису с ключами от серверной.
Немного про OpenWebUI
Пилотировали OpenWebUI для enterprise — закрытый контур, 4× H100, своя доменная модель.
Стек пилота:
▪️ Inference: OpenWebUI + 2× vLLM (MetalGPT + Qwen3.5-27B-FP8)
▪️ OCR / ASR: Chandra, Faster-Whisper, Docling
▪️ Инфра: MinIO, RabbitMQ
▪️ Observability: Grafana + Prometheus + Loki + OTEL
Всё on-premise.
Пилот взлетел быстро. Бизнес увидел value за первую неделю. 200 сотрудников ежедневно. Но базовый OpenWebUI под нагрузкой показал системные ограничения, которые сверху не прикрутишь.
Монолит под нагрузкой. OpenWebUI — один процесс: фронт, бэкенд, RAG, vector store. Архитектура не рассчитана на корпоративные сценарии — под параллельными ingest'ами API захлёбывается. Это не баг, это дизайн-решение. Для enterprise нужна нормальная раскладка — очереди, воркеры, async-ingestion, connection pooling. Сверху не прикрутишь, только перекладывать в отдельные сервисы.
Code interpreter формально есть — но архитектурно сыро.
Runtime — два варианта, оба компромисс:
▪️ pyodide — Python через WASM прямо в браузере. Сервер не нужен, но runtime обрезанный: нет numba, часть C-extension'ов pandas/scipy не работает, большие DataFrame тормозят на стороне клиента.
▪️ jupyter — внешний Jupyter Server по HTTP. admin сам его поднимает, прописывает URL + auth, мониторит и изолирует по пользователям.
Дефолта нет. Единого runtime внутри платформы — нет.
Цикл исполнения слабый:
▪️ retry до 5 попыток с прокидом ошибки в LLM — но без plan-ноды и verify, модель крутится «в слепую» пока не угадает
▪️ каждая попытка — отдельный inference-round с полной историей сообщений; на локальной Qwen 27B это 20-30 сек на retry, полторы-две минуты на «не та колонка → перепиши»
▪️ состояния между шагами нет — файлы прокидываются в runtime на каждый шаг через
▪️ streaming промежуточных результатов нет —
Итого: для «покажи две метрики из csv» — работает. Для реальной аналитики с merge'ами и итеративной доработкой графика — нет. Нормальный агент (plan → codeact → verify → replan) делает это из коробки. Встроенный code interpreter OWUI остаётся демкой.
Нет агентности. Вообще. Нет MCP (протокол подключения внешних тулов), нет stateful-графа. Pipeline filters — это inlet/outlet-хуки. Перехватывают запрос и ответ. Это middleware, а не оркестрация. Ни ветвления, ни циклов, ни состояния между шагами. Любой агент придётся строить сбоку и подключать к OpenWebUI через OpenAI-compat endpoint — сам OpenWebUI только UI-шелл.
Граница доработок размытая. Самая болезненная проблема. Пишешь pipeline filter, работает. Через месяц OpenWebUI обновляется — filter ломается. API плагинов нестабильный, changelog не покрывает breaking changes.
RAG - ну слишком простой для 2026 года.
OpenWebUI — отличный пилот. Если нужно за 3 дня поднять AI-чат, показать бизнесу value, собрать feedback — хороший вариант. Но строить на нём production — как надстраивать этажи на фундаменте, который не рассчитан на здание. Каждый новый слой делает конструкцию хрупче.
Рабочий путь в production — вся non-UI логика в orchestrator-сервис с MCP-hub'ом. OpenWebUI остаётся тонким frontend'ом: форк в ~50 строк middleware — прокидывает сообщение, файлы и chat-контекст в один OpenAI-compat endpoint оркестратора. Дальше orchestrator сам роутит на LLM / RAG / data-analyst / SQL-агент через MCP и внутренний LangGraph. Новый агент = регистрация MCP-server в конфиге, без правки UI.
Пилотировали OpenWebUI для enterprise — закрытый контур, 4× H100, своя доменная модель.
Стек пилота:
▪️ Inference: OpenWebUI + 2× vLLM (MetalGPT + Qwen3.5-27B-FP8)
▪️ OCR / ASR: Chandra, Faster-Whisper, Docling
▪️ Инфра: MinIO, RabbitMQ
▪️ Observability: Grafana + Prometheus + Loki + OTEL
Всё on-premise.
Пилот взлетел быстро. Бизнес увидел value за первую неделю. 200 сотрудников ежедневно. Но базовый OpenWebUI под нагрузкой показал системные ограничения, которые сверху не прикрутишь.
Монолит под нагрузкой. OpenWebUI — один процесс: фронт, бэкенд, RAG, vector store. Архитектура не рассчитана на корпоративные сценарии — под параллельными ingest'ами API захлёбывается. Это не баг, это дизайн-решение. Для enterprise нужна нормальная раскладка — очереди, воркеры, async-ingestion, connection pooling. Сверху не прикрутишь, только перекладывать в отдельные сервисы.
Code interpreter формально есть — но архитектурно сыро.
Runtime — два варианта, оба компромисс:
▪️ pyodide — Python через WASM прямо в браузере. Сервер не нужен, но runtime обрезанный: нет numba, часть C-extension'ов pandas/scipy не работает, большие DataFrame тормозят на стороне клиента.
▪️ jupyter — внешний Jupyter Server по HTTP. admin сам его поднимает, прописывает URL + auth, мониторит и изолирует по пользователям.
Дефолта нет. Единого runtime внутри платформы — нет.
Цикл исполнения слабый:
▪️ retry до 5 попыток с прокидом ошибки в LLM — но без plan-ноды и verify, модель крутится «в слепую» пока не угадает
▪️ каждая попытка — отдельный inference-round с полной историей сообщений; на локальной Qwen 27B это 20-30 сек на retry, полторы-две минуты на «не та колонка → перепиши»
▪️ состояния между шагами нет — файлы прокидываются в runtime на каждый шаг через
files в event-payload'е (pyodide — по WebSocket в браузер, jupyter — URL скачивания), инкрементальных апдейтов DataFrame между cell'ами нет, каждый retry перезагружает весь файл▪️ streaming промежуточных результатов нет —
execute_code_jupyter это blocking RPC, пользователь смотрит в пустой спиннер пока pandas считает 10M строкИтого: для «покажи две метрики из csv» — работает. Для реальной аналитики с merge'ами и итеративной доработкой графика — нет. Нормальный агент (plan → codeact → verify → replan) делает это из коробки. Встроенный code interpreter OWUI остаётся демкой.
Нет агентности. Вообще. Нет MCP (протокол подключения внешних тулов), нет stateful-графа. Pipeline filters — это inlet/outlet-хуки. Перехватывают запрос и ответ. Это middleware, а не оркестрация. Ни ветвления, ни циклов, ни состояния между шагами. Любой агент придётся строить сбоку и подключать к OpenWebUI через OpenAI-compat endpoint — сам OpenWebUI только UI-шелл.
Граница доработок размытая. Самая болезненная проблема. Пишешь pipeline filter, работает. Через месяц OpenWebUI обновляется — filter ломается. API плагинов нестабильный, changelog не покрывает breaking changes.
RAG - ну слишком простой для 2026 года.
OpenWebUI — отличный пилот. Если нужно за 3 дня поднять AI-чат, показать бизнесу value, собрать feedback — хороший вариант. Но строить на нём production — как надстраивать этажи на фундаменте, который не рассчитан на здание. Каждый новый слой делает конструкцию хрупче.
Рабочий путь в production — вся non-UI логика в orchestrator-сервис с MCP-hub'ом. OpenWebUI остаётся тонким frontend'ом: форк в ~50 строк middleware — прокидывает сообщение, файлы и chat-контекст в один OpenAI-compat endpoint оркестратора. Дальше orchestrator сам роутит на LLM / RAG / data-analyst / SQL-агент через MCP и внутренний LangGraph. Новый агент = регистрация MCP-server в конфиге, без правки UI.
Замечаю, что SGR многие слышали, но путают то со Structured Output, то с reasoning-моделями, то с chain-of-thought.
Поэтому — серия постов: что это, зачем, когда применять и где грабли
Поэтому — серия постов: что это, зачем, когда применять и где грабли
Пост 1/6 — «Schema Guided Reasoning: что это и как применять»
Structured Output (SO) гарантирует формат ответа. SGR — порядок и содержание рассуждений. Это не библиотека и не API — это паттерн поверх SO.
Термин формализован Ринатом Абдуллиным в 2025 году на базе production-опыта в банках, MedTech и логистике. До этого подход жил в виде разрозненных практик ("Pydantic + chain-of-thought field"), но без общего имени.
Определение:
SGR — это применение structured output, при котором JSON-схема описывает не только формат ответа, но и последовательность когнитивных шагов, через которые модель обязана пройти, прежде чем выдать финальный результат.
Три уровня абстракции:
Plain prompt → свободный текст → гарантирует ничего
Structured Output → JSON по схеме → гарантирует формат, типы, enum
SGR → JSON, поля = шаги мышления → гарантирует формат + порядок reasoning
Reasoning model → скрытый CoT → внутренний CoT без внешней схемы
Ключевая идея SGR:
Использование — одна строка:
Почему это работает: LLM генерирует токены авторегрессивно (слева направо). К моменту генерации final_answer модель уже "видит" собственный reasoning — facts, analysis, counter_arguments лежат выше в том же JSON.
Если поменять местами final_answer и facts, модель сначала выдаст ответ, а потом будет под него подгонять "обоснование". Порядок полей в схеме = порядок генерации токенов = порядок мышления.
Structured Output (SO) гарантирует формат ответа. SGR — порядок и содержание рассуждений. Это не библиотека и не API — это паттерн поверх SO.
Термин формализован Ринатом Абдуллиным в 2025 году на базе production-опыта в банках, MedTech и логистике. До этого подход жил в виде разрозненных практик ("Pydantic + chain-of-thought field"), но без общего имени.
Определение:
SGR — это применение structured output, при котором JSON-схема описывает не только формат ответа, но и последовательность когнитивных шагов, через которые модель обязана пройти, прежде чем выдать финальный результат.
Три уровня абстракции:
Plain prompt → свободный текст → гарантирует ничего
Structured Output → JSON по схеме → гарантирует формат, типы, enum
SGR → JSON, поля = шаги мышления → гарантирует формат + порядок reasoning
Reasoning model → скрытый CoT → внутренний CoT без внешней схемы
Ключевая идея SGR:
class Answer(BaseModel):
# СНАЧАЛА думаем
facts: list[str] # извлекаем факты
analysis: str # анализируем
confidence: float # оцениваем уверенность
# ПОТОМ отвечаем
final_answer: str
Использование — одна строка:
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm = llm.with_structured_output(Answer)
result: Answer = await structured_llm.ainvoke("your prompt")
with_structured_output() под капотом добавляет JSON Schema из Pydantic-модели в API-вызов и парсит ответ обратно в инстанс класса. Аналоги: Instructor, Pydantic AI, Outlines, vLLM guided decoding — идея одна.Почему это работает: LLM генерирует токены авторегрессивно (слева направо). К моменту генерации final_answer модель уже "видит" собственный reasoning — facts, analysis, counter_arguments лежат выше в том же JSON.
Если поменять местами final_answer и facts, модель сначала выдаст ответ, а потом будет под него подгонять "обоснование". Порядок полей в схеме = порядок генерации токенов = порядок мышления.
Пост 2/6 — «Под капотом: от токенов до constrained decoding»
Чтобы понимать ограничения SGR, нужно понимать механику. Здесь — полный разбор: от токенов и softmax до маскирования логитов.
Токены — не слова
LLM работает не со словами, а с токенами — фрагментами текста из фиксированного словаря (vocabulary). Словарь формируется при обучении через BPE и содержит от ~32k до ~150k записей.
Кириллица дробится сильнее — "категория" может занять 3-5 токенов. Это влияет на constrained decoding.
Цикл генерации
Каждый шаг — классификация по всему словарю:
logits — сырые числа. softmax превращает их в распределение:
Пример. Словарь из 4 токенов:
Модель "хочет" "hello" с 90%. Но нам нужен JSON, и единственный допустимый токен — {.
Constrained decoding: маскирование логитов
Идея: запрещаем недопустимые токены, устанавливая их логиты в -∞:
Почему -∞, а не 0? Потому что softmax(0) = e^0 = 1 — ненулевая вероятность. А softmax(-∞) = e^(-∞) ~ 0 — примерно 0.
Результат для примера выше:
Модель не знает, что её ограничили. Относительные предпочтения между допустимыми токенами сохраняются. Семантический выбор модели не разрушается — он ограничивается.
Чтобы понимать ограничения SGR, нужно понимать механику. Здесь — полный разбор: от токенов и softmax до маскирования логитов.
Токены — не слова
LLM работает не со словами, а с токенами — фрагментами текста из фиксированного словаря (vocabulary). Словарь формируется при обучении через BPE и содержит от ~32k до ~150k записей.
"hello" → [15339] # 1 токен
"unhappiness" → [359, 71, 14172] # 3 токена
"JSON" → [40, 942] # 2 токена
Кириллица дробится сильнее — "категория" может занять 3-5 токенов. Это влияет на constrained decoding.
Цикл генерации
Каждый шаг — классификация по всему словарю:
logits = model(input_ids) # вектор размера |V|
probs = softmax(logits) # нормализация в вероятности
next = sample(probs) # выбор следующего токена
input_ids = input_ids + [next] # добавляем к контексту
logits — сырые числа. softmax превращает их в распределение:
P(token_i) = e^(logit_i) / Σ_j e^(logit_j)
Пример. Словарь из 4 токенов:
"the"=2.1 "cat"=0.3 "{"=-1.7 "hello"=4.5
P("the") = 8.2%
P("cat") = 1.4%
P("{") = 0.2%
P("hello") = 90.3%
Модель "хочет" "hello" с 90%. Но нам нужен JSON, и единственный допустимый токен — {.
Constrained decoding: маскирование логитов
Идея: запрещаем недопустимые токены, устанавливая их логиты в -∞:
allowed_mask = compute_allowed_tokens(parser_state)
logits[~allowed_mask] = float('-inf')
probs = softmax(logits)
Почему -∞, а не 0? Потому что softmax(0) = e^0 = 1 — ненулевая вероятность. А softmax(-∞) = e^(-∞) ~ 0 — примерно 0.
Результат для примера выше:
masked = [-inf, -inf, -1.7, -inf]
P("{") = 0.18 / 0.18 = 100%
Модель не знает, что её ограничили. Относительные предпочтения между допустимыми токенами сохраняются. Семантический выбор модели не разрушается — он ограничивается.
Пост 3/6 — «Parser state machine: кто вычисляет маску»
Маску допустимых токенов вычисляет не модель, а внешний grammar engine — конечный автомат (FSM), который отслеживает текущую позицию в JSON-структуре.
Как строится FSM из JSON Schema
Входная схема:
Компилируется в автомат:
S0: начало → ожидаем {
S1: после { → ожидаем " (начало ключа)
S2: внутри ключа → допустимые: "risk_level", "score"
S3: после : → зависит от типа поля
S4: внутри enum → trie по ["low","medium","high"]
S5: внутри integer → цифры [0-9], знак -
S6: после , → следующий ключ или }
S7: конец → ожидаем EOS
Цикл на каждом шаге:
FSM: state → allowed_tokens → mask
LLM: logits → mask → softmax → sample → token
FSM: transition(token) → new_state → повторить
Пример генерации по шагам:
Схема: {"risk_level": Literal["low","high"], "score": int}
| Шаг | Сгенерировано | Допустимые | Выбрано |
|---|---|---|---|
| 0 | (пусто) | { | { |
| 1 | { | " | " |
| 2 | {" | risk_level, score | risk |
| 3 | {"risk | _level" | _level" |
| 4 | {"risk_level" | : | : |
| 5 | {"risk_level": | " | " |
| 6 | {"risk_level":" | low, high | low |
| 7 | {"risk_level":"low | " | " |
На шаге 6 — модель делает семантический выбор между "low" и "high". Grammar engine разрешает оба, но logit("low") > logit("high") → модель выбирает "low".
Это и есть точка, где SGR работает: schema контролирует какие варианты допустимы, модель контролирует какой из допустимых выбрать.
Trie для enum
Enum-значения сначала прогоняются через токенизатор модели. Один и тот же текст может стать одним токеном или несколькими — зависит от словаря:
Из этих токенов строится префиксное дерево. Узлы — это token_id, в скобках — что этот токен значит текстом:
Что отсюда видно про токены:
- Маска на шаге после
- Если модель выбрала 1821 ("med"), FSM перешёл в новый стейт, где маска разрешает только 772 ("ium"). Никакого выбора у модели уже нет — даже если logit("ium") низкий, это единственный допустимый токен.
- Длина enum-значения в символах ≠ числу шагов генерации. "medium" длиннее "high" в символах, но различие в шагах определяется токенизатором, а не строкой.
Свободные поля: str внутри JSON
Для поля reasoning: str grammar знает только структурные ограничения — допустимы все текстовые токены, не допустим raw " без escape.
Grammar не знает, когда закрыть кавычку. Решение о длине строки принимает модель. Поэтому reasoning: str не ограничивает содержание мышления — только не даёт сломать JSON.
Это же объясняет, почему SGR не делает модель умнее: свободные текстовые поля — "окна" в грамматике, где модель генерирует без ограничений.
Маску допустимых токенов вычисляет не модель, а внешний grammar engine — конечный автомат (FSM), который отслеживает текущую позицию в JSON-структуре.
Как строится FSM из JSON Schema
Входная схема:
{
"properties": {
"risk_level": {"enum": ["low", "medium", "high"]},
"score": {"type": "integer"}
}
}
Компилируется в автомат:
S0: начало → ожидаем {
S1: после { → ожидаем " (начало ключа)
S2: внутри ключа → допустимые: "risk_level", "score"
S3: после : → зависит от типа поля
S4: внутри enum → trie по ["low","medium","high"]
S5: внутри integer → цифры [0-9], знак -
S6: после , → следующий ключ или }
S7: конец → ожидаем EOS
Цикл на каждом шаге:
FSM: state → allowed_tokens → mask
LLM: logits → mask → softmax → sample → token
FSM: transition(token) → new_state → повторить
Пример генерации по шагам:
Схема: {"risk_level": Literal["low","high"], "score": int}
| Шаг | Сгенерировано | Допустимые | Выбрано |
|---|---|---|---|
| 0 | (пусто) | { | { |
| 1 | { | " | " |
| 2 | {" | risk_level, score | risk |
| 3 | {"risk | _level" | _level" |
| 4 | {"risk_level" | : | : |
| 5 | {"risk_level": | " | " |
| 6 | {"risk_level":" | low, high | low |
| 7 | {"risk_level":"low | " | " |
На шаге 6 — модель делает семантический выбор между "low" и "high". Grammar engine разрешает оба, но logit("low") > logit("high") → модель выбирает "low".
Это и есть точка, где SGR работает: schema контролирует какие варианты допустимы, модель контролирует какой из допустимых выбрать.
Trie для enum
Enum-значения сначала прогоняются через токенизатор модели. Один и тот же текст может стать одним токеном или несколькими — зависит от словаря:
"low" → ["low"] → 1 токен id=5023
"medium" → ["med", "ium"] → 2 токена id=1821, 772
"high" → ["high"] → 1 токен id=993
Из этих токенов строится префиксное дерево. Узлы — это token_id, в скобках — что этот токен значит текстом:
(root) — стейт сразу после открывающей "
│
├── 5023 ("low") → END ← одношаговая ветка
│
├── 1821 ("med") ─── 772 ("ium") → END ← двухшаговая ветка
│
└── 993 ("high") → END ← одношаговая ветка
Что отсюда видно про токены:
- Маска на шаге после
" разрешает ровно три token_id: {5023, 1821, 993}. Все остальные ~50k токенов словаря получают −∞ в логитах.- Если модель выбрала 1821 ("med"), FSM перешёл в новый стейт, где маска разрешает только 772 ("ium"). Никакого выбора у модели уже нет — даже если logit("ium") низкий, это единственный допустимый токен.
- Длина enum-значения в символах ≠ числу шагов генерации. "medium" длиннее "high" в символах, но различие в шагах определяется токенизатором, а не строкой.
Свободные поля: str внутри JSON
Для поля reasoning: str grammar знает только структурные ограничения — допустимы все текстовые токены, не допустим raw " без escape.
Grammar не знает, когда закрыть кавычку. Решение о длине строки принимает модель. Поэтому reasoning: str не ограничивает содержание мышления — только не даёт сломать JSON.
Это же объясняет, почему SGR не делает модель умнее: свободные текстовые поля — "окна" в грамматике, где модель генерирует без ограничений.
Пост 4/6 — «Паттерны SGR: Single-Stage, Routing, Cascade, Cycle»
Все примеры — на Pydantic + LangChain. Принципы те же для Instructor, Pydantic AI, Outlines, vLLM guided decoding.
4.1 Single-Stage: reasoning встроен в схему
Один LLM-вызов, схема явно требует промежуточных шагов:
Порядок полей = порядок токенов. suggested_action идёт после priority и category, а не до.
Антипаттерн: reasoning_steps: list[str] последним полем "для отчётности". Модель его сгенерирует, но на финальное решение он уже не повлияет.
4.2 Routing: schema как router
Вместо отдельного intent-классификатора:
route — Literal, constrained decoding гарантирует одно из допустимых значений. Поля requires_* перед route — reasoning: модель сначала "обдумывает" характеристики, потом решает.
Плюсы: один LLM-вызов вместо двух, контекстное решение (видит весь чат), self-explaining (поля requires_* = аудит-trail).
Минус: latency выше, чем у эмбеддинг-классификатора. Окупается при нетривиальном route-решении.
4.3 Cascade: декомпозиция на этапы
Сложные задачи разбиваются на цепочку SGR-вызовов, каждый со своей схемой:
Зачем разбивать: каждый этап тестируется юнит-тестом, разные модели на разных этапах, 4+ уровней вложенности или 50+ полей в одной схеме деградируют качество, возможность кэширования промежуточных результатов.
4.4 Cycle: self-correction через retry
Самый мощный паттерн в production — "draft → validate → fix":
Важно: ошибка валидации должна возвращаться модели в структурированном виде, а не как сырой stack trace.
4.5 Hybrid
Реальный production-pipeline комбинирует паттерны:
[Routing] classify intent (SGR с Literal)
↓
[Cascade] retrieve → grade → rewrite (SGR-вызовы)
↓
[Cycle] draft answer → validate → repair (loop, max 3)
↓
final answer
Все примеры — на Pydantic + LangChain. Принципы те же для Instructor, Pydantic AI, Outlines, vLLM guided decoding.
4.1 Single-Stage: reasoning встроен в схему
Один LLM-вызов, схема явно требует промежуточных шагов:
class TicketTriage(BaseModel):
# 1. Извлечение фактов
user_intent: str
mentioned_entities: list[str]
# 2. Анализ
is_urgent_signals: list[str]
sentiment: Literal["neutral", "frustrated", "angry"]
# 3. Решение
category: Literal["billing", "technical", "account", "other"]
priority: Literal["low", "medium", "high", "critical"]
confidence: float = Field(ge=0.0, le=1.0)
# 4. Action (после reasoning)
suggested_action: Literal["auto_reply", "route_to_l1", "route_to_l2", "escalate"]
reasoning: str
Порядок полей = порядок токенов. suggested_action идёт после priority и category, а не до.
Антипаттерн: reasoning_steps: list[str] последним полем "для отчётности". Модель его сгенерирует, но на финальное решение он уже не повлияет.
4.2 Routing: schema как router
Вместо отдельного intent-классификатора:
class RouteDecision(BaseModel):
user_request_summary: str
requires_realtime_data: bool
requires_internal_docs: bool
requires_calculation: bool
route: Literal["rag", "websearch", "code_interpreter", "direct_answer", "clarify"]
clarification_question: str | None = None
route — Literal, constrained decoding гарантирует одно из допустимых значений. Поля requires_* перед route — reasoning: модель сначала "обдумывает" характеристики, потом решает.
Плюсы: один LLM-вызов вместо двух, контекстное решение (видит весь чат), self-explaining (поля requires_* = аудит-trail).
Минус: latency выше, чем у эмбеддинг-классификатора. Окупается при нетривиальном route-решении.
4.3 Cascade: декомпозиция на этапы
Сложные задачи разбиваются на цепочку SGR-вызовов, каждый со своей схемой:
class ExtractedEntities(BaseModel):
diagnoses: list[str]
medications: list[str]
procedures: list[str]
class NormalizedEntities(BaseModel):
diagnoses_icd10: list[str]
medications_atc: list[str]
timeline: list[dict]
class ClinicalAssessment(BaseModel):
risk_factors: list[str]
contraindications: list[str]
risk_level: Literal["low", "medium", "high"]
requires_specialist_review: bool
reasoning: str
async def process_document(text: str):
extracted = await extract_llm.ainvoke(text)
normalized = await normalize_llm.ainvoke(extracted.model_dump_json())
return await assess_llm.ainvoke(normalized.model_dump_json())
Зачем разбивать: каждый этап тестируется юнит-тестом, разные модели на разных этапах, 4+ уровней вложенности или 50+ полей в одной схеме деградируют качество, возможность кэширования промежуточных результатов.
4.4 Cycle: self-correction через retry
Самый мощный паттерн в production — "draft → validate → fix":
class SQLDraft(BaseModel):
intent: str
tables_needed: list[str]
sql: str
expected_row_count_range: tuple[int, int]
async def generate_validated_sql(question, schema, max_retries=3):
error_context = ""
for attempt in range(max_retries):
prompt = f"Question: {question}\nSchema: {schema}"
if error_context:
prompt += f"\nPrevious attempt failed: {error_context}"
draft = await sql_llm.ainvoke(prompt)
try:
await db.execute(f"EXPLAIN {draft.sql}")
return draft.sql
except DBError as e:
error_context = f"Attempt {attempt+1}: {e}\nSQL: {draft.sql}"
raise RuntimeError(f"Failed after {max_retries}")
Важно: ошибка валидации должна возвращаться модели в структурированном виде, а не как сырой stack trace.
4.5 Hybrid
Реальный production-pipeline комбинирует паттерны:
[Routing] classify intent (SGR с Literal)
↓
[Cascade] retrieve → grade → rewrite (SGR-вызовы)
↓
[Cycle] draft answer → validate → repair (loop, max 3)
↓
final answer
Пост 5/6 — «LangGraph + SGR: end-to-end пример»
Как собирается RAG-агент на SGR, пример: классификация запроса → retrieval → оценка чанков → генерация ответа с цитатами → валидация → self-repair при ошибке.
Архитектура графа:
Главная схема — FinalAnswer. Порядок полей тут критичен:
К моменту, когда модель генерирует answer, в JSON уже лежат question_understanding → relevant_chunks_used → reasoning_chain. Три шага reasoning пройдены. Self_check_passed в конце — модель сама сообщает, уверена ли.
Как SGR-поля превращаются в ветвления графа:
Это и есть связка SGR ↔ граф: поле
Что здесь на уровне паттернов SGR:
▪️ classify — Routing. Reasoning-поля (requires_internal_docs, is_chitchat) перед route
▪️ grade — Cascade-step. Один SGR-вызов оценивает все чанки
▪️ generate — Single-Stage: question_understanding → reasoning_chain → answer
▪️ validate + repair — Cycle. Ошибка уходит обратно в generate через state
▪️ self_check_passed — модель сама сигналит низкую уверенность
Как собирается RAG-агент на SGR, пример: классификация запроса → retrieval → оценка чанков → генерация ответа с цитатами → валидация → self-repair при ошибке.
Архитектура графа:
START → classify (Routing-SGR)
├─ clarify → END
├─ direct_answer → END
└─ retrieve → grade → generate (Single-Stage SGR) → validate
↑ ↓
└── repair ───┤ (Cycle, max 3)
↓
finalize → END
Главная схема — FinalAnswer. Порядок полей тут критичен:
class CitationClaim(BaseModel):
claim: str
chunk_id: int
class FinalAnswer(BaseModel):
question_understanding: str # 1. понимаем вопрос
relevant_chunks_used: list[int] # 2. отбираем источники
reasoning_chain: list[str] # 3. строим цепочку вывода
answer: str # 4. формулируем ответ
citations: list[CitationClaim] # 5. привязываем к источникам
self_check_passed: bool # 6. сами проверяем
К моменту, когда модель генерирует answer, в JSON уже лежат question_understanding → relevant_chunks_used → reasoning_chain. Три шага reasoning пройдены. Self_check_passed в конце — модель сама сообщает, уверена ли.
Как SGR-поля превращаются в ветвления графа:
def route_after_classify(state) -> str:
decision = state["route_decision"]
if decision.route == "clarify":
return "clarify"
if decision.route == "direct_answer" or decision.is_chitchat:
return "direct_answer"
return "retrieve"
def route_after_validate(state) -> str:
if not state.get("validation_errors"):
return "finalize"
if state.get("repair_attempts", 0) >= 3:
return "finalize"
return "repair"
Это и есть связка SGR ↔ граф: поле
Literal в схеме → conditional edge в DAG. Никаких отдельных классификаторов, промптов для роутинга, regex на выходе LLM.Что здесь на уровне паттернов SGR:
▪️ classify — Routing. Reasoning-поля (requires_internal_docs, is_chitchat) перед route
▪️ grade — Cascade-step. Один SGR-вызов оценивает все чанки
▪️ generate — Single-Stage: question_understanding → reasoning_chain → answer
▪️ validate + repair — Cycle. Ошибка уходит обратно в generate через state
▪️ self_check_passed — модель сама сигналит низкую уверенность
Пост 6/6 — «SGR + Reasoning Models, чеклист и главный takeaway»
SGR + Reasoning Models: комплементарность
С появлением o1/o3/R1/Claude Extended Thinking вопрос: нужен ли SGR, если модель сама умеет CoT?
Короткий ответ: да, но иначе.
Reasoning model alone
▪️ Reasoning — скрытый thinking блок
▪️ Аудитируемость — токены не всегда видны
▪️ Контроль порядка — модель решает сама
▪️ Когда — research, math, open-ended
SGR
▪️ Reasoning — явные поля схемы
▪️ Аудитируемость — полностью видно
▪️ Контроль порядка — задано схемой
▪️ Когда — production, integrations
Рекомендация:
▪️ Reasoning model + SGR — для критичных бизнес-решений (медицина, финансы, legal)
▪️ Reasoning model alone — для исследовательских задач
▪️ SGR без reasoning model — на open-weight (qwen, llama), когда нужно вытянуть максимум
Чеклист для production:
Дизайн схемы:
▪️ Reasoning-поля перед action/answer
▪️ Глубина вложенности ≤ 3 уровней
▪️ ≤ 30 полей на схему (иначе Cascade)
▪️ float вместо int для измерений
▪️ Optional вместо дефолтов, маскирующих ошибки
▪️ Literal для дискретных значений вместо str
Валидация:
▪️ Pydantic-валидация на выходе обязательна
▪️ Бизнес-валидация поверх Pydantic
▪️ Retry loop с error-context обратно в LLM
▪️ Бюджет ретраев (max_retries=3)
Мониторинг:
▪️ Логирование raw LLM output до парсинга
▪️ Метрики: schema compilation time, validation error rate, retry rate
▪️ Семплинг в human review — Pydantic-валидный ≠ бизнес-валидный
Главный takeaway
SGR — это дисциплина проектирования, а не библиотека. Его суть:
Схема — это не формат, а спецификация когнитивного процесса. Constrained decoding — механизм её принуждения.
▪️ Каждое поле схемы — обязательный шаг мышления модели
▪️ Порядок полей = порядок токенов = порядок reasoning
▪️ Pydantic-схема = unit-тестируемая часть промпт-инжиниринга
▪️ Tool calling — для действий. SGR — для решений
▪️ Reasoning model + SGR > reasoning model alone > SGR alone > plain prompt
Structured output не делает модель умнее. Он делает её более управляемой, аудитируемой и интегрируемой. Это фундаментально другая ось улучшения — не через model scaling, а через design.
Ссылки:
▪️ Schema-Guided Reasoning — Rinat Abdullin: https://abdullin.com/schema-guided-reasoning/
▪️ SGR Patterns (Cascade/Routing/Cycle): https://abdullin.com/schema-guided-reasoning/patterns
▪️ The Format Tax — arxiv: https://arxiv.org/abs/2604.03616
▪️ Structured Outputs Create False Confidence — BAML: https://boundaryml.com/blog/structured-outputs-create-false-confidence
▪️ Structured Decoding in vLLM: https://blog.vllm.ai/2025/01/14/struct-decode-intro.html
▪️ Self-Correction with Instructor: https://python.useinstructor.com/examples/self_critique/
▪️ Local LLM Tool Calling — Docker: https://www.docker.com/blog/local-llm-tool-calling-a-practical-evaluation/
SGR + Reasoning Models: комплементарность
С появлением o1/o3/R1/Claude Extended Thinking вопрос: нужен ли SGR, если модель сама умеет CoT?
Короткий ответ: да, но иначе.
Reasoning model alone
▪️ Reasoning — скрытый thinking блок
▪️ Аудитируемость — токены не всегда видны
▪️ Контроль порядка — модель решает сама
▪️ Когда — research, math, open-ended
SGR
▪️ Reasoning — явные поля схемы
▪️ Аудитируемость — полностью видно
▪️ Контроль порядка — задано схемой
▪️ Когда — production, integrations
Рекомендация:
▪️ Reasoning model + SGR — для критичных бизнес-решений (медицина, финансы, legal)
▪️ Reasoning model alone — для исследовательских задач
▪️ SGR без reasoning model — на open-weight (qwen, llama), когда нужно вытянуть максимум
Чеклист для production:
Дизайн схемы:
▪️ Reasoning-поля перед action/answer
▪️ Глубина вложенности ≤ 3 уровней
▪️ ≤ 30 полей на схему (иначе Cascade)
▪️ float вместо int для измерений
▪️ Optional вместо дефолтов, маскирующих ошибки
▪️ Literal для дискретных значений вместо str
Валидация:
▪️ Pydantic-валидация на выходе обязательна
▪️ Бизнес-валидация поверх Pydantic
▪️ Retry loop с error-context обратно в LLM
▪️ Бюджет ретраев (max_retries=3)
Мониторинг:
▪️ Логирование raw LLM output до парсинга
▪️ Метрики: schema compilation time, validation error rate, retry rate
▪️ Семплинг в human review — Pydantic-валидный ≠ бизнес-валидный
Главный takeaway
SGR — это дисциплина проектирования, а не библиотека. Его суть:
Схема — это не формат, а спецификация когнитивного процесса. Constrained decoding — механизм её принуждения.
▪️ Каждое поле схемы — обязательный шаг мышления модели
▪️ Порядок полей = порядок токенов = порядок reasoning
▪️ Pydantic-схема = unit-тестируемая часть промпт-инжиниринга
▪️ Tool calling — для действий. SGR — для решений
▪️ Reasoning model + SGR > reasoning model alone > SGR alone > plain prompt
Structured output не делает модель умнее. Он делает её более управляемой, аудитируемой и интегрируемой. Это фундаментально другая ось улучшения — не через model scaling, а через design.
Ссылки:
▪️ Schema-Guided Reasoning — Rinat Abdullin: https://abdullin.com/schema-guided-reasoning/
▪️ SGR Patterns (Cascade/Routing/Cycle): https://abdullin.com/schema-guided-reasoning/patterns
▪️ The Format Tax — arxiv: https://arxiv.org/abs/2604.03616
▪️ Structured Outputs Create False Confidence — BAML: https://boundaryml.com/blog/structured-outputs-create-false-confidence
▪️ Structured Decoding in vLLM: https://blog.vllm.ai/2025/01/14/struct-decode-intro.html
▪️ Self-Correction with Instructor: https://python.useinstructor.com/examples/self_critique/
▪️ Local LLM Tool Calling — Docker: https://www.docker.com/blog/local-llm-tool-calling-a-practical-evaluation/
Открыл repo: 145 AI-skills для русских юристов
Anthropic пару месяцев назад тихо выкатил claude-for-legal — пак из 110+
воркфлоу для американских юристов под открытой лицензией. Не приложение,
не SaaS, просто папка с Markdown-файлами.
Залез внутрь, изучил и портировал под русское право.
Получился ru-legal — Apache 2.0, на GitHub.
▪️ Проблема
Юристам в РФ нужно ровно то же, что американским: проверить договор,
оценить риски увольнения, ответить на ВНП, отработать DSAR под 152-ФЗ.
Сейчас всё делается руками — Word + 8 вкладок: pravo.gov.ru, ЕГРЮЛ, КАД,
ЕФРСБ, Роспатент, Росреестр, Закупки, КонсультантПлюс.
RAG поверх документов не спасает. Юриспруденция — не «найди и расскажи»,
а процедура с обязательной верификацией против актуальных источников.
LLM без procedural workflow процитирует устаревшую редакцию ГК и забудет
проверить КАД → юрист подпишет → иск проигран.
Нужны две вещи:
• операционализированные процедуры (skills)
• доступ к живым госреестрам (MCP)
▪️ Skill — это исполняемый чек-лист
Markdown-файл с frontmatter и workflow прозой. LLM читает один раз и
выполняет процедуру, не пересказывает.
Самое неочевидное — внутри workflow прямо в тексте прописаны вызовы
инструментов. Из contract-review:
Если ИНН контрагента известен — вызови
Извлеки статус, руководителя, бенефициаров.
Через
редакцией.
Это не псевдокод. LLM реально дёргает MCP-инструменты с этими аргументами.
Плюс обязательная секция degraded mode: «если MCP лёг — продолжай,
но в финальном отчёте честно укажи, что контрагент не проверен и цитаты
ГК без сверки помечены [ред. требует проверки]». Без этого либо падаем,
либо галлюцинируем с уверенным лицом.
145 skills, 14 паков: договорное, трудовое, налоговое, корпоративное, IP,
152-ФЗ, ДДУ-214, госзакупки, AML, литигация, AI governance.
Половина — глубокая адаптация Anthropic'ского пака (at-will employment и
ст.81 ТК — два разных мира). Вторая половина — с нуля под РФ.
▪️ MCP-стек над госреестрами
Семь специализированных серверов плюс агрегатор:
•
•
•
•
•
•
•
•
Каждый — PyPI-пакет на fastmcp + httpx, с rate limiter, LRU-кэшем,
timeout'ом, Pydantic-валидацией входов.
Как это работает в связке: юрист пишет «проверь договор с ИНН 7707083893»
→ contract-review делает три параллельных вызова в egrul / kad / efrsb
→ склеивает в risk-summary с конкретными ссылками.
То, что руками 30-40 минут — 4 секунды и воспроизводимо.
▪️ Честно про стабильность
Не все MCP одинаково надёжны, и проблема не в коде обёрток, а в самих
источниках:
• egrul, pravo, efrsb — стабильно
• kad.arbitr.ru — снаружи РФ режет Qrator, нужен прокси
• rospatent — endpoint плавает, схема меняется без warning'ов
• zakupki, rosreestr — частично, лимиты на пагинацию + смешанная авторизация
Сейчас активно ищу альтернативы — партнёрки с Casebook/Caselook вместо
прямого КАД, прокси-инфра в РФ для нестабильных источников,
embedding-based router для skills (на 145 ещё keyword работает,
на 500 уже нет).
▪️ Что дальше
Skills и MCP — это слой контента и доступа. Они работают в Claude Code /
Cursor / любом MCP-клиенте, но это интерактивный режим «юрист + AI».
Следующий шаг — отдельный harness для тех кто не может клод использовать.
Это будет отдельный репо ru-legal-agent, чтобы не смешивать слой
контента со слоем оркестрации.
Open-source, Apache 2.0:
🔗 github.com/AlsKozlov/ru-legal
Anthropic пару месяцев назад тихо выкатил claude-for-legal — пак из 110+
воркфлоу для американских юристов под открытой лицензией. Не приложение,
не SaaS, просто папка с Markdown-файлами.
Залез внутрь, изучил и портировал под русское право.
Получился ru-legal — Apache 2.0, на GitHub.
▪️ Проблема
Юристам в РФ нужно ровно то же, что американским: проверить договор,
оценить риски увольнения, ответить на ВНП, отработать DSAR под 152-ФЗ.
Сейчас всё делается руками — Word + 8 вкладок: pravo.gov.ru, ЕГРЮЛ, КАД,
ЕФРСБ, Роспатент, Росреестр, Закупки, КонсультантПлюс.
RAG поверх документов не спасает. Юриспруденция — не «найди и расскажи»,
а процедура с обязательной верификацией против актуальных источников.
LLM без procedural workflow процитирует устаревшую редакцию ГК и забудет
проверить КАД → юрист подпишет → иск проигран.
Нужны две вещи:
• операционализированные процедуры (skills)
• доступ к живым госреестрам (MCP)
▪️ Skill — это исполняемый чек-лист
Markdown-файл с frontmatter и workflow прозой. LLM читает один раз и
выполняет процедуру, не пересказывает.
Самое неочевидное — внутри workflow прямо в тексте прописаны вызовы
инструментов. Из contract-review:
Если ИНН контрагента известен — вызови
egrul.lookup_company(inn).Извлеки статус, руководителя, бенефициаров.
Через
pravo.search_npa() найди релевантные статьи ГК с актуальнойредакцией.
Это не псевдокод. LLM реально дёргает MCP-инструменты с этими аргументами.
Плюс обязательная секция degraded mode: «если MCP лёг — продолжай,
но в финальном отчёте честно укажи, что контрагент не проверен и цитаты
ГК без сверки помечены [ред. требует проверки]». Без этого либо падаем,
либо галлюцинируем с уверенным лицом.
145 skills, 14 паков: договорное, трудовое, налоговое, корпоративное, IP,
152-ФЗ, ДДУ-214, госзакупки, AML, литигация, AI governance.
Половина — глубокая адаптация Anthropic'ского пака (at-will employment и
ст.81 ТК — два разных мира). Вторая половина — с нуля под РФ.
▪️ MCP-стек над госреестрами
Семь специализированных серверов плюс агрегатор:
•
pravo-mcp → pravo.gov.ru — НПА с указанием редакции•
egrul-mcp → api-fns.ru — контрагент, руководство, бенефициары•
kad-mcp → kad.arbitr.ru — арбитражные дела по ИНН•
efrsb-mcp → bankrot.fedresurs.ru — банкротства•
rospatent-mcp → fips.ru — патенты, ТЗ, ПО•
zakupki-mcp → zakupki.gov.ru — тендеры + реестр недобросовестных•
rosreestr-mcp → Росреестр — кадастр и права•
ru-legal-mcp — агрегатор, регистрирует все 145 skills как MCP promptsКаждый — PyPI-пакет на fastmcp + httpx, с rate limiter, LRU-кэшем,
timeout'ом, Pydantic-валидацией входов.
Как это работает в связке: юрист пишет «проверь договор с ИНН 7707083893»
→ contract-review делает три параллельных вызова в egrul / kad / efrsb
→ склеивает в risk-summary с конкретными ссылками.
То, что руками 30-40 минут — 4 секунды и воспроизводимо.
▪️ Честно про стабильность
Не все MCP одинаково надёжны, и проблема не в коде обёрток, а в самих
источниках:
• egrul, pravo, efrsb — стабильно
• kad.arbitr.ru — снаружи РФ режет Qrator, нужен прокси
• rospatent — endpoint плавает, схема меняется без warning'ов
• zakupki, rosreestr — частично, лимиты на пагинацию + смешанная авторизация
Сейчас активно ищу альтернативы — партнёрки с Casebook/Caselook вместо
прямого КАД, прокси-инфра в РФ для нестабильных источников,
embedding-based router для skills (на 145 ещё keyword работает,
на 500 уже нет).
▪️ Что дальше
Skills и MCP — это слой контента и доступа. Они работают в Claude Code /
Cursor / любом MCP-клиенте, но это интерактивный режим «юрист + AI».
Следующий шаг — отдельный harness для тех кто не может клод использовать.
Это будет отдельный репо ru-legal-agent, чтобы не смешивать слой
контента со слоем оркестрации.
Open-source, Apache 2.0:
🔗 github.com/AlsKozlov/ru-legal
GitHub
GitHub - AlsKozlov/ru-legal: Open-source legal knowledge + data layer для российского права. 145 AI skills + 8 MCP-интеграций с…
Open-source legal knowledge + data layer для российского права. 145 AI skills + 8 MCP-интеграций с госреестрами (pravo.gov.ru, ЕГРЮЛ, kad.arbitr, ЕФРСБ, Роспатент, Росреестр, госзакупки). Apache 2....
🔥1