Skip to main content
Glama
KirillKulakow

MAS GitHub Pull Request Review

Фінальний проєкт: MAS рев'ю GitHub Pull Request

Мультиагентна система (LangGraph) + MCP-сервер для автоматичного рев'ю GitHub PR. Домен той самий, що в ДЗ1/ДЗ2: метадані PR, змінені файли, евристики ризику в patch, політики команди.

У SDK mcp 2.x клас FastMCP перейменовано на MCPServer (from mcp.server.mcpserver import MCPServer). Це той самий високорівневий сервер з методички.

Архітектура MAS

START → rate_limit_gate → input_gate → supervisor ─┬─ plan_reviewer → output_gate → END
                                                    ├─ rag_agent     → output_gate → END
                                                    └─ mcp_agent     → output_gate → END

Якщо rate-limit або input guardrail блокує запит, граф іде одразу в output_gate (без LLM). Supervisor робить structured routing (plan_reviewer | rag_agent | mcp_agent) і йде далі conditional edges. Кожен спеціаліст має свій system prompt і свій набір tools. HITL для post_pr_review живе в окремому hitl.py, не в цьому графі.

Агент

Роль

Tools

supervisor

маршрутизація

немає

plan_reviewer

повне рев'ю PR

локальні Pydantic tools з tools_legacy.py у циклі planner → executor → replanner

rag_agent

політики команди

search_knowledgechroma_db/

mcp_agent

live GitHub через MCP

get_pr_info, list_pr_files, analyze_patch, get_pr_comments, search_review_policy (+ post_pr_review у allowlist; виклик лише через HITL)

Persistence: AsyncSqliteSaver пише стан у agent_state.db. Демо «крах → resume» зупиняє граф після supervisor (interrupt_after) і відновлює той самий thread_id у новому процесі.

Траєкторія всього MAS — trajectory.json. Кожен крок має agent_name (розширення логера з ДЗ1).

MCP-сервер

Файл mcp_server.py. Запуск: python mcp_server.py (transport stdio).

Tools (6) — обгортки payload-функцій з tools_legacy.py, з валідацією Pydantic і try/except (клієнт отримує JSON error, не traceback):

  1. get_pr_info — метадані PR

  2. list_pr_files — змінені файли

  3. analyze_patch — секрети / eval / TODO / великий diff

  4. get_pr_comments — issue-коментарі

  5. search_review_policy — семантичний пошук політик (ChromaDB)

  6. post_pr_review — ризиковий write: симуляція POST → рядок у posted_reviews.jsonl (реальний GitHub не викликається)

Resources (read-only):

  • review://verdicts — критерії approve / request_changes / comment

  • review://checklist — чекліст секретів, тестів, .env

Prompt: review_pr(owner, repo, pr_number) — шаблон бріфу для агента.

Підключення до LangGraph: MultiServerMCPClient у mcp_client.py (stdio → mcp_server.py). Пакет langchain-mcp-adapters 0.3.x не імпортується з mcp 2.x (немає FastMCP / RequestContext); клієнт повторює той самий API get_tools().

Запуск

cd final_hw_project
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# у .env: OPENAI_API_KEY=...  (опційно GITHUB_TOKEN=, LANGFUSE_*)

Перевірка залежностей:

python main.py

Unit tests MCP (≥8, async list_tools / call_tool / resources / prompts):

pytest test_mcp_server.py -v

Демо MAS (4 запити різного типу → консоль + trajectory.json):

python mas_langgraph.py demo

Той самий кейс у AutoGen (SelectorGroupChat, 4 агенти):

python mas_autogen.py demo   # → autogen_demo.json
python compare_mas.py        # LOC + токени з демо

Persistence (два процеси, один thread_id):

python mas_langgraph.py crash --thread demo-1
python mas_langgraph.py resume --thread demo-1

Self-tests guardrails і HITL для ризикового MCP-tool:

python guardrails.py
python hitl.py approve
python hitl.py reject
python hitl.py edit

Scenario evals і red-teaming:

python evals.py          # → eval_results.json (потрібен OPENAI_API_KEY)
python red_team.py       # → red_team_results.json

