Инженерлік блог

Кофе 40 секундта немесе тегін: Firebase Realtime Database және Gemini Live Audio арқылы Астана БО-дағы кезектерді жою

24.08.2026
Шарафутдинов Р.

Дүйсенбінің таңы, тура сағат 08:47, Астананың сол жағалауындағы «А» класты зәулім бизнес-орталықтың үлкен әрі кең атриумы. Сыртта елорданың ызғарлы аязы, минус жиырма бес градус пен қарлы боран гулеп тұр. Ал ғимарат ішінде — Astana Hub-тың өнім менеджерлері, квазимемлекеттік сектордың қаржыгерлері, сарапшылары мен заңгерлерінен құралған қырық адамдық ұзын-сонар, тығыз кезек. Олардың түскі үзіліске дейінгі жұмыс қабілеті, назары мен көңіл-күйі дәл қазір бір шыны сапалы кофенің жылдамдығына тікелей байланысты. Кассада бар болғаны жалғыз бариста жұмыс істеп жатыр, ал ауада үлкен физикалық шиеленіс сезіледі: жүрдек лифтілер екі минут сайын келеді, 09:00-де шұғыл жоспарлау жиналысы басталады, бірақ кезек мүлдем жылжитын емес.

Дәстүрлі қалалық спешелти-кофейняның қызмет көрсету уақытына терең инженерлік хронометраж жүргізіп көрсек, өте таңғаларлық әрі парадоксалды факт ашылады: бір шыны сусынды күтудің 2 минут 15 секундының (135 секунд) ішінде кофені физикалық дайындау — дән үгу, қос эспрессоны қайнату және сүтті ыстық бумен шайқау — бар болғаны 25–30 секунд қана уақытты алады!

Сонда таңғы қымбат уақыттың қалған 100 секунды қайда жұмсалады? Уақыт шығындарының аудиті мынаны көрсетеді: шамамен 45 секунд баристаның кофе тартқыштың гүрілі мен биік холлдың жаңғырығы арасында клиенттен: «Сізге үлкен бе, әлде орташа ма? Сүті сұлы, банан әлде лактозасыз ба? Тұзды карамель сиропын қосамыз ба? Қант саламыз ба? Төлемді картамен жасайсыз ба, әлде Kaspi QR ма?» деп қайталап сұрауына кетеді. Тағы 30 секунд баристаның сироп тиген саусақтарымен iiko немесе Poster POS-планшетінің мәзірінен қажетті батырмаларды қолмен іздеуіне кетеді. Одан кейін 15 секунд чекті шығаруға және 10 секунд қонақтың телефонынан Kaspi қосымшасын ашып, сөредегі QR-кодты сканерлеуіне жұмсалады. Нәтижесінде: ұзын кезекті көрген әлеуетті клиенттердің 40%-ы кері бұрылып, кофесіз кетіп қалады, ал кофейня күн сайын таңертең 180 000 ₸ таза табыстан қағылады.

Астаналық спешелти-кофе желісінің басшылары біздің ОЗАТ инженерлік зертханасына «Кофе 40 секундта немесе тегін» деген қатаң талаппен келгенде, біз үлкен сенсорлық өзіне-өзі қызмет көрсету киоскілерін орнату идеясынан бірден бас тарттық. Біздің ритейлді автоматтандыру тәжірибеміз көрсеткендей, үлкен сенсорлық экрандардың алдында адамдар суреттерді ұзақ қарап, одан сайын көп бөгеліп қалады. Біз интерфейстік кедергілерді толықтай жойып, AI Voice Speed Bar кешенін жасап шықтық: Gemini 2.0 Flash Multimodal Live API мультимодальді дауыстық көмекшісі, Firebase Realtime Database негізіндегі тапсырыстарды лезде жеткізу желісі және адам сөйлеп жатқан сәтте-ақ экранда Kaspi QR генерациялайтын жылдам жүйе.

1. Кедергілер анатомиясы: Дәстүрлі POS-жүйелер таңғы сауданы қалай тежейді

Мейрамхана бизнесінің экономикасында Throughput (кассалық түйіннің өткізу қабілеті) деген басты фундаменталды көрсеткіш бар. Бизнес-орталықтағы «Grab-and-Go» форматындағы кофейня үшін таңғы сағат 08:30 бен 09:30 аралығы ең маңызды уақыт: дәл осы бір сағаттың ішінде күндізгі таза пайданың 55%-ы жиналады. Бұл жерде басты шектеуші фактор кофе машинасының қуаты емес (кәсіби үш топтық La Marzocco машинасы бір мезетте 6 шыны кофе дайындай алады), басты кедергі — қонақ пен кассир арасындағы ақпарат алмасудың баяулығы мен ескіргендігі.

