composer require symfony/ai-bundle darkwood/ia-exception-bundle
config/packages/ai.yaml):
ai:
platform:
openai:
api_key: '%env(OPENAI_API_KEY)%'
agent:
default:
model: 'gpt-4o-mini'
config/bundles.php):
Darkwood\IaExceptionBundle\DarkwoodIaExceptionBundle::class => ['all' => true],
config/packages/darkwood_ia_exception.yaml):
darkwood_ia_exception:
enabled: true
only_status_codes: [500]
agent: 'ai.agent.default'
500 error (e.g., throw new \RuntimeException('Test error') in a controller).Debugging a Production 500 Error:
AI Analysis:
- Probable Cause: "Database transaction timed out during payment processing."
- Suggested Fix: "Increase `db_timeout` in `.env` or optimize the `PaymentService` query."
- Confidence: 0.92
kernel.exception event via ExceptionListener.App\Exceptions\Handler::render().ai.agent.default (from symfony/ai-bundle).async: true) loads analysis via a separate endpoint (/__ai_exception/{error_id}).templates/bundles/DarkwoodIaException/error.html.twig) to customize AI output.
Example:
{% extends '@DarkwoodIaException/error.html.twig' %}
{% block ai_analysis %}
<div class="ai-suggestion">
{{ parent() }}
<p class="disclaimer">Note: AI suggestions are hypotheses.</p>
</div>
{% endblock %}
config/routes.yaml):
darkwood_ia_exception:
resource: '@DarkwoodIaExceptionBundle/Resources/config/routes.yaml'
Useful for high-traffic sites where blocking AI calls degrades performance.// app/Services/AiExceptionAnalyzer.php
class AiExceptionAnalyzer
{
public function analyze(Exception $e): array
{
$client = new \GuzzleHttp\Client();
$response = $client->post('https://api.openai.com/v1/chat/completions', [
'json' => [
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'system', 'content' => 'You are a PHP error analyst.'],
['role' => 'user', 'content' => "Exception: {$e->getMessage()}\nTrace: {$e->getTraceAsString()}"],
],
],
'headers' => ['Authorization' => 'Bearer '.config('services.openai.key')],
]);
return json_decode($response->getBody(), true);
}
}
render() in Handler:
// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if ($exception instanceof \Symfony\Component\HttpKernel\Exception\HttpException && $exception->getStatusCode() === 500) {
$aiAnalysis = app(AiExceptionAnalyzer::class)->analyze($exception);
return response()->view('errors.ai_500', [
'exception' => $exception,
'aiAnalysis' => $aiAnalysis,
]);
}
return parent::render($request, $exception);
}
resources/views/errors/ai_500.blade.php):
@extends('errors::minimal')
@section('title', 'AI Debug Help')
@section('content')
<h2>AI Analysis</h2>
<p>{{ $aiAnalysis['english_exception'] }}</p>
<h3>Probable Causes</h3>
<ul>
@foreach($aiAnalysis['probable_causes'] as $cause)
<li>{{ $cause }}</li>
@endforeach
</ul>
@endsection
# config/packages/ai.yaml
ai:
agent:
dev:
model: 'gpt-4'
prod:
model: 'gpt-4o-mini'
# config/packages/darkwood_ia_exception.yaml
darkwood_ia_exception:
agent: '{{ app.environment === "dev" ? "ai.agent.dev" : "ai.agent.prod" }}'
// Symfony EventListener
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getThrowable();
$context = [
'user_role' => $event->getRequest()->attributes->get('user_role'),
'endpoint' => $event->getRequest()->getPathInfo(),
];
$this->aiAnalyzer->analyze($exception, $context);
}
// Laravel Handler
public function report(Throwable $exception)
{
if ($exception instanceof \Symfony\Component\HttpKernel\Exception\HttpException) {
Sentry\captureException($exception);
}
parent::report($exception);
}
Sensitive Data in Stack Traces:
.env keys, API tokens, or paths (e.g., /app/config/secrets.php).include_trace: false in production and sanitize logs:
darkwood_ia_exception:
include_trace: false
Str::of($trace)->replaceMatched('/[^a-zA-Z0-9_\-\/]/', '') to redact sensitive parts.AI Hallucinations:
confidence score to filter low-confidence responses:
// Symfony EventListener
if ($aiResponse['confidence'] < 0.7) {
$this->fallbackToRawTrace($event);
}
Async Mode Quirks:
php bin/console debug:router | grep ai_exception).# config/packages/nelmio_cors.yaml
nelmio_cors:
defaults:
allow_origin: ['*']
allow_methods: ['GET']
allow_headers: ['Content-Type']
expose_headers: []
max_age: 3600
// In your error page
fetch(`/__ai_exception/${errorId}`)
.then(response => response.json())
.catch(() => {
document.getElementById('ai-analysis').innerHTML =
'<p class="fallback">AI analysis unavailable. Showing raw trace.</p>';
});
Caching Gotchas:
How can I help you explore Laravel packages today?