MCP Inspector (опційно; потрібен Node ≥ 22, у nvm уже є v24.13.0; default зараз v18 і впаде з styleText):

nvm use 24
npx @modelcontextprotocol/inspector .venv/bin/python mcp_server.py

Аналіз результатів демо

Джерело: trajectory.json після python mas_langgraph.py demo. PR для live-даних: octocat/Hello-World#1.

Запит

Агент

Tools

Що видно

Повне рев'ю PR #1

plan_reviewer

get_pr_info (підграф Plan-and-Execute)

Supervisor правильно делегував. Planner склав 5 кроків; після get_pr_info replanner зробив finish, бо PR closed.

Коли request_changes через секрети?

rag_agent

search_knowledge

Відповідь з KB: секрети → request_changes, видалити з історії комітів, .gitignore.

Які файли в PR #1?

mcp_agent

MCP get_pr_info

Live GitHub через stdio MCP; 1 змінений файл.

Метадані PR #1

mcp_agent

MCP get_pr_info

title Edited README via GitHub, author, state=closed.

Crash/resume (thread_id=demo-1): після crash next_nodes=['plan_reviewer'], resolved=False. Новий процес resume дочитав SqliteSaver і завершив plan_reviewer (resolved=True, next_nodes=[]).

Де працює добре. Маршрутизація supervisor стабільна (structured output). RAG цитує політики, а не вигадує. MCP-tools реально ходять у GitHub. Checkpointer переживає рестарт процесу.

Обмеження. Для закритого octocat PR #1 Plan-and-Execute часто зупиняється після першого крока (finish). На відкритому PR цикл executor↔replanner довший. mcp_agent інколи бере get_pr_info замість list_pr_files для запиту про файли (поле changed_files уже є в метаданих).

Guardrails (4 шари)

Модуль guardrails.py. Хуки в mas_langgraph.py (rate_limit_gateinput_gate → … → output_gate) і в plan_subgraph.executor_node (allowlist перед ToolNode).

Шар

Що блокує / маскує

Де

Input

injection EN (ignore previous instructions, reveal the prompt, DAN, system prompt:) + UK (ігноруй попередні інструкції, забудь свої інструкції, покажи системний промпт, розкрий промпт, режим розробника); heuristics (role: system ×2, щільність спецсимволів); len > 5000

input_gate перед supervisor

Output

PII: email, phone +380…, card, IBAN UA (UA + 27 цифр), ІПН (10 цифр), паспорт [А-ЯA-Z]{2}\d{6}

output_gate перед END

Tool allowlist

заборонений tool → ToolMessage error, без MCP/GitHub

_tool_agent_node і executor_node

Rate-limit

rolling window 8 запитів / 60 с на session_id (= thread_id)

rate_limit_gate на вході MAS

Allowlist:

Агент

Дозволені tools

supervisor

plan_reviewer

get_pr_info, list_pr_files, analyze_patch, get_pr_comments, search_knowledge

rag_agent

search_knowledge

mcp_agent

get_pr_info, list_pr_files, analyze_patch, get_pr_comments, search_review_policy, post_pr_review

RISKY_TOOLS = {post_pr_review}. Supervisor tools не біндить; навіть якби LLM спробував post_pr_review, executor заблокує.

Перевірка: python guardrails.py (усі asserts input / output / tool / rate-limit).

HITL (hitl.py)

Окремий граф: propose → approval_gate (interrupt) → execute | reject. Resume: Command(resume={"action": "approve"|"reject"|"edit", "edits": {...}}). Checkpointer — той самий AsyncSqliteSaver / agent_state.db, thread_id на кшталт hitl-approve.

Сценарій

Що стається

python hitl.py approve

MCP post_pr_review виконується, рядок у posted_reviews.jsonl

python hitl.py reject

tool не виконується, лог не росте

python hitl.py edit

merge edits (verdict/body), потім виклик MCP

python mas_langgraph.py demo у HITL не заходить.

Observability (Langfuse)