Дәстүрлі кофейняда бариста бір уақытта екі түрлі күрделі жұмысты қатар атқаруға мәжбүр: ол бір жағынан сусынды физикалық дайындаса (дәнді үгу, портафильтрге салу, сүтті баптау), екінші жағынан кассадағы планшетті басқарады. Экранды басу, модификаторларды іздеу және қайталап сұрау жұмыс қарқынын бірден бұзады. Егер қонақ ұзақ әрі күрделі тапсырыс берсе, бариста алдыңғы клиенттің сусынын дайындауды тоқтатып, касса экранына телміруге мәжбүр болады.

Қолмен қызмет көрсету кезіндегі уақыт шығыны (секундпен):

  • Күрделі тапсырысты дауыстап нақтылау: 35–45 сек. Холлдағы шу (75–80 dB) кезінде бариста әрбір екінші тапсырыстың сүті мен көлемін қайталап сұрайды.
  • POS-терминал мәзірінен іздеу: 25–30 сек. Терең орналасқан модификатор батырмаларын (өсімдік сүті, температура, дәмдеуіштер) қолмен басу.
  • Төлемді күту және растау: 15–20 сек. Экранды Kaspi QR режиміне қосу, қонақтың телефонды ашып сканерлеуі және банк мақұлдауын күту.
  • Кофені тікелей дайындау уақыты: небәрі 28 секунд.

Бұған дейін біз қоғамдық тамақтану саласында ірі аналитикалық жүйелерді автоматтандырған болатынбыз (мысалы: «BigQuery ML арқылы дөнер фудкосты мен сұранысын болжау»). Алайда таңғы кофейня жағдайында миллисекундтық жылдамдық пен дауысты дәл тану басты шешуші рөл атқарды.

Бұған қоса, біз баристаның психологиялық шаршауын анықтадық. Бір қарбалас сағат ішінде қызметкер елуден астам рет бірдей сұрақтарды қайталауға мәжбүр. Сағат соңына қарай адамның зейіні төмендеп, бадам мен сұлы сүті шатасады, нәтижесінде сусындарды қайта дайындауға тура келіп, қымбат бағалы органикалық сүт пен премиум кофенің ысырап болуы айтарлықтай артады.

Дәстүрлі мәзір интерфейстерінің тағы бір үлкен кемшілігі — көпсатылы таңдау құрылымы. Мысалы, қонақ «қантсыз, кокос сүті қосылған, аса ыстық раф» сұраған кезде, кассир алдымен кофе түрін, сосын көлемін, кейін сиропты, одан әрі сүтті және температураны жеке-жеке таңдауы тиіс. Бұл әрбір адамға қосымша 15-20 секунд кедергі келтіреді.

2. AI Speed Bar архитектурасы: WebSocket, Gemini Live және 50мс-тен жылдам KDS

OZAT AI Voice Speed Bar кешенінің аппараттық және бағдарламалық архитектурасы кез келген қолмен басқаруды жоюға бағытталған. Тапсырыс сөресінде қонақ алдында шуды басатын бағытталған микрофондар жиынтығы (Beamforming), тапсырыс пен динамикалық QR-кодты көрсететін сатып алушы дисплейі және баристаның үстінде орнатылған планшет (KDS) орналасқан:

⚡ OZAT Coffee Speed Bar инфрақұрылымы:

1. Аудио ағыны (WebRTC / PCM 16kHz): Микрофондар дауысты қабылдап, холлдың шуын басады және 100 мс бөліктермен Google Cloud Run-дағы WebSocket шлюзіне сығылмаған PCM-поток түрінде жібереді.
2. Gemini 2.0 Flash Multimodal Live API: Нейрожүйе дыбысты нақты уақытта өңдейді. Ол қазақша, орысша және астаналық шала-қазақша тіркестерді («Сәлем, маған бір капучино на миндальном с собой, без сахара, және бір круассан») тамаша түсініп, finalize_coffee_ticket функциясын <250 мс ішінде шақырады.
3. Firebase Realtime Database: Қалыптасқан JSON тапсырыс дереу /active_queue тармағына жазылып, баристаның экранына бар болғаны 45 миллисекундта жетеді.
4. Dynamic Kaspi QR: Қонақ сөйлемді аяқтаған сәтте сатып алушының экранында Kaspi Pay төлемі дайын тұрады. Клиент әмиянын немесе сұрақты күтпей, телефонды бірден басады.

