Разработка AI Code Review системы

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI Code Review системы
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-система автоматического код-ревью

Автоматизированное код-ревью не заменяет архитектурное review — оно убирает механическую часть: проверку стиля, очевидных уязвимостей, покрытия тестами, нарушений принятых паттернов. Senior-разработчик тратит 15–20% времени на ревью; большую часть этого времени уходит на комментарии типа "здесь нет обработки ошибок" или "переменная называется data, назови конкретнее". AI-система берёт этот слой на себя.

Компоненты системы

Diff Analyzer — получает GitHub/GitLab webhook с PR diff, разбирает изменения по файлам.

Code Analyzer — LLM-агент с инструментами: поиск по кодовой базе, чтение связанных файлов, запуск статического анализа.

Review Generator — формирует структурированные комментарии с указанием строк, severity, и предложением исправления.

PR Commenter — публикует комментарии через GitHub/GitLab API на конкретные строки.

Review агент с инструментами

from anthropic import Anthropic
from github import Github
import subprocess
import json
from dataclasses import dataclass
from typing import Literal

client = Anthropic()
gh = Github("GITHUB_TOKEN")

@dataclass
class ReviewComment:
    file: str
    line: int
    severity: Literal["critical", "warning", "suggestion", "nitpick"]
    category: Literal["security", "performance", "style", "logic", "test_coverage", "error_handling"]
    comment: str
    suggestion: str | None = None

def get_pr_diff(repo_name: str, pr_number: int) -> dict:
    """Получает diff PR по файлам"""
    repo = gh.get_repo(repo_name)
    pr = repo.get_pull(pr_number)

    files = {}
    for f in pr.get_files():
        files[f.filename] = {
            "patch": f.patch,
            "additions": f.additions,
            "deletions": f.deletions,
            "status": f.status,  # added/modified/removed
        }
    return files

def run_static_analysis(code: str, language: str) -> str:
    """Запускает статический анализатор"""
    if language == "python":
        result = subprocess.run(
            ["ruff", "check", "--select=ALL", "-"],
            input=code.encode(),
            capture_output=True,
        )
        return result.stdout.decode()[:2000]
    return ""

def read_related_file(file_path: str) -> str:
    """Читает связанный файл для контекста"""
    try:
        with open(file_path) as f:
            return f.read()[:3000]
    except FileNotFoundError:
        return f"File {file_path} not found"

REVIEW_TOOLS = [
    {
        "name": "run_static_analysis",
        "description": "Run static analysis (ruff/eslint/etc) on code",
        "input_schema": {
            "type": "object",
            "properties": {
                "code": {"type": "string"},
                "language": {"type": "string", "enum": ["python", "typescript", "go"]}
            },
            "required": ["code", "language"]
        }
    },
    {
        "name": "read_related_file",
        "description": "Read a related file to understand context (models, tests, etc)",
        "input_schema": {
            "type": "object",
            "properties": {"file_path": {"type": "string"}},
            "required": ["file_path"]
        }
    }
]

def review_file(filename: str, diff: str, full_code: str = None) -> list[ReviewComment]:
    """Проводит ревью одного файла"""

    messages = [{
        "role": "user",
        "content": f"""Review this code change. File: {filename}

Diff:

{diff}


{f"Full file content:{chr(10)}```{chr(10)}{full_code}{chr(10)}```" if full_code else ""}

Use tools to run static analysis and read related files if needed.
Then return a JSON array of review comments with this structure:
{{
  "comments": [
    {{
      "line": <line_number_in_diff>,
      "severity": "critical|warning|suggestion|nitpick",
      "category": "security|performance|style|logic|test_coverage|error_handling",
      "comment": "<explanation>",
      "suggestion": "<code suggestion if applicable>"
    }}
  ]
}}"""
    }]

    # Agentic loop
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=REVIEW_TOOLS,
            messages=messages,
        )

        if response.stop_reason == "end_turn":
            # Извлекаем JSON из ответа
            text = response.content[-1].text
            try:
                data = json.loads(text[text.find("{"):text.rfind("}") + 1])
                return [ReviewComment(file=filename, **c) for c in data.get("comments", [])]
            except Exception:
                return []

        # Обрабатываем tool calls
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                if block.name == "run_static_analysis":
                    result = run_static_analysis(**block.input)
                elif block.name == "read_related_file":
                    result = read_related_file(**block.input)
                else:
                    result = "Unknown tool"

                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })

        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": tool_results})

Публикация комментариев в PR

def post_review(repo_name: str, pr_number: int, comments: list[ReviewComment]):
    """Публикует review комментарии в GitHub PR"""
    repo = gh.get_repo(repo_name)
    pr = repo.get_pull(pr_number)

    # Получаем commit для привязки комментариев
    commit = list(pr.get_commits())[-1]

    review_comments = []
    critical_count = 0

    for comment in comments:
        if comment.severity == "critical":
            critical_count += 1

        body = f"**[{comment.severity.upper()}]** `{comment.category}`\n\n{comment.comment}"
        if comment.suggestion:
            body += f"\n\n**Suggestion:**\n```python\n{comment.suggestion}\n```"

        review_comments.append({
            "path": comment.file,
            "line": comment.line,
            "body": body,
        })

    # Общий статус review
    if critical_count > 0:
        event = "REQUEST_CHANGES"
        body = f"AI Review: найдено {critical_count} критических проблем. Требуется исправление."
    elif len([c for c in comments if c.severity == "warning"]) > 3:
        event = "REQUEST_CHANGES"
        body = f"AI Review: найдено {len(comments)} замечаний, из них {critical_count} критических."
    else:
        event = "COMMENT"
        body = f"AI Review: найдено {len(comments)} несущественных замечаний."

    pr.create_review(
        commit=commit,
        body=body,
        event=event,
        comments=review_comments,
    )

Специализированные чекеры

LLM хорошо видит логические ошибки, но для паттерн-матчинга эффективнее специализированные проверки:

import ast
import re

class SecurityChecker:
    """Проверяет код на типичные уязвимости"""

    DANGEROUS_FUNCTIONS = {"eval", "exec", "compile", "pickle.loads", "yaml.load"}
    SQL_INJECTION_PATTERNS = [
        r'execute\s*\(\s*[f"\']',  # f-string в execute()
        r'\.format\s*\(',           # .format() в SQL
        r'%\s*\(',                  # % в SQL запросе
    ]

    def check_python(self, code: str) -> list[dict]:
        issues = []

        try:
            tree = ast.parse(code)
        except SyntaxError:
            return issues

        # Проверяем вызовы опасных функций
        for node in ast.walk(tree):
            if isinstance(node, ast.Call):
                func_name = ""
                if isinstance(node.func, ast.Name):
                    func_name = node.func.id
                elif isinstance(node.func, ast.Attribute):
                    func_name = f"{node.func.value.id if isinstance(node.func.value, ast.Name) else ''}.{node.func.attr}"

                if func_name in self.DANGEROUS_FUNCTIONS:
                    issues.append({
                        "line": node.lineno,
                        "severity": "critical",
                        "category": "security",
                        "comment": f"Использование {func_name} потенциально опасно",
                    })

        # SQL injection patterns
        for pattern in self.SQL_INJECTION_PATTERNS:
            for match in re.finditer(pattern, code):
                line_no = code[:match.start()].count("\n") + 1
                issues.append({
                    "line": line_no,
                    "severity": "critical",
                    "category": "security",
                    "comment": "Возможная SQL-инъекция: используй параметризованные запросы",
                })

        return issues

GitHub Actions интеграция

# .github/workflows/ai-review.yml
name: AI Code Review

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      contents: read

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Run AI Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          pip install anthropic pygithub ruff
          python scripts/ai_review.py \
            --repo "${{ github.repository }}" \
            --pr "${{ github.event.pull_request.number }}"

Практический кейс: команда 8 разработчиков

Проблема: senior-разработчик тратил 6–8 часов в неделю на ревью. 40% комментариев — повторяющиеся замечания (нет error handling, хардкод конфигурации, не написаны тесты).

Внедрение AI Review:

  • Критические проблемы (безопасность, явные баги): блокируют merge
  • Предупреждения: показываются, но не блокируют
  • Nitpicks: опциональный чеклист

Результаты:

  • Механических комментариев от senior: -71%
  • Среднее время до первого ревью: 4 часа → 3 минуты (AI срабатывает сразу)
  • Количество багов, дошедших до production: -34%
  • Время senior на ревью: 7 часов → 2 часа (только архитектурные решения)

Важный вывод: AI-система находила реальные баги в 23% PR — не просто стилевые замечания.

Сроки

  • Базовый review с постингом в GitHub: 3–5 дней
  • Специализированные чекеры безопасности + статический анализ: 1 неделя
  • Тонкая настройка под конвенции проекта: 1–2 недели
  • Интеграция в CI/CD с политиками merge: 1 неделя