- BrainTools - https://www.braintools.ru -
Меня зовут Андрей Бирюков. Я — независимый эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу статьи и книги. И сегодня мы поговорим о том, как можно с помощью ИИ (да, опять этот ИИ) находить конфликты в пользовательских историях.
Ситуация, знакомая каждому менеджеру по продукту [1]: вы написали бэклог на спринт. Команда оценила, всё красиво разложено по Jira. Начинается разработка, и на третий день разработчик подходит с вопросом: «Слушай, тут история US-17 говорит, что кнопка должна быть красной, а US-23 — что зелёной. Какую делать?».
Вы лезете в документацию, и правда — противоречие. Вы теряете час на созвон с заказчиком, разработчик теряет полдня на ожидание, тестировщик потом пишет баг «несоответствие требований». Но это еще упрощенный вариант, ведь конфликт [2] на самом деле может быть не таким явным.
Например, история про пагинацию (разделение большого объёма контента на отдельные страницы с возможностью навигации между ними) требует по 50 элементов на странице, а история про производительность обещает время ответа API 200 мс при загрузке всего массива данных. Одно с другим не вяжется, но разработчик это поймёт только когда начнёт писать код.
Здесь проблема заключается в том, что человеческий мозг [3] не может удерживать одновременно 40–50 пользовательских историй и проверять их на непротиворечивость. Классические инструменты вроде Jira или Confluence такую проверку не делают. А ручной анализ занимает полдня — и вы всё равно что‑то упускаете.
Для решения этой проблемы мы можем использовать нейросети с большим контекстным окном (Gemini 3, Claude 3). Они могут проанализировать весь ваш бэклог за один проход и выявить логические, временные, ресурсные и семантические конфликты. Причём сделать это жёстко, без «вежливых советов», а именно с детекцией противоречий, которые приведут к багам.
Давайте построим работающий пайплайн обнаружения конфликтов требований на Python.
Перед тем как писать код, важно понять, какие типы конфликтов мы ищем. Опираясь на практику анализа требований в сложных системах, выделим четыре основных типа:
Тип 1. Логические (семантические) конфликты
Две истории говорят о противоположных вещах. Например: «Система должна отправлять email‑уведомление» и «Система не должна отправлять email‑уведомления». Или более тонкий случай: «Кнопка „Сохранить“ активна всегда» и «Кнопка „Сохранить“ активна только при заполненных полях».
Тип 2. Производительностные конфликты
Одна история требует скорости, другая — функциональности, которая эту скорость убивает. Классика: «API возвращает ответ за <200 мс» и «API возвращает полную историю транзакций пользователя за 3 года».

Тип 3. Ресурсные конфликты
Две истории требуют изменения одного и того же модуля, компонента или экрана в одном спринте, но не синхронизированы между собой.
Тип 4. Временные (порядковые) конфликты
История B требует результата истории A, но по бэклогу они запланированы в обратном порядке или параллельно.
Наша нейросетевая система будет выявлять все четыре типа. Причём не просто «есть подозрение на конфликт», а с указанием конкретных ID историй, типа конфликта и чёткой формулировкой проблемы.
Прежде всего нам необходимо определиться с моделью, которую мы будем использовать. Для анализа требований нам нужна модель с тремя характеристиками:
Большой контекст — чтобы загрузить все 30–50 историй сразу.
Дешевизна — анализ бэклога не должен стоить как аренда самолёта.
Структурированный вывод — чтобы результаты можно было обработать программно.
Для этих целей лучше всего подойдет Gemini 3 Flash, так как контекст в 1M токенов позволит уместить 40 историй с огромным запасом и есть официальный Python SDK.