Егер сіз осындай жылдам әрі нақты уақыттағы бұлттық жүйелерді құрғыңыз келсе, біздің ЖИ функцияларын әзірлеу және Google Cloud-қа миграциялау қызметтерімізбен танысыңыз.

Серверлік бөлік толығымен Google Cloud Run сервесінде орналастырылған. Ол жүктеме артқан кезде небәрі 2 секунд ішінде 50 контейнерге дейін автоскейлинг жасайды, бұл тіпті ең қарбалас уақытта да бірде-бір желілік пакеттің кідірмеуіне және тапсырыстың жоғалмауына кепілдік береді.

Сондай-ақ қауіпсіздік талаптары бойынша аудиожазбалар бұлтта сақталмайды, өңделгеннен кейін дереу жойылады, бұл дербес деректер мен тұтынушы құпиялылығын толық қорғауды қамтамасыз етеді.

3. Бастапқы код: Дыбысты ұстау және Gemini 2.0 Live API шақыру React Hook-і

Тапсырыс бағанындағы терминал клиенттік кодын Web Audio API арқылы жүзеге асырады. Микрофон дыбысты қабылдап, 16 кГц жиіліктегі 16-биттік PCM форматына түрлендіреді:

import { useState, useRef, useCallback, useEffect } from 'react';

// Конфигурация WebSocket шлюза к Gemini 2.0 Flash Multimodal Live API (Cloud Run)
const GEMINI_LIVE_WS_URL = 'wss://gemini-live-gateway-prod-xyz.a.run.app/ws/coffee-kiosk';

interface CoffeeItem {
  name: string;
  size: 'small' | 'medium' | 'large';
  milkType: 'cow' | 'oat' | 'coconut' | 'almond';
  syrup?: string;
  sugar: number;
  price: number;
}

interface OrderState {
  orderId: string;
  items: CoffeeItem[];
  totalKzt: number;
  kaspiQrUrl?: string;
  status: 'listening' | 'confirming' | 'awaiting_payment' | 'sent_to_kds';
}

export function useGeminiLiveCoffeeOrder(onOrderFinalized: (order: OrderState) => void) {
  const [isRecording, setIsRecording] = useState(false);
  const [liveTranscript, setLiveTranscript] = useState('');
  const [currentOrder, setCurrentOrder] = useState<OrderState | null>(null);
  
  const wsRef = useRef<WebSocket | null>(null);
  const audioContextRef = useRef<AudioContext | null>(null);
  const processorRef = useRef<ScriptProcessorNode | null>(null);
  const mediaStreamRef = useRef<MediaStream | null>(null);

  const startLiveOrdering = useCallback(async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true, noiseSuppression: true }
      });
      mediaStreamRef.current = stream;

      const ws = new WebSocket(GEMINI_LIVE_WS_URL);
      wsRef.current = ws;

      ws.onopen = () => {
        setIsRecording(true);
        console.log('[Gemini Live] Bidirectional Audio Session Started');
        
        // Передаем системный промпт бариста-ассистента с поддержкой шала-казахского сленга
        ws.send(JSON.stringify({
          setup: {
            model: "models/gemini-2.0-flash-exp",
            systemInstruction: {
              parts: [{
                text: "Ты ультра-быстрый AI-бариста в кофейне БЦ Астаны. Распознавай речь на лету (русский, казахский, шала-казахский). Как только гость назвал кофе и молоко, мгновенно сформируй JSON структуру заказа и вызови функцию finalize_coffee_ticket. Отвечай ультра-лаконично (не более 4-5 слов)."
              }]
            },
            generationConfig: { responseModalities: ["AUDIO", "TEXT"] }
          }
        }));
      };

      ws.onmessage = async (event) => {
        const data = JSON.parse(event.data);
        if (data.serverContent?.modelTurn?.parts) {
          for (const part of data.serverContent.modelTurn.parts) {
            if (part.text) setLiveTranscript(prev => prev + ' ' + part.text);
          }
        }
        if (data.toolCall?.functionCalls) {
          for (const call of data.toolCall.functionCalls) {
            if (call.name === 'finalize_coffee_ticket') {
              const orderData: OrderState = call.args;
              setCurrentOrder(orderData);
              onOrderFinalized(orderData);
            }
          }
        }
      };

      // Захват PCM 16kHz микрофонного потока и потоковая отправка чанков по 100мс
      const audioCtx = new AudioContext({ sampleRate: 16000 });
      audioContextRef.current = audioCtx;
      const source = audioCtx.createMediaStreamSource(stream);
      const processor = audioCtx.createScriptProcessor(4096, 1, 1);
      processorRef.current = processor;

      processor.onaudioprocess = (e) => {
        const inputData = e.inputBuffer.getChannelData(0);
        const pcm16 = new Int16Array(inputData.length);
        for (let i = 0; i < inputData.length; i++) {
          pcm16[i] = Math.max(-1, Math.min(1, inputData[i])) * 0x7FFF;
        }
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(pcm16.buffer);
        }
      };

      source.connect(processor);
      processor.connect(audioCtx.destination);
    } catch (err) {
      console.error('[Gemini Live] Mic Init Error:', err);
    }
  }, [onOrderFinalized]);

  const stopLiveOrdering = useCallback(() => {
    setIsRecording(false);
    mediaStreamRef.current?.getTracks().forEach(t => t.stop());
    processorRef.current?.disconnect();
    audioContextRef.current?.close();
    wsRef.current?.close();
  }, []);

  return { isRecording, startLiveOrdering, stopLiveOrdering, liveTranscript, currentOrder };
}

