Кофе 40 секундта немесе тегін: Firebase Realtime Database және Gemini Live Audio арқылы Астана БО-дағы кезектерді жою
Дүйсенбі таңы, 08:45, Астананың сол жағалауындағы «А» класты бизнес-орталығының орталық атриумы. Сыртта минус жиырма сегіз градус қақаған аяз бен астаналық дүлей боран, ал іште — ұлттық компаниялардың жүздеген қызметкерлері, финтех-әзірлеушілер мен корпоративтік заңгерлердің үзіліссіз ағыны. Кіреберіс аймағы қадамдардың дыбысына, қысқы киімдердің сыбдырына, зәулім шыны күмбездің жаңғырығына (76–82 dB SPL акустикалық шу деңгейі) және кофе тартқыштардың гүріліне толы. Grab-and-Go форматындағы жалғыз аралдық спешелти-кофехананың алдында 25–30 адамдық ұзын-сонар кезек түзіледі. Жеделсатылар әр екі минут сайын адамдарды топ-тобымен тасымалдайды, таңғы стендаптар тура 09:00-де басталады, ал кезек тасбақаның жылдамдығымен жылжиды.
Егер баристаның жұмысына секундомермен детальді инженерлік талдау жүргізсек, өткізу қабілетінің классикалық парадоксы (Throughput bottleneck) анықталады: бір клиентке қызмет көрсетудің жалпы 135 секундтық циклінен сусынды тікелей физикалық дайындау (дәнді тарту, холдерде эспрессоны экстракциялау және сүтті бумен көпірту) небәрі 26–30 секундты алады!
Қалған 100+ секунд қайда кетеді? Уақыт шығындарының құрылымы мынадай:
- Акустикалық кедергі және тапсырысты ауызша нақтылау: 35–45 сек. Бариста холлдағы шудың кесірінен дауысты әрең естіп, 4-ке жуық сұрақ қоюға мәжбүр: «Сізге кәдімгі ме, сұлы ма, әлде лактозасыз сүт пе? Шәрбат қосамыз ба? Қант? Өзіңізбен бе, әлде осында ма?».
- Касса интерфейсіндегі навигация (POS): 25–30 сек. Резеңке қолғап киген қолдармен iiko/Poster сенсорлық экранының күрделі мәзірінен модификаторларды іздеу.
- Төлем жасау транзакциясы: 15–20 сек. POS экранын төлем режиміне ауыстыру, пречекті басып шығару, клиенттің смартфонды бұғаттан шығарып, Kaspi қосымшасы арқылы үстелдегі статикалық QR-кодты сканерлеуін күту.
Нәтиже: кезектегі 15+ адамды көрген әлеуетті сатып алушылардың 38%-ға жуығы кофе алмай, кері бұрылып жұмыс орнына кетіп қалады. Нүкте таңғы бір сағаттың ішінде 160 000 – 190 000 ₸ таза түсімнен қағылады.
Желі иелері ОЗАТ инженерлік зертханасына жүгінген кезде, біз бірден дәстүрлі үлкен сенсорлық дүңгіршектерді (киоскілерді) орнатудан бас тарттық. Біздің бұрынғы тәжірибеміз көрсеткендей, асығыс адамдар сенсорлық экран алдында көпдеңгейлі каталогтарды парақтап, 80 секундқа дейін уақыт жоғалтады. Шешім ретінде AI Voice Speed Bar кешені жасалды: бағытталған микрофондық массив (Beamforming), Google Cloud Run-дағы Gemini 2.5 Flash Multimodal Live API (Bidirectional WebSocket) дауыстық шлюзі, Firebase Realtime Database негізіндегі тикеттер шинасы және динамикалық Kaspi QR жасау жүйесі.
1. Аппараттық-бағдарламалық контур және шуды басу (Acoustic Pipeline)
Қоғамдық кеңістіктерде сөйлеу ЖИ-ін енгізу кезіндегі басты кедергі — сигналдың акустикалық деградациясы (Cocktail Party Problem). 80 dB фонолық шу кезінде стандартты микрофон жақын тұрған адамдардың дауысын және кофе тартқыштың шуын қоса жазып алады, бұл қате транскрипцияларға алып келеді.
🎙️ Дыбысты ұстаудың аппараттық тракті:
- ReSpeaker 4-Mic Array микрофон массиві: Тек стойканың алдында тұрған клиенттің бетіне 45° апертурамен бағытталған 4 сандық MEMS-микрофон (Beamforming).
- Client-side WebRTC DSP & VAD: Аппараттық жаңғырықты басу (AEC), динамикалық Noise Gate (-42 dBFS) және терминал браузеріндегі AudioWorklet негізіндегі Voice Activity Detection (Silero VAD).
- Екіжақты WebSocket шлюзі (Cloud Run Gateway): Терминал Cloud Run-дағы қауіпсіз бэкендке WSS арқылы қосылады. Бэкенд TLS-аутентификацияны басқарады, GCP кілтін сақтайды және Gemini 2.5 Flash Live API-ге 16 кГц, 16-бит моно PCM ағынын 100 мс қадаммен жіберіп отырады.
Егер сізді нақты уақыттағы жоғары жүктемелі бұлттық микросервистерді құру қызықтырса, біздің ақылды сервистер мен ЖИ әзірлеу және Google Cloud-қа көшу қызметтерімізбен танысыңыз.
2. Аралас тілді түсіну және үзу механизмі (Barge-in Handling)
Астана тұрғындарының сөйлеу тілі — бұл қазақша, орысша және шала-қазақшаның динамикалық қоспасы (код-свитчинг). Тұтынушы: «Сәлем! Маған бір капучино на овсяном, үлкен болсын, без сахара, и шоколадный макарон. Қанша болады?» деп еркін тапсырыс бере алады.
Дәстүрлі Whisper STT -> LLM -> TTS каскадты жүйелері 3.5-тен 6.2 секундқа дейін кідіріс тудырып, қазақ тілінің морфемаларында қателеседі. Gemini 2.5 Flash Multimodal Live API аудио ағынын тікелей өңдеп, аралық мәтіндік кезеңсіз лезде құралдарды шақыруды (Tool Calling) жүзеге асырады.
Barge-in сәулеттік паттерні: егер бот жауап бере бастаған кезде тұтынушы оның сөзін бөліп: «Ой, жоқ, шәрбат қоспай-ақ қойыңызшы!» десе, клиенттік VAD дыбысты тіркеп, дыбысты ойнатуды дереу үзеді де, серверге clientContent.interrupted = true сигналын жібереді.
⚡ Сәулеттік ерекшеліктер: Cloud Run WebSockets & Payment State Machine
- WebSocket Timeouts: Cloud Run 60 минутқа дейінгі тұрақты байланысты қолдайды. Node.js шлюзі әр 25 секунд сайын
ping/pongтексеріп, желі үзілгенде сессия контексін автоматты түрде қалпына келтіреді. - Бағаны қатаң серверлік есептеу: LLM тек тапсырыс детальдарын құрылымдайды. Қорытынды бағаны Firestore деректері негізінде сервер есептейді (prompt injection арқылы бағаны өзгерту қаупі жойылған).
- Төлем State Machine: Төлем мәртебесі қатаң жүйеленген:
CREATED → PENDING_KASPI → AUTHORIZED → SETTLED → FULFILLED. - Кідіріс профилі: $p50 = 380 ext{ ms}$, $p95 = 640 ext{ ms}$, $p99 = 1120 ext{ ms}$. Бұлттық шығын айына $16.40.
3. Бастапқы код: AudioWorklet және Gemini Live сессиясын басқару хугі
Қауіпсіздіктің негізгі қағидасы: клиенттік терминал мен LLM ешқашан бағаны өздігінен есептемейді. Модель тек сұралған позицияларды жинайды, ал сенімді бэкенд қойманы тексеріп, прайс бойынша чекті есептейді және криптографиялық orderId береді:
import { useState, useRef, useCallback } from 'react';
const GATEWAY_WS_URL = 'wss://gemini-live-gateway.ozat.kz/v1/kiosk-session';
export type PaymentStatus = 'pending' | 'qr_emitted' | 'authorized' | 'captured' | 'failed' | 'refunded';
export interface ValidatedOrderItem {
sku: string;
name: string;
size: 'regular' | 'large';
milk: 'standard' | 'oat' | 'coconut' | 'lactose_free';
syrup?: string;
temperature: 'hot' | 'iced';
unitPriceKzt: number;
}
export interface KioskOrder {
orderId: string;
items: ValidatedOrderItem[];
totalKzt: number;
paymentPayloadKaspi: string;
paymentState: PaymentStatus;
status: 'draft' | 'validated' | 'paid' | 'pushed_to_kds';
}
export function useGeminiLiveKiosk(onOrderReady: (order: KioskOrder) => Promise<void>) {
const [sessionActive, setSessionActive] = useState(false);
const [currentOrder, setCurrentOrder] = useState<KioskOrder | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const audioCtxRef = useRef<AudioContext | null>(null);
const workletNodeRef = useRef<AudioWorkletNode | null>(null);
const mediaStreamRef = useRef<MediaStream | null>(null);
const startSession = useCallback(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: 1,
sampleRate: 16000,
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
mediaStreamRef.current = stream;
const ws = new WebSocket(GATEWAY_WS_URL);
wsRef.current = ws;
ws.onopen = () => {
setSessionActive(true);
ws.send(JSON.stringify({
type: 'session.setup',
model: 'models/gemini-2.5-flash',
generationConfig: {
temperature: 0.1,
responseModalities: ['AUDIO', 'TEXT']
},
systemInstruction: {
parts: [{
text: "Сен ОЗАТ Coffee Bar AI-көмекшісісің. Тез әрі нақты жұмыс істе. Қазақша, орысша немесе аралас сөйлегенді табиғи түсін. Клиент кофе түрі мен сүтін айтқан бойда request_order_validation құралын шақыр. Бағаны өзің ойлап таппа."
}]
},
tools: [{
functionDeclarations: [{
name: 'request_order_validation',
description: 'Қоймадағы қалдық бойынша тапсырыс құрамын тексереді және бағасын есептеуге жібереді',
parameters: {
type: 'OBJECT',
properties: {
requestedItems: {
type: 'ARRAY',
items: {
type: 'OBJECT',
properties: {
sku: { type: 'STRING' },
name: { type: 'STRING' },
size: { type: 'STRING', enum: ['regular', 'large'] },
milk: { type: 'STRING', enum: ['standard', 'oat', 'coconut', 'lactose_free'] },
syrup: { type: 'STRING' },
temperature: { type: 'STRING', enum: ['hot', 'iced'] }
},
required: ['sku', 'name', 'size', 'milk']
}
}
},
required: ['requestedItems']
}
}]
}]
}));
};
ws.onmessage = async (evt) => {
const msg = JSON.parse(evt.data);
if (msg.toolCall?.functionCalls) {
for (const call of msg.toolCall.functionCalls) {
if (call.name === 'request_order_validation') {
const res = await fetch('/api/v1/orders/validate-and-price', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: call.args.requestedItems })
});
const validatedPayload: KioskOrder = await res.json();
setCurrentOrder(validatedPayload);
await onOrderReady(validatedPayload);
ws.send(JSON.stringify({
type: 'tool_response',
toolResponses: [{
response: {
output: {
success: true,
orderId: validatedPayload.orderId,
totalKzt: validatedPayload.totalKzt,
currency: 'KZT'
}
},
id: call.id
}]
}));
}
}
}
};
const audioCtx = new AudioContext({ sampleRate: 16000 });
audioCtxRef.current = audioCtx;
const source = audioCtx.createMediaStreamSource(stream);
await audioCtx.audioWorklet.addModule('/audio-stream-processor.js');
const workletNode = new AudioWorkletNode(audioCtx, 'audio-stream-processor');
workletNodeRef.current = workletNode;
workletNode.port.onmessage = (e) => {
if (ws.readyState === WebSocket.OPEN && e.data.pcm16Chunk) {
ws.send(e.data.pcm16Chunk);
}
};
source.connect(workletNode);
} catch (err) {
console.error('[Gemini Live Gateway] Init error:', err);
setSessionActive(false);
}
}, [onOrderReady]);
const stopSession = useCallback(() => {
setSessionActive(false);
mediaStreamRef.current?.getTracks().forEach(t => t.stop());
workletNodeRef.current?.disconnect();
audioCtxRef.current?.close();
wsRef.current?.close();
}, []);
return { sessionActive, startSession, stopSession, currentOrder };
}Вирустық Reels-ті AdSense арқылы монетизациялау («Күңгірт» схемаларсыз ақ арбитраж)
4. Асханалық тикеттер шинасы: Firebase Realtime Database негізіндегі KDS-менеджер
Бариста экраны үшін деректердің кідіріссіз синхрондалуы шешуші мәнге ие. Төменде Firebase Realtime Database транзакцияларымен жұмыс істейтін KDS менеджерінің коды келтірілген (архивке көшіру детерминирленген get() арқылы жасалған):
import { initializeApp, getApps } from 'firebase/app';
import { getDatabase, ref, set, get, onValue, push, runTransaction, update } from 'firebase/database';
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 type PaymentState = 'pending' | 'qr_emitted' | 'authorized' | 'captured' | 'failed' | 'refunded';
export interface KDSTicket {
id: string;
orderNumber: number;
items: Array<{
name: string;
size: string;
milk: string;
syrup?: string;
}>;
totalKzt: number;
paymentState: PaymentState;
paymentRefKaspi?: string;
status: 'pending' | 'in_progress' | 'completed' | 'canceled';
createdAt: number;
}
export class CoffeeKDSManager {
private queueRef = ref(rtdb, 'kiosks/astana_bc_01/queue');
async pushTicket(order: Omit<KDSTicket, 'id' | 'orderNumber' | 'createdAt' | 'status'>): Promise<string> {
const counterRef = ref(rtdb, 'kiosks/astana_bc_01/daily_counter');
let generatedOrderNum = 1;
await runTransaction(counterRef, (currentVal) => {
generatedOrderNum = (currentVal || 0) + 1;
return generatedOrderNum;
});
const newTicketRef = push(this.queueRef);
const ticketPayload: KDSTicket = {
...order,
id: newTicketRef.key!,
orderNumber: generatedOrderNum,
status: 'pending',
createdAt: Date.now()
};
await set(newTicketRef, ticketPayload);
return newTicketRef.key!;
}
subscribeQueue(onUpdate: (tickets: KDSTicket[]) => void): () => void {
return onValue(this.queueRef, (snapshot) => {
const data = snapshot.val();
if (!data) {
onUpdate([]);
return;
}
const list: KDSTicket[] = Object.values(data);
list.sort((a, b) => a.createdAt - b.createdAt);
onUpdate(list);
});
}
async completeAndArchive(ticketId: string): Promise<void> {
const itemRef = ref(rtdb, `kiosks/astana_bc_01/queue/${ticketId}`);
const archiveRef = ref(rtdb, `kiosks/astana_bc_01/archive/${ticketId}`);
const snapshot = await get(itemRef);
if (snapshot.exists()) {
const ticketData = snapshot.val();
await set(archiveRef, {
...ticketData,
status: 'completed',
completedAt: Date.now()
});
await set(itemRef, null);
}
}
async updatePaymentState(ticketId: string, paymentState: PaymentState, paymentRefKaspi?: string): Promise<void> {
const itemRef = ref(rtdb, `kiosks/astana_bc_01/queue/${ticketId}`);
await update(itemRef, {
paymentState,
...(paymentRefKaspi ? { paymentRefKaspi } : {})
});
}
}GitHub-та кодты қарау (OZAT-kz)
5. Өнімділіктің нақты метрикалары: Кідіріс перцентильдері, N іріктемесі және Fallback тетіктері
Барлық метрикалар Cloud Logging және терминал жазбалары негізінде 60 күндік пилот бойынша жиналды (жалпы іріктеме N = 4 218 аяқталған тапсырыс). Толық кідірісті өлшеу әдістемесі: тұтынушының сөзі аяқталған сәттен (VAD speech_end белгісі) бастап экранда Kaspi QR пайда болып, баристаның экранына тикет түскенге дейінгі уақыт.
Таңғы қарбалас уақыттағы тапсырыс уақыты мен кассаның өткізу қабілеті (08:30 - 09:30)
Жүйенің толық кідіріс профилі (End-to-End Latency, N = 4 218):
- p50 (Медиана): 340 мс (тұрақты желілік арна, жылы Cloud Run Gateway инстансы және Gemini 2.5 Flash Live).
- p95: 620 мс (таңғы уақыттағы бизнес-орталықтың Wi-Fi/LTE арнасындағы қысқа мерзімді джиттер).
- p99: 890 мс (жүктеме шарықтағандағы қосымша Cloud Run репликаларының суық іске қосылуы).
Баристаның KDS дисплейіне тапсырыс жету кідірісі (мс)
Ерекше жағдайлар мен баристаға бағыттау (Edge Cases):
- Тапсырыстардың 4.8%-ы (4 218 ішінен 202 жағдай) баристаның араласуын талап етті:
- 2.1% (89 жағдай): шулы холлда микрофон алдында бір уақытта екі адамның сөйлеуі.
- 1.6% (68 жағдай): мәзірде жоқ немесе қоймада таусылған өнімдерді сұрау.
- 1.1% (45 жағдай): қолма-қол ақшамен немесе корпоративтік бонустармен төлеу ниеті.
- Кіріктірілген Circuit Breaker: егер сокет кідірісі 1.5 секундтан асса немесе пакеттер жоғалуы 8%-дан жоғары болса, жүйе қатып қалмай, лезде сенсорлық резервтік батырмаларға ауысады.
6. Экономикалық тиімділік және FinOps-декомпозиция
60 күндік пилот нәтижесінде бір қонаққа қызмет көрсетудің орташа уақыты 135 секундтан 38 секундқа дейін қысқарды. Сағат 08:30–09:30 аралығындағы өткізу қабілеті сағатына 26-дан 78 тапсырысқа дейін өсті, ал таңғы түсім қосымша қызметкер алмай-ақ +68%-ға артты.
💰 Google Cloud толық FinOps-биллингі ($16.40 / айына):
- Google Cloud Run (Gateway WebSocket proxy, 1 vCPU, 512 MB RAM, min-instances=0, max=3): $4.20 / айына.
- Gemini 2.5 Flash Multimodal Live API (инференс аудио-токендері, ~70 сағат таза дыбыс): $9.80 / айына.
- Firebase Realtime Database (1.8 GB сақтау / KDS сокет трафигі): $1.60 / айына.
- Cloud Logging & Cloud Trace (10% сэмплинг): $0.80 / айына.
- БАРЛЫҒЫ: $16.40 айына. Орташа 1 600 ₸ чекпен бұл шығын дүйсенбі таңғы 08:35-те сатылған алғашқы 4 шыныаяқ кофеден өтеледі.
Компанияңыздың бұлттық шығындарын азайту үшін Google Cloud-тағы шығындарды оңтайландыру бөлімін қараңыз.
Шешімнің шынайы шектеулері мен компромисстері
OZAT инженерлік адалдығы: «Кофе 40 секундта немесе тегін: Firebase Realtime Database және Gemini Live Audio арқылы Астана БО-дағы кезектерді жою» архитектурасын өндіріске енгізу кезінде мына технологиялық шектеулерді ескеру қажет:
- Кофе қайнатқыштардың шуы және акустикалық кедергілер: Сүтті көпіршіту кезіндегі қатты шу (78 дБ дейін) арнайы шуды басатын микрофонсыз Gemini Live Audio дауыс тануын төмендетеді.
- Бірнеше қонақтың қатар сөйлеуі: Кезекте тұрған адамдардың дауысын бір-бірінен ажырату үшін спикерлерді диаризациялау (Speaker Diarization) қажет.
- Тапсырыстар кезегінің синхрондалуы: Баристаның планшетіндегі Firebase Realtime Database деректері бір кофені екі рет жасап қоймас үшін 150 миллисекундтан жылдам жаңаруы тиіс.
- Ингредиенттер стоп-листі: Сұлы сүті таусылған жағдайда, ЖИ барлық сөйлесулерде оны ұсынуды лезде тоқтатуы қажет.
💡 ОЗАТ кеңесі: Енгізуге дайынсыз ба? Архитектура мен бюджетті Scope Builder арқылы есептеңіз немесе тегін ЖИ-аудиттен өтіңіз.

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