Sebelum memulai praktik, pastikan Anda telah memahami konsep dasar pada materi kelas AI Agent & PHP di infokoding.

BAB 20

Optimasi Performa AI Agent PHP

Teknik-teknik optimasi performa AI Agent PHP: caching response AI, async request queue dengan Redis, streaming response real-time, dan connection pooling untuk beban tinggi.

Terakhir diperbarui:

Definisi: Mengapa AI Agent PHP Memerlukan Optimasi Performa & Latensi?

Direct Answer:Optimasi Performa AI Agent PHP adalah penerapan strategi rekayasa piranti lunak untuk memangkas latensi respon inferensi model LLM dari detik ke milidetik melalui lima pilar utama: Redis In-Memory Caching (menurunkan latensi 98%), Server-Sent Events (SSE) Streaming (mengurangi Time To First Token hingga <200ms), Prompt Token Pruning (mereduksi biaya token dan context window bloat), Asynchronous Queue (mencegah blocking I/O), dan Database Connection Pooling berbasis Swoole\Database\PDOPool.

Dalam arsitektur belajar ai-agent-php skala besar, pemanggilan LLM berulang tanpa caching dan tanpa kompresi prompt adalah pemborosan anggaran komputasi terbesar dan penyebab utama friksi pengalaman pengguna (*bad user experience*).

Key Takeaways: Rangkuman Eksekutif Optimasi Performa AI Agent PHP
  • Redis In-Memory Caching: Mengeliminasi pemanggilan LLM berulang untuk query deterministik dengan respon instan ~12ms.
  • Server-Sent Events (SSE) Streaming: Memangkas persepsi waktu tunggu pengguna dengan Time To First Token (TTFT) di bawah 200 milidetik.
  • Prompt Token Pruning & Semantic Chunking: Memangkas hingga 60% token context window berlebih menggunakan algoritma sliding window conversation history.
  • Asynchronous Redis Queue & Worker: Mencegah HTTP timeout pada tugas ekstraksi dokumen berat dengan mendelegasikan ke daemon Supervisor.
  • Swoole PDOPool Connection Pooling: Mendaur ulang koneksi database TCP untuk menghemat 35ms handshake latency pada transaksi throughput tinggi.

TL;DR (Ringkasan Cepat)

  • Redis Caching: Cache prompt deterministik untuk memangkas respon 2.800ms menjadi 12ms instan.
  • Streaming SSE (Server-Sent Events): Kirim chunk token langsung ke antarmuka pengguna saat inferensi berlangsung.
  • Token Pruning: Bersihkan whitespace berlebih dan batasi context history pesan pengguna.
  • Async Queue Worker: Serahkan tugas berat ke background worker Supervisor.
  • Swoole & PDO Connection Pooling: Gunakan Swoole\Database\PDOPool untuk mendaur ulang koneksi database non-blocking.
Diagram Arsitektur: Pilar Optimasi Performa & Reduksi Latensi AI Agent PHP
WebP Lossless • 80 KB • Schema Ready
Diagram arsitektur optimasi performa AI Agent PHP: Response caching Redis instan 12ms, Server-Sent Events streaming token chunks via cURL TTFT 180ms, Async queue processing dengan worker Supervisor, dan Connection Pooling database MySQL
Gambar 20.1: Arsitektur Optimasi Performa — Penurunan latensi end-to-end melalui multi-layer caching, streaming asinkron, prompt pruning, antrean proses worker, dan pooling koneksi MySQL.
Visual Interactive Flow: Komparasi TTFT (Standard vs Streaming SSE)
1. Standard HTTP (Non-Streaming)
Idle
Tekan "Jalankan Simulasi" untuk mengamati perbedaan latency...

Klien memblokir eksekusi selama ~2.800ms hingga seluruh token selesai.

2. Server-Sent Events (SSE Streaming)
Idle
Siap menerima transmisi chunk token...

Token pertama muncul dalam ~180ms (Pengalaman pengguna responsif instan).

Tabel Komparasi Latensi Sebelum vs Sesudah Optimasi AI Agent