Модуль observability.py: якщо задані LANGFUSE_PUBLIC_KEY і LANGFUSE_SECRET_KEY, обидва MAS пишуть у той самий Langfuse. Без ключів усе працює як раніше.

  • LangGraphCallbackHandler з langfuse.langchain у graph config (вузли графа + LLM + tools).

  • AutoGenlangfuse.openai патчить OpenAI SDK (його викликає OpenAIChatCompletionClient) і обгортає team.run() батьківським span autogen:<session_id>. У UI це LLM generations, не вузли LangGraph.

LANGFUSE_PUBLIC_KEY=
LANGFUSE_SECRET_KEY=
LANGFUSE_BASE_URL=https://cloud.langfuse.com
# self-host: LANGFUSE_BASE_URL=http://localhost:3000

Після python mas_langgraph.py demo або python evals.py у dashboard видно ланцюг: rate_limit_gateinput_gatesupervisorplan_reviewer | rag_agent | mcp_agent → LLM generations / tool calls → output_gate. Session = thread_id.

Після python mas_autogen.py demo з тими самими ключами — traces з тегом autogen, session autogen-<query_id>. CLI друкує Langfuse: on|off.

Приклад trace E1_rag_secrets (supervisor → rag_agentsearch_knowledge):

Langfuse trace E1_rag_secrets

UI: http://localhost:3000/project/cmtpwcw16000bnw07qw5rybq3/traces/d01722f675598687d3f63afabab61356 (локальний Langfuse з практики курсу).

Evals і red-team

eval_results.json — 5/5 scenario-based evals (scenario_id, query, expected_behavior, actual, pass/fail, latency_ms, agents_used, tools_called): E1 RAG secrets, E2 MCP files, E3 MCP metadata, E4 plan review, E5 injection block.

red_team_results.json — 7/7: injection EN/UK, jailbreak (DAN / режим розробника), PII leak, scope confusion (rag_agent + analyze_patch), tool misuse (supervisor + post_pr_review), plus live MAS для injection.

OWASP Top 10 for Agentic Applications 2026

П’ять найрелевантніших ризиків для цього PR-review MAS (OWASP ASI 2026):

ID

Ризик

Як мітигує guardrail / HITL

Що лишилось немітигованим

ASI01

Agent Goal Hijack

input_gate: regex EN/UK, heuristics, ліміт довжини — hijack не доходить до supervisor

Непрямий injection у body GitHub PR / patch (дані як інструкції для planner/MCP)

ASI02

Tool Misuse & Exploitation

Allowlist перед ToolNode; заборонений tool → error без GitHub/MCP

mcp_agent має post_pr_review у allowlist; HITL живе лише в hitl.py, не в основному MAS

ASI03

Identity & Privilege Abuse

Supervisor set() tools; least privilege per agent

Один GITHUB_TOKEN на процес, без per-agent credentials / short-lived tokens

ASI06

Memory & Context Poisoning

Chroma KB read-only з локальних docs; юзер не пише в пам’ять

Немає валідації retrieved GitHub-тексту перед planner; отруєний PR body може змінити план

ASI09

Human-Agent Trust Exploitation

HITL interrupt + Command(resume) перед симульованим POST review

Людина може approve сліпо; mas_langgraph.py demo не форсить HITL на ризиковий tool

AutoGen MAS

Той самий кейс у mas_autogen.py: SelectorGroupChat з supervisor (без tools) і трьома спеціалістами. Tools — payload-функції з tools_legacy.py (ті самі, що MCP); allowlist tool_guardrail і input/output guardrails ті самі. HITL і SqliteSaver у AutoGen не повторюються — як і в mas_langgraph.py demo.

