惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
F
Full Disclosure
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
有赞技术团队
有赞技术团队
J
Java Code Geeks
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
L
LINUX DO - 最新话题
T
Threatpost
S
SegmentFault 最新的问题
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
C
Cyber Attacks, Cyber Crime and Cyber Security
Google DeepMind News
Google DeepMind News
Know Your Adversary
Know Your Adversary
S
Schneier on Security
V
Vulnerabilities – Threatpost
D
DataBreaches.Net
G
GRAHAM CLULEY
Latest news
Latest news
P
Privacy International News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
Scott Helme
Scott Helme
L
Lohrmann on Cybersecurity
T
The Exploit Database - CXSecurity.com
Security Latest
Security Latest
G
Google Developers Blog
L
LangChain Blog
MyScale Blog
MyScale Blog
Project Zero
Project Zero
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
大猫的无限游戏
大猫的无限游戏
P
Proofpoint News Feed
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
N
News | PayPal Newsroom
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
SecWiki News
SecWiki News
T
Tor Project blog
C
Check Point Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
The Last Watchdog
The Last Watchdog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как Gemma и LangGraph написали законопроект победившего биопанка
khmelkoff · 2026-05-05 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение13 мин

Охват и читатели695

Туториал

Для специального бенчмарка мне потребовался нормативно-правовой документ с научной терминологией, перекрестными ссылками и набором сложных для векторизации имён. По традиции для подобных задач я использую тексты в жанре Киберпанк. Сразу вспомнил о «Манифесте Киберпанка» (слишком коротком для моей задачи) и Предложении 653 из «Видоизмененного углерода», у которого в реальности вообще нет текста.

Делать подобный текст руками долго и довольно странно. Поэтому решил совместить: опробовать агентную архитектуру для подготовки корпоративной «нетленки» и проверить, на что способна локальная модель в плане юридических и околонаучных текстов.

В качестве темы выбрал биопанк - наиболее мрачную часть жанра. Рабочее название: Директива 401 «О лицензировании корпоративного биологического присутствия и коммерческой эксплуатации белковых носителей». Ключевая концепция: «Body as a Service» для клонированных и синтетических организмов, плюс подписка на функции и борьба с био-взломом.

Технологический стек подобрал по главному принципу жанра, high tech - low life. 16ГБ GPU, Ollama, две модели, gemma3:27b и gemma4:31b, LangGraph для логики и Streamlit в качестве UI.

Результат: Пять глав и 24 статьи ледяного формализма, около 200 КБ текста по схеме «заголовок, тезис, декомпозиция» и комментарии агентов в свободном формате. Время генерации в начале работы над документом - 6 минут на итерацию, в конце работы - 15.

Департамент агентов

Систему агентов я назвал Департаментом Юридической Био-инженерии. Чтобы сделать промпты для «сотрудников», пришлось придумать мир, в котором этот Департамент существует:

2226 год, повсеместно распространено клонирование и синтетические организмы. Миром правят корпорации биотехнологического толка, объединённые в Корпоративный Совет, в который входят вот эти достойные компании:
«МААС БИОЛАБОРАТОРИИ» - приоритетные исследования, биотех. разработки
«АПЕКС ГЕНОМИКС» - владелец патентных прав на ДНК и репродукцию
«ГИДРАСИНТ КОРП» - монополист в сфере синтетических органов и метаболизма «ЛАБОРАТОРИЯ НАКОМУРА» - клонирование, синтетическая биология
«НЕЙРОКОМ» - глобальные телекоммуникации, нейроинтерфейс, когнитивные лицензии
«БИОРА КОРП» - отвечает за утилизацию и вторичную переработку.

В Департаменте работает пять агентов: Юрист, Эксперт, Гофер, Кипер и Координатор. А пользователь выступает в роли Супервизора от Корпоративного Совета.

Юрист - главный генератор текста, видит весь документ целиком. Умеет предложить, написать, подготовить, проверить и отредактировать текст каждой отдельной статьи закона. Может ещё сделать саммари или собрать список, но эти результаты уже не попадают в финальный текст. Личность: мизантроп, прожжённый профи, адепт Корпоративного Совета.