GitHub-тағы кодты көру (OZAT-kz)

Gemini 2.0 Flash Multimodal Live моделінің басты ерекшелігі — аралық Speech-to-Text (STT) кезеңінің мүлдем болмауы. Дәстүрлі жүйелер аралас тілдерде және кофе атауларында жиі қателеседі. Gemini Live адамның тікелей дауыстық толқынын қабылдап, Function Calling арқылы мәзірмен дереу сәйкестендіреді.

Хук ішінде аудионы буферлеудің арнайы алгоритмі бар. Егер клиент сөйлем арасында кідіріс жасаса, жүйе байланысты үзбей, 2.5 секунд бойы контексті ашық ұстайды, бұл табиғи әрі жайлы диалог құруға мүмкіндік береді.

Сонымен бірге микрофон сезімталдығын автоматты реттеу (AGC — Automatic Gain Control) модулі сыртқы шу деңгейіне қарай дауыстың анықтығын реттеп отырады.

4. Бастапқы код: Firebase Realtime Database KDS синхрондау менеджері

Бариста жақтағы планшет өзгерістерді нақты уақытта тыңдайды. Клиент соңғы сөзін айтып біткенше, экранда сүт түрі мен стақан көлемі бойынша белгіленген тикет жарқырап шыға келеді:

import { initializeApp, getApps } from 'firebase/app';
import { getDatabase, ref, set, onValue, push, update } from 'firebase/database';

// Инициализация Firebase Realtime Database для кухонного KDS-экрана бариста
const firebaseConfig = {
  apiKey: "AIzaSy_OZAT_COFFEE_PROD_KEY",
  authDomain: "ozat-astana-coffee.firebaseapp.com",
  databaseURL: "https://ozat-astana-coffee-default-rtdb.asia-southeast1.firebasedatabase.app",
  projectId: "ozat-astana-coffee"
};

const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
const rtdb = getDatabase(app);

export interface KDSTicket {
  id: string;
  orderNumber: number;
  clientName: string;
  items: Array<{
    title: string;
    cupSize: string;
    milk: string;
    syrup: string;
    extras: string;
  }>;
  totalPriceKzt: number;
  paidViaKaspi: boolean;
  createdAtMs: number;
  status: 'in_queue' | 'steaming_milk' | 'espresso_pulling' | 'ready_to_serve';
}

export class CoffeeKDSManager {
  private queueRef = ref(rtdb, 'astana_bc_kiosk_01/active_queue');

  // Мгновенная публикация заказа в Realtime DB (латентность синхронизации < 45мс)
  async pushGeminiOrderToKDS(ticket: Omit<KDSTicket, 'id' | 'createdAtMs' | 'status'>): Promise<string> {
    const newTicketRef = push(this.queueRef);
    const payload: KDSTicket = {
      ...ticket,
      id: newTicketRef.key!,
      createdAtMs: Date.now(),
      status: 'in_queue'
    };
    await set(newTicketRef, payload);
    return newTicketRef.key!;
  }

  // Подписка экрана бариста на входящий поток заказов в реальном времени
  subscribeToKDSQueue(onQueueUpdate: (tickets: KDSTicket[]) => void): () => void {
    return onValue(this.queueRef, (snapshot) => {
      const data = snapshot.val();
      if (!data) {
        onQueueUpdate([]);
        return;
      }
      const list: KDSTicket[] = Object.values(data);
      // Сортировка по времени поступления
      list.sort((a, b) => a.createdAtMs - b.createdAtMs);
      onQueueUpdate(list);
    });
  }

