Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Have Your Cake and Eat It Too: Hybrid AI with A...

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Have Your Cake and Eat It Too: Hybrid AI with Apple Intelligence and Firebase AI Logic

Want to use Apple Intelligence and have the power of cloud AI? This session is a practical guide to building a 'local-first' AI architecture in Swift. We'll use protocols to create a single, resilient system that tries fast on-device inference first, then intelligently falls back to the cloud, giving you the instant speed of on-device inference and the power of large-scale cloud models.

Avatar for Peter Friese

Peter Friese

May 06, 2026

More Decks by Peter Friese

Other Decks in Technology

Transcript

  1. @peterfriese.dev Created by Mamank from Noun Project https: / /

    peterfriese.dev peterfriese Peter Friese, Staff Developer Relations Engineer, Google Hybrid AI with Apple Intelligence and Gemini
  2. Peter Friese The Google office in Amsterdam Fun fact: same

    glasses as Paul Hudson @peterfriese.dev
  3. 1/ Collect links 2/ Read them later 3/ Simplified formatting

    4/ Offline reading 5/ Summarise 6/ Second brain Livestreams
  4. Here's to the crazy ones. The misfits. The rebels. The

    troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They invent. They imagine. They heal. They explore. They create. They inspire. They push the human race forward. Maybe they have to be crazy. Summarise text
  5. Here's to the crazy ones. The misfits. The rebels. The

    troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They invent. They imagine. They heal. They explore. They create. They inspire. They push the human race forward. Maybe they have to be crazy. How else can you stare at an empty canvas An Owner's Manual for Google's Shareholders Introduction Google is not a conventional company. We do not intend to become one. Throughout Google's evolution as a privately held company, we have managed Google di ff erently. We have also emphasized an atmosphere of creativity and challenge, which has helped us provide unbiased, accurate and free access to information for those who rely on us around the world. Now the time has come for the company to move to public ownership. This change will bring impo rt ant bene fi ts for our employees, for our present and future shareholders, for our customers, and most of all for Google users. But the standard structure of public ownership may jeopardize the independence and focused objectivity that have been most impo rt ant in Google's past success and that we consider most fundamental for its future. Therefore, we have implemented a corporate structure that is designed to protect Google's ability to innovate and retain its most distinctive characteristics. We are con fi dent that, in the long run, this will bene fi t Google and its shareholders, old and new. We want to clearly explain our plans and the reasoning and values behind them. We are delighted you are considering an investment in Google and are reading this le tt er. tt
  6. Content Warning The following slides contain scenes that may be

    extremely traumatizing to some audiences 􀋯
  7. Here's to the crazy ones. The misfits. The rebels. The

    troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They invent. They imagine. They heal. They explore. They create. They inspire. They push the human race forward. Maybe they have to be crazy. How else can you stare at an empty canvas An Owner's Manual for Google's Shareholders Introduction Google is not a conventional company. We do not intend to become one. Throughout Google's evolution as a privately held company, we have managed Google di ff erently. We have also emphasized an atmosphere of creativity and challenge, which has helped us provide unbiased, accurate and free access to information for those who rely on us around the world. Now the time has come for the company to move to public ownership. This change will bring impo rt ant bene fi ts for our employees, for our present and future shareholders, for our customers, and most of all for Google users. But the standard structure of public ownership may jeopardize the independence and focused objectivity that have been most impo rt ant in Google's past success and that we consider most fundamental for its future. Therefore, we have implemented a corporate structure that is designed to protect Google's ability to innovate and retain its most distinctive characteristics. We are con fi dent that, in the long run, this will bene fi t Google and its shareholders, old and new. We want to clearly explain our plans and the reasoning and values behind them. We are delighted you are considering an investment in Google and are reading this le tt er. tt
  8. Here's to the crazy ones. The misfits. The rebels. The

    troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They invent. They imagine. They heal. They explore. They create. They inspire. They push the human race forward. Maybe they have to be crazy. How else can you stare at an empty canvas An Owner's Manual for Google's Shareholders Introduction Google is not a conventional company. We do not intend to become one. Throughout Google's evolution as a privately held company, we have managed Google di ff erently. We have also emphasized an atmosphere of creativity and challenge, which has helped us provide unbiased, accurate and free access to information for those who rely on us around the world. Now the time has come for the company to move to public ownership. This change will bring impo rt ant bene fi ts for our employees, for our present and future shareholders, for our customers, and most of all for Google users. But the standard structure of public ownership may jeopardize the independence and focused objectivity that have been most impo rt ant in Google's past success and that we consider most fundamental for its future. Therefore, we have implemented a corporate structure that is designed to protect Google's ability to innovate and retain its most distinctive characteristics. We are con fi dent that, in the long run, this will bene fi t Google and its shareholders, old and new. We want to clearly explain our plans and the reasoning and values behind them. We are delighted you are considering an investment in Google and are reading this le tt er. tt App Store Review Guidelines Apps are changing the world, enriching people's lives, and enabling developers like you to innovate like never before. As a result, the App Store has grown into an exciting and vibrant ecosystem for millions of developers and more than a billion users. Whether you are a first-time developer or a large team of experienced programmers, we are excited that you are creating apps for our platforms, and want to help you understand our guidelines so you can be confident your app will get through the review process quickly. Introduction The guiding principle of the App Store is simple—we want to provide a safe experience for users to get apps and a great opportunity for all developers to be successful. We do this by offering a highly curated App Store where every app is reviewed by experts and an editorial team helps users discover new apps every day. We also scan each app for malware and other software that may impact user safety, security, and privacy. These efforts have made Apple's platforms the safest for consumers around the world. In the European Union, developers can also distribute notarized iOS and iPadOS apps from alternative app marketplaces and directly from their website; in Japan, developers can also distribute iOS apps from alternative app marketplaces. For everything else there is always the open
  9. disagree with them, glorify or vilify them. About the only

    thing you can't do is ignore them. Because they change things. They invent. They imagine. They heal. They explore. They create. They inspire. They push the human race forward. Maybe they have to be crazy. How else can you stare at an empty canvas and see a work of art? Or sit in silence and hear a song that's never been written? Or gaze at a red planet and see a laboratory on wheels? We make tools for these kinds of people. While some see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do. fi duciaries for our shareholders, and we will ful fi ll those responsibilities. We will continue to strive to a tt ract creative, commi tt ed new employees, and we will welcome suppo rt from new shareholders. We will live up to our "don't be evil" principle by keeping user trust and not accepting payment for search results. We have a dual class structure that is biased toward stability and independence and that requires investors to bet on the team, especially Sergey and me. In this le tt er we have talked about our IPO auction method and our desire for stability and access for all investors. We have discussed our goal to have investors who invest for the long term. Finally, we have discussed our desire to create an ideal working environment that will ultimately drive the success of Google by retaining and a tt racting talented Googlers. We have tried hard to anticipate your questions. It will be di ffi cult for us to respond to them given legal constraints during our o ff ering process. We look forward to a long and hopefully prosperous relationship with you, our new investors. We wrote this le tt er to help you understand our company. We have a strong commitment to our users worldwide, their communities, the web sites in our network, our adve rt isers, our investors, and of course our employees. Sergey and I, and the team will do our best to make Google a long term success and the world a be tt er place. other appropriate steps to prevent abuse. 2.3.8 Metadata should be appropriate for all audiences, so make sure your app and in-app purchase icons, screenshots, and previews adhere to a 4+ age rating even if your app is rated higher. For example, if your app is a game that includes violence, select images that don't depict a gruesome death or a gun pointed at a specific character. Use of terms like "For Kids" and "For Children" in app metadata is reserved in the App Store for the Kids Category. Remember to ensure your metadata, including app name and icons (small, large, Apple Watch app, alternate icons, etc.), are similar to avoid creating confusion. 2.3.9 You are responsible for securing the rights to use all materials in your app icons, screenshots, and previews, and you should display fictional account information instead of data from a real person. 2.3.10 Make sure your app is focused on the experience of the Apple platforms it supports, and don't include names, icons, or imagery of other mobile platforms or alternative app marketplaces in your app or metadata, unless there is specific, approved interactive functionality. Make sure your app metadata is focused on the app itself and its experience. Don't include irrelevant information. There is more…
  10. Gemini Developer API Ve rt ex AI Gemini API Gemini

    models Firebase client SDKs 􀟜
  11. import FirebaseAILogic let instructions = """ **Task:** Create a short,

    snappy, and informative summary of the following text. **Constraints:** - The summary must be a single paragraph, containing no more than 3 sentences. - Capture the single most important takeaway from the text. - Use clear, direct, and easy-to-understand language. **Text to Summarize:** """ let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI(location: "global")) let model = firebaseAI.generativeModel( modelName: "gemini-3.1-flash-lite-preview", System instructions Summarisation w/ Firebase AI Logic
  12. - Use clear, direct, and easy-to-understand language. **Text to Summarize:**

    """ let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI(location: "global")) let model = firebaseAI.generativeModel( modelName: "gemini-3.1-flash-lite-preview", systemInstruction: ModelContent( role: "system", parts: [instructions] ) ) let response = try await model.generateContent(text) let summary = response.text Connect to AI Logic Summarisation w/ Firebase AI Logic
  13. - Use clear, direct, and easy-to-understand language. **Text to Summarize:**

    """ let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI(location: "global")) let model = firebaseAI.generativeModel( modelName: "gemini-3.1-flash-lite-preview", systemInstruction: ModelContent( role: "system", parts: [instructions] ) ) let response = try await model.generateContent(text) let summary = response.text Set up the model Summarisation w/ Firebase AI Logic
  14. - Use clear, direct, and easy-to-understand language. **Text to Summarize:**

    """ let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI(location: "global")) let model = firebaseAI.generativeModel( modelName: "gemini-3.1-flash-lite-preview", systemInstruction: ModelContent( role: "system", parts: [instructions] ) ) let response = try await model.generateContent(text) let summary = response.text Provide system instructions Summarisation w/ Firebase AI Logic
  15. - Use clear, direct, and easy-to-understand language. **Text to Summarize:**

    """ let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI(location: "global")) let model = firebaseAI.generativeModel( modelName: "gemini-3.1-flash-lite-preview", systemInstruction: ModelContent( role: "system", parts: [instructions] ) ) let response = try await model.generateContent(text) let summary = response.text Prompt the model Summarisation w/ Firebase AI Logic
  16. - Use clear, direct, and easy-to-understand language. **Text to Summarize:**

    """ let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI(location: "global")) let model = firebaseAI.generativeModel( modelName: "gemini-3.1-flash-lite-preview", systemInstruction: ModelContent( role: "system", parts: [instructions] ) ) let response = try await model.generateContent(text) let summary = response.text Get the model response Summarisation w/ Firebase AI Logic
  17. Summarisation w/ Apple Foundation Model let instructions = """ **Task:**

    Create a short, snappy, and informative summary of the following text. **Constraints:** - The summary must be a single paragraph, containing no more than 3 sentences. - Capture the single most important takeaway from the text. - Use clear, direct, and easy-to-understand language. **Text to Summarize:** """ let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: text) let responseText = response.content System instructions
  18. Summarisation w/ Apple Foundation Model let instructions = """ **Task:**

    Create a short, snappy, and informative summary of the following text. **Constraints:** - The summary must be a single paragraph, containing no more than 3 sentences. - Capture the single most important takeaway from the text. - Use clear, direct, and easy-to-understand language. **Text to Summarize:** """ let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: text) let responseText = response.content Set up the model
  19. Summarisation w/ Apple Foundation Model let instructions = """ **Task:**

    Create a short, snappy, and informative summary of the following text. **Constraints:** - The summary must be a single paragraph, containing no more than 3 sentences. - Capture the single most important takeaway from the text. - Use clear, direct, and easy-to-understand language. **Text to Summarize:** """ let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: text) let responseText = response.content Provide system instructions
  20. Summarisation w/ Apple Foundation Model let instructions = """ **Task:**

    Create a short, snappy, and informative summary of the following text. **Constraints:** - The summary must be a single paragraph, containing no more than 3 sentences. - Capture the single most important takeaway from the text. - Use clear, direct, and easy-to-understand language. **Text to Summarize:** """ let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: text) let responseText = response.content Prompt the model
  21. Summarisation w/ Apple Foundation Model let instructions = """ **Task:**

    Create a short, snappy, and informative summary of the following text. **Constraints:** - The summary must be a single paragraph, containing no more than 3 sentences. - Capture the single most important takeaway from the text. - Use clear, direct, and easy-to-understand language. **Text to Summarize:** """ let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: text) let responseText = response.content Get the model response
  22. /t͡ʃʌŋk/ 1/ 2/ A discrete segment of a file, stream,

    etc. A discrete segment of a file, stream, etc. A part of something that has been separated A part of something that has been separated
  23. /ˈhʌɪbrɪd/ 1/ 2/ Of mixed character; composed of different elements.

    Of mixed character; composed of different elements. A thing made by combining two different elements. A thing made by combining two different elements.
  24. Requirements 1/ Automatic fallback 2/ Specify priority (local / remote)

    3/ Unified API 4/ Extensible 5/ Configurable (e.g. prompts)
  25. Inferencer /// A protocol defining the contract for a summarization

    provider. /// /// This allows for a strategy pattern where different summarization /// methods (e.g., local, remote) can be used interchangeably. @MainActor protocol Inferencer { / // The system instructions to be used by the generative model. var instructions: String { get } / // A boolean indicating whether the inferencer is available for use. var isAvailable: Bool { get } / // Performs the summarization. func infer(_ input: String) async throws - > String? } E.g. device specific availability
  26. Inferencer /// A protocol defining the contract for a summarization

    provider. /// /// This allows for a strategy pattern where different summarization /// methods (e.g., local, remote) can be used interchangeably. @MainActor protocol Inferencer { / // The system instructions to be used by the generative model. var instructions: String { get } / // A boolean indicating whether the inferencer is available for use. var isAvailable: Bool { get } / // Performs the summarization. func infer(_ input: String) async throws - > String? } Perform the actual work
  27. Inferencer /// A protocol defining the contract for a summarization

    provider. /// /// This allows for a strategy pattern where different summarization /// methods (e.g., local, remote) can be used interchangeably. @MainActor protocol Inferencer { / // The system instructions to be used by the generative model. var instructions: String { get } / // A boolean indicating whether the inferencer is available for use. var isAvailable: Bool { get } / // Performs the summarization. func infer(_ input: String) async throws - > String? } If you want to use custom instructions
  28. Inferencer /// A protocol defining the contract for a summarization

    provider. /// /// This allows for a strategy pattern where different summarization /// methods (e.g., local, remote) can be used interchangeably. @MainActor protocol { / // The system instructions to be used by the generative model. get / // A boolean indicating whether the inferencer is available for use. get / // Performs the summarization. var instructions: String { } var isAvailable: Bool { } func infer(_ input: String) async throws - > String? Inferencer }
  29. LocalSummarizer: Inferencer struct LocalSummarizer: { func infer(_ input: String) async

    throws - > String? Inferencer var instructions: String { } var isAvailable: Bool { } } { }
  30. struct LocalSummarizer: { """ **Task:** Create a short, snappy, and

    informative summary of the following text. **Constraints:** - The summary must be a single paragraph, containing no more than 3 sentences. - Capture the single most important takeaway from the text. - Use clear, direct, and easy-to-understand language. **Text to Summarize:** """ LocalSummarizer: Inferencer var instructions: String { Inferencer func infer(_ input: String) async throws - > String? } var isAvailable: Bool { } { Same instructions as in first code sample
  31. LocalSummarizer: Inferencer struct LocalSummarizer: { func infer(_ input: String) async

    throws - > String? Inferencer var isAvailable: Bool { } } { } var instructions: String { }
  32. if #available(iOS 26.0, *) { return SystemLanguageModel.default.isAvailable } return false

    LocalSummarizer: Inferencer struct LocalSummarizer: { func infer(_ input: String) async throws - > String? Inferencer var isAvailable: Bool { } } { } var instructions: String { } Local model only available on iOS 26 and above
  33. if #available(iOS 26.0, *) { return SystemLanguageModel.default.isAvailable } return false

    LocalSummarizer: Inferencer struct LocalSummarizer: { func infer(_ input: String) async throws - > String? Inferencer var isAvailable: Bool { } } { } var instructions: String { } Model might be disabled / not yet downloaded
  34. { } if #available(iOS 26.0, *) { guard SystemLanguageModel.default.isAvailable else

    { throw SummarizationError.modelNotAvailable } let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: input) let responseText = response.content return responseText } else { throw SummarizationError.modelNotAvailable } struct LocalSummarizer: { var instructions: String { LocalSummarizer: Inferencer func infer(_ input: String) async throws - > String? Inferencer } var isAvailable: Bool { } } Better safe than sorry - model might have become unavailable in the meantime!
  35. { } if #available(iOS 26.0, *) { guard SystemLanguageModel.default.isAvailable else

    { throw SummarizationError.modelNotAvailable } let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: input) let responseText = response.content return responseText } else { throw SummarizationError.modelNotAvailable } struct LocalSummarizer: { var instructions: String { LocalSummarizer: Inferencer func infer(_ input: String) async throws - > String? Inferencer } var isAvailable: Bool { } } Same code as in initial code sample for summarising text
  36. RemoteSummarizer: Inferencer struct RemoteSummarizer: Inferencer { var isAvailable: Bool {

    / / network availability / feature flags / subscription status } private var model: GenerativeModel { let modelName = configuration.remoteModelName let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI()) return firebaseAI.generativeModel( modelName: modelName, systemInstruction: ModelContent(role: "system", parts: [instructions]) ) } public func infer(_ input: String) async throws -> String? { let response = try await model.generateContent(input) let responseText = response.text return responseText } } Availability depends on other factors
  37. RemoteSummarizer: Inferencer struct RemoteSummarizer: Inferencer { var isAvailable: Bool {

    / / network availability / feature flags / subscription status } private var model: GenerativeModel { let modelName = configuration.remoteModelName let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI()) return firebaseAI.generativeModel( modelName: modelName, systemInstruction: ModelContent(role: "system", parts: [instructions]) ) } public func infer(_ input: String) async throws -> String? { let response = try await model.generateContent(input) let responseText = response.text return responseText } } Pro tip: use Remote Config to set model name!
  38. RemoteSummarizer: Inferencer struct RemoteSummarizer: Inferencer { var isAvailable: Bool {

    / / network availability / feature flags / subscription status } private var model: GenerativeModel { let modelName = configuration.remoteModelName let firebaseAI = FirebaseAI.firebaseAI(backend: .vertexAI()) return firebaseAI.generativeModel( modelName: modelName, systemInstruction: ModelContent(role: "system", parts: [instructions]) ) } public func infer(_ input: String) async throws -> String? { let response = try await model.generateContent(input) let responseText = response.text return responseText } } Generate summary using Firebase AI Logic
  39. SummarizerService public func summarize(_ input: String) async throws -> String?

    { var lastError: Error? let priorityOrder = configuration.summarizerPriorityOrder for inferencerKey in priorityOrder { guard let inferencer = inferencers[inferencerKey], inferencer.isAvailable else { continue } do { if let result = try await inferencer.infer(input) { return result } } catch { logger.error("Inferencer \(type(of: inferencer)) failed: \(String(describing: error))") lastError = error / / Try the next available inferencer } } / / If all inferencers fail, throw the last error we encountered if let lastError { throw lastError } / / If no suitable inferencer was found at all throw SummarizationError.noSuitableInferencerFound } Iterate over registered inferences
  40. ME EXPLAINING THE STRATEGY PATTERN, REMOTE CONFIG FALLBACKS, AND PROTOCOL-ORIENTED

    ERROR HANDLING. JUST TO SUMMARIZE A 5,000 TOKEN DOCUMENT.
  41. let systemModel = FirebaseAI.SystemLanguageModel.default let cloudModel = firebaseAI.geminiModel(name: "gemini-3.1-flash-lite-preview") let

    hybridModel = HybridModel(primary: systemModel, secondary: cloudModel) let session = firebaseAI.generativeModelSession( model: hybridModel, instructions: instructions ) let response = try await session.respond(to: text) let summary = response.content Summarisation w/ Firebase AI Logic - Hybrid Set up system model (AFM)
  42. let systemModel = FirebaseAI.SystemLanguageModel.default let cloudModel = firebaseAI.geminiModel(name: "gemini-3.1-flash-lite-preview") let

    hybridModel = HybridModel(primary: systemModel, secondary: cloudModel) let session = firebaseAI.generativeModelSession( model: hybridModel, instructions: instructions ) let response = try await session.respond(to: text) let summary = response.content Summarisation w/ Firebase AI Logic - Hybrid Set up cloud model (Gemini 3.1 Flash Lite)
  43. let systemModel = FirebaseAI.SystemLanguageModel.default let cloudModel = firebaseAI.geminiModel(name: "gemini-3.1-flash-lite-preview") let

    hybridModel = HybridModel(primary: systemModel, secondary: cloudModel) let session = firebaseAI.generativeModelSession( model: hybridModel, instructions: instructions ) let response = try await session.respond(to: text) let summary = response.content Summarisation w/ Firebase AI Logic - Hybrid Set up hybrid model
  44. let systemModel = FirebaseAI.SystemLanguageModel.default let cloudModel = firebaseAI.geminiModel(name: "gemini-3.1-flash-lite-preview") let

    hybridModel = HybridModel(primary: systemModel, secondary: cloudModel) let session = firebaseAI.generativeModelSession( model: hybridModel, instructions: instructions ) let response = try await session.respond(to: text) let summary = response.content Summarisation w/ Firebase AI Logic - Hybrid Set up the session
  45. let systemModel = FirebaseAI.SystemLanguageModel.default let cloudModel = firebaseAI.geminiModel(name: "gemini-3.1-flash-lite-preview") let

    hybridModel = HybridModel(primary: systemModel, secondary: cloudModel) let session = firebaseAI.generativeModelSession( model: hybridModel, instructions: instructions ) let response = try await session.respond(to: text) let summary = response.content Summarisation w/ Firebase AI Logic - Hybrid Pass in the system instructions
  46. let systemModel = FirebaseAI.SystemLanguageModel.default let cloudModel = firebaseAI.geminiModel(name: "gemini-3.1-flash-lite-preview") let

    hybridModel = HybridModel(primary: systemModel, secondary: cloudModel) let session = firebaseAI.generativeModelSession( model: hybridModel, instructions: instructions ) let response = try await session.respond(to: text) let summary = response.content Summarisation w/ Firebase AI Logic - Hybrid Prompt the model
  47. Recap 1/ Firebase AI Logic Cloud-based, large context window Requires

    network, data leaves device 2/ Apple Foundation Models On-device, works offline, no data leaves device Limited token window 3/ Chunking On-device, works offline, no data leaves device Slower, more complex 4/ Hybrid Automatic fallback (can specify order) Requires Firebase setup
  48. Thanks! @peterfriese.dev Created by Mamank from Noun Project https: /

    / peterfriese.dev peterfriese peterfriese Slides for this talk
  49. Q&A @peterfriese.dev Created by Mamank from Noun Project https: /

    / peterfriese.dev peterfriese peterfriese