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

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
Розробка AI Code Review системи
Середній
~1-2 тижні
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

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 тиждень