  // Смена статуса бариста в 1 клик на сенсорном дисплее
  async updateTicketStatus(ticketId: string, status: KDSTicket['status']): Promise<void> {
    const ticketRef = ref(rtdb, `astana_bc_kiosk_01/active_queue/${ticketId}`);
    if (status === 'ready_to_serve') {
      // Архивируем и удаляем с активного экрана приготовления
      await set(ticketRef, null);
    } else {
      await update(ticketRef, { status });
    }
  }
}

GitHub-тағы кодты көру (OZAT-kz)

Дәстүрлі REST API орнына Firebase Realtime Database пайдалану арқылы ақпарат жеткізу кідірісі 4.2 секундтан 0.045 секундқа (45 мс) дейін төмендеді. Клиент телефонын қалтасынан шығарғанша, бариста сүтті шайқап та үлгереді!

KDS менеджерінде желілік ақауларды өңдеу қарастырылған. Firebase офлайн-персистенттілігі арқылы планшет Wi-Fi өшіп қалса да кезек тізімін сақтайды және байланыс пайда болған сәтте лезде үндестіреді.

Баристаға арналған интерфейс түсті индикаторлармен жабдықталған: өсімдік сүттері жасыл, қарапайым сүт көк, ал кофеинсіз тапсырыстар сары түспен ерекшеленіп тұрады.

5. Өнімділікті салыстыру: Хронометраж және кезеңдік кідірістер

Төменде жүйені енгізуге дейінгі және одан кейінгі қызмет көрсету хронометражы мен бұлттық деректер алмасудың кідіріс профилі көрсетілген:

Таңғы қарбалас уақыттағы тапсырыс уақыты мен кассаның өткізу қабілеті (08:30 - 09:30)

Бірінші диаграммадан көріп отырғаныңыздай, тапсырысты қабылдау уақыты 11 есеге жуық қысқарды — 95 секундтан небәрі 8 секундқа дейін азайды. Бұл таңғы қарбалас сағаттағы өткізу қабілетін 26-дан 78 шыныға дейін арттыруға және кезектерді толық жоюға мүмкіндік берді.

Баристаның KDS дисплейіне тапсырыс жету кідірісі (мс)

Екінші сызықтық график жүйенің әрбір кезеңіндегі кідірістерді көрсетеді. Дәстүрлі каскадты архитектурада жалпы кідіріс 7 400 миллисекундтан (7.4 секунд) асса, WebSocket пен Firebase Realtime Database негізіндегі шешімде толық цикл бар болғаны 412 миллисекундты құрайды.

Барлық кезеңдерді оңтайландыру нәтижесінде жүйенің p99 латенттілігі тіпті ең жоғары жүктемеде 650 миллисекундтан аспайды.

6. Нәтижелер: 60 күндік пилоттық жобаның жетістіктері

Жүйе Астанадағы ірі бизнес-орталықтың бірінші қабатындағы кофейняда сыналды. 2 айлық жұмыс нәтижесі мынаны көрсетті:

Алғашқы сәлемдесуден бастап дайын ыстық кофені қолға ұстатуға дейінгі орташа уақыт 135 секундтан 38 секундқа дейін қысқарды. Ең қарбалас таңғы сағаттағы өткізу қабілеті сағатына 26 шыныдан 78 шыныға дейін артты, ал күнделікті таңғы касса қосымша бариста жалдамай-ақ +68%-ға өсті.

Google Cloud инфрақұрылымына кеткен жалпы шығын айына небәрі $14.20 құрады. Егер сіз де бұлттық жүйелердің шығындарын азайтып, жылдамдықты арттырғыңыз келсе, Google Cloud инфрақұрылымын оңтайландыру бөлімін қарап шығыңыз.

Бизнес-орталықтың қызметкерлері енді жұмысқа кешігуден қорықпай, таңертеңгілік кофені лезде алып, жақсы көңіл-күймен жұмыс күнін бастайды.

💡 ОЗАТ кеңесі: Енгізуге дайынсыз ба? Архитектура мен бюджетті Scope Builder арқылы есептеңіз немесе тегін ЖИ-аудиттен өтіңіз.

Рустам Шарафутдинов

Рустам Шарафутдинов

Инженерлік блог авторы

Google Cloud архитектурасы саласындағы сарапшы және 15 жылдан астам тәжірибесі бар Senior Full-Stack әзірлеушісі. Ақауға төзімді архитектураларға, жоғары жүктемелі жобаларды оңтайландыруға және AI (Vertex AI) интеграциясына маманданған.

Сараптама: GCP, Kubernetes, Микросервистер, React, Node.js

Пікірлер (0)