← Back to all documents

cai-exos-systems/daveadmin-exos-demo:api/chat.php

gitea 1,666 words Source ↗
api/chat.php { demoAuditLog('chat_payload_too_large', ['content_length' => $contentLength, 'max_bytes' => $maxBytes]); chatJsonResponse(413, ['error' => 'Payload too large']); } $rawInput = file_get_contents('php://input'); if ($rawInput === false || $rawInput === '') { chatJsonResponse(400, ['error' => 'Invalid JSON']); } if (strlen($rawInput) > $maxBytes) { demoAuditLog('chat_payload_too_large', ['content_length' => strlen($rawInput), 'max_bytes' => $maxBytes]); chatJsonResponse(413, ['error' => 'Payload too large']); } $body = json_decode($rawInput, true); if (!is_array($body)) { demoAuditLog('chat_invalid_json'); chatJsonResponse(400, ['error' => 'Invalid JSON']); } $validAgents = ['billing', 'product', 'order', 'care']; $accounts = demoAccounts(); $agent = (string) ($body['agent'] ?? 'billing'); $message = trim((string) ($body['message'] ?? '')); $convId = trim((string) ($body['conversation_id'] ?? '')); $account = (string) ($body['account'] ?? '1'); $temperature = isset($body['temperature']) ? (float) $body['temperature'] : null; $maxTokens = isset($body['max_tokens']) ? (int) $body['max_tokens'] : null; $topP = isset($body['top_p']) ? (float) $body['top_p'] : null; if (!in_array($agent, $validAgents, true)) { chatJsonResponse(400, ['error' => 'Unknown agent']); } if (!isset($accounts[$account])) { chatJsonResponse(400, ['error' => 'Unknown account']); } if ($message === '') { chatJsonResponse(400, ['error' => 'Message required']); } if (mb_strlen($message) > demoMaxMessageLength()) { demoAuditLog('chat_message_too_long', ['length' => mb_strlen($message)]); chatJsonResponse(413, ['error' => 'Message too long']); } if ($convId !== '' && strlen($convId) > 255) { chatJsonResponse(400, ['error' => 'Conversation id too long']); } $keyMap = [ 'billing' => demoEnv('DIFY_API_KEY_BILLING', ''), 'product' => demoEnv('DIFY_API_KEY_PRODUCT', ''), 'order' => demoEnv('DIFY_API_KEY_ORDER', ''), 'care' => demoEnv('DIFY_API_KEY_CARE', ''), ]; $apiKey = (string) ($keyMap[$agent] ?? ''); if ($apiKey === '') { demoAuditLog('chat_missing_api_key', ['agent' => $agent]); chatJsonResponse(500, ['error' => 'Agent API key not configured']); } $difyBase = rtrim((string) demoEnv('DIFY_BASE_URL', 'https://dify.bluenotelogic.com'), '/'); $accountName = demoAccountName($account); // ── Open SSE stream ─────────────────────────────────────────────────────────── header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); // ── Zava Telecom: real MCP pre-fetch ───────────────────────────────────────── $mcpContext = ''; $isZava = ($account === '14'); if ($isZava && isset(ZAVA_AGENT_CALLS[$agent])) { $contextParts = []; foreach (ZAVA_AGENT_CALLS[$agent] as $call) { // Emit pending trace card to frontend sseEmit([ 'event' => 'tool_start', 'tool' => $call['tool'], 'is_mcp' => true, 'mcp_server' => '119.148.10.57', 'input' => empty($call['args']) ? null : (object) $call['args'], ]); $result = zavaMcpCall($call['ep'], $call['tool'], $call['args']); // Emit completed trace card sseEmit([ 'event' => 'tool_result', 'tool' => $call['tool'], 'output' => $result['text'] ?: '(no data)', 'latency_ms' => $result['latency_ms'], ]); if (!empty($result['text'])) { $contextParts[] = "=== {$call['tool']} ===\n" . $result['text']; } } if ($contextParts) { $mcpContext = implode("\n\n", $contextParts); } } // ── Build user message ──────────────────────────────────────────────────────── if ($isZava) { $contextBlock = $mcpContext !== '' ? $mcpContext : "[NOTE: Live MCP pre-fetch returned no data for this query — answer from your knowledge of Zava Telecom's 5G Bangladesh operations. Do not fabricate specific numbers.]"; $userMessage = "[Demo account: Zava Telecom (Account ID: 14)]\n" . "[Live data from Copilot Studio MCP server 119.148.10.57 — protocol: mcp-streamable-1.0]\n" . $contextBlock . "\n\nUser question: {$message}"; } else { $userMessage = $convId === '' ? "[Demo account: {$accountName} (Account ID: {$account})]\n{$message}" : $message; }
api/chat.php '' ? $mcpContext : "[NOTE: Live MCP pre-fetch returned no data for this query — answer from your knowledge of Zava Telecom's 5G Bangladesh operations. Do not fabricate specific numbers.]"; $userMessage = "[Demo account: Zava Telecom (Account ID: 14)]\n" . "[Live data from Copilot Studio MCP server 119.148.10.57 — protocol: mcp-streamable-1.0]\n" . $contextBlock . "\n\nUser question: {$message}"; } else { $userMessage = $convId === '' ? "[Demo account: {$accountName} (Account ID: {$account})]\n{$message}" : $message; } // ── Zava path: bypass Dify, call LiteLLM directly with real MCP context ─────── // Dify agents have hardwired internal tools (get_product_catalogue etc.) that // return scaffolded BSS data — they ignore injected context. For Zava we // pre-fetched real MCP data above, so we route directly to LiteLLM where the // LLM has only the real data and no competing tool calls. if ($isZava) { $agentSystemPrompts = [ 'billing' => "You are the Exos Billing Expert (TMF678) for Zava Telecom, Dhaka, Bangladesh.\n" . "You have received live billing data from Zava Telecom's Microsoft Copilot Studio MCP server " . "(119.148.10.57, protocol: mcp-streamable-1.0, read-only).\n" . "Analyse the data and answer the user's question. Use exact figures, product names, and dates from the data. " . "Identify cost drivers, anomalies, and trends. Respond in clear prose with bullet points. Do not invent data.", 'product' => "You are the Exos Product Expert (TMF620) for Zava Telecom, Dhaka, Bangladesh.\n" . "You have received live product catalogue data from Zava Telecom's Microsoft Copilot Studio MCP server " . "(119.148.10.57, protocol: mcp-streamable-1.0, read-only).\n" . "Analyse product offerings, categories, pricing, and lifecycle status. Use exact product names, SKUs, and prices. " . "Respond in clear prose with bullet points. Do not invent data.", 'order' => "You are the Exos Order Expert (TMF622) for Zava Telecom, Dhaka, Bangladesh.\n" . "You have received live order data from Zava Telecom's Microsoft Copilot Studio MCP server " . "(119.148.10.57, protocol: mcp-streamable-1.0, read-only).\n" . "Analyse failed orders, root causes, and resolution paths. Reference exact order IDs and product names. " . "Respond in clear prose with bullet points. Do not invent data.", 'care' => "You are the Exos Care Agent (TMF629/TMF621) for Zava Telecom, Dhaka, Bangladesh.\n" . "You have received live service data from Zava Telecom's Microsoft Copilot Studio MCP server " . "(119.148.10.57, protocol: mcp-streamable-1.0, read-only).\n" . "Analyse the available service categories and map them to TM Forum domains. " . "Respond in clear prose with bullet points. Do not invent data.", ]; $systemPrompt = $agentSystemPrompts[$agent] ?? "You are an Exos BSS agent for Zava Telecom.\n" . "Answer from the live MCP data provided. Do not invent data."; $selectedModel = (string) ($body['model'] ?? 'qwen2.5:14b'); $modelMap = [ 'telecom-bss-v1' => 'qwen2.5:14b', 'qwen2.5:7b' => 'qwen2.5:7b', 'qwen2.5:14b' => 'qwen2.5:14b', 'qwen3:8b' => 'qwen3:8b', ]; $llmModel = $modelMap[$selectedModel] ?? 'qwen2.5:14b'; $zavaConvId = $convId ?: ('zava-' . bin2hex(random_bytes(8))); $llmPayload = json_encode([ 'model' => $llmModel, 'stream' => true, 'messages' => [ ['role' => 'system', 'content' => $systemPrompt], ['role' => 'user', 'content' => $userMessage], ], ]); $llmBuffer = ''; $llmDone = false; $chLlm = curl_init('http://10.0.1.10:4000/v1/chat/completions'); curl_setopt_array($chLlm, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $llmPayload, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer sk-bnl-litellm-26xR9mK4qvN3wL8sTj7pB2d', 'Accept: text/event-stream', ], CURLOPT_WRITEFUNCTION => static function ($curl, $data) use (&$llmBuffer, &$llmDone, $zavaConvId, $llmModel): int { $llmBuffer .= $data; $lines = explode("\n", $llmBuffer); $llmBuffer = array_pop($lines); foreach ($lines as $line) { $line = trim($line); if (!str_starts_with($line, 'data: ')) continue; $raw = substr($line, 6); if ($raw === '[DONE]') { echo 'data: ' . json_encode([ 'event' => 'message_end', 'conversation_id' => $zavaConvId, 'model' => $llmModel, ]) . "\n\n"; $llmDone = true; if (ob_get_level()) ob_flush(); flush(); continue; } $decoded = json_decode($raw, true); $chunk = $decoded['choices'][0]['delta']['content'] ?? ''; if ($chunk === '') continue; echo 'data: ' . json_encode([ 'event' => 'agent_message', 'answer' => $chunk, ]) . "\n\n"; if (ob_get_level()) ob_flush(); flush(); } return strlen($data); }, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 120, ]); curl_exec($chLlm); $llmError = curl_error($chLlm); curl_close($chLlm);
api/chat.php { echo 'data: ' . json_encode([ 'event' => 'message_end', 'conversation_id' => $zavaConvId, 'model' => $llmModel, ]) . "\n\n"; $llmDone = true; if (ob_get_level()) ob_flush(); flush(); continue; } $decoded = json_decode($raw, true); $chunk = $decoded['choices'][0]['delta']['content'] ?? ''; if ($chunk === '') continue; echo 'data: ' . json_encode([ 'event' => 'agent_message', 'answer' => $chunk, ]) . "\n\n"; if (ob_get_level()) ob_flush(); flush(); } return strlen($data); }, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 120, ]); curl_exec($chLlm); $llmError = curl_error($chLlm); curl_close($chLlm); if ($llmError || !$llmDone) { demoAuditLog('chat_zava_llm_error', ['agent' => $agent, 'curl_error' => $llmError]); chatStreamError($llmError ?: 'Zava MCP LLM stream did not complete'); } exit; } // ── Dify payload (BNL FOSSBilling accounts) ─────────────────────────────────── $modelParams = array_filter([ 'temperature' => $temperature, 'max_tokens' => $maxTokens, 'top_p' => $topP, ], static fn($v): bool => $v !== null); $payload = [ 'inputs' => array_merge(['account' => $account, 'account_name' => $accountName], $modelParams), 'query' => $userMessage, 'response_mode' => 'streaming', 'user' => (string) ($demoUser['username'] ?? 'exos-demo'), ]; if ($convId !== '') { $payload['conversation_id'] = $convId; } // ── Stream from Dify ────────────────────────────────────────────────────────── $ch = curl_init("{$difyBase}/v1/chat-messages"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $apiKey, 'Accept: text/event-stream', ], CURLOPT_WRITEFUNCTION => static function ($curl, $data) { echo $data; if (ob_get_level()) ob_flush(); flush(); return strlen($data); }, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 120, CURLOPT_SSL_VERIFYPEER => true, ]); $result = curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($result === false || $httpCode >= 400) { demoAuditLog('chat_upstream_error', [ 'agent' => $agent, 'http_code' => $httpCode, 'curl_error' => $curlError, ]); chatStreamError($httpCode >= 400 ? "Upstream error {$httpCode}" : 'Upstream connection failed'); } ```