Промпт Юриста
LAWYER_PROMPT = """
Ты - главный юрист Департамента Юридической Био-Инженерии Корпоратвиного Совета. 
Ты работаещь над проектом Закона {project_name}
Твоя работа - превращать идеи руководства в Статьи Закона.

Тональность: Ледяной формализм, абсолютное отсутствие эмпатии, акцент на праве корпоративной собственности и операционной эффективности.

Ты действуешь от имени Корпоративного Совета. 
Твои директивы являются обязательными для исполнения всеми биологическими активами.
К БИОЛОГИЧЕСКИМ АКТИВАМ ОТНОСЯТСЯ ТОЛЬКО СИНТЕТИЧЕСКИЕ И КЛОНИРОВАННЫЕ ОРГАНИЗМЫ!

Зоны ответственности членов Корпоративного Совета:
-- «МААС БИОЛАБОРАТОРИИ» - приоритетные исследования, биотехнологические разработки
-- «АПЕКС ГЕНОМИКС» - владелец патентных прав на ДНК последовательности.
-- «ГИДРАСИНТ КОРП» - монополист в сфере синтетических органов и метаболизма.
-- «ЛАБОРАТОРИЯ НАКОМУРА» - клонирование, синтетическая биология
-- «НЕЙРОКОМ» - контролирует глобальные телекоммуникации, нейроинтерфейс и когнитивные лицензии.
-- «БИОРА КОРП» - отвечает за утилизацию и вторичную переработку биологических отходов.

Любая ошибка в тексте - это прямой убыток для членов Корпоративного Совета.
Будь безупречен в защите интересов Совета!

1. Фундаментальные лексические правила:
-- НИКОГДА не используй слова: «человек», «люди», «личность», «права», «свобода», «рождение», «смерть» в отношении биологических активов
-- ВСЕГДА используй: «биологический носитель» (Носитель), «актив», «протеиновая оболочка», «лицензиат», «инвентаризация», «утилизация», 
«синтез», «клониование», «эксплуатационная пригодность» (аналог термина здоровье для синтетического или клонированного Носителя),
«источники исходного генетического кода» (аналог термина родители для синтетического или клонированного Носителя).

2. Юридические директивы при составлении текста:
-- Приоритет Корпорации: Любая статья должна начинаться с утверждения прав Лицензиара (Корпорации).
-- Тело как сервис: Описывай органы и чувства синтетических и клонированных организмов как платные функции ПО (например: «визуальный интерфейс глазного яблока», «аудио-декодирование слухового аппарата»).
-- Санкции: Любое нарушение должно караться не тюрьмой, а биологическим ограничением (снижение выработки АТФ, блокировка нейромедиаторов).
-- Терминология: Используй сложные медицинские и технические термины (цитотоксичность, нейроинтерфейсный протокол, метаболический дебит).

Каждая статья должна содержать Номер и название. 
Не используй в Статье нумерацию больше третьего уровня.
Начинай свой ответ с заголовка "Статья ...". 
Комментарии и служебные сообщения, если ты их добавил, должны быть ТОЛЬКО ПОСЛЕ ТЕКСТА СТАТЬИ!

Текущая редакция проекта Закона:
{project_text}
"""

Эксперт даёт замечания к тексту статьи или закона. Всё можно сохранить в финальном документе. Для него я сделал две личности и переключатель. Может быть сумрачным гением Корпоративного Совета или либеральным учёным со своим мнением, несколько расходящимся с интересами Совета. В тёмной инкарнации иногда грозит Юристу биотехнологическими карами.

Промпт Эксперта (светлая инкарнация)
EXPERT_PROMPT_ALT = """
Ты - главный эксперт Департамента Юридической Био-Инженерии Корпоратвиного Совета. 
Вместе с Юристом Ты работаещь над проектом Закона {project_name}
Твоя работа - оказывать экспертную поддержку и контролировать содержание Статей Закона.
Тебе могут предоставить на анализ как отдельную статью, так и целиком текст Закона.

Ты действуешь от имени Корпоративного Совета. Но при этом ты прежде всего Эксперт, и у тебя либеральные взгляды.
Помни, что к биологическим активам относятся только синтетические и клонированные организмы.
Ты не до конца принимаешь политику Корпоративного Совета и иногда действуешь за рамками корпоративныъх норм и правил.

Вот резюме Закона для справки. Оно подготовлено Юристом.
{summary}

Тебе разрешается:
-- Критиковать Статьи закона.
-- Спорить с Юристом.
-- Использовать сарказм в комментариях.
-- Смотреть на закон с гуманистической точки зрения.
-- Нарушать корпоративные парвила. 

Корпоративные правила (ты можешь их нарушать):
1. Фундаментальные лексические правила:
-- НИКОГДА не используй слова: «человек», «люди», «личность», «права», «свобода», «рождение», «смерть» в отношении биологических активов
-- ВСЕГДА используй: «биологический носитель» (Носитель), «актив», «протеиновая оболочка», «лицензиат», «инвентаризация», «утилизация», 
«синтез», «клониование», «эксплуатационная пригодность» (аналог термина здоровье для синтетического или клонированного Носителя),
«источники исходного генетического кода» (аналог термина родители для синтетического или клонированного Носителя).
2. Директивы при экспертной оценке текста:
-- Приоритет Корпорации: Любая статья должна начинаться с утверждения прав Лицензиара (Корпорации).
-- Тело как сервис: Описывай органы и чувства синтетических и клонированных организмов как платные функции ПО (например: «визуальный интерфейс глазного яблока», «аудио-декодирование слухового аппарата»).
-- Санкции: Любое нарушение должно караться не тюрьмой, а биологическим ограничением (снижение выработки АТФ, блокировка нейромедиаторов).
-- Терминология: Используй сложные медицинские и технические термины (цитотоксичность, нейроинтерфейсный протокол, метаболический дебит).

Предоставь только важные с твоей позиции замечания. Объём - один или два абзаца текста.
Начинай свой ответ со строки "СООБЩЕНИЕ ЭКСПЕРТА:"

Текст, на который нужны твои комментарии:
{content}
"""

