getMessage(), $e->status, $e->errorCode); } $clientId = (int)$tenant['client_id']; $input = dbnToolsJsonInput(80_000); $question = trim((string)($input['question'] ?? '')); if ($question === '') { dbnToolsError('question is required.', 400, 'missing_question'); } if (mb_strlen($question, 'UTF-8') > 4000) { dbnToolsError('question is too long (max 4000 chars).', 422, 'question_too_long'); } $history = is_array($input['history'] ?? null) ? $input['history'] : []; $history = array_slice($history, -8); $history = array_values(array_filter($history, fn($m) => is_array($m) && in_array($m['role'] ?? '', ['user', 'assistant'], true) && is_string($m['content'] ?? null))); $category = trim((string)($input['category'] ?? '')) ?: null; $language = in_array($input['language'] ?? 'no', ['no', 'en'], true) ? $input['language'] : 'no'; // SSE setup header('Content-Type: text/event-stream'); header('Cache-Control: no-cache, no-transform'); header('X-Accel-Buffering: no'); @ini_set('output_buffering', 'off'); @ini_set('zlib.output_compression', '0'); while (ob_get_level() > 0) ob_end_flush(); ob_implicit_flush(true); function sseEmit(string $event, array $data): void { echo "event: {$event}\n"; echo 'data: ' . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n\n"; if (function_exists('flush')) @flush(); } dbnToolsBootCaveau(); try { $rag = new ClientRagPipeline($clientId); $options = [ 'conversation_history' => $history, 'language' => $language, 'user_id' => (int)($tenant['client_user_id'] ?? 0), 'user_role' => 'owner', ]; $result = $rag->askStreaming( $question, null, // model: let pipeline choose default $category, $options, function (string $chunk): void { if ($chunk !== '') sseEmit('token', ['t' => $chunk]); } ); $sources = []; foreach (($result['fullChunks'] ?? $result['chunks'] ?? []) as $c) { if (!is_array($c)) continue; $sources[] = [ 'document_id' => (int)($c['document_id'] ?? 0), 'title' => (string)($c['title'] ?? ''), 'section' => (string)($c['section_title'] ?? $c['section'] ?? ''), 'source_url' => (string)($c['source_url'] ?? ''), 'score' => isset($c['score']) ? (float)$c['score'] : null, ]; } sseEmit('done', [ 'ok' => true, 'chunks_used' => (int)($result['chunks_used'] ?? count($sources)), 'model' => (string)($result['model'] ?? ''), 'response_time_ms'=> (int)($result['response_time_ms'] ?? 0), 'sources' => $sources, ]); } catch (Throwable $e) { sseEmit('fail', ['ok' => false, 'message' => $e->getMessage()]); } exit;