Начнем со структуры подготовки входных данных. Итак, нам нужен CSV‑файл с пользовательскими историями. Минимальные поля:
id — уникальный идентификатор (US-01, US-02…).
title — краткое название.
description — полное описание (как пользователь, я хочу… чтобы…).
acceptance_criteria — критерии принятия (по одному на строку или списком).
dependencies — ID историй, от которых зависит данная (если есть).
component — модуль/компонент системы (опционально, но сильно помогает с ресурсными конфликтами).
Пример строки в CSV (разделитель запятая):
id,title,description,acceptance_criteria,dependencies,component
US-01, Аутентификация через Google, Как пользователь, я хочу войти в систему через Google аккаунт, При клике на кнопку открывается окно авторизации Google, Пользователь перенаправляется на главную после успешной авторизации, US-00, Auth.
US-02, «Запомнить меня», Как пользователь, я хочу, чтобы система запоминала мой вход на 30 дней, Чекбокс «Запомнить меня» на форме входа, При установке чекбокса сессия не истекает 30 дней, Auth.
Итак, мы подготовили входные данные и теперь самое время перейти к реализации.
Код: пошаговая реализация
Наш проект будет иметь следующую структуру:
|
├── requirements.csv # ваш бэклог ├── detector.py # основной скрипт ├──.env # API ключ └── results/ # папка для результатов ├── conflicts.json └── conflicts_report.md |
Прежде всего установим SDK с помощью pip:
pip install google-generativeai python-dotenv pandas –upgrade
Далее создадим.env файл:
GEMINI_API_KEY=ваш_ключ_из_Google_AI_Studio
Мы установили все необходимые инструменты и теперь можно переходить к реализации.
На этом шаге мы загрузим требования из CSV и отформатируем их для использования LLM.
import os
import json
from typing import List, Dict
import pandas as pd
import google.generativeai as genai
from dotenv import load_dotenv
load_dotenv()
# Указываем API
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
# Для Gemini 3 Flash указываем имя модели
MODEL_NAME = "gemini-3-flash-preview"
def load_requirements(csv_path: str) -> List[Dict]:
#Загружаем требования из CSV и форматируем для LLM
df = pd.read_csv(csv_path)
requirements = []
for _, row in df.iterrows():
req = {
"id": row["id"],
"text": f"[{row['id']}] {row['title']}n"
f"Описание: {row['description']}n"
f"Критерии принятия: {row['acceptance_criteria']}n"
f"Компонент: {row.get('component', 'не указан')}n"
f"Зависимости: {row.get('dependencies', 'нет')}"
}
requirements.append(req)
return requirements
Вывод:

Мы подготовили необходимые данные и теперь наша задача научиться общаться с нейросетью. Для этого мы формируем промпт для детекции конфликтов. При этом, промпт остаётся жёстким и структурированным — нейросеть должна работать как системный аналитик, а не как вежливый консультант.
def build_conflict_detection_prompt(requirements: List[Dict]) -> str:
req_text = "n---n".join([r["text"] for r in requirements])
prompt = f"""
Ты — системный аналитик, задача которого — найти КОНКРЕТНЫЕ противоречия в требованиях.
Загружены следующие пользовательские истории:
{req_text}
Найди конфликты следующих типов:
1. Логические конфликты: Две истории утверждают прямо противоположные вещи.
Пример: История A: "Кнопка должна быть синей" и История B: "Кнопка должна быть красной".
2. Производительностные конфликты: Одна история требует высокой производительности, другая — функциональности, которая эту производительность убивает.
Пример: "API отвечает за 100 мс" и "API загружает 10 000 записей".
3. Ресурсные конфликты: Истории требуют изменения одного и того же компонента без синхронизации.
4. Временные конфликты: История B зависит от истории A, но по зависимостям или порядку это не отражено.
ПРАВИЛА:
- НЕ придумывай конфликты там, где их нет.
- НЕ используй слова "возможно", "вероятно", "может быть".
- Если конфликт есть — укажи его ЖЁСТКО и КОНКРЕТНО.
- Если конфликтов нет — напиши "CONFLICTS_NOT_FOUND".
ВЫВОДИ ТОЛЬКО JSON в следующем формате (без пояснений, без markdown-разметки):
{{
"conflicts": [
{{
"type": "logical|performance|resource|temporal",
"requirements": ["US-01", "US-05"],
"description": "Чёткое описание противоречия",
"impact": "Что произойдёт, если это дойдёт до разработки",
"suggestion": "Конкретное предложение по исправлению"
}}
]
}}
Если конфликтов нет:
{{
"conflicts": []
}}
"""
return prompt
Ключевыми элементами этого промпта является использование ролевой модели — «ты системный аналитик», а не «помощник», конкретные примеры типов конфликтов и жесткие запреты. Также для парсинга результатов мы используем JSON.
После того, как мы подготовили данные и промпт, можно переходить к запуску детекции конфликтов. Gemini 3 Flash поддерживает новый параметр thinking_level, который позволяет контролировать глубину размышлений модели. Для задачи анализа конфликтов идеально подходит MEDIUM — баланс скорости и качества.
def detect_conflicts(requirements: List[Dict]) -> Dict:
prompt = build_conflict_detection_prompt(requirements)
# Получаем модель с правильной конфигурацией
model = genai.GenerativeModel(MODEL_NAME)
try:
# Для Gemini 3 Flash используем generation_config с thinking_level
# MEDIUM — оптимальный баланс для аналитических задач [citation:1]
response = model.generate_content(
prompt,
generation_config={
"temperature": 0.1, # Минимум "креатива"
"top_p": 0.95,
"top_k": 40,
# Ключевое нововведение Gemini 3 Flash — управление уровнем мышления
# MEDIUM даёт хорошее качество без чрезмерных задержек
"thinking_level": "MEDIUM" # Доступные значения: MINIMAL, LOW, MEDIUM, HIGH [citation:9]
}
)
# Очищаем ответ от возможной markdown-разметки
result_text = response.text.strip()
if result_text.startswith("```json"):
result_text = result_text[7:-3]
elif result_text.startswith("```"):
result_text = result_text[3:-3]
return json.loads(result_text)
except Exception as e:
print(f"Ошибка при запросе к Gemini 3 Flash: {e}")
return {"conflicts": [], "error": str(e)}
def main():
# Загружаем требования
requirements = load_requirements("requirements.csv")
print(f"Загружено {len(requirements)} требований")
print(f"Используется модель: {MODEL_NAME}")
# Запускаем детекцию
print("Анализ конфликтов с Gemini 3 Flash...")
result = detect_conflicts(requirements)
# Сохраняем результаты
os.makedirs("results", exist_ok=True)
with open("results/conflicts.json", "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
# Выводим в консоль
conflicts = result.get("conflicts", [])
if conflicts:
print(f"n НАЙДЕНО {len(conflicts)} КОНФЛИКТОВ:n")
for c in conflicts:
print(f"Тип: {c['type']}")
print(f"Истории: {', '.join(c['requirements'])}")
print(f"Проблема: {c['description']}")
print(f"Исправление: {c['suggestion']}")
print("-" * 50)
else:
print("n Конфликтов не найдено")
if name == "__main__":
main()
Результатом работы скрипта на этом шаге будет вывод статистики по требованиям:

И собственно, результаты работы нашей нейросети:

Мы получили вывод результатов в консоль, но для полноты картины хотелось бы получить читаемый отчет о конфликтах, и для этого нам потребуется следующий код:
def generate_report(result: Dict, output_path: str = "results/conflicts_report.md"):
conflicts = result.get("conflicts", [])
report = f"""# Отчёт о конфликтах требований
Дата анализа: {time.strftime("%Y-%m-%d %H:%M:%S")}
Модель: {MODEL_NAME}
Найдено конфликтов: {len(conflicts)}
if not conflicts:
report += " Конфликтов не найдено. Бэклог консистентен.n"
else:
by_type = {}
for c in conflicts:
t = c["type"]
if t not in by_type:
by_type[t] = []
by_type[t].append(c)
type_names = {
"logical": " Логические конфликты",
"performance": " Производительностные конфликты",
"resource": " Ресурсные конфликты",
"temporal": " Временные конфликты"
}
for t, type_conflicts in by_type.items():
report += f"n {type_names.get(t, t)}nn"
for c in type_conflicts:
report += f" Конфликт: {', '.join(c['requirements'])}nn"
report += f"Проблема: {c['description']}nn"
report += f"Последствия: {c.get('impact', 'Не указаны')}nn"
report += f"Рекомендация: {c['suggestion']}nn---nn"
with open(output_path, "w", encoding="utf-8") as f:
f.write(report)
print(f"Отчёт сохранён: {output_path}")
Полученный в итоге отчет может иметь следующий вид:

Прежде всего, стоит напомнить, что нейросети всё ещё могут галлюцинировать. Даже с низкой температурой и строгим промптом Gemini может «придумать» конфликт там, где его нет. Так что всегда делайте выборочную ручную проверку, особенно если конфликтов много.
Также бывают проблемы с контекстом при очень больших бэклогах. Например, если у вас 100+ историй, даже Gemini может начать терять точность. Для решения используйте предварительную кластеризацию по компонентам.
А еще API‑лимиты. У Gemini это 15 запросов в минуту. При обработке 50 историй вы сделаете 5–10 запросов, что укладывается в лимиты. Но для постоянной интеграции в CI/CD подумайте о кэшировании результатов.
Также наш промпт написан для русского и английского. Но если ваши истории смешанные или содержат специфическую терминологию, модель может путаться. Решение: стандартизируйте язык в рамках одного бэклога или используйте модель с сильной поддержкой русского (например, YandexGPT).
В итоге мы построили систему, которая находит логические, производительностные, ресурсные и временные конфликты в пользовательских историях. Также она умеет работать с любым бэклогом, который можно выгрузить в CSV, и даёт не просто рекомендации «что плохо», а конкретные предложения по исправлению. И ее можно легко интегрировать в процесс планирования спринта за 10 минут.
Следующий шаг — подключить это к вашему CI/CD и сделать детекцию конфликтов автоматическим чеком перед тем, как бэклог уходит в разработку. Представьте: вы пушите изменения в Confluence или Jira, и бот через минуту пишет в Slack: «Обнаружено противоречие между US-17 и US-23. Варианты исправления: …».
И это не фантастика. Вы только что написали 80% кода для этого бота. Сохраните скрипты, добавьте в них обработку ошибок под ваш бэклог, и пусть конфликты умирают до того, как доберутся до разработчика.
Хотите глубже разобраться, как использовать ИИ в работе с требованиями и продуктовой аналитикой? В июле в OTUS пройдут два бесплатных урока:
8 июля в 20:00 — «Как писать PRD, ТЗ и user stories с помощью ИИ — быстро, структурно и без мусора». [Записаться] [4]
Разберём, как давать модели достаточный контекст и получать документы, пригодные для реальной работы.
20 июля в 20:00 — «ИИ для продуктового discovery: как анализировать рынок, конкурентов и пользователей быстрее». [Записаться] [5]
Поговорим о применении ИИ в исследовании рынка и проверке продуктовых гипотез без потери качества анализа.
Занятия проходят онлайн: можно задавать вопросы преподавателю и разбирать подходы на практических примерах.
Больше материалов о системном и продуктовом анализе, работе с требованиями и применении ИИ — на канале OTUS в MAX [6]. Подписывайтесь, чтобы не пропускать новые статьи, открытые уроки и практические разборы.
Автор: Andrey_Biryukov
Источник [7]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/31560
URLs in this post:
[1] продукту: https://otus.pw/10Ve/
[2] конфликт: http://www.braintools.ru/article/7708
[3] мозг: http://www.braintools.ru/parts-of-the-brain
[4] [Записаться]: https://otus.pw/VRGn/
[5] [Записаться]: https://otus.pw/7oeE/
[6] канале OTUS в MAX: https://max.ru/otusru
[7] Источник: https://habr.com/ru/companies/otus/articles/1044030/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1044030
Нажмите здесь для печати.