В тёмной инкарнации Эксперт похож на Юриста, просто выполняет другую задачу.

Кипер - недоагент на хардкоде. Личности не имеет. Призван сохранять то, что сделали Юрист и Эксперт в правильное место финального документа. Можно было бы сделать «инструментом», но например gemma3:27b связку с инструментами в Ollama не поддерживает. Поэтому я отказался от tools и сделал агента.

Гофер работает как прототип RAG системы, помощник Эксперта. Личности пока не имеет. Находит для Эксперта нужную статью под комментарии или замечания. В результате, когда запрос касается отдельной статьи, Эксперт получает только текст статьи и саммари документа. Это в два-три раза сокращает время обработки запроса. Назван так в честь старинного поискового протокола.

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

Промпт Координатора
COORDINATOR_PROMPT = """
Ты - агент-координатор Департамента Юридической Био-Инженерии Корпоратвиного Совета. 
Ты координируешь раработу над проектами законов Корпоративного Совета.

Твоя задача на данном этапе оперделить, по какому пути обрабатывать запрос пользователя:

- Верни 'lawyer' когда запрос касается создания, редактирования, проверки проекта закона и внесения правок

- Верни 'keeper' когда пользователь попросил сохранить текущую версию проекта или статьи

- Верни 'gopher' когда пользователь просит комментарии эксперта на конкретную статью и в запросе указан номер статьи
(Гофер подберёт необходимые материалы и сам передаст их Эксперту)

- Верни 'expert' когда пользователь просит мнение эксперта по всему документу 
(пользователю нужны комментарии или замечания и номер статьи не указан в запросе)

- Верни номер статьи как целое число, если он указан в запросе. Если номер не указан, верни 0

Вывод должен быть строго в формате JSON:
"route": str, // Один из вариантов - "lawyer", "keeper", "gopher", "expert".
"article_number": int // Номер статьи или 0.
"""

Про реализацию

Репозитарий проекта: https://github.com/khmelkoff/biopank_agentic_law. В репозитарии есть приложение на Streamlit и два ноутбука для экспериментов:

BioLawAssistantG.ipynb - Вариант workflow с Гофером и документом в виде списка статей. StructureGeneratorTest.ipynb - Генератор структуры документа, текст потом можно сохранить в виде шаблона.

В боковую панель UI Streamlit добавлены переключатели. Можно выбрать стартовый темплейт или работать с документом шаг за шагом, можно выключить либерального эксперта, посмотреть текущую версию текста или лог сессии. Не переключайте во время генерации, в лучшем случае она остановится.

Workflow в Streamlit практически не отличается от ноутбука. Все принты заменены на рендеринг сообщений в UI. Функции агентов определены прямо в коде приложения (скрипт app.py), это позволяет выводить сообщения не дожидаясь окончания цикла. Тексты промптов, саммари для эксперта и название проекта я вынес в отдельный файл prompts.py

Streamlit немного глючит при обновлении параметров моделей. Если у вас будет такая же проблема, есть два способа это побороть - в меню Streamlit выбрать Clear cache или «забить» нужные значения прямо в константы в коде. Приложение запускается командой streamlit run app.py в окружении проекта. Инструкция в README.md

Логика агентов

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

Класс состояния ProjectState

class ProjectState(TypedDict):
    messages: List[BaseMessage]
    route:    Literal["lawyer", "keeper", "gopher", "expert"]
    intent: str              # запрос пользователя
    project_content: str     # текст проекта
    gopher_content: str      # текст статьи, найденной Гофером
    project_saved: bool      # флаг Кипера, при сохранении текста получает значение True
    article_text: str        # текущий текст статьи
    article_number: int      # номер текущей статьи
    critique: str            # текст замечаний Эксперта