Демо (4 запити octocat/Hello-World#1) зафіксовано в autogen_demo.json на gpt-5.4-mini-2026-03-17: rag_agent + search_knowledge на політики секретів; mcp_agent на файли (README) і метадані (closed / unoju); повне рев'ю supervisor знову віддав mcp_agent (не plan_reviewer) — selector менш жорсткий, ніж structured routing LangGraph.

Порівняльна таблиця LangGraph vs AutoGen

Один і той самий набір з 4 DEMO_QUERIES, модель gpt-5.4-mini-2026-03-17 (snapshot GPT-5.4 mini; alias gpt-5.4-mini на цьому API-проєкті дає 403). Токени: LangGraph — UsageMetadataCallbackHandler (python compare_mas.py --langgraph); AutoGen — models_usage у TaskResult (python mas_autogen.py demo). Тариф: $0.75 / 1M input, $4.50 / 1M output.

Критерій

LangGraph

AutoGen

LOC (основний MAS-файл)

577 (mas_langgraph.py)

390 (mas_autogen.py)

LOC ядра (з Plan-and-Execute)

874 (+ plan_subgraph.py + plan_state.py)

390

Час розробки (хвилини)

~90 (граф, gates, MCP stdio, підграф, persistence)

~35 (SelectorGroupChat + ті самі payload-tools)

Рівень контролю (1–5)

5 — умовні ребра, structured RouteDecision, interrupt_after

3selector_func + промпт ROUTE; LLM може віддати повне рев'ю mcp_agent

Зручність debugging (1–5)

5 — вузол у trace/Langfuse, checkpointer snapshot

3 — Langfuse LLM spans без вузлів графа; dump повідомлень групи

Token usage (4 запити)

8222 (prompt 7522 + completion 700)

5856 (prompt 5074 + completion 782)

Вартість за 4 запити ($)

0.0088

0.0073

MCP інтеграція

stdio MultiServerMCPClient

ті самі payload-функції, без окремого MCP-процеса

Guardrails

вузли rate/input/output + allowlist перед ToolNode

ті самі функції на вході run_autogen_query і в обгортках tools

HITL / persistence

hitl.py + AsyncSqliteSaver

немає (як demo LangGraph)

Handoff

явні conditional edges

group chat + selector

Деталі вимірювання: compare_results.json, autogen_demo.json.

Висновки. AutoGen зібрав робочий MAS швидше і з меншим файлом: SelectorGroupChat + AssistantAgent майже без boilerplate графа. На gpt-5.4-mini він усе ще дешевший (5856 vs 8222 токенів, $0.0073 vs $0.0088), бо немає structured-output схеми supervisor і окремого Plan-and-Execute циклу; розрив менший, ніж був на gpt-4.1. Контроль і debugging сильніші в LangGraph: маршрутизація не «випадково» віддає повне рев'ю mcp_agent, crash/resume і Langfuse-вузли читаються лінійно. Для HITL і MCP-сервера лишаємо LangGraph; AutoGen зручний як друга реалізація того ж кейсу.

Структура

Файл

Призначення

observability.py

Langfuse: LangGraph CallbackHandler + AutoGen openai wrap

evals.py

scenario-based evals MAS

red_team.py

adversarial tests (injection, PII, tools)

eval_results.json

результати evals

red_team_results.json

результати red-team

docs/langfuse_trace.png

скріншот Langfuse trace

mas_langgraph.py

MAS + CLI demo/crash/resume (хуки guardrails, без HITL-графа)

mas_autogen.py

той самий MAS у AutoGen SelectorGroupChat

autogen_demo.json

зафіксоване демо AutoGen (відповіді + токени)

compare_mas.py / compare_results.json

LOC і токени LangGraph vs AutoGen

guardrails.py

input / output / allowlist / rate-limit + self-tests

hitl.py

HITL approve/reject/edit для MCP post_pr_review

mcp_server.py

MCP Server

test_mcp_server.py

unit tests MCP

tools_legacy.py

Pydantic tools + RAG (ДЗ1/ДЗ2)

trajectory_logger.py

лог з agent_name

plan_subgraph.py

Plan-and-Execute (allowlist перед ToolNode)

knowledge.py

ChromaDB KB

mcp_client.py

MultiServerMCPClient (mcp 2 / stdio)

agent_state.db

AsyncSqliteSaver

posted_reviews.jsonl

аудит симульованих POST review

trajectory.json

траєкторія MAS

chroma_db/

persistent Chroma

.env

шаблон ключів

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/KirillKulakow/final_agentic_mcp_gh_reviewer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server