221 lines
7.4 KiB
PHP
221 lines
7.4 KiB
PHP
<?php
|
|
/* ═══════════════════════════════════════════════════════════════════
|
|
CLAUDE PATTERN SSE · Opus v17 · 22-avr
|
|
|
|
SSE streaming version of claude-pattern-api
|
|
Emits phases in real-time (thinking → plan → RAG → execute → tests → response → critique)
|
|
|
|
Usage:
|
|
GET /api/claude-pattern-sse.php?message=X&chatbot=Y
|
|
|
|
Event types: thinking, plan, rag, execute, tests, response, critique, done
|
|
═══════════════════════════════════════════════════════════════════ */
|
|
header('Content-Type: text/event-stream; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
header('X-Accel-Buffering: no');
|
|
header('Access-Control-Allow-Origin: *');
|
|
|
|
// Disable output buffering for real-time streaming
|
|
@ini_set('output_buffering', 'off');
|
|
@ini_set('zlib.output_compression', false);
|
|
while (ob_get_level() > 0) ob_end_flush();
|
|
ob_implicit_flush(true);
|
|
|
|
function emit($event, $data) {
|
|
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
echo "event: {$event}\n";
|
|
echo "data: {$json}\n\n";
|
|
@flush();
|
|
}
|
|
|
|
$message = trim($_GET['message'] ?? $_POST['message'] ?? '');
|
|
$chatbot = $_GET['chatbot'] ?? $_POST['chatbot'] ?? 'wevia-master';
|
|
$session = $_GET['session'] ?? 'sse-' . bin2hex(random_bytes(3));
|
|
|
|
if (!$message) {
|
|
emit('error', ['error' => 'message required']);
|
|
exit;
|
|
}
|
|
|
|
$BACKENDS = [
|
|
'wevia-master' => '/api/wevia-autonomous.php',
|
|
'wevia' => '/api/ambre-thinking.php',
|
|
'claw' => '/api/wevia-json-api.php',
|
|
'claw-chat' => '/api/wevia-json-api.php',
|
|
'claw-code' => '/api/weval-unified-pipeline.php',
|
|
'director' => '/api/wevia-autonomous.php',
|
|
'director-chat' => '/api/wevia-director.php',
|
|
'ethica' => '/api/ethica-brain.php',
|
|
'ethica-chatbot' => '/api/ethica-brain.php',
|
|
'openclaw' => '/api/openclaw-proxy.php',
|
|
'blade-ai' => '/api/blade-api.php',
|
|
'wevcode' => '/api/wevcode-superclaude.php',
|
|
'wevia-console' => '/api/wevia-json-api.php',
|
|
'wevia-widget' => '/api/wevia-json-api.php',
|
|
'wevia-cortex' => '/api/wevia-stream-api.php',
|
|
'wevia-chat' => '/api/wevia-autonomous.php',
|
|
'sovereign-claude' => '/api/wevia-json-api.php',
|
|
'weval-arena' => '/api/wevia-multi-provider.php',
|
|
'weval-arena-v2' => '/api/wevia-webchat-direct.php',
|
|
'l99-brain' => '/api/wevia-master-api.php',
|
|
'multiagent' => '/api/wevia-v83-multi-agent-orchestrator.php',
|
|
'auto' => '/api/opus5-autonomous-orchestrator-v3.php',
|
|
];
|
|
|
|
$backend = $BACKENDS[$chatbot] ?? $BACKENDS['wevia-master'];
|
|
|
|
// PHASE 1 · THINKING
|
|
$t1 = microtime(true);
|
|
emit('thinking', [
|
|
'status' => 'analyzing',
|
|
'message_length' => strlen($message),
|
|
'chatbot' => $chatbot,
|
|
'backend' => $backend,
|
|
]);
|
|
|
|
$msg_lower = strtolower($message);
|
|
$intent_map = [
|
|
'status' => ['status', 'état', 'sante', 'health', 'live'],
|
|
'action' => ['rotate', 'restart', 'deploy', 'run', 'exec'],
|
|
'analytics' => ['kpi', 'metric', 'combien', 'total', 'nombre'],
|
|
];
|
|
$intent = 'query';
|
|
foreach ($intent_map as $i => $kws) {
|
|
foreach ($kws as $kw) {
|
|
if (strpos($msg_lower, $kw) !== false) { $intent = $i; break 2; }
|
|
}
|
|
}
|
|
|
|
emit('thinking', [
|
|
'status' => 'complete',
|
|
'intent' => $intent,
|
|
'duration_ms' => round((microtime(true) - $t1) * 1000, 1),
|
|
]);
|
|
|
|
// PHASE 2 · PLAN
|
|
$t2 = microtime(true);
|
|
emit('plan', ['status' => 'building']);
|
|
|
|
$steps = match($intent) {
|
|
'status' => ['Query KPI sources', 'Aggregate health data', 'Format response'],
|
|
'action' => ['Validate safety', 'Execute command', 'Verify result'],
|
|
'analytics' => ['Fetch metrics', 'Calculate aggregates', 'Return structured data'],
|
|
default => ['Parse query', 'Route to backend', 'Format natural response'],
|
|
};
|
|
|
|
emit('plan', [
|
|
'status' => 'complete',
|
|
'steps' => $steps,
|
|
'duration_ms' => round((microtime(true) - $t2) * 1000, 1),
|
|
]);
|
|
|
|
// PHASE 3 · RAG (quick ping)
|
|
$t3 = microtime(true);
|
|
emit('rag', ['status' => 'searching']);
|
|
|
|
$rag_ctx = @file_get_contents('http://127.0.0.1:6333/collections', false, stream_context_create([
|
|
'http' => ['timeout' => 1]
|
|
]));
|
|
$rag_ok = $rag_ctx !== false;
|
|
|
|
emit('rag', [
|
|
'status' => $rag_ok ? 'qdrant_available' : 'skipped',
|
|
'duration_ms' => round((microtime(true) - $t3) * 1000, 1),
|
|
]);
|
|
|
|
// PHASE 4 · EXECUTE (REAL backend)
|
|
$t4 = microtime(true);
|
|
emit('execute', ['status' => 'calling_backend', 'backend' => $backend]);
|
|
|
|
$body = json_encode(['message' => $message, 'session' => $session]);
|
|
$ctx = stream_context_create([
|
|
'http' => [
|
|
'method' => 'POST',
|
|
'header' => "Content-Type: application/json\r\nHost: weval-consulting.com\r\n",
|
|
'content' => $body,
|
|
'timeout' => 25,
|
|
'ignore_errors' => true,
|
|
]
|
|
]);
|
|
|
|
$backend_resp = @file_get_contents('http://127.0.0.1' . $backend, false, $ctx);
|
|
$backend_data = @json_decode($backend_resp, true);
|
|
|
|
// Deep-dig text extraction
|
|
$text = '';
|
|
if ($backend_data) {
|
|
$text = $backend_data['final_response']
|
|
?? $backend_data['text']
|
|
?? $backend_data['response']
|
|
?? $backend_data['answer']
|
|
?? $backend_data['reply']
|
|
?? $backend_data['message']
|
|
?? $backend_data['thinking']
|
|
?? '';
|
|
if (is_array($text)) $text = json_encode($text, JSON_UNESCAPED_UNICODE);
|
|
}
|
|
|
|
// Handle SSE chunks if response is SSE format
|
|
if (!$text && strpos((string)$backend_resp, 'data:') !== false) {
|
|
preg_match_all('/data:\s*(\{[^\n]+\})/', $backend_resp, $m);
|
|
$chunks = [];
|
|
foreach ($m[1] ?? [] as $chunk) {
|
|
$cd = @json_decode($chunk, true);
|
|
if (!empty($cd['text'])) $chunks[] = $cd['text'];
|
|
}
|
|
$text = implode("\n", $chunks);
|
|
}
|
|
|
|
$backend_ok = !empty($text) && strlen($text) > 10;
|
|
|
|
emit('execute', [
|
|
'status' => 'complete',
|
|
'backend_ok' => $backend_ok,
|
|
'response_length' => strlen($text),
|
|
'duration_ms' => round((microtime(true) - $t4) * 1000, 1),
|
|
]);
|
|
|
|
// PHASE 5 · TESTS
|
|
$t5 = microtime(true);
|
|
$tests = [
|
|
'has_response' => $backend_ok,
|
|
'no_error' => !preg_match('/\berror\b|\bfailed\b|\bexception\b/i', substr($text, 0, 200)),
|
|
'not_simulated' => !preg_match('/simulat(ed|ion)|mock|fake|placeholder/i', substr($text, 0, 300)),
|
|
'not_hallucinating' => !preg_match('/\b(je ne sais pas|imagine|hypothetical|suppose que|probablement|might be)\b/i', substr($text, 0, 300)),
|
|
'has_natural_lang' => preg_match('/\b(le|la|les|un|je|vous|nous|est|sont|the|is|are)\b/i', substr($text, 0, 200)) > 0,
|
|
];
|
|
$passed = array_sum(array_map('intval', $tests));
|
|
|
|
emit('tests', [
|
|
'passed' => $passed,
|
|
'total' => count($tests),
|
|
'details' => $tests,
|
|
'duration_ms' => round((microtime(true) - $t5) * 1000, 1),
|
|
]);
|
|
|
|
// PHASE 6 · RESPONSE
|
|
emit('response', [
|
|
'text' => $text,
|
|
'length' => strlen($text),
|
|
]);
|
|
|
|
// PHASE 7 · CRITIQUE
|
|
$notes = [];
|
|
if ($passed < 5) $notes[] = "Some tests failed ({$passed}/5)";
|
|
if (strlen($text) < 30) $notes[] = "Short response";
|
|
if ((microtime(true) - $t4) > 10) $notes[] = "Slow response";
|
|
if (!$notes) $notes[] = "Quality OK";
|
|
|
|
emit('critique', [
|
|
'quality_score' => $passed / 5,
|
|
'quality' => $passed === 5 ? 'EXCELLENT' : ($passed >= 4 ? 'GOOD' : ($passed >= 3 ? 'OK' : 'LOW')),
|
|
'notes' => $notes,
|
|
]);
|
|
|
|
// DONE
|
|
emit('done', [
|
|
'total_duration_ms' => round((microtime(true) - $t1) * 1000, 1),
|
|
'chatbot' => $chatbot,
|
|
'session' => $session,
|
|
]);
|