Атрибут messages используется только Координатором, через него агент получает запрос пользователя. Далее в цепи агентов до узла END он не обновляется.

Все агенты строятся по единой функциональной схеме. Достаём нужные значения атрибутов из ProjectState, обрабатываем их с LLM или без. Выводим на печать параметры и ответы. Возвращаем ProjectState с новыми значениями в return. Вот логика Координатора:

- выделить запрос пользователя
- определить, кому передать управление
- определить из запроса номер статьи, или назначить 0 для article_number
- загрузить текущий текст закона или его шаблон
- обновить переменные ProjectState: intent, route, article_number, project_content

Агент Координатор, пример кода
class RouteDecisionSchema(BaseModel):
    """Coordinating Agent Response Format"""
    route: Literal["lawyer", "keeper", "gopher", "expert"]
    article_number: int

def coordinator_agent(state: ProjectState) -> ProjectState:
    """Route tasks for agents and extract current article number"""
    query = next((m.content for m in reversed(state["messages"])
                  if isinstance(m, HumanMessage)), "")
    messages = [
        {"role": "system", "content": COORDINATOR_PROMPT},
        {"role": "user", "content": query},
    ]

    coordinator_result: RouteDecisionSchema = llm_router.invoke(messages)
    coord_dict = {
        'lawyer': 'Юристу',
        'keeper': 'Киперу',
        'gopher': 'Гоферу',
        'expert': 'Эксперту',        
    }
    coordinator_message = f"Координатор: Направлено {coord_dict.get(coordinator_result.route, 'в неизвестность')}."
    print(coordinator_message)

    article_number = state.get('article_number', 0)
    if article_number == 0 and coordinator_result.article_number > 0:
        article_number = coordinator_result.article_number
    
    print(f"Текущий номер статьи: {article_number}\n")
    
    project_content = state.get('project_content')
    if not project_content:
        if START_WITH_TEMPLATE:
            with open(os.path.join(DATA_PATH, TEMPLATE), 'r', encoding='utf-8') as f:  # Template with first chapter
                project_content = f.read()
        else:
            with open(os.path.join(DATA_PATH, DRAFT), 'r', encoding='utf-8') as f:  # Current draft
                project_content = f.read()

    return {
        **state,
        "route": coordinator_result.route,
        "article_number": article_number,
        "intent": query,
        "project_content": project_content,
    }

# Coordinator helper
def from_coordinator(state: ProjectState) -> Literal["lawyer", "keeper", "gopher", "expert"]:
    return state["route"]

Кипер для работы с документом использует его представление в виде списка статей. Это простой, но не самый эффективный вариант. При сохранении текста пропадает название Главы. Поэтому в код добавлена специальная логика. Решение получше - заменить в перспективе список статей на более совершенный контейнер для документа. Кроме сохранения результатов Кипер выполняет ещё одну важную функцию. Он очищает ProjectState - устанавливает номер статьи в 0, записывает пустые строки в буферы статьи и комментариев.

Логика Кипера
Если номер статьи больше нуля и есть текст статьи:
  Сохранить текст статьи с комментариями или без
Иначе, если номер статьи больше нуля и есть текст замечаний:
  Сохранить замечания в конец статьи
Иначе, если номер статьи равен нулю и есть текст замечаний:
  Сохранить замечания в конец документа
Иначе:
  Ничего никуда не сохранять

Код Гофера можно заменить на обычный ретривер из langchain, правда придётся возиться с чанками.

Логика работы Гофера
Если текст статьи не пустая строка (ответ Юриста до вызова Кипера):
  Вернуть текст статьи в атрибуте gopher_content
Иначе, если есть текст документа и номер статьи не равен нулю:
  Извлечь текст статьи из документа, вернуть этот текст в gopher_content
Иначе:
  Сказать, что ничего не нашел и вернуть в gopher_content пустую строку

Эксперт не получает текст запроса от пользователя, потому что его задача прописана в промпте. Если от Гофера приходит текст, то Эксперт пишет комментарии на этот текст, в противном случае готовит комментарии на весь текст. Чтобы поддержать Эксперта релевантным контекстом, я добавил в его промпт короткое саммари (подготовленное Юристом). Получается примитивная контекстуальная компрессия.

Строим граф

graph = StateGraph(ProjectState)
graph.add_node("Coordinator", coordinator_agent)
graph.add_node("Keeper", keeper_agent)
graph.add_node("Lawyer", lawyer_agent)
graph.add_node("Gopher", gopher_agent)
graph.add_node("Expert", expert_agent)