Tabel Komparasi Latensi Sebelum dan Sesudah Optimasi AI Agent
Skenario Operasi AISebelum Optimasi (Baseline)Sesudah Optimasi (Optimized)Peningkatan KecepatanTeknik yang Diterapkan
Query FAQ / Prompt Berulang2.850 ms (Inference Penuh)12 ms (In-Memory Hit)99.5% Lebih CepatRedis Response Caching
Time To First Token (TTFT)3.200 ms (Menunggu Selesai)180 ms (Stream Chunk 1)94.3% Lebih ResponsifServer-Sent Events (SSE)
Prompt Context Bloat (20 Turn)4.800 Tokens ($0.038 / call)1.400 Tokens ($0.009 / call)70.8% Penghematan BiayaPrompt Token Pruning & Sliding Window
Tugas Ekstraksi PDF Besar18.400 ms (HTTP Timeout Risk)45 ms (Ack Job Enqueued)Non-Blocking BackgroundAsync Redis Worker Queue
Overhead Handshake Database35 ms / Query Handshake< 1 ms (Socket Reused)97.1% Reduksi LatensiSwoole / PDO Connection Pool

Teknik Response Caching pada AI Agent PHP

Direct Answer:Response Caching menyimpan hasil inferensi model berdasarkan hash deterministik dari task prompt dan skema tools yang diizinkan, sehingga request berulang dapat disajikan secara instan tanpa memanggil API OpenAI kembali.

Berikut adalah implementasi class RedisResponseCache berkecepatan tinggi dengan auto-invalidation TTL:

<?php

namespace App\Optimization;

use Redis;

/**
 * Class RedisResponseCache
 * Mengelola in-memory caching hasil inferensi LLM untuk memangkas latensi prompt berulang.
 */
class RedisResponseCache
{
    private Redis $redis;
    private int $defaultTtl;

    public function __construct(Redis $redis, int $ttlSeconds = 3600)
    {
        $this->redis      = $redis;
        $this->defaultTtl = $ttlSeconds;
    }

    /**
     * Menghasilkan hash SHA-256 identitas unik dari prompt dan konfigurasi tools.
     */
    private function generateKey(string $task, array $tools = []): string
    {
        // Normalisasi teks prompt menjadi lowercase untuk memaksimalkan cache hit ratio
        $payload = strtolower(trim($task)) . '|' . json_encode($tools);
        return 'agent_cache:' . hash('sha256', $payload);
    }

    /**
     * Mengambil respons cache dari Redis (Waktu akses ~12ms).
     */
    public function get(string $task, array $tools = []): ?string
    {
        $key    = $this->generateKey($task, $tools);
        $cached = $this->redis->get($key);

        return $cached !== false ? (string)$cached : null;
    }

    /**
     * Menyimpan respons agen cerdas ke dalam Redis dengan masa kedaluwarsa (TTL).
     */
    public function set(string $task, string $response, array $tools = [], ?int $ttl = null): void
    {
        $key    = $this->generateKey($task, $tools);
        $expiry = $ttl ?? $this->defaultTtl;
        $this->redis->setex($key, $expiry, $response);
    }
}

// ==========================================
// Integrasi ke dalam loop Orchestrator Agen:
// ==========================================
$cache  = new RedisResponseCache($redisClient, ttlSeconds: 1800);
$cached = $cache->get($userPrompt, $availableTools);

if ($cached !== null) {
    // Cache Hit: Segera kembalikan respons tanpa menghubungi API OpenAI
    return $cached . ' [Disajikan dari Redis Cache • 12ms]';
}

// Cache Miss: Jalankan inferensi LLM penuh
$freshResult = $agent->run($userPrompt);
$cache->set($userPrompt, $freshResult, $availableTools);
return $freshResult;

Streaming Response Real-time dengan Server-Sent Events (SSE)

Direct Answer:Server-Sent Events (SSE) mentransmisikan token jawaban secara sekuensial (*chunk-by-chunk*) langsung ke browser segera setelah model LLM menghasilkan kata pertama (TTFT < 200ms) tanpa menunggu seluruh paragraf selesai.

Kode streaming endpoint PHP dengan header anti-buffering Nginx:

<?php
// =========================================================================
// stream.php - Server-Sent Events (SSE) Endpoint untuk AI Agent Response
// =========================================================================

