mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 15:52:58 +00:00
- API now properly detects N8N errors in 200 responses (OpenAI quota, etc.) - Returns 400 status with simple "Agent unavailable" message for webhook failures - Frontend shows errors in results container instead of disappearing toast notifications - Users now see clear, persistent error messages when agents fail - Keeps success toasts working, only fixes error display 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
166 lines
6.6 KiB
Python
166 lines
6.6 KiB
Python
"""
|
|
REST API views for agent execution and management.
|
|
Handles API endpoints for executing agents, retrieving execution history, etc.
|
|
"""
|
|
|
|
from rest_framework import status
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.pagination import PageNumberPagination
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils import timezone
|
|
from .models import AgentExecution
|
|
from .serializers import AgentExecutionSerializer
|
|
from .services import AgentFileService
|
|
from .utils import validate_webhook_url, format_agent_message
|
|
import requests
|
|
import time
|
|
import uuid
|
|
|
|
|
|
@api_view(['POST'])
|
|
@permission_classes([IsAuthenticated])
|
|
def execute_agent(request):
|
|
"""Execute an agent with provided input data"""
|
|
agent_slug = request.data.get('agent_slug')
|
|
input_data = request.data.get('input_data', {})
|
|
|
|
if not agent_slug:
|
|
return Response({'error': 'agent_slug is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
agent_data = AgentFileService.get_agent_by_slug(agent_slug)
|
|
if not agent_data or not agent_data.get('is_active', True):
|
|
return Response({'error': 'Agent not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
agent_price = float(agent_data['price'])
|
|
|
|
# Check if user has sufficient balance (using existing wallet system)
|
|
if hasattr(request.user, 'has_sufficient_balance') and not request.user.has_sufficient_balance(agent_price):
|
|
return Response({'error': 'Insufficient wallet balance'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
# Create execution record
|
|
execution = AgentExecution.objects.create(
|
|
agent_slug=agent_data['slug'],
|
|
agent_name=agent_data['name'],
|
|
user=request.user,
|
|
input_data=input_data,
|
|
fee_charged=agent_price,
|
|
status='pending'
|
|
)
|
|
|
|
try:
|
|
# Deduct fee from user wallet (using existing wallet system)
|
|
if hasattr(request.user, 'deduct_balance'):
|
|
success = request.user.deduct_balance(
|
|
agent_price,
|
|
f'{agent_data["name"]} - Execution {str(execution.id)[:8]}',
|
|
agent_data['slug']
|
|
)
|
|
if not success:
|
|
execution.status = 'failed'
|
|
execution.error_message = 'Failed to deduct wallet balance'
|
|
execution.save()
|
|
return Response({'error': 'Failed to deduct wallet balance'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
# Validate webhook URL to prevent SSRF attacks
|
|
try:
|
|
validate_webhook_url(agent_data['webhook_url'])
|
|
except ValueError as e:
|
|
execution.status = 'failed'
|
|
execution.error_message = f'Invalid webhook URL: {str(e)}'
|
|
execution.save()
|
|
return Response({'error': f'Invalid webhook URL: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
# Call n8n webhook with proper payload format
|
|
execution.status = 'running'
|
|
execution.save()
|
|
|
|
# Generate session ID
|
|
session_id = f"session_{int(time.time() * 1000)}_{str(uuid.uuid4())[:8]}"
|
|
|
|
# Format message text for N8N based on agent type
|
|
message_text = format_agent_message(agent_data['slug'], input_data)
|
|
|
|
webhook_payload = {
|
|
'sessionId': session_id,
|
|
'message': {'text': message_text},
|
|
'webhookUrl': agent_data['webhook_url'],
|
|
'executionMode': 'production',
|
|
'agentId': agent_data['slug'],
|
|
'executionId': str(execution.id),
|
|
'userId': str(request.user.id)
|
|
}
|
|
|
|
response = requests.post(
|
|
agent_data['webhook_url'],
|
|
json=webhook_payload,
|
|
timeout=90, # Increased timeout for complex processing
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
|
|
# Store webhook response
|
|
execution.webhook_response = response.json() if response.headers.get('content-type', '').startswith('application/json') else {'raw': response.text}
|
|
|
|
# Check if response contains N8N error indicators
|
|
has_error = False
|
|
if response.status_code == 200 and execution.webhook_response:
|
|
# Check for N8N error patterns
|
|
if isinstance(execution.webhook_response, dict):
|
|
if 'errorMessage' in execution.webhook_response or 'error' in execution.webhook_response:
|
|
has_error = True
|
|
|
|
if response.status_code == 200 and not has_error:
|
|
execution.status = 'completed'
|
|
execution.output_data = execution.webhook_response
|
|
execution.completed_at = timezone.now()
|
|
execution.save()
|
|
|
|
serializer = AgentExecutionSerializer(execution)
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
else:
|
|
execution.status = 'failed'
|
|
if has_error:
|
|
error_msg = execution.webhook_response.get('errorMessage', 'Webhook execution failed')
|
|
execution.error_message = f"N8N Error: {error_msg[:500]}"
|
|
else:
|
|
execution.error_message = f"Webhook returned {response.status_code}: {response.text[:500]}"
|
|
execution.completed_at = timezone.now()
|
|
execution.save()
|
|
|
|
return Response({
|
|
'error': 'Agent is temporarily unavailable. Please try again later.'
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
except requests.RequestException as e:
|
|
execution.status = 'failed'
|
|
execution.error_message = str(e)
|
|
execution.completed_at = timezone.now()
|
|
execution.save()
|
|
|
|
return Response({
|
|
'error': 'Failed to execute agent',
|
|
'execution_id': str(execution.id)
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
@api_view(['GET'])
|
|
@permission_classes([IsAuthenticated])
|
|
def execution_list(request):
|
|
"""List user's agent executions"""
|
|
executions = AgentExecution.objects.filter(user=request.user)
|
|
|
|
paginator = PageNumberPagination()
|
|
paginator.page_size = 20
|
|
result_page = paginator.paginate_queryset(executions, request)
|
|
serializer = AgentExecutionSerializer(result_page, many=True)
|
|
return paginator.get_paginated_response(serializer.data)
|
|
|
|
|
|
@api_view(['GET'])
|
|
@permission_classes([IsAuthenticated])
|
|
def execution_detail(request, execution_id):
|
|
"""Get detailed execution information"""
|
|
execution = get_object_or_404(AgentExecution, id=execution_id, user=request.user)
|
|
serializer = AgentExecutionSerializer(execution)
|
|
return Response(serializer.data) |