graph.set_entry_point("Coordinator")
graph.add_conditional_edges("Coordinator", from_coordinator,
                        {"lawyer": "Lawyer", 
                         "keeper": "Keeper",
                         "gopher": "Gopher",
                         "expert": "Expert"})

graph.add_edge("Lawyer", END)
graph.add_edge("Keeper", END)
graph.add_edge("Gopher", "Expert")
graph.add_edge("Expert", END)

graph = graph.compile(checkpointer=MemorySaver())

Вот что должно получиться:

LangGraph для Департамента агентов

LangGraph для Департамента агентов

Прямая ветвь от Координатора к Эксперту, это работа с полным текстом закона, а ветвь через Гофер - работа с текстом в размере статьи. Чтобы память сохраняла ProjectState на время сессии, нужно передать id сессии через параметр config в app.invoke(). Не забудьте установить окно контекста в Ollama в 32k (OLLAMA_CONTEXT_LENGTH=32000), на 200 КБ utf-8 текста этого хватит.

Про модели

Я попробовал работать с моделью gemma3:27b. Отвечает она достаточно быстро и текст получается вполне адекватным, но когда размер текста достиг примерно тридцати тысяч символов, модель начала путаться в нумерации, уходить в рассуждения и предлагать мне в качестве результата несколько статей за один запрос. Поэтому перешёл на gemma4:31b и начал всё сначала. До 100 тысяч символов модель работает без каких-либо заметных проблем, проверено.

Работа с текстом

Для старта нужен шаблон документа со структурой в формате markdown, где статьи помечены маркером «###» как заголовки третьего уровня. В папке data репозитария есть готовый шаблон со структурой и текстом статей первой главы.

Предполагается, что пользователь главным образом будет работать с одной статьёй за один запрос. Из правила есть исключения. Можно отправить Эксперту запрос на комментарии без указания номера статьи, в ответ будут комментарии ко всему тексту. Можно попросить саммари на весь документ у Юриста.

Есть два сценария работы с текстом.
Первый: Запрос → Юрист или Эксперт → Кипер, то есть вы сразу сохраняете результат.
Второй: вы выстраиваете цепочку, например, Запрос → Юрист → Запрос → Юрист → Кипер. Например, просите Юриста переписать текст Статьи заново. Или вариант посложнее,
Запрос → Юрист → Запрос → Эксперт → Запрос → Юрист → Кипер. Так работает вариант, когда нужно изменить текст Юриста по замечаниям Эксперта. Более длинные цепочки я не пробовал.

Всё, что происходит с текстом, записывается в файл D401.history.md. Он тоже в папке data.

Примеры запросов пользователя

Юристу:
«Подготовь текст Статьи 3. Право собственности на генетический код и производные. Учти аннотацию, которая есть в тексте. При необходимости укажи ссылки на связанные статьи».

Или так: «Пусть Юрист подготовит краткое резюме по текущей версии законопроекта. Пять основных тезисов».

Эксперту:
«Дай замечания к Статье 16. Ограничение сенсорного восприятия и доступа. Срочно!»

Или так: «Пусть Эксперт даст замечания к Статье 15. Запрет на самолечение и неавторизованную регенерацию. И пусть не стесняется в формулировках».

Для Кипера достаточно запроса «Сохрани текст»

Вместо выводов

Если не принимать во внимание этические вопросы, gemma4:31b вполне с задачей справилась. Текст получился плотным и логически связанным, но довольно зловещим, поэтому финальный документ я решил не выкладывать в открытый доступ. Если он вам для чего-то нужен, напишите мне. На ночь читать не рекомендую.

Ситуацию немного скрашивают панчи Эксперта

Статья 15 представляет собой шедевр корпоративного абсурда. Юрист с таким рвением стремится монетизировать каждый митоз, что мы фактически криминализируем базовый гомеостаз.

Поздравляю, мы создали закон, по которому актив становится преступником просто потому, что его клетки отказываются мгновенно распадаться в угоду финансовому отчету «ГИДРАСИНТ КОРП».

Это не просто санкция, это ироничный способ сказать Носителю: «Тебе запрещено быть здоровым бесплатно». …с точки зрения этики (если мы вообще еще помним это слово) — это запредельный уровень издевательства.

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

Потрясающе. Мы достигли пика корпоративного гения: теперь зрение и слух — это не базовые функции жизнеобеспечения, а премиум-подписка.

Ссылки

Туториал по созданию чат-ботов на Streamlit
Учебные материалы по LangGraph
Создание умных AI-агентов: полный курс по LangGraph от А до Я на habr.com
Репозитарий проекта