Streaming Real-Time AI Telemetry with Firebase Genkit and Angular Signals A developer built a serverless AI pipeline using Firebase Genkit and Gemini 3.5 Flash that streams real-time progress telemetry to an Angular Signals frontend, avoiding intermediate database writes. The pipeline chains multiple model calls for a resume optimization engine, pushing status keys over an HTTP connection to update the UI declaratively. We’ve all been there. You click a button in a modern web app, and you’re immediately greeted by a generic loading spinner. You sit there, staring at it for 10 or 15 seconds while some heavy LLM pipeline runs in the background. It’s a frustrating user experience, and honestly, it’s usually the result of a lazy architectural pattern. When you're building complex, multi-step AI features like a resume optimization engine that needs to parse text, check formatting, evaluate metrics, and then generate tips , waiting for the entire chain to finish before returning data feels painfully slow. Instead of forcing your users to stare blindly at a loading screen, you can build a live, interactive pipeline experience. Let's look at how to orchestrate a serverless AI pipeline using Firebase Genkit and Gemini 3.5 Flash, streaming real-time progress telemetry milestones directly down to a reactive Angular Signals architecture in the browser. The core goal here is straightforward: we want to avoid the overhead of writing intermediate processing states to a database collection just to update the client. Instead, we want direct, memory-to-client streaming. As our serverless backend hits specific checkpoints in the execution loop, it pushes small status keys down an open HTTP connection. The Angular application catches these chunks on the fly and updates local Signals, driving our UI completely declaratively. First, let’s look at the backend. We'll leverage Firebase Genkit’s onCallGenkit wrapper, which exposes our pipeline as a secure, type-safe Callable Cloud Function out of the box. Instead of passing everything into a single prompt, we are going to chain multiple model calls sequentially. This allows us to trigger unique telemetry steps for the user while increasing overall accuracy. We will also define a strict structure using Zod to force the final Gemini model call to return precisely the data contract our frontend expects. js // functions/src/index.ts import { ai } from '@genkit-ai/firebase'; import { onCallGenkit } from '@genkit-ai/firebase/functions'; import { z } from 'zod'; const ResumeAnalysisSchema = z.object { atsScore: z.number .min 0 .max 100 , formattingReview: z.string , actionableTips: z.array z.string , } ; export const analyzeResumeFlow = onCallGenkit { authPolicy: auth = auth?.token.email verified === true, }, async input, streamingCallback = { const { resumeText } = input; // Milestone 1: Structural Scan if streamingCallback { streamingCallback { status: 'parsing structure' } ; } const structureResponse = await ai.models.generate { model: 'gemini-3.5-flash', prompt: Analyze the layout and structural elements of this resume for ATS friendliness: ${resumeText} , } ; const structuralNotes = structureResponse.text; // Milestone 2: Content Impact Analysis if streamingCallback { streamingCallback { status: 'analyzing impact' } ; } const impactResponse = await ai.models.generate { model: 'gemini-3.5-flash', prompt: Review the bullet points in this resume. Focus heavily on strong action verbs and quantifiable metrics: ${resumeText} , } ; const impactNotes = impactResponse.text; // Milestone 3: Compile Tips & Enforce Final Schema if streamingCallback { streamingCallback { status: 'generating tips' } ; } const finalResponse = await ai.models.generate { model: 'gemini-3.5-flash', prompt: You are a resume optimization engine. Review the structural analysis and impact notes provided below. Compile them into the requested JSON schema. Structural Analysis: ${structuralNotes} Impact Analysis: ${impactNotes} , config: { responseMimeType: 'application/json', responseSchema: ResumeAnalysisSchema, } } ; // Returning this fully resolves the function call and closes the stream return JSON.parse finalResponse.text ; } ; On the Angular side, we need to handle this live chunked response protocol. Firebase’s standard httpsCallable handler actually returns a stream reference that contains an asynchronous iterable stream . We can write a clean, injectable service that handles the plumbing and updates read-only Signals for our UI components to consume safely. js // src/app/services/resume-analyzer.service.ts import { Injectable, signal } from '@angular/core'; import { getFunctions, httpsCallable } from 'firebase/functions'; export type ResumeStep = 'idle' | 'parsing structure' | 'analyzing impact' | 'generating tips' | 'complete' | 'error'; @Injectable { providedIn: 'root' } export class ResumeAnalyzerService { private functions = getFunctions ; // 1. Keep the writable signals private so components can't touch them private readonly currentStep = signal