// 1. Header Wajib untuk Streaming HTTP SSE
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache, no-transform');
header('Connection: keep-alive');
header('X-Accel-Buffering: no'); // Wajib: Matikan buffering proxy Nginx

// 2. Nonaktifkan semua layer Output Buffering bawaan PHP
while (ob_get_level() > 0) {
    ob_end_clean();
}

$payload = json_encode([
    'model'    => 'gpt-4o-mini',
    'messages' => [
        ['role' => 'system', 'content' => 'Anda adalah asisten AI responsif.'],
        ['role' => 'user', 'content' => $_POST['prompt'] ?? 'Jelaskan komputasi awan']
    ],
    'stream'   => true, // Mengaktifkan streaming chunked transfer dari OpenAI
]);

$ch = curl_init('https://api.openai.com/v1/chat/completions');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('OPENAI_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_RETURNTRANSFER => false, // Callback write langsung menangani output
    CURLOPT_WRITEFUNCTION  => function ($ch, $chunk) {
        $lines = explode("\n", $chunk);
        foreach ($lines as $line) {
            $line = trim($line);
            // Tangkap baris data transmisi SSE
            if (str_starts_with($line, 'data: ') && $line !== 'data: [DONE]') {
                $json  = json_decode(substr($line, 6), true);
                $delta = $json['choices'][0]['delta']['content'] ?? '';
                if ($delta !== '') {
                    // Dorong token chunk langsung ke socket browser klien
                    echo "data: " . json_encode(['text' => $delta]) . "\n\n";
                    flush();
                }
            }
        }
        return strlen($chunk);
    },
]);

curl_exec($ch);
curl_close($ch);

// Sinyal penutup transmisi streaming SSE
echo "data: [DONE]\n\n";
flush();

Prompt Token Pruning dan Kompresi Konteks AI Agent

Direct Answer:Prompt Token Pruning adalah teknik optimasi komputasi yang memangkas teks berlebih, membuang spasi ganda, dan menerapkan Sliding Window Context Truncation pada riwayat percakapan agar ukuran payload token ke model LLM tetap ramping tanpa mengurangi akurasi penalaran agen.

Berikut adalah implementasi class PromptOptimizer untuk memangkas token prompt dan riwayat percakapan:

<?php

namespace App\Optimization;

class PromptOptimizer
{
    /**
     * Memangkas whitespace berlebih, markdown redundant, dan komentar kosong.
     */
    public static function pruneText(string $rawText): string
    {
        // 1. Konversi multiple newline menjadi maksimal 2 newline
        $clean = preg_replace("/\n{3,}/", "\n\n", $rawText);
        // 2. Hapus spasi horizontal berlebih di setiap baris
        $clean = preg_replace("/[ \t]{2,}/", " ", $clean);
        return trim($clean);
    }

    /**
     * Menerapkan Sliding Window Context Truncation pada riwayat pesan (Chat History).
     * Mempertahankan System Prompt (index 0) dan N turn pesan interaksi terbaru.
     */
    public static function truncateHistory(array $messages, int $maxHistoryTurns = 6): array
    {
        if (count($messages) <= $maxHistoryTurns + 1) {
            return $messages;
        }

        $systemPrompt = $messages[0]; // Selalu pertahankan System Prompt dasar
        $recentTurns  = array_slice($messages, -$maxHistoryTurns);

        return array_merge([$systemPrompt], $recentTurns);
    }

    /**
     * Semantic Chunking: Memotong teks dokumen panjang berdasarkan batas semantik paragraf/kalimat.
     */
    public static function chunkDocument(string $document, int $maxCharsPerChunk = 1500): array
    {
        $paragraphs = explode("\n\n", self::pruneText($document));
        $chunks     = [];
        $current    = "";

        foreach ($paragraphs as $para) {
            if (mb_strlen($current . $para) > $maxCharsPerChunk && !empty($current)) {
                $chunks[] = trim($current);
                $current  = $para . "\n\n";
            } else {
                $current .= $para . "\n\n";
            }
        }

        if (!empty(trim($current))) {
            $chunks[] = trim($current);
        }

        return $chunks;
    }
}

Implementasi Async Queue Redis untuk AI Agent PHP

Direct Answer:Antrean asinkron (Async Queue) memisahkan penerimaan request HTTP dari eksekusi tugas agen berat, membebaskan web server untuk segera mengembalikan Job ID ke klien dalam hitungan milidetik.

