feat: Enhance review process with streaming events and detailed logging

This commit is contained in:
Primakov Alexandr Alexandrovich 2025-10-13 17:26:41 +03:00
parent a762d09b3b
commit 2f29ccff74
10 changed files with 309 additions and 205 deletions

View File

@ -6,10 +6,8 @@
## 🚀 Быстрый старт ## 🚀 Быстрый старт
### Запуск одной командой:
**Windows:** **Windows:**
```bash ```cmd
start.bat start.bat
``` ```
@ -19,13 +17,15 @@ chmod +x start.sh
./start.sh ./start.sh
``` ```
Это автоматически: Скрипт:
- ✅ Проверит зависимости 1. Соберет фронтенд в `backend/public`
- ✅ Установит пакеты 2. Запустит backend на http://localhost:8000
- ✅ Соберет frontend 3. Фронтенд отдается с бэкенда
- ✅ Запустит сервер
**Готово!** Откройте http://localhost:8000 **Один процесс, один порт, как в production.**
Перезапуск после изменений:
- Просто запусти скрипт заново (Ctrl+C → start.bat)
--- ---

55
RUN.bat Normal file
View File

@ -0,0 +1,55 @@
@echo off
REM ===============================
REM AI Review - Simple Launcher
REM ===============================
title AI Review
echo.
echo ================================
echo AI Review - Starting
echo ================================
echo.
REM Переходим в корень проекта
cd /d "%~dp0"
REM Собираем фронтенд
echo [1/3] Building frontend...
cd frontend
if not exist "node_modules\" npm install
call npm run build
if %ERRORLEVEL% NEQ 0 (
echo.
echo [ERROR] Build failed!
pause
exit /b 1
)
cd ..
REM Переходим в backend
echo.
echo [2/3] Setup backend...
cd backend
REM Создаем venv если нет
if not exist "venv\" (
python -m venv venv
)
REM Активируем и устанавливаем зависимости
call venv\Scripts\activate.bat
pip install -q -r requirements.txt
REM Запускаем сервер
echo.
echo [3/3] Starting server...
echo ================================
echo.
echo http://localhost:8000
echo.
echo ================================
echo.
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

View File