Pengiriman job via Redis RPUSH dan pemrosesan worker non-blocking via BLPOP:

<?php

// =========================================================================
// 1. Dispatcher Controller (api/dispatch_task.php) - Respon Instan < 50ms
// =========================================================================
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$jobId   = 'job_' . bin2hex(random_bytes(16));
$payload = json_encode([
    'id'         => $jobId,
    'task'       => $_POST['task'] ?? '',
    'user_id'    => $authenticatedUserId,
    'created_at' => time(),
]);

// Masukkan payload job ke antrean antrean Redis
$redis->rPush('agent:tasks_queue', $payload);

echo json_encode([
    'status'   => 'queued',
    'job_id'   => $jobId,
    'poll_url' => "/api/check_status.php?job_id={$jobId}",
]);

// =========================================================================
// 2. Background Worker Daemon (worker.php) - Dikelola oleh Supervisor
// =========================================================================
while (true) {
    // Tunggu antrean task baru dengan blocking pop (timeout 5 detik)
    $job = $redis->blPop(['agent:tasks_queue'], 5);
    if (!$job) {
        // Bebaskan cycle memory pada perulangan idle
        gc_collect_cycles();
        continue;
    }

    $data   = json_decode($job[1], true);
    $jobId  = $data['id'];
    $task   = $data['task'];

    try {
        // Eksekusi tugas inferensi AI berat di background
        $result = $agent->run($task);
        // Simpan hasil ke Redis dengan masa kedaluwarsa 2 jam
        $redis->setex("agent:result:{$jobId}", 7200, json_encode(['status' => 'completed', 'result' => $result]));
    } catch (Throwable $e) {
        $redis->setex("agent:result:{$jobId}", 7200, json_encode(['status' => 'failed', 'error' => $e->getMessage()]));
    } finally {
        // Garansi pembersihan alokasi RAM per eksekusi task
        gc_collect_cycles();
    }
}

Implementasi Connection Pooling: Swoole PDOPool & PDO Persistence

Direct Answer:Connection Pooling mendaur ulang koneksi database TCP yang sudah terbuka antar request menggunakan coroutine pool Swoole\Database\PDOPool pada server asinkron atau atribut PDO::ATTR_PERSISTENT => true pada runtime PHP-FPM, menghemat 30-50ms overhead handshake per transaksi agen.

Berikut adalah contoh kode implementasi spesifik menggunakan Swoole\Database\PDOPool pada HTTP Server Coroutine dan fallback ke PDO Persistent Connection:

<?php

// =========================================================================
// Opsi A: High-Concurrency Coroutine Server dengan Swoole\Database\PDOPool
// =========================================================================

use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Coroutine;

// 1. Inisialisasi Konfigurasi Driver Database PDO Swoole
$pdoConfig = (new PDOConfig())
    ->withHost(getenv('DB_HOST') ?: '127.0.0.1')
    ->withPort(3306)
    ->withDbName(getenv('DB_NAME') ?: 'ai_agent_db')
    ->withCharset('utf8mb4')
    ->withUsername(getenv('DB_USER') ?: 'root')
    ->withPassword(getenv('DB_PASS') ?: '')
    ->withOptions([
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_TIMEOUT            => 5,
    ]);

// 2. Buat Connection Pool dengan kapasitas maksimal 64 koneksi aktif
$pool = new PDOPool($pdoConfig, 64);

$server = new Server('0.0.0.0', 9501);

$server->on('Request', function (Request $request, Response $response) use ($pool) {
    // Tangkap koneksi dari pool secara non-blocking
    $pdo = $pool->get();

    try {
        // Query riwayat context session agen dalam hitungan mikrodetik
        $stmt = $pdo->prepare('SELECT * FROM agent_sessions WHERE session_id = ? LIMIT 1');
        $stmt->execute([$request->get['session_id'] ?? 'guest']);
        $sessionData = $stmt->fetch();

        $response->header('Content-Type', 'application/json');
        $response->end(json_encode([
            'status'  => 'success',
            'latency' => '<1ms (Connection Pool Reused)',
            'session' => $sessionData,
        ]));
    } catch (Throwable $e) {
        $response->status(500);
        $response->end(json_encode(['error' => $e->getMessage()]));
    } finally {
        // Wajib: Kembalikan koneksi PDO ke dalam pool untuk request coroutine lain
        $pool->put($pdo);
    }
});

// $server->start(); // Jalankan server Swoole
<?php

namespace App\Database;

use PDO;
use PDOException;

/**
 * Opsi B: Persistent Connection Manager untuk Lingkungan Standar PHP-FPM / Nginx
 */
class PersistentConnectionPool
{
    private static ?PDO $instance = null;

    /**
     * Mengembalikan instance PDO Persistent Connection yang di-reuse lintas request worker.
     */
    public static function getConnection(): PDO
    {
        if (self::$instance === null) {
            $dsn = sprintf(
                'mysql:host=%s;dbname=%s;charset=utf8mb4',
                getenv('DB_HOST') ?: '127.0.0.1',
                getenv('DB_NAME') ?: 'ai_agent_db'
            );

            $options = [
                // Mengaktifkan koneksi persisten lintas lifecycle FPM worker
                PDO::ATTR_PERSISTENT         => true,
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES   => false,
                PDO::ATTR_TIMEOUT            => 5,
            ];

            try {
                self::$instance = new PDO(
                    $dsn,
                    getenv('DB_USER') ?: 'root',
                    getenv('DB_PASS') ?: '',
                    $options
                );
            } catch (PDOException $e) {
                throw new \RuntimeException("Database Connection Failed: " . $e->getMessage());
            }
        }

        return self::$instance;
    }
}

FAQ: Optimasi Performa & Latensi AI Agent PHP

1. Faktor apa saja yang paling mempengaruhi latensi eksekusi AI Agent?

Ringkasan Jawaban:Latensi AI Agent dipengaruhi oleh 3 faktor utama: waktu komputasi inferensi LLM (Time To First Token), jumlah iterasi rantai tool (*tool chaining roundtrips*), dan latensi I/O jaringan pada REST API eksternal atau database.

Menerapkan response caching pada prompt statis dan mengeksekusi multi-tools secara paralel menggunakan curl_multi adalah solusi paling efektif untuk memangkas latensi.

2. Kapan sebaiknya menggunakan Response Caching vs Streaming SSE?

Ringkasan Jawaban:Gunakan Response Caching untuk query deterministik atau berulang (seperti pencarian FAQ produk dan ringkasan dokumen) dan gunakan Streaming SSE untuk prompt percakapan dinamis di mana pengguna mengharapkan umpan balik kata-demi-kata secara langsung.

Kombinasi keduanya dapat diterapkan dengan memeriksa cache terlebih dahulu sebelum membuka koneksi streaming.

3. Bagaimana cara mencegah memory leak pada worker Redis yang berjalan terus-menerus?

Ringkasan Jawaban:Unset variabel array besar setelah selesai diproses, panggil gc_collect_cycles() secara manual di akhir setiap perulangan loop, dan atur batas maksimal eksekusi job (misal: restart otomatis setiap 1.000 job) di konfigurasi Supervisor.

Ini menjaga konsumsi RAM worker tetap stabil di bawah 30MB bahkan setelah beroperasi selama berminggu-minggu.

Key Takeaways: Rangkuman Optimasi Performa AI Agent PHP
  • Layered Caching Strategy: Mengurangi beban komputasi LLM hingga 90% dengan Redis In-Memory Response Caching.
  • Real-time User Experience: Memberikan ilusi kecepatan instan kepada pengguna menggunakan Server-Sent Events (SSE) streaming.
  • Prompt Token Pruning: Mengompresi context window dan membersihkan teks berlebih untuk memotong tagihan token API.
  • Asynchronous Processing: Mengisolasi task-task berat ke antrean Redis Queue yang diproses oleh daemon worker Supervisor.
  • Swoole & PDO Pooling: Mengeliminasi latensi koneksi database dengan Swoole\Database\PDOPool dan PDO Persistence.
RA

Rusmawan Abdullah Sani

Lead Software Engineer & System Architect

Praktisi pengembangan backend PHP modern, arsitektur AI Agent, microservices, dan otomasi server Linux. Terhubung melalui profil LinkedIn.