@ -526,7 +526,11 @@ class ReviewerAgent:
on_event: callable = None on_event: callable = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Run the review workflow with streaming events""" """Run the review workflow with streaming events"""
print(f"\n{'='*80}")
print(f"🎬 Starting review stream for PR #{pr_number}") print(f"🎬 Starting review stream for PR #{pr_number}")
print(f" Review ID: {review_id}")
print(f" Callback: {on_event is not None}")
print(f"{'='*80}\n")
# Store callback in instance for access in nodes # Store callback in instance for access in nodes
self._stream_callback = on_event self._stream_callback = on_event
@ -545,9 +549,10 @@ class ReviewerAgent:
final_state = None final_state = None
event_count = 0 event_count = 0
callback_count = 0
# Stream through the graph # Stream through the graph
print(f"📊 Starting graph stream with mode=['updates']") print(f"📊 Starting graph.astream() with mode=['updates']\n")
try: try:
async for event in self.graph.astream( async for event in self.graph.astream(
@ -555,33 +560,59 @@ class ReviewerAgent:
stream_mode=["updates"] stream_mode=["updates"]
): ):
event_count += 1 event_count += 1
print(f"📨 Event #{event_count} received from graph") print(f"\n{''*80}")
print(f" Type: {type(event)}") print(f"📨 STREAM Event #{event_count}")
print(f" Event content: {event}") print(f" Type: {type(event).__name__}")
print(f" Is tuple: {isinstance(event, tuple)}")
print(f" Content: {event}")
print(f"{''*80}")
# LangGraph returns events as dict: {node_name: node_output} # LangGraph returns events as tuple: ('updates', {node_name: node_output})
if isinstance(event, dict): if isinstance(event, tuple) and len(event) == 2:
for node_name, node_data in event.items(): event_type, event_data = event[0], event[1]
print(f" 🔔 Node update: {node_name}") print(f"✓ Tuple detected:")
print(f" 🔔 Node data type: {type(node_data)}") print(f" [0] event_type: '{event_type}'")
print(f" [1] event_data type: {type(event_data).__name__}")
if on_event:
print(f" 📤 Sending event to callback for node: {node_name}") # Handle 'updates' events
await on_event({ if event_type == 'updates' and isinstance(event_data, dict):
"type": "agent_step", print(f"✓ Updates event with dict data")
"step": node_name, for node_name, node_state in event_data.items():
"message": f"Шаг: {node_name}", print(f"\n 🔔 Node: '{node_name}'")
"data": { print(f" State type: {type(node_state).__name__}")
"status": node_data.get("status") if isinstance(node_data, dict) else None
} if on_event:
}) callback_count += 1
print(f" 📤 Calling callback #{callback_count}...")
# Store final state try:
if isinstance(node_data, dict): await on_event({
final_state = node_data "type": "agent_step",
"step": node_name,
"message": f"Шаг: {node_name}",
"data": {
"status": node_state.get("status") if isinstance(node_state, dict) else None
}
})
print(f" ✓ Callback executed successfully")
except Exception as e:
print(f" ❌ Callback error: {e}")
import traceback
traceback.print_exc()
else:
print(f" ⚠️ No callback set!")
# Store final state
if isinstance(node_state, dict):
final_state = node_state
else:
print(f" ⚠️ Not an 'updates' event or data is not dict")
print(f" event_type={event_type}, isinstance(event_data, dict)={isinstance(event_data, dict)}")
else: else:
print(f" ⚠️ Unexpected event format (not dict): {type(event)}") print(f" ❌ NOT a tuple or wrong length!")
print(f" isinstance(event, tuple)={isinstance(event, tuple)}")
if isinstance(event, tuple):
print(f" len(event)={len(event)}")
except Exception as e: except Exception as e:
print(f"❌ Error in graph streaming: {e}") print(f"❌ Error in graph streaming: {e}")

View File

@ -131,9 +131,41 @@ async def get_review(
async def run_review_task(review_id: int, pr_number: int, repository_id: int, db: AsyncSession): async def run_review_task(review_id: int, pr_number: int, repository_id: int, db: AsyncSession):
"""Background task to run review""" """Background task to run review with streaming"""
from app.main import manager
from datetime import datetime as dt
# Create event handler for streaming
async def on_review_event(event: dict):
"""Handle review events and broadcast to clients"""
try:
event_data = {
"type": event.get("type", "agent_update"),
"review_id": review_id,
"pr_number": pr_number,
"timestamp": dt.utcnow().isoformat(),
"data": event
}
# Save to DB
from app.models.review_event import ReviewEvent
db_event = ReviewEvent(
review_id=review_id,
event_type=event.get("type", "agent_update"),
step=event.get("step"),
message=event.get("message"),
data=event
)
db.add(db_event)
await db.commit()
# Broadcast
await manager.broadcast(event_data)
except Exception as e:
print(f"Error in review event handler: {e}")
agent = ReviewerAgent(db) agent = ReviewerAgent(db)
await agent.run_review(review_id, pr_number, repository_id) await agent.run_review_stream(review_id, pr_number, repository_id, on_event=on_review_event)
@router.post("/{review_id}/retry") @router.post("/{review_id}/retry")

View File

@ -13,11 +13,43 @@ router = APIRouter()
async def start_review_task(review_id: int, pr_number: int, repository_id: int): async def start_review_task(review_id: int, pr_number: int, repository_id: int):
"""Background task to start review""" """Background task to start review with streaming"""
from app.database import async_session_maker from app.database import async_session_maker
from app.main import manager
from datetime import datetime as dt
async with async_session_maker() as db: async with async_session_maker() as db:
# Create event handler for streaming
async def on_review_event(event: dict):
"""Handle review events and broadcast to clients"""
try:
event_data = {
"type": event.get("type", "agent_update"),
"review_id": review_id,
"pr_number": pr_number,
"timestamp": dt.utcnow().isoformat(),
"data": event
}
# Save to DB
from app.models.review_event import ReviewEvent
db_event = ReviewEvent(
review_id=review_id,
event_type=event.get("type", "agent_update"),
step=event.get("step"),
message=event.get("message"),
data=event
)
db.add(db_event)
await db.commit()
# Broadcast
await manager.broadcast(event_data)
except Exception as e:
print(f"Error in webhook review event handler: {e}")
agent = ReviewerAgent(db) agent = ReviewerAgent(db)
await agent.run_review(review_id, pr_number, repository_id) await agent.run_review_stream(review_id, pr_number, repository_id, on_event=on_review_event)
@router.post("/gitea/{repository_id}") @router.post("/gitea/{repository_id}")

View File

@ -29,11 +29,23 @@ class ConnectionManager:
async def broadcast(self, message: dict): async def broadcast(self, message: dict):
"""Broadcast message to all connected clients""" """Broadcast message to all connected clients"""
for connection in self.active_connections: print(f"\n[BROADCAST] Sending to {len(self.active_connections)} clients")
print(f"[BROADCAST] Message type: {message.get('type')}")
print(f"[BROADCAST] Message: {str(message)[:200]}...")
sent_count = 0
error_count = 0
for i, connection in enumerate(self.active_connections):
try: try:
await connection.send_json(message) await connection.send_json(message)
except Exception: sent_count += 1
pass print(f"[BROADCAST] ✓ Sent to client #{i+1}")
except Exception as e:
error_count += 1
print(f"[BROADCAST] ✗ Failed to send to client #{i+1}: {e}")
print(f"[BROADCAST] Result: {sent_count} sent, {error_count} failed")
# Create connection manager # Create connection manager

View File

@ -206,6 +206,14 @@ class ReviewTaskWorker:
# Create event handler # Create event handler
async def on_review_event(event: dict): async def on_review_event(event: dict):
"""Handle review events and broadcast to clients""" """Handle review events and broadcast to clients"""
print(f"\n{'*'*80}")
print(f"CALLBACK INVOKED!")
print(f" Event type: {event.get('type')}")
print(f" Event step: {event.get('step')}")
print(f" Event message: {event.get('message')}")
print(f" Active WS connections: {len(manager.active_connections)}")
print(f"{'*'*80}")
try: try:
# Prepare event data # Prepare event data
event_data = { event_data = {
@ -216,6 +224,7 @@ class ReviewTaskWorker:
"data": event "data": event
} }
print(f" Prepared event_data: {event_data}")
logger.info(f" 🔔 Broadcasting event: type={event.get('type')}, connections={len(manager.active_connections)}") logger.info(f" 🔔 Broadcasting event: type={event.get('type')}, connections={len(manager.active_connections)}")
# Save event to database # Save event to database
@ -229,10 +238,13 @@ class ReviewTaskWorker:
) )
db.add(db_event) db.add(db_event)
await db.commit() await db.commit()
print(f" ✓ Event saved to DB: {db_event.id}")
logger.debug(f" 💾 Event saved to DB: {db_event.id}") logger.debug(f" 💾 Event saved to DB: {db_event.id}")
# Broadcast to all connected clients # Broadcast to all connected clients
print(f" Broadcasting to {len(manager.active_connections)} connections...")
await manager.broadcast(event_data) await manager.broadcast(event_data)
print(f" ✓ Broadcast completed")
# Log the event # Log the event
if event.get("type") == "agent_step": if event.get("type") == "agent_step":
@ -242,6 +254,7 @@ class ReviewTaskWorker:
message = event.get("message", "")[:100] message = event.get("message", "")[:100]
logger.info(f" 💬 LLM: {message}...") logger.info(f" 💬 LLM: {message}...")
except Exception as e: except Exception as e:
print(f" ❌ ERROR in callback: {e}")
logger.error(f" ❌ Ошибка broadcast события: {e}") logger.error(f" ❌ Ошибка broadcast события: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()

View File

@ -0,0 +1 @@

146
start.bat
View File

@ -1,100 +1,78 @@
@echo off @echo off
REM Единый скрипт запуска AI Code Review Platform REM AI Review - Build & Start
echo. echo ================================
echo ======================================== echo AI Review - Starting
echo AI Code Review Platform - Запуск echo ================================
echo ========================================
echo. echo.
REM 1. Проверка Node.js REM 1. Build Frontend
echo [STEP 1/7] Проверка Node.js... echo [1/3] Building frontend...
where node >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] Node.js не установлен! Установите Node.js 18+ и попробуйте снова.
pause
exit /b 1
)
node --version
echo [OK] Node.js установлен
echo.
REM 2. Проверка Python
echo [STEP 2/7] Проверка Python...
where python >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] Python не установлен! Установите Python 3.10+ и попробуйте снова.
pause
exit /b 1
)
python --version
echo [OK] Python установлен
echo.
REM 3. Установка зависимостей frontend
echo [STEP 3/7] Установка зависимостей frontend...
cd frontend cd frontend
if not exist "node_modules\" ( if not exist "node_modules\" (
echo Установка npm пакетов... echo Installing npm packages...
call npm install call npm install
) else ( if %ERRORLEVEL% NEQ 0 (
echo node_modules уже существует, пропускаем... echo [ERROR] npm install failed
) cd ..
echo [OK] Зависимости frontend установлены pause
echo. exit /b 1
REM 4. Сборка frontend
echo [STEP 4/7] Сборка frontend...
REM Создаем .env.production для production
echo VITE_API_URL=/api > .env.production
echo VITE_WS_URL= >> .env.production
call npm run build
echo [OK] Frontend собран в backend/public
echo.
REM 5. Установка зависимостей backend
cd ..\backend
echo [STEP 5/7] Установка зависимостей backend...
if not exist "venv\" (
echo Создание виртуального окружения...
python -m venv venv
)
REM Активация venv
call venv\Scripts\activate.bat
REM Установка зависимостей
pip install -r requirements.txt
echo [OK] Зависимости backend установлены
echo.
REM 6. Проверка .env
echo [STEP 6/7] Проверка конфигурации...
if not exist ".env" (
echo [WARNING] Файл .env не найден!
if exist ".env.example" (
echo Создаем .env из примера...
copy .env.example .env
echo [OK] Создан .env файл
echo [WARNING] ВАЖНО: Отредактируйте .env и добавьте необходимые токены!
) else (
echo [ERROR] .env.example не найден!
) )
) )
echo.
REM 7. Запуск backend echo Building...
echo [STEP 7/7] Запуск сервера... call npm run build
echo ========================================
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] Frontend build failed
cd ..
pause
exit /b 1
)
echo [OK] Frontend built to backend\public
cd ..
REM 2. Setup Backend
echo. echo.
echo Backend: http://localhost:8000 echo [2/3] Setting up backend...
echo Frontend: http://localhost:8000 cd backend
if not exist "venv\" (
echo Creating venv...
python -m venv venv
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] Failed to create venv
cd ..
pause
exit /b 1
)
)
echo Activating venv...
call venv\Scripts\activate.bat
echo Installing dependencies...
pip install -q -r requirements.txt
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] Failed to install dependencies
cd ..
pause
exit /b 1
)
REM 3. Start Backend
echo.
echo [3/3] Starting server...
echo ================================
echo.
echo URL: http://localhost:8000
echo API Docs: http://localhost:8000/docs echo API Docs: http://localhost:8000/docs
echo. echo.
echo Для остановки нажмите Ctrl+C echo Press Ctrl+C to stop
echo ================================
echo. echo.
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

110
start.sh
View File

@ -1,104 +1,54 @@
#!/bin/bash #!/bin/bash
# Единый скрипт запуска AI Code Review Platform # AI Review - Build & Start
set -e set -e
echo "🚀 AI Code Review Platform - Запуск" echo "================================"
echo "====================================" echo "AI Review - Starting"
echo "================================"
echo "" echo ""
# Цвета для вывода # 1. Build Frontend
GREEN='\033[0;32m' echo "[1/3] Building frontend..."
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 1. Проверка Node.js
echo -e "${YELLOW}📦 Проверка Node.js...${NC}"
if ! command -v node &> /dev/null; then
echo "❌ Node.js не установлен! Установите Node.js 18+ и попробуйте снова."
exit 1
fi
echo -e "${GREEN}✅ Node.js $(node --version)${NC}"
echo ""
# 2. Проверка Python
echo -e "${YELLOW}🐍 Проверка Python...${NC}"
if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then
echo "❌ Python не установлен! Установите Python 3.10+ и попробуйте снова."
exit 1
fi
PYTHON_CMD="python3"
if ! command -v python3 &> /dev/null; then
PYTHON_CMD="python"
fi
echo -e "${GREEN}✅ Python $($PYTHON_CMD --version)${NC}"
echo ""
# 3. Установка зависимостей frontend
echo -e "${YELLOW}📦 Установка зависимостей frontend...${NC}"
cd frontend cd frontend
if [ ! -d "node_modules" ]; then if [ ! -d "node_modules" ]; then
echo "Installing npm packages..."
npm install npm install
else
echo "node_modules уже существует, пропускаем..."
fi fi
echo -e "${GREEN}✅ Зависимости frontend установлены${NC}"
echo ""
# 4. Сборка frontend
echo -e "${YELLOW}🔨 Сборка frontend...${NC}"
# Создаем .env.production для production
cat > .env.production << 'EOF'
VITE_API_URL=/api
VITE_WS_URL=
EOF
echo "Building..."
npm run build npm run build
echo -e "${GREEN}✅ Frontend собран в backend/public${NC}"
echo ""
# 5. Установка зависимостей backend echo "[OK] Frontend built to backend/public"
cd ../backend cd ..
echo -e "${YELLOW}📦 Установка зависимостей backend...${NC}"
# 2. Setup Backend
echo ""
echo "[2/3] Setting up backend..."
cd backend
if [ ! -d "venv" ]; then if [ ! -d "venv" ]; then
echo "Создание виртуального окружения..." echo "Creating venv..."
$PYTHON_CMD -m venv venv python3 -m venv venv
fi fi
# Активация venv echo "Activating venv..."
source venv/bin/activate source venv/bin/activate
# Установка зависимостей echo "Installing dependencies..."
pip install -r requirements.txt pip install -q -r requirements.txt
echo -e "${GREEN}✅ Зависимости backend установлены${NC}"
echo ""
# 6. Проверка .env # 3. Start Backend
if [ ! -f ".env" ]; then
echo -e "${YELLOW}⚠️ Файл .env не найден!${NC}"
echo "Создаем .env из примера..."
if [ -f ".env.example" ]; then
cp .env.example .env
echo -e "${GREEN}✅ Создан .env файл${NC}"
echo -e "${YELLOW}⚠️ ВАЖНО: Отредактируйте .env и добавьте необходимые токены!${NC}"
else
echo "❌ .env.example не найден!"
fi
echo ""
fi
# 7. Запуск backend
echo -e "${GREEN}🎉 Запуск сервера...${NC}"
echo "===================================="
echo "" echo ""
echo "📍 Backend: http://localhost:8000" echo "[3/3] Starting server..."
echo "📍 Frontend: http://localhost:8000" echo "================================"
echo "📍 API Docs: http://localhost:8000/docs"
echo "" echo ""
echo "Для остановки нажмите Ctrl+C" echo "URL: http://localhost:8000"
echo "API Docs: http://localhost:8000/docs"
echo ""
echo "Press Ctrl+C to stop"
echo "================================"
echo "" echo ""
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload