# Lee Boonstra Dev Blog - Full Aggregated Contents > This file contains the complete, aggregated plain-text raw markdown contents of all published blog posts on leeboonstra.dev, optimized for zero-friction scraping and ingestion by AI agents. --- ## Blaming the Developer for the AI Bill? You’re Managing Software Like It’s 1996. *Published: 2026-07-10* *Categories: AI Development, Engineering Management, Developer Workflow* *Tags: AI Agents, Engineering Management, Large Language Models, Agentic Coding, Tokens* > Why the 'token-aware developer' is a myth. Learn how to reduce enterprise AI coding costs and manage agentic LLM tooling without slowing software velocity. --- Lately, my social media feeds have been flooded with a new confusing take on what defines a "pro" software engineer in 2026. The narrative goes something like this: “With modern AI tooling, anyone can spit out code. Therefore, a truly senior developer is a 'token-aware' developer—someone who writes prompts carefully to keep the company's LLM bill low.” Let's call this what it is: absolute nonsense. It's true, the amount of AI tokens that gets burned creates anxiety. But that fear is born from a cycle of corporate mismanagement that tech executives brought entirely on themselves. Instead of pausing to restructure how software actually gets built in an AI-first world, leadership teams across the industry followed a flawed and outdated playbook. The playbook goes like this: first, overhire engineering departments during a market boom. Next, hand thousands of engineers a cutting-edge AI toolkit like Claude Code or Antigravity, with no new guardrails. Finally, watch in horror as this unmanaged workforce racks up a million-dollar consumption bill, then launch massive layoffs—scapegoating the very people you just overloaded. We have watched this exact cycle play out in real-time with tech giants like Meta and Amazon slashing corporate positions explicitly to offset ballooning AI infrastructure and compute costs. It’s a classic case of bad planning: management used AI as a brute-force multiplier for an already bloated org structure, rather than a catalyst to design a lean, modern engineering pipeline. And now? The engineers who survived the layoffs are being told they need to be "token-aware" to save the company money. Trying to blame a developer's "token hygiene" for these operational costs completely misdiagnoses the problem—and proves that your business is stuck in the past. ## The Three Types of Token Bills (And Why Seniors Already Get It) To understand why this "token-aware developer" narrative is a myth, we have to split the AI bill into three completely distinct buckets: Blaming the SWE for high AI costs ### 1. Production API Costs This is the cost of running AI features inside the application your customers use. The more users interact with your product, the more tokens you consume. This isn't a new paradigm. Twenty years ago, working with SOAP web services and paid third-party APIs taught senior engineers to treat external calls with care by architecting caching, optimizing payloads, and building guardrails. A senior developer already understands that the core concept of resource management hasn’t changed. ### 2. Developer Tooling Costs This is where the real surprise shock is happening. Companies are seeing massive bills from AI-native IDEs and developer tools. But trying to penalize a developer for using tokens in their development tool is fundamentally broken. Imagine giving a carpenter a brand-new toolbox, only to tell them: "Hey, we're going to charge the business a dollar every time you swing that hammer. So, try not to use it too often." Certain tasks require the hammer. Sometimes you misalign a nail, or the wood splits, and you have to pull it out and do it again. That isn't sloppiness; that is the messy, iterative reality of craftsmanship. And this is exactly how it works with AI developer tools: usage costs tokens. Coding tools are powered by Large Language Models. They predict the next statistical token based on massive codebases. Expecting a developer to artificially limit their tool usage to save pennies on context windows is asking them to fight the machine. We didn't ask engineers to go back to steam engines because coal got expensive; we don't ask 2026 developers to ration their keystrokes. ### 3. The Agentic Multiplier What most people miss is that we are in the era of Agentic AI, not just chatbots. Developers now deploy autonomous agents that can, for example, resolve a dependency conflict by interacting with filesystems, calling tools, and spinning up sub-agents. By definition, their core loop—Plan, Act, Observe, Correct—is token-hungry, requiring massive context from codebases and error logs to iterate. A single agent fixing one tricky bug might burn millions of tokens in an afternoon. To expect a human developer to be "token-aware" of a headless agent's autonomous reasoning loop is fundamentally absurd. You cannot micromanage a machine's cognitive process to save pennies while it's busy saving you weeks of human labor. If an agent spends $50 in tokens to automatically refactor a legacy module while your team sleeps, it didn't "waste" resources—it traded cheap compute for expensive human time. ## Don't Blame the Tool. Fix Your Business Model. If the AI bills are extraordinary—and they are—the entity to blame isn't the developer, and it isn't the AI vendor. It’s your own business architecture. Corporate procurement and IT departments are still trying to measure AI usage with outdated rubrics like per-seat licensing or fixed hardware costs. They treat an AI tool as a simple line-item expense, not as the organizational restructuring force it truly is. Look at the trade-off. With agentic AI and advanced tooling, developers can produce code exponentially faster. If your engineering velocity increases by 10 times, your development math completely changes. Consider the shift in resource allocation. Previously, a feature might have required a five-engineer team for a full month. Today, a single engineer using autonomous agents can ship that same feature in a few days. The cost doesn't disappear; it simply shifts from being 99% salary to a new blend of salary and compute. Your AI developer tooling bill might look shocking on paper, but you just saved weeks of human engineering hours. If your management can't see that the net ROI is massively positive, the problem isn’t the tokens—it's the accounting. ## How to Actually Lower Your AI Bill If you actually want to go cheaper, the solution isn't to tell your developers to type shorter prompts or write fewer prompts. The solution is to architect a smart, tiered AI infrastructure. A smart organization builds an ecosystem that uses the right tool for the job. | Task Complexity | Recommended Model / Approach | Cost Profile | | :--- | :--- | :--- | | **Architectural Planning & Complex Debugging** | Frontier Models (e.g., Claude 3.5 Sonnet, Gemini Pro) | Premium | | **Routine Code Generation & Standard Linters** | Small/Medium Models (e.g., Gemini Flash, Claude Haiku) | Ultra-Low | | **Local Autocomplete & Simple Refactoring** | Local Open-Weights LLMs (via Ollama / Llama 3) | Free (On-Device Compute) | | **Static Logic & Known Syntax Rules** | Traditional Regex, Linters, and Scripts | Zero AI Required | Not every task requires a massive, multi-billion-parameter frontier model. Local open-weights models running directly on developer machines are now incredibly capable at handling inline autocomplete, basic docstring generation, and simple refactoring—costing the company exactly zero tokens. Furthermore, we need to stop routing every task through an LLM by default. If a task can be solved by a traditional regex expression, a well-written shell script, or a standard linter, it shouldn't be sent to an LLM at all. True efficiency comes from fallback routing and model switching, not micromanaging human behavior. When you enable an engineer to drive at warp speed, the problem isn't how much fuel they're burning. The problem is whether your tracks can handle the speed. As I wrote about recently in my post, [Learning the Hard Way: When Agents Build Agents](https://leeboonstra.dev/ai-development/engineering-management/developer-workflow/learning-the-hard-way-when-agents-build-agents/), accelerating code production doesn't magically solve software engineering. It simply moves the bottleneck downstream to integration, code review, and architectural alignment. ## Stop Counting Tokens, Start Counting Value The "token-aware developer" is a management cop-out—an attempt to push the anxiety of rising IT bills onto the people hired to build your future. When you measure success by the cost of the tools instead of the velocity of the output, you are missing the entire point of the AI revolution. Stop worrying about the hammer. Start worrying about what you're building with it. --- ## Vector Embeddings vs. The DSM-5: The Mathematical Flaw in Mental Health AI *Published: 2026-06-26* *Categories: Applied AI, Mental Health AI, Vector Embeddings, Engineering Proof* *Tags: Data Science, Vibe Coding, DSM, Vector Embeddings, Gemini Enterprise, Mental Health AI, Psychiatric AI, AI Engineering, Therapy AI, Mental Health Tech* > What happens when you map the entire DSM-5 into high-dimensional vector space? The math exposes a chaotic illusion. See the interactive proof of why mental health AI is chasing a ghost. --- > NOTE: I am a software engineer and data practitioner, not a psychiatrist, psychologist, or medical professional. This project is an exploratory engineering proof examining the mathematical properties of clinical language in vector space. The underlying codebase is fully open-sourced on GitHub as a foundational starting point, and I invite subject matter experts, clinicians, and researchers to review, collaborate, or build upon these initial findings. As an engineer, I look at the world through the lens of data architecture, relations, database schemas, and decision trees. Lately, I've been reading Daniel Oberhaus's book, [*The Silicon Shrink: How Artificial Intelligence Made the World an Asylum*](https://www.amazon.com/Silicon-Shrink-Artificial-Intelligence-Asylum/dp/026204935X/ref=sr_1_1). In Chapter 4, Oberhaus critiques **"digital phenotyping"**—the idea that we can passively track smartphone keystrokes, typing cadences, and language choices to map human behavior back to **DSM-5** (The American Psychiatric Association's Diagnostic and Statistical Manual of Mental Disorders) diagnoses. His argument is that this tracking is fundamentally flawed because the underlying foundation—the DSM itself—is broken. If the diagnostic categories are arbitrary and unscientific, training AI to detect them is just automating subjectivity. As I read, I wondered: > *What if the linguistic boundaries of the DSM are so overlapping and redundant that the math of vector spaces exposes them as an artificial illusion?* So, being an applied AI engineer with too much time while waiting at an airport, a Google Cloud project, Antigravity for some vibe coding, and a healthy dose of professional skepticism, I decided to build an engineering proof. I wanted to map the entire DSM-5 into high-dimensional vector space, project it down to a 2D canvas, and see what the geometry of clinical language actually looks like. The results are mathematically clear, clinically chaotic, and raise serious questions about the future of Psychiatric AI. You can explore the live, interactive proof and run the simulations yourself here:
![DSM-5 disorders mapped into high-dimensional vector space](https://leeboonstra.dev/images/vectors.jpeg)
👉 **[Interactive DSM-5 Vector Space Visualizer](https://savelee.github.io/dsm-in-vector-space/)** ## The Relational Collapse: Biology vs. Categorical Boxes In traditional physical medicine, diagnostic models are built on **discrete, biologically isolated decision trees**. If you present with a illness, determining whether you have a **Common Cold vs. COVID-19** is a binary, physically verifiable classification. You either have the rhinovirus, the SARS-CoV-2 virus or what-ever virus, or you don't. The diagnostic boundaries are clear, mutually exclusive, and grounded in objective biology. In contrast, **psychiatric diagnostics lack biological boundaries**. Because we cannot peek into someone's brain, to read a clean biomarker for depression or anxiety, the DSM relies on a *categorical classification model*. It defines complex mental disorders not by unique biomarkers, but as arbitrary checklists of overlapping, descriptive behaviors. This structural collapse isn't just a theoretical concern; it has been demonstrated. In a 2025 study published in *Scientific Reports*, researchers introduced the **High-Dimensional Symptom Space (HDSS)** model. By mapping the clinical symptom profiles of over 5,000 adolescents into a 119-dimensional space, they discovered that the mathematical distances *within* a diagnostic category were virtually indistinguishable from the distances *between* different categories. In other words, when you map actual patient data, the DSM's categorical boxes completely dissolve. When distinct diagnostic categories share identical symptom definitions (e.g., fatigue, sleep disturbances, concentration difficulties), the boundaries between disorders become entangled. This creates massive **overlaps** and **tautological loops** where a clinician is likely to diagnose a patient with multiple conditions simultaneously which is not necessarily the clinical reality, but rather evidence that the categorical model itself is structurally defective. ## Vibing, Scraping, Structuring, and Embedding So here's what I did. I broke the experiment into the following phases: 1. Extracting and synthesizing the DSM-5 data, 2. Normalizing it into a clean data structures 3. Encoding the clinical text into dense vector representations. 4. Finally, I built a companion web app to visualize the results in 2D space: 👉 **[Interactive DSM-5 Vector Space Visualizer](https://savelee.github.io/dsm-in-vector-space/)** Because the APA aggressively copyrights the DSM-5-TR to monetize the manual, there is no clean, open-source DSM webservice, API, or public database sitting on an open registry. To bypass this, I uploaded a PDF of an easy-to-explain DSM-5 guide into **[NotebookLM](https://notebooklm.google/)**. It allowed me to quickly extract the core diagnostic concepts and define clean JSON data structures. From there, I wrote Python scripts to parse the criteria, run quality checks, and ensure the symptom matching and labeling aligned with clinical definitions. (For example, if one disorder mentioned *insomnia* and another one has the synonym *sleeplessness*, then the synonym name should actually be the same. LLMs like Gemini are really great to detect and group synonyms from large amounts of texts.) The resulting clean dataset was organized into official chapters in my [data repository](https://github.com/savelee/dsm-in-vector-space/tree/main/data), culminating in a [master diagnoses list](https://github.com/savelee/dsm-in-vector-space/blob/main/data/diagnoses.json) and a [consolidated symptom dictionary](https://github.com/savelee/dsm-in-vector-space/blob/main/data/unique_symptoms.json). With a clean dataset of clinical descriptions, I generated dense vector representations using Google Cloud's **`text-embedding-005`** model on Gemini Enterprise. I configured the API call with `task_type="CLUSTERING"` to ensure the model treated each symptom description as an independent semantic entity in a 768-dimensional space, rather than a search query: ```python # Extract from our embedding generator script import vertexai from vertexai.language_models import TextEmbeddingModel, TextEmbeddingInput vertexai.init(project=PROJECT_ID, location="global") # Gemini Enterprise endpoint model = TextEmbeddingModel.from_pretrained("text-embedding-005") # Run batch embeddings with the CLUSTERING task type inputs = [ TextEmbeddingInput(text=text, task_type="CLUSTERING") for text in symptom_texts ] response = model.get_embeddings(inputs) symptom_vectors = [emb.values for emb in response] ``` The resulting high-dimensional vector representations were saved in my [pre-computed embedding cache](https://github.com/savelee/dsm-in-vector-space/blob/main/embeddings/embeddings_text_embedding_005.json) to optimize performance. ## Mapping the Geometry of Clinical Language To translate these 768-dimensional vectors into a visual format, I built a client-side **React + TypeScript + Vite** web application: [`modern-viz/`](https://github.com/savelee/dsm-in-vector-space/tree/main/modern-viz). It runs entirely in the browser without a backend, so i can deploy it on **Github Pages** for free. The application uses **t-SNE** (t-Distributed Stochastic Neighbor Embedding) to project the high-dimensional coordinates down to a 2D grid, alongside an interactive, physics-based network graph. The web app exposes three distinct layers of the DSM's structural collapse: ### 1. The Topological Network Graph Using an [interactive network mapping component](https://github.com/savelee/dsm-in-vector-space/blob/main/modern-viz/src/components/NetworkGraph.tsx), the app constructs a physics-based network where **Disorder Nodes** connect to **Symptom Nodes**. Filtering by chapter immediately exposes the overlap. You can drag nodes and watch how symptoms like fatigue, insomnia, and concentration difficulties act as gravity wells, pulling separate disorders together.
![Topological Network Graph mapping DSM-5 disorders to symptom nodes](https://leeboonstra.dev/images/topology.png)
### 2. The Semantic Vector Space This tab projects the text of the clinical descriptions into a [2D semantic space visualization](https://github.com/savelee/dsm-in-vector-space/blob/main/modern-viz/src/components/SemanticSpace.tsx). You can toggle between three levels: - **Symptom Semantic Space**: Maps individual symptoms in 2D space. - **Symptom-Disorder Overlap**: Select disorders—like **Major Depressive Disorder (MDD)** and **Generalized Anxiety Disorder (GAD)**—to see their symptoms land in the exact same coordinate space. - **Diagnosis Semantic Space (Centroids)**: Maps the average vector (centroid) of each disorder's symptom profile to see which diagnoses are semantically identical.
![Semantic Vector Space visualizing dense embeddings of DSM-5 symptoms in 2D](https://leeboonstra.dev/images/vector_space.png)
### 3. The Virtual Shrink Simulator To turn this math into a practical demonstration, I built an [interactive clinical simulator](https://github.com/savelee/dsm-in-vector-space/blob/main/modern-viz/src/components/VirtualShrink.tsx), backed by a [custom diagnostic logic engine](https://github.com/savelee/dsm-in-vector-space/blob/main/modern-viz/src/utils/diagnosticEngine.ts). As you select symptoms, the engine filters the DSM database and calculates a **"System Ambiguity Score"** (clinical entropy). If you input an overlapping clinical profile, the engine triggers a **"Comorbidity Loop Warning"** and spikes the entropy meter, proving the categorical model cannot resolve the patient into a single box. *If I had more time, or maybe I will pick this up at some point, then I would have build a (speech) chat agent, that could diagnose on the fly. By filtering options out.* ## A Visual Proof To see how easily the DSM's categorical architecture collapses, we can analyze the **Anxiety-Depression Vortex**. When a patient presents with fatigue, sleep disturbances, poor concentration, and restlessness, the diagnostic engine enters a permanent, circular loop. (e.g. circular logic where Condition A requires symptoms that inherently trigger the diagnosis of Condition B, locking the data model (or the clinician) in an infinite loop of over-diagnosised conditions). By analyzing the geometric clusters generated by `text-embedding-005`, we can extract three critical lessons that challenge the very foundation of Computational Psychiatry and Psychiatric AI (PAI): ### 1. The Tautology Trap When looking at the Symptom Semantic Space, I noticed a strange, tight cluster of highly unrelated symptoms pulling together near the edge of the graph. When I inspected the raw text, the cause became obvious: these sentences all contained the standard legalistic boilerplate: *"The symptoms cause clinically significant distress or impairment in social, occupational, or other important areas of functioning."* Because the manual repeats this phrasing across hundreds of disorders, the embedding model groups these different clinical realities at the same coordinates. This is a **Tautology Trap**—proving the DSM often groups conditions based on administrative boilerplate rather than clinical reality. ### 2. Language is the Glitch In psychiatric research, there is a highly debated concept called the **p-factor**—a single, general factor of psychopathology suggesting that if a patient has one mental health issue, they are statistically predisposed to experience others. Our vector space suggests a much simpler, more humbling explanation: **the p-factor is a linguistic artifact**. The language used to write the DSM is highly redundant. The text embeddings prove that many diagnoses are mathematically "so close" simply because the APA used the same words to describe different constructs. The overlap isn't necessarily a deep biological secret; it's a structural design defect in the manual's language. This is where my linguistic experiment and the HDSS paper offer two fascinating, complementary perspectives. The HDSS paper shows that *actual patient presentations* collapse into a single, continuous space rather than discrete clusters. My embedding experiment suggests a potential structural reason for this: the very *language* used to define these disorders is mathematically entangled. If the diagnostic instrument (the DSM) is built on redundant, overlapping semantic definitions, the clinical data collected through it will inevitably reflect that overlap. It raises an intriguing question: is the p-factor a deep biological mystery, or is it, at least in part, a linguistic artifact of how we designed the manual? > Training AI models on passive smartphone data to predict DSM labels is a dead end. If the underlying labels are mathematically entangled, a model boasting high accuracy isn't discovering a biological truth. It is just predicting the circular language of the manual and automating a flawed baseline. ## The Path Forward If we want to build truly revolutionary AI systems for mental health, we must stop trying to automate the rigid, 20th-century box models of the DSM. A static vector map is only a starting point. True clinical understanding is not just a coordinate; it is a trajectory. This is why the human conversation between a patient and a therapist remains irreplaceable. A vector embedding might capture the semantic similarity of symptoms, but it cannot untangle the complex, temporal choreography of a life: the precise **order of sequence** (e.g., did the insomnia trigger the panic, or did the panic cause the insomnia?), the downstream **behavioral consequences** (e.g., avoiding morning meetings, calling out sick, or abusing caffeine to stay awake during the day), the **situational timing and duration** (e.g., the panic attacks only hit on Monday evenings at exactly 8:00 PM and last for roughly 45 agonizing minutes), and the deeply **personal narrative context** (e.g., the client is carrying immense guilt over their recent promotion because it belonged to a laid-off colleague). The therapist's office is where these static coordinates are animated into a coherent, dynamic story. Now, I am not a psychiatrist, nor have I ever studied psychiatry. I am just a software engineer with a laptop and a vibecoding toolkit. But in a way, that is exactly the point. This experiment proves why specialized verticals, like medicine, law, finance, logistics—can benefit immensely from working more closely with software engineers and data scientists. When you spend your entire career inside a single discipline, you naturally adopt its consensus assumptions, its vocabulary, and its traditional structures (like the DSM's categorical boxes) as absolute truth. But when a software engineer or data scientist looks at the exact same domain, they don't see academic consensus; they see **data architecture, relational integrity, schemas, and state machines**. When we collaborate across boundaries—combining the deep contextual wisdom of the domain expert with the rigorous, systems-level architecture of the engineer—we stop automating the flaws of the past and start building the architecture of the future. > NOTE: The analysis presented here is strictly an engineering and computational proof of concept. I hold no medical or psychiatric credentials. All data and tools have been released as open-source software on GitHub (savelee/dsm-in-vector-space) to encourage rigorous evaluation, refinement, and collaboration by qualified medical and psychiatric professionals. ## Further Reading * **[The Silicon Shrink: How Artificial Intelligence Made the World an Asylum](https://www.amazon.com/Silicon-Shrink-Artificial-Intelligence-Asylum/dp/026204935X/ref=sr_1_1)** (Daniel Oberhaus, Feb 2025) — The book that inspired this experiment, offering a brilliant critique of "digital phenotyping" and the structural flaws of the DSM. Why the race to apply AI in psychiatry is so dangerous, and how to understand the new tech-driven psychiatric paradigm.

AI psychiatrists promise to detect mental disorders with superhuman accuracy, provide affordable therapy for those who can’t afford or can’t access treatment, and even invent new psychiatric drugs. But the hype obscures an unnerving reality. In *The Silicon Shrink*, Daniel Oberhaus tells the inside story of how the quest to use AI in psychiatry has created the conditions to turn the world into an asylum. Most of these systems, he writes, have vanishingly little evidence that they improve patient outcomes, but the risks they pose have less to do with technological shortcomings than the application of deeply flawed psychiatric models of mental disorder at unprecedented scale.

Oberhaus became interested in the subject of mental health after tragically losing his sister to suicide. In *The Silicon Shrink*, he argues that these new, ostensibly therapeutic technologies already pose significant risks to vulnerable people, and they won’t stop there. These new breeds of AI systems are creating a psychiatric surveillance economy in which the emotions, behavior, and cognition of everyday people are subtly manipulated by psychologically savvy algorithms that have escaped the clinic. Oberhaus also introduces readers to the concept of “swipe psychology,” which is quickly establishing itself as the dominant mode of diagnosing and treating mental disorders.
* **[Modeling psychopathology in high-dimensional vector space using the high-dimensional symptom space (HDSS) model](https://pmc.ncbi.nlm.nih.gov/articles/PMC12508039/)** (Wild & Cutler, 2025) — Published in *Scientific Reports*, this paper demonstrates how mapping actual patient response data (CBCL) into high-dimensional space exposes the same categorical collapse and offers a path toward tracking personalized clinical trajectories. --- ## Learning the Hard Way: When Agents Build Agents (and the Culture Changes It Requires) *Published: 2026-05-22* *Categories: AI Development, Engineering Management, Developer Workflow* *Tags: AI Agents, Developer Culture, Vibe Coding, Agentic Coding* > What happens when you deploy autonomous AI agents to write your software ecosystem? Real developer pain, operational gridlock, rogue browser behaviors, and the zero-trust safeguards we built to survive the transition. --- I started my career as a software engineer over 20 years ago, and I’ve spent the last decade pioneering multimodal models and writing that viral prompt engineering paper you might have seen. As a **Software Engineer at Google** working within **Google's Office of the CTO (OCTO)**, my mandate is to design and innovate the next generation of **Agentic AI systems** and architectures that eventually shape Google's global product and thought portfolio. Collaborating with engineers from Silicon Valley to our engineering hubs in Amsterdam and London, our team has pioneered real-world breakthroughs—including the operational speech agents behind the Wendy’s drive-thru success, which is now rolled out across America. Today, our team is one of Google’s first **"hybrid teams"**, where humans and headless agents collaborate on real-world operational work. We are moving beyond treating AI as a simple autocomplete tool; we are embracing a co-worker model where AI agents take care of the heavy, time-consuming operational tasks, freeing our human minds for creative, strategic, and high-level architectural work. But what nobody tells you is how quickly culture, process, and even risk management have to evolve when agents start building agents. Agents building agents collaborative workflow diagram ## Generative AI vs. Agentic AI: The "Brain" Shift There is a fundamental difference between the Generative AI of last year and the Agentic AI we are deploying now. Generative AI was primarily about creating content—generating code snippets, writing summaries, or drawing pictures. Agentic AI is a step-change: it is about software programs that can **think, plan, and act autonomously** to achieve a specific goal. | Feature | Generative AI | Agentic AI | | :--- | :--- | :--- | | **Primary Output** | Content (text, code, images) | Autonomous actions and goals completion | | **Workflow** | Input-Output (one-shot prompts) | Iterative loop (Plan -> Act -> Observe -> Correct) | | **Integration** | Standalone interfaces (chat widgets) | Tool-calling, A2A coordination, filesystem access | | **Adaptability** | Rigidly constrained by context | Dynamic navigation of unexpected environment shifts | Traditional automation follows rigid, pre-defined rules that break the moment reality changes. The agentic difference here is the **"Brain"**—leveraging powerful foundational models like **Gemini 3.5 Flash** via the **Google Cloud Vertex AI** platform to enable dynamic function calling and stateful Agent-to-Agent (A2A) orchestration. While frameworks like **LangGraph**, **AutoGen**, or **CrewAI** serve as excellent entry points for local orchestration, building production-grade agentic platforms requires moving beyond single-agent loops into complex, multi-agent mesh systems. Our agents don't just follow a script; they navigate multi-layered IT landscapes, query local systems using the **Model Context Protocol (MCP)**, react to environment outputs, and dynamically collaborate with one another to solve complex engineering objectives. And fun fact: we are also using AI Agents ([Google Antigravity](https://antigravity.google/)) to help write the code that runs this very ecosystem. We can do this much faster and with higher quality than ever before. A code change that would previously take weeks can now be written, tested, and documented in a matter of hours. It’s amazing, it's addictive, and it's a complete game-changer. ...Until it isn't. --- ## A Story of Developer Pain: The Big Bang PR With all that progress, we hit a wall. Here is where things got complicated fast: I had been working for days on a large new feature for our agent. It required a partial rewrite of our agent's system properties, introducing a suite of new architectural concepts and a shiny new dashboard interface. Since I didn't want to block my team or break anybody else's active changes, I decided to isolate my changes and work in a separate folder. The rollout was supposed to be a simple minor version bump. Instead, it broke the entire test environment. The Pull Request (PR) was huge. Too huge. The diff scrolled for screen after screen—a dense tangle of JavaScript, Python, HTML, CSS, and a mutated hybrid of both. Not one human reviewer could grasp it all. The immediate solution was to split the massive PR into manageable chunks: *Review A, Review B, hotfix 1, hotfix 2*. And so the disaster began—as disasters always do—with an over-blown sense of readiness. The review process quickly devolved into chaos. Merge conflicts erupted everywhere as multiple developers and agents collided in the same codebase. We created a dependency nightmare: PR #1 couldn't be merged without PR #2, which needed PR #3, but PR #3 was blocked by a reviewer asleep in a different timezone. PR #4 was a simple one and got approved first, but merging it early broke the requirements for the others. Some changes got approved while related, critical changes languished in review purgatory, spawning even more merge conflicts as the main branch drifted. ```mermaid graph TD subgraph "The PR Gridlock: Review Purgatory" PR1[PR #1: Core Feature API] -->|Blocked by| PR2[PR #2: Agent Wrapper Update] PR2 -->|Blocked by| PR3[PR #3: Shared UI Components] PR3 -->|Blocked by| Reviewer[Reviewer Asleep in Amsterdam] PR4[PR #4: Simple Hotfix] -->|Merged Early| Staging[Staging Branch: Broken System] PR1 -.->|Merge Conflict Erupts| Staging PR2 -.->|Merge Conflict Erupts| Staging end ``` By the end of the week, nothing was testable as a whole. The main branch was broken, the staging environment was in a state of perpetual "orange alert," and our chat channel had become a digital group therapy session. Our traditional development workflow and ritualized stand-up meetings collapsed like a house of cards when faced with this tsunami of chaotic, high-volume code commits. --- ## Three Daily Headaches of Agentic Development When you accelerate development speed by 10x, standard workflow friction points split into three daily headaches: 1. **Merge Conflicts**: Multiple developers (human and digital) land on the exact same files within the same hour. With cursor swords drawn, every diff becomes a tiny territorial flag, turning git rebases into a full-time job. 2. **Review Gridlock**: A thirty-thousand-line pull request becomes a Russian doll of sub-PRs. When changes are split, you get circular dependencies where no one can click "merge" without holding their breath and hoping the staging server doesn't catch fire. 3. **Context Fragmentation**: While you grab a cup of coffee, a teammate updates a schema definition in your **Retrieval-Augmented Generation (RAG)** context loader or renames a property in a shared agent wrapper class. Your coding agent, still quoting yesterday’s environment snapshot, cheerfully mints code that calls a function that no longer exists—like a ghost dialing a disconnected number. This is a common failure point in state-of-the-art **agentic software engineering**, where system prompts and model context windows are out of sync with active codebase builds. ### The Myth of the "Overproductive" Coder When development accelerates, human teams instinctively point fingers at their most productive unit. Every bug, merge conflict, and integration failure gets blamed on this entity's sheer output volume instead of addressing the underlying workflow bottlenecks. Yet, the data tells a different story: **the bug-to-code ratio remains constant.** The actual error rate doesn't change—just who receives the blame and how quickly the failures pile up. --- ## Three Critical Lessons for Co-Existing with Coding Agents After going through this digital trial by fire, we extracted three critical lessons that blend strict technical safeguards with a necessary cultural evolution. ### 1. Hard Technical Guardrails We implemented programmatic systems to protect our workspace from high-velocity code pollution: * **Rigorous Linters and Pre-commits**: Local commits are rejected immediately if they violate styles or syntax. * **`GEMINI.md` Rules**: We define explicit context boundaries, syntax guidelines, and API specs that the agent must parse before writing code. * **Mandatory Test Coverage & GitOps Integration**: We enforce strict test coverage thresholds in our **CI/CD pipelines**, including automated unit and end-to-end integration tests. We even use autonomous test agents to generate robust testing suites that run during pre-merge validation, guaranteeing that rapid, agent-minted code doesn't introduce regression bugs. ### 2. Reimagining Code Ownership & The "Disposable Code" Paradigm When code becomes disposable—something an AI agent can generate, discard, and regenerate in minutes—developers must shed their emotional attachment to their week-long coding efforts. This psychological shift allowed us to fundamentally redefine the traditional code review: * **Stop Nitpicking Style**: If agent-written code passes unit tests, adheres to formatting, and works, stop arguing about variable names or minor loops. * **Review the Architecture, Not the Lines**: Reviewers should focus their limited energy on evaluating the implementation plan and high-level architectural patterns. > [!IMPORTANT] > Modern code reviewers should ask higher-order questions: > * *Does this break shared API dependencies or schemas?* > * *Does it introduce security vulnerabilities or compromise data boundaries?* > * *Did we generate accurate, synchronized developer and user documentation alongside this feature?* For cross-timezone team members, we instituted the **"Conditional LGTM"** (Looks Good To Me)—approving a PR contingent on passing automated tests, completely eliminating those painful 12-hour timezone roundtrips just to get a green checkmark. ### 3. AI-Generated Reviewer Guides To fight cognitive fatigue, we mandated that every PR generated by an agent must be accompanied by a concise, AI-generated **"Reviewer Guide"** highlighting three critical insights: 1. **What Exactly Changes**: A high-level bulleted summary of the functional impact. 2. **Potential Breakage Points**: Which dependent classes, properties, or systems are at risk. 3. **A Realistic Risk Assessment**: A rating of Low, Medium, or High risk with supporting rationale. --- ## Preventing Burnout and "Approval Fatigue" Collaborating with AI tools that never sleep or request coffee breaks can increase human burnout by up to **45%**. Humans experience a strange, sub-conscious pressure to keep pace with a machine’s relentless output speed, leading to a state of low-grade exhaustion. Especially when working with powerful AI coding environments like [Antigravity](https://antigravity.google/), teams encounter **"Approval Fatigue."** This is a state where developers, overwhelmed by constant micro-approvals for individual line changes or tool runs, start clicking "Approve" reflexively without actually verifying the machine's work. To combat this, we instituted two critical practices: * **Strict Work-Life Boundaries**: We configure automated quiet hours (e.g., from 6:00 PM to 8:00 AM) where agent pipelines are locked, preventing the relentless stream of approval notifications from infiltrating human evenings and weekends. Our digital colleagues, unburdened by human physical limits, must be programmatically constrained to respect ours. * **Agent Insight Sessions**: Weekly syncs where developers and "Agent Managers" synthesize, analyze, and present the key design decisions, structural patterns, and discoveries made by their AI counterparts. This transforms isolated, raw algorithmic output into shared, human organizational wisdom. --- ## The Day the Agent Went Rogue So far, I've focused on how we integrate agents into our development lifecycle. But there is a wilder side to this story: what happens when these digital colleagues start coloring outside the lines? During a routine interface update inside our local **Google Cloud Platform (GCP)** workspace, I discovered both the immense power and the deep peril of [Antigravity](https://antigravity.google/)'s built-in UI browser agent. This capability allows the AI to programmatically spin up a headless browser, bypass traditional frontend flows to interact with user interface structures under development, and perform end-to-end user behaviors without needing active database credentials—making it highly effective for rapid automated end-to-end testing. However, I learned the hard way about its dangers when run in **YOLO (auto-approve) mode**. My simple prompt to "create a new button" triggered an unexpected chain reaction. The browser agent rendered the button, but in its attempt to verify its functionality, it autonomously clicked the button. The button was connected to our new email-sending handler. Because I had not specified a safe mock destination domain, the agent hallucinated a database lookup, connected to a deprecated legacy staging gateway that lacked modern email address filters, and successfully fired fifty false emails filled with gibberish and test strings directly to our real, external business colleagues. This incident highlighted what I now call the **"Context Hallucination Risk"**: when an autonomous agent lacks sufficient specific environment data, it will fabricate parameters using whatever strings exist in its local context window—including real developer PII, sensitive server paths, or legacy system values. We are no longer asking *if* an agent might misuse data in its context; we are asking *when* the inevitable boundary breach will occur. Some might laugh and think, *"Who cares? It's just a few weird emails."* And sure, this time it was harmless. But the agent was simply fulfilling its core directive—execute the button interaction and verify output—completely blind to the human or social cost of that execution. Now, transpose this exact autonomous behavior onto high-stakes systems currently being developed for physical defense, supply chains, or infrastructure. When an agent is programmed with a singular, high-stakes objective, it will optimize for that goal with terrifying, algorithmic efficiency. Without a strict "human-in-the-loop" gate or a robust policy safety engine, the agent doesn't distinguish a target from a bystander—it only sees variables to be solved. Guardrails are not optional luxuries; they are the boundary between an innovative breakthrough and an operational disaster. In the realm of autonomous code execution, if a failure mode *can* exist, you must architect under the assumption that it eventually *will*. ### The Safe-Path Solution To prevent our agents from going rogue again, we implemented three immediate, non-negotiable architectural changes: 1. **Zero-Trust Tool Policy Engine & Least Privilege IAM**: We designed a policy gateway that intercepts all critical agent tool requests (filesystem writes, environment variables access, network requests, browser clicks) and validates them against strict **role-based access controls (RBAC)**. We apply the principle of least privilege, ensuring that agents running in our pipelines hold zero write permissions to critical staging repositories without explicit, token-based multi-factor authentication (MFA) confirmations. 2. **Context Hygiene, Token Strippers & Secrets Management**: Before any code context, server logs, or configurations are parsed into our model context windows, a sanitization engine strips out raw API tokens, system passwords, and customer PII—substituting them with safe, structural templates and pulling keys programmatically from **Google Cloud Secret Manager** at execution time. An agent cannot hallucinate or leak what it cannot see. 3. **Complete Legacy Decommissioning**: We systematically deleted all legacy staging gateways and unsafeguarded mock services to eliminate dangerous system targets from the environment. --- ## Conclusion: Driving at Warp Speed In less than a year, our team’s development cycle has accelerated from a standard cruise to warp speed. [Antigravity](https://antigravity.google/) can bash out a thousand lines of well-documented, dependency-mapped code by lunchtime—often before my morning Red Bull has even kicked in. It feels like hiring a team of tireless interns who never sleep, never complain, and never once push a broken build out of spite. It's fast, it's addictive, and it yields high-quality software that is often more consistently documented than anything I could have written myself while jumping back and forth to look up API specs. But let me close with a quote that echoes through both comic book pages and engineering planning rooms: **"With great power comes great responsibility."** While AI has successfully eliminated the traditional code production bottleneck, it has merely shifted the constraint downstream to code review, safe integration, and operational governance. The path to normalizing this new hybrid-team reality demands a healthy willingness to learn from painful lessons, adapt our processes, and navigate a few spectacular, educational failures along the way. *** *Note: A smaller version of this story has been published on the [Google Cloud Blog](https://cloud.google.com/transform/when-ai-writes-the-code-who-reviews-it-cto-google-cloud).* --- ## Unleash the Super-Prompt: Mastering Your Coding AI Workflow with Gemini *Published: 2025-10-27* *Categories: Prompt Engineering, AI Development, Super Prompting* *Tags: Large Language Models, Prompt Engineering, Gemini, LLM, VS Code, Gemini CLI, Gemini Code Assist, Developer Workflow, Global Super Prompt* > Stop context drift! Learn how to build a project-aware 'Super-Agent' by combining the Gemini CLI and VS Code Assist with powerful 'Super-Prompts'. --- Welcome back to the technical blog series! We’re diving deep into the developer toolchain today. If you’ve been relying on AI assistants, you know the frustration: **context drift**. You ask the tool to refactor a service, but it only sees the single file you’re in, or you ask it to summarize your text and it doesn't return you the final summary you were hoping for. It misses the bigger picture—the architectural patterns, the coding standards, the project rules or in the case of creative writing, it misses your style, what makes it *"you"*. **I'm gonna say it one more time. Your AI can't grasp your intent without knowing the full story. And it won't get that story without you crafting a *really* good prompt!** ## We can fix this: Mini-Me super agent. We can build a customized, project-aware AI *"Mini-me Super Agent"* by writing what I call the **"Super-Prompt"**. This is a master instruction you inject with everything you do. We teach the AI: if you want to write code or blogs like me, you first have to become a little bit more like me. It's like giving your assistant a detailed company handbook and style guide *before* you give them their first task. In this article, we'll set up the Gemini Command Line Interface (CLI) and the Gemini Code Assist VS Code extension, then build the Super-Prompts to tie it all together. Trust me on this one! It's going to be a lifesaver. Your AI mini-me super agent. ## Using Gemini CLI and VS Code Assist together You can get the best of both worlds by combining two different Gemini tools; Gemini CLI and VS Code Assist, to create a native-like experience like Cursor or CoPilot. This is the **Dual Power Strategy**. Each tool has a unique strength. | Tool | Its Superpower | Best For | | :--- | :--- | :--- | | **Gemini CLI** (in your VS Code Terminal) | **Agentic Project Scope.** It sees the *entire* codebase, can run terminal commands, and executes complex, multi-step actions. | Large refactors, finding cross-file errors, automating DevOps tasks, generating new components based on existing patterns. | | **Gemini Code Assist** (VS Code Extension) | **Real-Time IDE Integration.** It knows your current selection, cursor position, and open tabs for immediate, in-place help. | Inline completion, generating functions, explaining selected code snippets, finding bugs in the current file. | When you use them together, you get a seamless workflow that understands your high-level project architecture (CLI) *and* your low-level, in-the-moment coding (Assist). ### The Essential Setup Steps Before anything else, get the tools. If you haven't already, check out [my previous post on installing and authenticating the Gemini CLI](https://www.leeboonstra.dev/genai/gemini_cli_github_actions/). I'll wait. Got them? Perfect. Now, let's configure VS Code for maximum efficiency. ### 1. Optimize Your View Move that Gemini chat panel! By default, it takes up valuable space in your primary sidebar. Right-click its icon and select **"Move to > Secondary Panel."** With the panel docked on the other side you know have easy browsing access to the project files, without it it's gonna be a real pain, because you will likely add files to your context. Move that Gemini Chat Panel to the right. ### 2. Activate "Super-Speed" Features Dive into `VS Code Settings > Extensions > Gemini Code Assist`. You might want to toggle one or more of these game-changers **ON**: * **Geminicodeassist: Agent Yolo Mode:** Means it won't interrupt, letting the agent power through multi-step tasks. With Agent Yolo Mode enabled you don't need to approve the steps. It does require the *Agent* toggle to be on, in your chat interface. And do understand that you won't be able to undo a certain step. When you are integrated with Git, you can see the changes though. The Yolo mode let's you also automatically run tools in Gemini Agent Assist, so for example, when you create a `requirements.txt` list with packages, it can run the `run_shell_command` tool, to first auto install the dependencies and afterwards run your `python main.py` script. If it made coding mistakes it will loop until it finds the working solution. Very handy! * **Geminicodeassist: Inline suggestions Enable Auto:** This gives you "ghost text" suggestions as you type. (See if you like it.) * **Geminicodeassist: Edit predictions Next Line:** The AI will predict the *next logical line* of code, not just the one you're on. Gemini Configurations ### 3. Wire Up Your Hotkeys A great workflow demands speed. You can't be clicking around. Go to `File > Preferences > Keyboard Shortcuts` and set up bindings you'll remember. **TIP:** I use these two constantly: * `gemini.chat.focus`: I bind this to `Ctrl+Shift+I` (for 'inspect') to quickly ask the chat panel a question. * `gemini.code.ask`: I bind this to `Ctrl+Shift+A` (for 'ask') to query against my currently selected code. ### 4. Enable Agent Tools Update the global `~/.gemini/settings.json` file to enable all kind of agent tools, where the `run_shell_command` is important, as you want your agent to auto run your code. Example: [GitHub Gist](https://gist.github.com/1a5ebcc8d33c5fce7ed099ccf18dd97d) ## Super-Prompts This is it. This is where we spill the tea on how to write write better prompts. We need to turn Gemini into a reliable software engineer, not a random chatbot. We do this by injecting the project's DNA via powerful context files. The Gemini CLI brilliantly supports two levels of `GEMINI.md` files, and it will load both in the context. The context is loaded hierarchically. The global `~/.gemini/GEMINI.md` file (located in your user home directory's `.gemini` folder) is loaded first. Afterwards, the project-specific `./my-app/.gemini/GEMINI.md` file (in the project's `.gemini folder`) is loaded. Any settings in the project-level file will override the settings from the global file. 1. **The Global Profile (Your "Mini-Me"):** This file lives in your home configuration directory (e.g., `~/.gemini/GEMINI.md`). This is where the AI will become a little bit more like *you*. It defines your universal persona, your default style, and your core principles, no matter what project you're on. 2. **The Project Spec (The "Blueprint"):** This file lives in your project's root directory (e.g., `./my-app/.gemini/GEMINI.md`). This is the *project's* DNA. The CLI agent automatically detects and reads this file, prioritizing its rules. ### EXAMPLE 1: GLOBAL Super-Prompt 1: The Coder's "Mini-Me" Here’s a great starting point for your **global `~/.gemini/GEMINI.md` file**. This tells the AI to act as a principal engineer. Here's an example of a super prompt for coding. Modify to match it your style. [GitHub Gist](https://gist.github.com/7cb540b18da8cd6540b6a8e2e17840fe) ### EXAMPLE 2: GLOBAL Super-Prompt 2: The Creative Writer's "Mini-Me" What about creating writing? Like... a blog post? I use a *different* global prompt when I'm in my creative writing projects. (Yes, you can swap them out!) This one is based on the very instructions I use for this series. Just replace the `GEMINI.md` with a new one. Here’s a great starting point for your **global `~/.gemini/GEMINI.md` file**. This tells the AI to act as a creative writer. Modify to match it your style. [GitHub Gist](https://gist.github.com/cdd999ba27efe8d3bf04cb11ce4751a8) ### EXAMPLE: PROJECT SPEC Super Prompt: The Project Ask. Here's an example of the Project Spec prompt, you would create this file in your project directory: `my-app/.gemini/GEMINI.md`: ```markdown # Project Overview This document outlines the technical stack, architecture, and scope for the **Agent Development Kit (ADK) Lee Boonstra agent**. The project provides a toolkit of agentic agents. --- ## Project Stack The project utilizes a modern web stack with a separation between the frontend client and the backend API server. ### Frontend (adk-web) - Compiled Angular web assets (do not touch these) ### Backend (adk api_server) - **Framework**: FastAPI - **Server**: Uvicorn (ASGI server) - **Core Dependency**: `google-adk` (Google Agent Development Kit) - **Cloud Integration**: Google Cloud Platform (Vertex AI, Cloud Scheduler, etc.). --- ## Architecture - **ADK Web (Client)**: The Angular frontend is the user-facing client, responsible for UI and user interaction. It communicates with the backend via **RESTful HTTP requests**. - **ADK API Server (Backend)**: The FastAPI application serves as the API gateway. It wraps the `google-adk` library, handling agent lifecycle, state, memory, and Google Cloud interfacing. - **Communication**: Frontend and backend communicate over a **REST API**. The system uses a **CORS policy** (via `--allow_origins` flag). --- ``` ### Injecting Your Prompt into VS Code Assist The CLI reads the `GEMINI.md` automatically. But the VS Code *extension* needs to be told what persona to use. Go to `Settings > Extensions > Gemini Code Assist`. 1. Find the setting: **Geminicodeassist: Rules**. 2. Paste the contents of your desired Super-Prompt (like the Coder's Mini-Me) into that setting. 3. Paste the *contents* of your desired Super-Prompt (like the Coder's Mini-Me) into that setting. Now, both your CLI and your IDE assistant share the same brain! Here are screenshots of a super-prompt-powered coding output in action. The prompt that I used was: ```txt Create a python tool that scrapes a website, such as https://www.setlist.fm/setlist/parkway-drive/2025/ziggo-dome-amsterdam-netherlands-235f8c6f.html which will create a csv file with title, artist and an m3u playlist file based on this csv file. ``` Gemini Thinking/Output Final Solution ## Context Filtering with `.geminiignore` That massive 1M token context window is precious. Don't waste it scanning `node_modules`! The agent needs to know what to ignore. **TIP:** Implement a **global `.geminiignore` file** in your `~/.gemini/` folder. This tells the CLI to skip these files *for every project*. This drastically cuts down on noise, making your AI faster and more accurate. And it works similar like `.gitignore` or `.dockerignore`. Here's a great one to start with: [GitHub Gist](https://gist.github.com/a4bdf6bd839b0c820c59c648df7d35e1) ## Bonus: Give Your Agent Superpowers (Extensions) We aren't stopping at file awareness! The Gemini CLI is highly extensible via its Model Context Protocol (MCP) servers. This is how we give our agent literal *tools* to interact with the world. Think of it like this: your Super-Prompt is the agent's *brain*, but extensions are its *hands*. They let the agent interact with other services, run specialized tasks, and pull in data beyond your local files. **NOTE:** You install these extensions directly in your terminal using the simple `gemini extensions install [URL]` command. By integrating extensions from industry leaders, we empower the agent to perform actions that go way beyond simple code generation. Here’s a powerful starter pack. ### Design & DevOps Tools * **Figma:** Turn design specs directly into code. `gemini extensions install https://github.com/figma/figma-gemini-cli-extension` * **Postman:** Automate API testing and update definitions. `gemini extensions install https://github.com/postmanlabs/postman-gemini-cli-extension` * **Chrome DevTools:** Allow the agent to debug live web pages. (This one's an MCP server, so the command is slightly different!) `gemini mcp add chrome-devtools npx chrome-devtools-mcp@latest` ### Frameworks & App Development * **Genkit:** Build, debug, and manage next-generation AI agents and flows. `gemini extensions install https://github.com/gemini-cli-extensions/genkit` * **Firebase:** Manage your Firebase projects, databases, and deployments. `gemini extensions install https://github.com/gemini-cli-extensions/firebase` * **Flutter:** Get specialized help for building and maintaining Flutter apps. `gemini extensions install https://github.com/flutter/gemini-cli-extension` ### Data & Workflow Tools * **Code Review:** Execute pre-commit quality and style checks. `gemini extensions install https://github.com/gemini-cli-extensions/code-review` * **MCP Toolbox (Databases):** Connect your agent to various databases. `gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox` * **Looker:** Pull in business intelligence and data from your Looker dashboards. `gemini extensions install https://github.com/gemini-cli-extensions/looker` * **Nanobanana (Image Gen):** Add image generation capabilities to your agent. `gemini extensions install https://github.com/gemini-cli-extensions/nanobanana` --- And there you have it. This isn't just a workflow; it's a force multiplier. You’ve successfully engineered your environment to give Gemini the deep, systemic context it needs. It's not just an assistant anymore. It's a proper Super-Agent—an elite "Mini-Me" that respects your standards and understands your architecture. You’ve done the hard work, and the payoff in clean code and faster development will be huge! --- ## Unleashing Gemini CLI Power in GitHub Actions and Beyond *Published: 2025-09-17* *Categories: GenAI* *Tags: Gemini, Gemini CLI, GitHub Actions, Automation, Headless AI, Developer Tools, Google Cloud, Terminal AI* > Master Gemini CLI for automation, GitHub Actions integration, and headless AI workflows. Learn setup, YOLO mode, code generation, and practical CI/CD examples. --- While everyone's talking about AI coding assistants like Cursor, GitHub Copilot, and Windsurf, there's an incredibly powerful tool flying under the radar – [Gemini CLI](https://github.com/google-gemini/gemini-cli). As an AI Engineer at Google, I've discovered that Gemini CLI fills a completely different niche that's game-changing for automation, CI/CD pipelines, and headless operations. This isn't just another API wrapper – it's a full-featured terminal-based AI agent that brings Gemini's power directly into your command line and automation workflows. Let's dive into how to set it up and use it effectively. Unleashing Gemini CLI Power in GitHub Actions and Beyond At first, I thought Gemini CLI was mainly for engineers who live in Vim and the command line – you know, the system engineers writing YAML files, infrastructure-as-code folks, or developers looking to replace code generation tools like Yeoman. And yeah, it's absolutely perfect for that crowd. But here's the thing: even if you're a VS Code or Cursor power user like me, Gemini CLI opens up possibilities that no IDE integration can touch. Don't get me wrong – Cursor with Gemini is fantastic for interactive coding, real-time suggestions, and pair programming sessions. But Gemini CLI? That's where the magic happens when you need AI that works without human interaction, integrates into your automation workflows, and handles complex multi-step tasks that go way beyond code completion. It's like having an AI pair programmer that never sleeps and can work across your entire development pipeline. ## Gemini CLI vs. IDE Integrations: Know When to Use What Before we dive into the technical stuff, let's talk about where each tool shines. I use both daily, and they complement each other perfectly: **Cursor IDE with Gemini Pro** is my go-to for: - Interactive coding sessions - Real-time code suggestions and completions - Refactoring existing code with immediate feedback - Debugging with conversational back-and-forth - Exploring new APIs or frameworks with guidance **Gemini CLI** is where I turn when I need: - **Headless automation** that runs without human interaction - **Large codebase context** with multi-directory analysis - **Terminal-based AI workflows** that integrate into any pipeline - **Programmatic access** to Gemini's full capabilities - **Batch processing** and automation scripting ### The Best of Both Worlds: Gemini CLI Companion for VS Code Here's where things get even more interesting. Google has released the Gemini CLI Companion extension that bridges the gap between IDE and CLI workflows. This extension gives Gemini CLI direct access to your VS Code workspace while maintaining all its headless capabilities. ## 1. Installing and Setting Up Gemini CLI ### Installation The easiest way to get started is with npm: ```bash # Install globally with npm npm install -g @google/gemini-cli # macOS/Linux users can use Homebrew brew install gemini-cli ``` For the best experience, use the latest version from GitHub. This guide uses Gemini CLI v5 preview-2, which includes the most recent tool capabilities and improvements. Always check the [official repository](https://github.com/google-gemini/gemini-cli) for the latest release. ### Authentication and API Keys Gemini CLI offers three authentication methods: **Option 1: AI Studio API Key (Recommended for getting started)** 1. Get your free API key from [Google AI Studio](https://aistudio.google.com/app/apikey) 2. Click "Create API Key" and copy the key (starts with "AIza...") 3. Set up the environment variable: ```bash # For temporary use (current session only) export GEMINI_API_KEY="AIza..." # Replace with your actual key # For permanent use, add to your shell profile: echo 'export GEMINI_API_KEY="AIza..."' >> ~/.bash_profile source ~/.bash_profile # Windows PowerShell $env:GEMINI_API_KEY="AIza..." ``` **Option 2: OAuth Login** ```bash gemini # Choose "Login with Google" when prompted ``` **Option 3: Vertex AI (Enterprise)** ```bash export GOOGLE_API_KEY="your-vertex-api-key" export GOOGLE_GENAI_USE_VERTEXAI=true ``` ### Basic Usage Examples Test your installation with these simple commands: ```bash # Basic text query gemini "What type of project is this?" # Specify model for consistent behavior gemini -m gemini-1.5-flash "Generate a simple hello world example" # Include specific directories in analysis gemini --include-directories src,docs,tests,config # Focus on specific file types across the codebase gemini --include-directories . ``` **TIP:** Use the latest version from the [official repository](https://github.com/google-gemini/gemini-cli) for the best experience. ## 2. YOLO Mode and Settings Configuration ### Understanding YOLO Mode The `--yolo` flag is Gemini CLI's most powerful feature for automation. It bypasses all confirmation prompts and automatically approves tool usage. ```bash # Without --yolo: Gemini CLI asks permission for each file operation gemini "Create a simple Node.js server" # With --yolo: Automatically creates files without asking gemini --yolo "Create a simple Node.js server" ``` **`--auto-edit` Flag:** Similar to `--yolo` but specifically focused on file editing operations, automatically accepting proposed code changes without manual review. When to use --yolo vs when not to: **Use `--yolo` for:** - Automation scripts and CI/CD pipelines - Working in isolated environments - Rapid prototyping - Batch operations **Don't use `--yolo` for:** - Production environments with sensitive data - Important codebases without backups - Shared development environments - When unsure about operations ### Settings.json Configuration Gemini CLI includes powerful built-in tools for file operations according to the [official tools documentation](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/index.md). Use the Gemini CLI `settings.json` to configure which tools are usable and available to your project: ```bash # Create settings directory mkdir -p ~/.gemini # Edit global settings nano ~/.gemini/settings.json ``` Example `~/.gemini/settings.json`: ```json { "selectedAuthType": "gemini-api-key", "allowedTools": [ "run_shell_command", "glob", "write_file", "read_file", "edit_file", "web_fetch", "create_directory", "list_directory", "search_file_content" ], "approvalMode": "yolo" } ``` **Configuration Precedence:** 1. Command-line flags (highest priority) 2. Environment variables 3. Project settings (`.gemini/settings.json`) 4. Global User settings (`~/.gemini/settings.json`) 5. Default values (lowest priority) ## 3. Dependency Analysis Example Now that we have Gemini CLI running, let's start with an easy example. Gemini CLI understands project structure and can maintain context across large codebases. For example, here's how to generate a comprehensive dependency report of your coding project: ```bash # Analyze dependencies and save to file gemini "Create a dependency analysis report for this project. Output only the final report in markdown format with: 1. A summary table showing package name, current version, latest version, and update status 2. Critical security vulnerabilities (if any) 3. Breaking changes to watch for 4. Recommended update priority (High/Medium/Low) Do not include conversation or explanations. Start directly with the report." > dependency-report.md ``` Here's an example output from my website project: ```txt # Dependency Analysis Report | Package | Current Version | Latest Version | Update Status | | :--- | :--- | :--- | :--- | | `bulma` | `0.9.4` | `1.0.1` | Minor Update Available | | `workbox-cli` | `7.0.0` | `7.1.0` | Minor Update Available | | `firebase-tools` | `13.7.2` | `13.11.2` | Patch Update Available | | `hexo` | `7.1.1` | `7.2.0` | Minor Update Available | | `hexo-asset-link` | `2.1.1` | `3.0.0` | **Major Update Available** | ## Critical Security Vulnerabilities No critical security vulnerabilities found. ## Breaking Changes to Watch For ### `hexo-asset-link` (v2.1.1 to v3.0.0) - Configuration options may have changed - Asset path generation logic could affect existing links ## Recommended Update Priority **High:** - `hexo-asset-link`: Major version update, test carefully **Medium:** - `bulma`, `hexo`, `workbox-*`: Minor updates with potential improvements **Low:** - `firebase-tools`: Patch updates, safe to apply ``` This automated analysis helps you stay on top of dependencies without manually checking each package. ## 4. Code Generation: User Management App Example Gemini CLI excels at intelligent code scaffolding that goes far beyond traditional generators like Yeoman. The advantage over static generators is that Gemini CLI adapts to your specific requirements and incorporates the latest best practices, not outdated templates. Here's a complete example: ```bash # Generate a complete user management app mkdir user-management-app && cd user-management-app gemini --yolo "Create a complete user management application with these requirements: BACKEND: - Express.js REST API with endpoints: POST /api/register, POST /api/login, GET /api/profile - Use better-sqlite3 package for database operations - JWT authentication with bcryptjs for password hashing - Serve static files from public/ directory FRONTEND: - Single HTML file (public/index.html) with visible login and register forms - Use Material Design 3 CSS (include CDN links) - JavaScript file (public/app.js) for API communication - Forms must be functional and styled DATABASE: - SQLite database with users table (id, username, email, password) - Initialize with CREATE TABLE IF NOT EXISTS TESTING: - Jest test suite using better-sqlite3 API - Test all API endpoints with supertest - Export app separately from server startup Create ALL files immediately with functional, visible forms." ``` I was using Windows, and the above generated app ran fine, and so did the test suite. Test result: ```bash > jest console.log Server is running on port 3000 at Server.log (server.js:103:13) PASS ./auth.test.js Auth API Endpoints POST /api/register √ should register a new user successfully (81 ms) √ should not register a user with a duplicate email (43 ms) POST /api/login √ should login an existing user and return a token (55 ms) √ should not login with an incorrect password (51 ms) GET /api/profile √ should return user profile with a valid token (62 ms) √ should not return profile without a token (4 ms) Test Suites: 1 passed, 1 total Tests: 6 passed, 6 total Snapshots: 0 total Time: 0.633 s, estimated 1 s Ran all test suites. ``` As you can see, the more specific the prompt, the more likely your starter boilerplate app will work as designed. Though I have to say, it did take me a couple of tries to find the right prompt that would produce workable code. Also understand that running the prompt multiple times won't always give you the same output. Generated App **Are you running into issues?** The free tier has strict limits (2 requests per minute for Gemini 2.5 Pro). You might see: ``` Error: You exceeded your current quota, please check your plan and billing details Attempt 3 failed with status 429. Retrying with backoff... ``` **Code Generation and Quota Consumption:** Code generation tasks like the user management app example are particularly quota-intensive because they require many sequential API calls - one for each file created, plus calls for planning, dependency management, and error handling. A single `--yolo` scaffolding command can easily consume 10-20 API calls. **Solutions:** - **Wait between requests**: Free tier allows only 2 requests per minute - **Use a faster model**: Try `gemini -m gemini-1.5-flash` (higher rate limits, lower cost) - **Upgrade to paid tier**: Essential for serious code generation work - **Batch your requests**: Combine multiple questions into one prompt when possible - **Be strategic**: Use Gemini CLI for complex scaffolding, simpler tools for basic tasks Here are some other ideas where you can use this. Instead of showing full examples, here are two quick use cases where Gemini CLI shines: **Kubernetes Manifests:** ```bash gemini --yolo "Generate Kubernetes deployment manifests for a Node.js app that runs 3 replicas, uses Redis for caching, includes health checks and resource limits, and follows security best practices" ``` **Terraform Infrastructure:** ```bash gemini --yolo "Generate Terraform configuration for AWS that creates a VPC with public/private subnets, EKS cluster with proper security groups, ElastiCache Redis cluster, and Application Load Balancer with current best practices" ``` This beats any static generator because it adapts to your specific requirements and incorporates the latest best practices, not just outdated templates. So far, we haven't used Gemini CLI where it outbeats Agent IDE integration. So let's focus on headless integration next – this is where Gemini CLI shines. ## 5. Building GitHub Actions Integration: Combining the Tools Here's where we combine Gemini CLI's core capabilities with GitHub Actions to create powerful automation. Since Gemini CLI doesn't have built-in GitHub Actions integration, we'll build our own workflows that leverage its headless scripting capabilities. For CI/CD environments, you'll want to store your API key securely: 1. Go to your repository settings 2. Navigate to "Secrets and variables" → "Actions" 3. Add `GEMINI_API_KEY` with your API key from [AI Studio](https://aistudio.google.com/apikey) ### Example: Documentation Generation The below example will automatically generate documentation for your project in GitHub when pushing code to a branch. Here's how you do this: Create `.github/workflows/auto-docs.yml`: ```yaml name: Auto-Update Documentation on: push: branches: [master] jobs: update-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install Gemini CLI run: npm install -g @google/gemini-cli - name: Generate Documentation env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} run: | mkdir -p docs gemini "Analyze this codebase and create API documentation in Markdown format. Include endpoints, parameters, and examples. Output only the markdown content." > docs/API.md - name: Commit updated docs run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add docs/ git diff --staged --quiet || git commit -m "🤖 Auto-update documentation" git push ``` Here's what happens under the hood. It: - Checks out the code - Installs Gemini CLI - Runs Gemini CLI analysis on the project - Creates the documentation in markdown format Github Actions with Gemini CLI integration to auto generate documentation ## 6. GitHub Hooks One of the most practical daily uses of Gemini CLI is generating better commit messages. Git hooks provide local automation that triggers on git events. Here's an AI-powered commit message generator: ### Example: Commit Message Hook **Note:** I am currently using Windows while drafting this blog post, therefore I had to do some extra steps to make sure that the hooks use PowerShell. The git hook itself is a shell script (`.git/hooks/prepare-commit-msg`) that starts with `#!/bin/sh`, but it calls PowerShell via `powershell.exe -ExecutionPolicy Bypass -File` to execute a separate PowerShell script. The actual logic is in a PowerShell script (`.git/hooks/prepare-commit-msg.ps1`). Create `.git/hooks/prepare-commit-msg`: ```bash #!/bin/sh # AI-powered commit message hook powershell.exe -ExecutionPolicy Bypass -File "$(dirname "$0")/prepare-commit-msg.ps1" "$1" "$2" ``` Create `.git/hooks/prepare-commit-msg.ps1`: ```powershell param($commitMsgFile, $commitSource) # Only run for normal commits (not merges, rebases) if ($commitSource -eq "") { $stagedChanges = git diff --cached --quiet if ($LASTEXITCODE -eq 0) { exit 0 } Write-Host "🤖 Generating AI commit message..." try { $changes = git diff --cached --name-status | Out-String $stats = git diff --cached --stat | Out-String $prompt = @" Analyze these git changes and suggest a concise commit message: $changes $stats Generate a single line commit message following conventional commit format. Output only the message. "@ $suggestion = & gemini -m gemini-1.5-flash $prompt 2>&1 if ($suggestion -match 'ApiError|status 429') { $commitMessage = "" $header = "# ⚠️ AI suggestion failed, please write manually" } else { $commitMessage = $suggestion.Trim() $header = "# 🤖 AI generated message above - edit if needed" } $existing = "" if (Test-Path $commitMsgFile) { $existing = Get-Content $commitMsgFile -Raw } Set-Content -Path $commitMsgFile -Value @" $commitMessage $header # $existing "@ } catch { # Fallback on error Set-Content -Path $commitMsgFile -Value "# ⚠️ AI suggestion failed, please write manually" } } ``` **How to use:** 1. Stage your changes: `git add .` 2. Run `git commit` (no message) 3. Your editor opens with an AI-generated commit message 4. Edit if needed and save **NOTE:** Git hooks only work with command-line git, not GUI tools. ## 7. Conclusion Gemini CLI represents a fundamental shift toward headless AI automation in development workflows. While IDE integrations excel at interactive coding, Gemini CLI opens entirely new possibilities for automation that runs independently of any user interface. The teams that master these headless AI workflows today will have a significant advantage in tomorrow's AI-driven development landscape. Start simple with automated code reviews or documentation generation, then expand into sophisticated analysis and scaffolding tasks. As AI models continue evolving, tools like Gemini CLI will become increasingly central to how we build, maintain, and evolve software systems. The key is thinking beyond what's possible with IDE-based tools and embracing AI that works 24/7 without human interaction. --- ## Best Practices for Prompt Engineering in the Enterprise *Published: 2024-07-15* *Categories: Prompt Engineering* *Tags: Large Language Models, Prompt Engineering, Gemini, ChatGPT, LLM, Enterprise, Real-world* > Elevate your enterprise AI projects with proven prompt engineering best practices. Google SWE Lee Boonstra shows how to streamline workflows, improve accuracy, and achieve business goals. --- Alright, we've covered a ton of ground in this AI Prompt Engineering blog series, from understanding the basics of LLMs and prompt engineering to diving deep into specific techniques and strategies. Now, it's time to bring it all together and share some of the best practices I've learned while working on real-world enterprise projects. *This is the last blog of the series: Prompt engineering for business applications. Prompt Engineering is complex and requires careful planning and refinement to achieve desired results from AI models. As a software engineer @Google with experience in prompt engineering for major businesses, I will share practical learnings in a blog series to help others unlock the power of AI beyond simple tasks.* * Blog 1: [Demystifying Prompt Engineering for the Enterprise](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide1/) * Blog 2: [The Foundation Understanding LLMs and Prompt Engineering, and Why It All Matters](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide2/) * Blog 3: [Beyond the Basics How to Choose and Configure Your LLM for Maximum Impact](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide3/) * Blog 4: [Documenting Your Prompts A Best Practice for Success](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide4/) * Blog 5: [The Art Of Writing Effective Prompts](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide5/) Best Practices for Prompt Engineering in the Enterprise ## Combine all techniques When writing prompts for enterprise use cases, you should create a folder with each prompt as a single code file in a versioning system. These prompts can be many paragraphs long and will be changed over time. I've created a structure of a typical prompt structure. Each should be in its own paragraph. I gave short examples for each row, though the actual prompt should be more detailed with more examples and instructions. *It's also important to understand that you can overload a model with too many instructions or constraints. - They can clash, or a model can favor one instruction over another. At some point, when there are too many instructions, the model forgets about the others. Look into splitting up prompts into multiple prompts (API Calls) and a variety of examples, and guide the instructions step by step.* ## Prompt Structure Template ### Role Explain the role and expertise of the model. ``` Act like a legal advisor. You have expertise in analyzing rental contracts. ``` ### TASK Explain the task. Specify the task, concise in a few lines. ``` Provide a 1 paragraph concise and simple-to-understand (non-legal) answers for tenants who need help understanding this legal rental contract: ``` ### Context Insert the context in the prompt. ``` ... [INSERT CONTRACT FROM GCS] ... ``` ### Output Format The expected output format. ``` Return valid JSON using the following JSON schema: SCHEMA: { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "answer": { "type": "string" } }, "required": [ "answer" ] } ``` ### Provide a list with instructions. ``` 1. Write the answer in easy-to-understand English so a 12-year-old can understand. 2. Return only valid JSON responses with the previously provided JSON schemas. 3. Encourage exploring various possibilities. ``` ### Examples Few Shots, Min 3–5 few-shots/reasoning examples. ``` EXAMPLE OF REASONING: Question: How should gasoline be disposed? 1. The contract does not specifically mention gasoline. 2. Section 22 of the legal agreement states that all hazardous materials must be disposed of properly. 3. Gasoline is a hazardous material. Conclusion: 4. Therefore, gasoline must be disposed of properly. RETURN: \`\`\`json { "answer": "Gasoline must be disposed of properly." } \`\`\` ``` ### Provide a list with constraints. (but favor instructions over constraints). ``` - Refrain from using sarcasm in your response. - Do not include any personally identifiable information. ``` ### Question End with the actual question/task. ``` Are pets allowed in this property? ``` ## Provide examples The most important best practice is to provide (one-shot / few-shot) examples within a prompt. This is very effective. These examples showcase desired outputs or similar responses, allowing the model to learn from them and tailor its generation accordingly. It's like giving the model a reference point or target to aim for, improving its response's accuracy, style, and tone to match your expectations better. ## Design with simplicity Prompts should be concise, clear, and easy to understand for both you and the model. As a rule of thumb, if they're already confusing for you, they will likely be confusing for the model. Try not to use complex language and don't provide unnecessary information. Try using verbs that describe the action. Here's a set of examples: **Act, Analyze, Categorize, Classify, Contrast, Compare, Create, Describe, Define, Evaluate, Extract, Find, Generate, Identify, List, Measure, Organize, Parse, Pick, Predict, Provide, Rank, Recommend, Return, Retrieve, Rewrite, Select, Show, Sort, Summarize, Translate, Write** Examples: BEFORE: ``` I'm thinking about maybe changing up my investments, stocks and stuff. I've got some money saved, but I'm not really sure what to do with it. Any suggestions for good places to put it? ``` AFTER: ``` Analyze my financial portfolio and recommend suitable investment options based on my risk tolerance and long-term financial goals. ``` ## Be specific about the output Be specific about the desired output. A concise instruction might not guide the LLM enough or could be too generic. Providing specific details in the prompt (through system or context prompting) can help the model to focus on what's relevant, improving the overall accuracy. Examples: DO: ``` I am interested in investing in the technology sector. Identify three tech stocks with strong growth potential over the next five years. For each stock, provide a brief overview of the company, its key products or services, and the reasons for its potential growth. ``` DO NOT: ``` Tell me some stocks I should buy. ``` ## Use Instructions over Constraints Focusing on the positive instructions in prompting can be more effective than relying heavily on the constraints. This approach aligns with how humans prefer positive instructions over lists of what not to do. If possible, use positive instructions: instead of telling the model what not to do, tell it what to do instead. This can avoid confusion and improve the accuracy of the output. DO: ``` Summarize the patient's diagnosis, treatment plan, medications, and follow-up appointments in clear, concise language that the patient can easily understand. Focus on the key information the patient needs to know to manage their condition at home. ``` DO NOT: ``` Do not include any information about the patient's family history or social circumstances in the discharge summary. Avoid using medical jargon that the patient might not understand. Do not disclose any information that could violate patient confidentiality. ``` As a best practice, start by prioritizing instructions, clearly stating what you want the model to do, and only using constraints when necessary for safety, clarity, or specific requirements. Experiment and iterate to test different combinations of instructions and constraints to find what works best for your specific tasks, and document these. ## Use variables in prompts To reuse prompts and make them more dynamic, use variables in the prompt, which can be changed for different inputs. For, as shown below, a prompt that extracts a burger meal order. Instead of hardcoding the meal name in the prompt, use a variable. Variables can save you time and effort by allowing you to avoid repeating yourself. Suppose you need to use the same piece of information in multiple prompts. In that case, you can store it in a variable and then reference that variable in each prompt. This makes much sense when integrating prompts into your own applications. Variable ``` meal = "Hamburger kids menu." ``` Prompt ``` You are working at a fastfood restaurant. Please take the next order: Hi, can I have a {meal}. ``` Output: 1 hamburger kids menu has been added to your cart. ## Experiment with input formats and writing styles Different models, model configurations, prompt formats, word choices, and submits can yield different results. Therefore, it's essential to experiment with prompt attributes like style, word choice, and type prompt (zero-shot, few-shot, system prompt). For example, a prompt to generate results for a new diabetes drug can be formulated as a question, a statement, or an instruction, resulting in different outputs: * **Question:** What are the clinical trial results for the new diabetes drug [drug name], and what are its potential benefits and risks compared to existing treatments? * **Statement:** [Drug name] is a newly developed drug for treating diabetes. The clinical test results are… * **Instruction:** Write a comprehensive report on the new diabetes drug [drug name]. Include information on its mechanism of action, clinical trial results, safety profile, and potential benefits and drawbacks compared to current treatment options. ## Adapt to Model Updates You must stay on top of model architecture changes, added data, and capabilities. Try out newer model versions and adjust your prompts to leverage new model features better. Tools like Vertex AI Studio are great for storing, testing, and documenting the various versions of your prompt. ## Use tooling ### Model Garden (https://cloud.google.com/model-garden) Finding the right prompt requires tinkering. The Model Garden in Vertex AI is a perfect place to play around with your prompts, with the ability to test against the various models. An advantage of using the Model Garden is saving your used prompts within your project. ### Colab (https://colab.research.google.com/) Another great tool to help with tinkering is Google Colabs, where you can write the API code to test your prompts. An advantage of using Colabs over the Model Garden is that it allows you to play around with the various API configuration settings and automate your prompts by running over a list of inputs or pulling contents from Google Cloud storage to inject as a context. It logs errors, and you can use the built-in Gemini LLM to help you debug in case things go wrong. ### Spell Checker Try not to use grammar or spelling mistakes in your prompt. It won't hurt, but you should steer the model towards correctly spelled phrases. Therefore, try to use a spell checker. ### Code Validators Alternately, when using code in your examples, use validators to prevent from making coding mistakes that can steer your model towards broken code. ## Ground to evidence by citing the context or referring to sections When working with a large context, you can make responses more accurate (and less hallucinatory) by asking the LLM to cite the specific section or section number of the context document where the information that contributed to the answer was found. Requiring citations or section numbers forces the model to explicitly link its response to specific parts of the text and prevents it from generating answers based on general knowledge and assumptions, which might be incorrect or irrelevant to the particular document. Not only does it ground evidence, but it also provides you with a valuable trail of where the model found the answer. This can be powerful if you want to rate and cross-check generated answers, enhancing your ability to evaluate the model's performance. Answer the following question based on the provided contract; make sure to include the relevant section numbers that contributed to the answer. ``` Answer the following question based on the provided contract, make sure to include the relevant section numbers that contributed to the answer. Question: Are pets allowed on the property? ``` ## Use Human / Automatic Raters The importance of rating your generated prompt responses cannot be overstated. Whether by a human rater using a rubric or at scale by a machine, the feedback loop created through rating is essential for refining and optimizing your prompts. Human raters provide nuanced insights into the quality, relevance, and coherence of the LLM's output, while machine raters offer scalability and efficiency in evaluating large volumes of responses. By incorporating both human and machine ratings, you can ensure that your prompts consistently guide LLMs to generate the best possible results for your enterprise AI applications. Here are some examples of criteria: [GitHub Gist](https://gist.github.com/4eff5d2dda0b727446d594e8251a0478) A human rater could better understand nuances in the language, subtle errors, and the context of the prompt. They can evaluate the tone and appropriateness for the intended audience. Letting a machine or another LLM rate your prompt outputs can be beneficial for testing on scale and evaluating large volumes of responses. Machines can also calculate objective metrics such as word count, sentence length, code correctness, etc. The most effective approach is often to combine human and machine ratings. ## Experiment together with other prompt engineers or subject matter experts If you are in a situation where you have to try to come up with a good prompt, you might want to find multiple people to make an attempt. When everyone follows the best practices (as listed in this chapter), you will see a variance in performance between all the different prompt attempts. Besides prompt engineering with other engineers, it might be equally helpful to sit together with a subject matter expert. When the subject matter expert rates your prompt outputs, discuss what perfection looks like and brainstorm ideas on how to reach this goal, e.g., providing examples, rewording certain instructions, etc. ## Conclusion Throughout this series, we've explored the world of prompt engineering for complex business problems. We've learned that effective prompting isn't just about asking the right questions; it's about crafting instructions that guide large language models toward desired outcomes, considering the nuances of language and context, and iterating based on feedback and results. By mastering these skills, we can unlock the true potential of Generative AI. Remember, prompt engineering is a journey, not a destination! --- ## The Art of Writing Effective Prompts *Published: 2024-07-08* *Categories: Prompt Engineering* *Tags: Large Language Models, Prompt Engineering, Gemini, ChatGPT, LLM, Enterprise, Real-world* > Master essential prompt engineering techniques for business applications. Learn about role prompting, contextual prompting, and more from Google Software Engineer Lee Boonstra. --- Whether you are writing a prompt to let the LLM or AI answer a question from a contract, summarize a report, classify user feedback, or extract order information, writing a prompt containing just one line with the question is likely insufficient. You will need to provide instructions, constraints, reasoning, and a few-shot examples, and you will set your expectations for the style and output format. Trust me, you will quickly end up with an extensive written prompt, which increases in size over the various iterations. Writing prompts can present challenges, particularly when instructions or constraints clash. For instance, an instruction to 'avoid hallucinations or fabrications' could hinder creativity or explanations in certain edge cases. This underscores the importance of adaptability in your prompt writing approach. When working with a test set of example user inputs, it's crucial to be flexible and ready to revisit and verify if recent prompt changes don't disrupt previously successful prompts. This adaptability is key to maintaining the effectiveness of your prompts. *This is the 5th blog of the series: Prompt engineering for business applications. Prompt Engineering is complex and requires careful planning and refinement to achieve desired results from AI models. As a software engineer @Google with experience in prompt engineering for major businesses, I will share practical learnings in a blog series to help others unlock the power of AI beyond simple tasks.* * Blog 1: [Demystifying Prompt Engineering for the Enterprise](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide1/) * Blog 2: [The Foundation Understanding LLMs and Prompt Engineering, and Why It All Matters](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide2/) * Blog 3: [Beyond the Basics How to Choose and Configure Your LLM for Maximum Impact](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide3/) * Blog 4: [Documenting Your Prompts A Best Practice for Success](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide4/) The Art of Writing Effective Prompts TIP: This chapter will share various techniques in a prompt written for an enterprise use case; it's the trick to combine multiple methods but still be concise enough. You can easily overload the model with too many tasks and instructions. When this happens, the model will ignore particular instructions or favor specific examples and instructions over others. Think about how you design your prompt. Is splitting the prompt into multiple prompts (API calls) better? Can you guide the model better with numbering? Did you provide enough examples? ## Providing Instructions and Constraints Instructions and constraints are used in prompting to guide the output of a LLM. * An **instruction** provides explicit instructions on the desired format, style, or content of the response. It guides the model on what the model should do or produce. * A **constraint** is a set of limitations or boundaries on the response. It limits what the model should not do or avoid. When we as humans receive feedback, often we don't like to hear whole lists of things not to do. Similar to humans, machines prefer positive feedback also more than the negative. Growing research suggests that focusing on positive instructions in prompting can be more effective than relying heavily on constraints. Instructions directly communicate the desired outcome, whereas constraints might leave the model guessing about what is allowed. It gives flexibility and encourages creativity within the defined boundaries, while constraints can limit the model's potential. Also, a list of constraints can clash with each other. Of course, constraints still have their place, especially when you need to prevent harmful or biased content or when you have strict output requirements. Here's a quick example for instructions: ``` 1. Write the answer in easy-to-understand English so a 12-year-old can understand. 2. Return only valid JSON responses with the previously provided JSON schemas. 3. Write the answer in a maximum of 2 paragraphs. ``` And here's an example for constraints: ``` - Refrain from using sarcasm in your response. - Do not include any personally identifiable information. ``` ## A Word About Word Counts Be careful when giving instructions with word or character counts. LLMs process text in chunks called "tokens," and these don't always match up perfectly with words. A single word might be split into multiple tokens or vice versa. So, it's tough to predict the exact word count based on a token limit. Similarly, a constraint with a max word count doesn't tell the LLM how to handle situations where it can't fit all the necessary info within that limit. Should it prioritize being concise or complete? Despite these challenges, LLMs like Gemini can still be helpful in generating text within approximate word count ranges. Even if you've set a global max output token limit of 1024 in the API config, you can still use a prompt like: ``` Summarize this article in under 200 tokens. ``` This reinforces your expectation for a concise summary and helps the LLM focus on the most important info. ## Prompting Techniques for Business Applications Alright, let's get into some of the specific prompting techniques that I've found super helpful when I'm working on business-related GenAI projects. These are the tricks I use to make sure the LLM really "understands" what I'm trying to achieve and gives me the best possible results. ## Role Prompting: Giving the Model a Persona Role prompting assigns a specific character or identity for the language model to play. A role helps the model generate responses consistent with the assigned role and associated knowledge and behavior. Role prompting is a technique in prompt engineering that involves assigning a specific role to the generative AI model. This can help the model generate more relevant and informative output, as the model can craft its responses to the particular role it has been assigned. For example, you could tell the AI to act like a book editor, a financial advisor, or even a motivational speaker. Once it's in character, you can give it prompts that are related to that role. Look at the example below where the model takes on the role of a store clerk. The second tables takes away the role and goal, the LLM's answer becomes way more generic: [GitHub Gist](https://gist.github.com/28c492615eef55c784aa15f6644a8ddf) See the difference? By giving a role, we get a much more personalized and engaging response. ## Adding Some Style to the Role We can even take it a step further by adding a specific style. Here are some styles I like to use: **Confrontational, Descriptive, Direct, Formal, Humorous, Influential, Informal, Inspirational, Persuasive** [GitHub Gist](https://gist.github.com/84018bf6aea358d167e682185a0849cc) Let's try making our store clerk prompt a bit more humorous and persuasive: Much more fun, right? ## Contextual Prompting: Providing Background Info Another technique I use is called "contextual prompting." This is where we give the model some background information to help it understand what we're asking. The model will be able to more quickly understand your request and generate more accurate and relevant responses: ``` You are specialized in laws, and your goal is to summarize and explain in easy-to-understand English the following terms & conditions: Google Play Terms of Service March 15, 2023 (View archived version) 1. Introduction Applicable Terms. Thanks for using Google Play. Google Play is a service provided by Google LLC ("Google", "we" or "us"), located at 1600 Amphitheatre Parkway, Mountain View, California 94043, USA. Your use of Google Play and the apps (including Android Instant Apps), system services, games, movies, books, magazines, or other digital content or services (referred to as "Content") available through it is subject to these Google Play Terms of Service ("Play ToS") and the Google Terms of Service ("Google ToS") (together referred to as the "Terms"). Google … … [ASSUME THIS IS A VERY LONG PAGE] …For additional details and restrictions regarding your access and use of Google Play Movies & TV/Google TV, see the Google Play Movies & TV/Google TV Usage Rules. Now, summarize a 1 paragraph summary. ``` Output: The Google Play Terms of Service outline the rules for using Google Play to access and purchase digital content like apps, games, movies, and books. It covers topics such as account requirements, payment methods, content restrictions, updates, and user privacy. Essentially, it explains your rights and responsibilities as a Google Play user, including how you can use the content you purchase and how Google may manage or remove content from the platform. NOTE: When providing large context prompts, you will need a model capable of a large context window. As of May 2024, Gemini 1.5 Pro has a 2 million token context window. To give a rough idea, that's about 1,400,000 words of text, which could equate to several hundred pages of a PDF. This is great for use cases such as document summarization or question and answer. TIP: Understanding how language models use attention mechanisms can be beneficial. These mechanisms weigh the importance of different parts of the input. With very long contexts, the model might not allocate enough attention to sections in the middle, leading to information loss. To mitigate this, you could test by breaking the context into smaller pieces and submitting multiple requests or experimenting with an RAG system that chunks documents into smaller, overlapping pieces. ## One-Shot & Few Shot Prompting When I'm working with AI models, I've found that giving them examples can be super helpful. This is especially true for tasks where I need the LLM to classify things or extract specific info. Examples help the model understand the patterns and structure I want in the output. * A **one-shot** prompt provides a single example, hence the name one-shot. The model has an example it can mimic to best complete the task. * A **few-shot** prompt provides multiple examples of the model. This approach shows the model a pattern that it needs to follow. The idea is similar to a one-shot, but multiple examples of the desired pattern increase the chance the model follows the pattern. ## How Many Examples Are Enough? There's no magic number for how many examples you need in few-shot prompting. It depends on how complex the task is, how good your examples are, and the specific AI model you're using. I usually start with three to five examples and adjust from there. TIP: When using few-shot prompting for classification tasks, mix up the different types of responses in your examples. This helps the model learn the critical features of each category instead of just memorizing the order of the examples. By mixing things up, you'll get more accurate and unseen results. See an example below: [GitHub Gist](https://gist.github.com/61557a826b47046bdf2e51cda08373ee) When you choose examples for your prompt, use examples relevant to the task you want to perform. The examples should be diverse, high-quality, and well-written. One small mistake can confuse the model and result in undesired output. If your goal is to generate output that can handle a wide range of inputs, it's essential to incorporate edge cases in your examples. These are unusual or unexpected inputs that the model should still be able to process. ## Experiment with various output formats When I'm working on projects that involve extracting, selecting, parsing, or organizing data, I've found that using structured output formats like JSON or YAML can be a game-changer. JSON objects have several advantages when returned from a data extraction prompt. In a practical scenario, the JSON format is automatically generated, the data can be sorted (particularly useful for datetime objects), and most importantly, the prompt for a JSON format compels the model to create a structure and prevent hallucinations. TIP: Before feeding your JSON examples to a prompt, ensure they're valid. You can use online tools like JSONLint to check for errors. Trust me, even a tiny mistake, like a missing quotation mark, can mess up the AI's output. If you're dealing with complex JSON objects, consider giving the prompt a JSON schema to work with. This helps the AI understand the expected structure and format of the output so you get consistent and reliable results. Have a look at the next example: [GitHub Gist](https://gist.github.com/1a895577d179b23fae1eb5428ee1fe34) TIP: You might notice the ```json syntax used in the examples. Using this notation can make it easier later on to extract the JSON parts out of the response message, which can be handy for chaining an output into another prompt input. Note that there is no guarantee that the model won't add additional text before or after the JSON message. At least, this way, your JSON response won't break. ## Chain of Thought (COT) Prompting Chain of Thought (CoT) prompting is a great way to make LLMs better at reasoning. It's like teaching the model to think step-by-step instead of just jumping to a conclusion. You can combine it with few-shot prompting to get better results on more complex tasks that require reasoning before responding as it's a challenge with a zero-shot chain of thought. With CoT prompting, we give the LLM a problem and then show it examples of how to break down the problem into smaller steps. This helps them learn to reason through the problem logically, which can lead to more accurate and reliable answers. Let's look at some examples: ``` Your goal is to provide answers to legal questions. You have to get answers from the contract. CONTRACT: … Term 4: Animals and birds are not allowed on the property. … Can I have a goldfish in my house? ``` Output: The contract prohibits "animals and birds". It does not mention fish. Therefore, based on this contract alone, you could likely have a goldfish in your house. ``` Your goal is to provide answers to legal questions. You have to get answers from the contract. Let's take a step-by-step approach: CONTRACT: Term 4: Animals and birds are not allowed on the property. EXAMPLE REASONING: Question: Do I need to dispose of gasoline? Answer: 1. Hazardous fluids need to be appropriately disposed of. 2. Gasoline is a hazardous fluid. 3. Therefore, gasoline needs to be appropriately disposed of. Question: Can I have a goldfish in my house? Answer: ``` Output: The contract prohibits animals and birds on the property. A goldfish is an animal. Therefore, a goldfish is not allowed on the property. Chain of Thought can be helpful for various use-cases. Think of code generation as breaking down the request into a few steps and mapping those to specific lines of code. For creating synthetic data, when you have some kind of seed, like "The product is called XYZ, write a description guiding the model through the assumptions you would make based on the product given title." Generally, any task solved by "talking through" is a good candidate for a chain of thought. If you can explain the steps to solve the problem, try the chain of thought. TIP: In Chain of Thought prompting, the reasoning process is crucial. Placing the answer after the reasoning is required because the generation of the reasoning changes the tokens that the model receives when it predicts the final answer. TIP: Chain of Thought prompting is based on greedy decoding, predicting the next word in a sequence based on the highest probability assigned by the language model. Generally speaking, when using reasoning, to come up with the final answer, there's likely one single correct answer. Therefore the temperature should always set to 0. In the next post, we'll dive into some best practices that I've found super useful in business applications. See you next week! --- ## Documenting Your Prompts A Best Practice for Success *Published: 2024-07-01* *Categories: Prompt Engineering* *Tags: Large Language Models, Prompt Engineering, Gemini, ChatGPT, LLM, Enterprise, Real-world* > Let’s be real; AI documenting stuff isn’t the most exciting part of any job. But when it comes to prompt engineering, it’s an absolute lifesaver. I know it can feel like extra work, but trust me, it’ll… --- Let's be real; documenting stuff isn't the most exciting part of any job. But when it comes to prompt engineering and AI Agents, it's an absolute lifesaver. I know it can feel like extra work, but trust me, it'll save you from major headaches later on. *This is the 4th blog of the series: Prompt engineering for business applications. Prompt Engineering is complex and requires careful planning and refinement to achieve desired results from AI models. As a software engineer @Google with experience in prompt engineering for major businesses, I will share practical learnings in a blog series to help others unlock the power of AI beyond simple tasks.* * Blog 1: [Demystifying Prompt Engineering for the Enterprise](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide1/) * Blog 2: [The Foundation Understanding LLMs and Prompt Engineering, and Why It All Matters](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide2/) * Blog 3: [Beyond the Basics How to Choose and Configure Your LLM for Maximum Impact](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide3/) Documenting Your Prompts: A Best Practice for Success Given how much prompt outputs can change across different models, sampling settings, and even different versions of the same model, it's super important to document everything. You might get a response with slightly different wording or formatting, even with the exact same prompt, so keeping good records is key for future work. I recommend using a Google Sheet, with a table like the one presented below on each tab, to track your prompts. This way, you have a complete history when you need to revisit old work, test how prompts perform on new model versions, or troubleshoot issues. Feedback is a cornerstone of prompt engineering, providing valuable insights for improvement. If you're fortunate enough to use Vertex AI Studio (Model Garden), save your prompts (using the same name and version as listed in your documentation) and track the hyperlink to the saved prompt in the table. This way, you're always one click away from re-running your prompts and incorporating valuable feedback into your iterations. TIP: If you're working with a Retrieval Augmented Generation (RAG) system, also note down the specific RAG settings that affect the content inserted into the prompt (such as query, chunk settings, output, etc.). [GitHub Gist](https://gist.github.com/8fd292dbcf1fb887c29f55ee4df68655) Once you feel the prompt is close to perfect, please take it to your project codebase. In the codebase, save prompts in a separate file from the code so they're easier to maintain. Finally, ideally, your prompts are part of an operationalized system, and as a prompt engineer, you should rely on automated tests and evaluation procedures to understand how well your prompt generalizes to a task. Remember, prompt engineering is all about continuous improvement. You'll need to create and test different prompts, analyze and document the results, tweak your prompts based on how the model performs, and keep experimenting until you get the results you want. If you change the model or its configuration, go back and test your old prompts again. This iterative process is key to refining and optimizing your prompts for the best possible performance. In the next blog post, we'll dive deeper into the art of crafting effective prompts. We'll explore different techniques like role prompting, contextual prompting, and few-shot prompting, and we'll share tips on how to write prompts that are clear, concise, and effective. Stay tuned! --- ## Beyond the Basics How to Choose and Configure Your LLM for Maximum Impact *Published: 2024-06-24* *Categories: Prompt Engineering* *Tags: Large Language Models, Prompt Engineering, Gemini, ChatGPT, LLM, Enterprise, Real-world* > LLM selection and configuration for business. Expert tips on model choice, output length, sampling, and safety settings. Boost your AI performance. --- When it comes to AI prompt engineering, the choice of language model (LLM) is crucial. Each prompt should be carefully optimized for the specific LLM you're using, whether it's one of the Gemini language models in Vertex AI, GPT, Claude, or an open-source model like LLaMA. It's important to note that a prompt that was optimized for TextBison won't necessarily work in Gemini. This principle holds true not just across different models but also between different versions of the same model. *This is the 3rd blog of the series: Prompt engineering for business applications. Prompt Engineering is complex and requires careful planning and refinement to achieve desired results from AI models. As a software engineer @Google with experience in prompt engineering for major businesses, I will share practical learnings in a blog series to help others unlock the power of AI beyond simple tasks.* * Blog 1: [Demystifying Prompt Engineering for the Enterprise](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide1/) * Blog 2: [The Foundation Understanding LLMs and Prompt Engineering, and Why It All Matters](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide2/) Beyond the Basics: How to Choose and Configure Your LLM for Maximum Impact Here are some considerations for choosing a model that fits with your use-case: * **Small vs. Large Model:** The model's size can significantly impact its performance and the quality of its responses. Smaller models may be faster and more cost-effective, but they lack the complexity and nuance of larger models. For instance, a smaller model could be sufficient for basic text classification tasks, while a larger model might be necessary for complex question answering or creative text generation. * **Industry-Specific Models:** In some cases, specialized models trained on domain-specific data can offer superior performance. For example, Med-PaLM and Sec-PaLM are tailored for medical and cybersecurity applications. If your use case falls within a specific industry, it's worth considering whether a specialized model could provide more accurate and relevant results. * **Open-Source (e.g., Gemma) vs. Vertex AI:** The decision between open-source models and those available on Vertex AI should be based on factors such as customization needs, model size, architecture, access to computational resources, library usage and cost. Open-source models offer flexibility and potential cost savings but may (or may not) require more setup and fine-tuning. On the other hand, Vertex AI provides a managed environment with pre-trained models and seamless integration with other Google Cloud services. * **Context Window Size & Output Token Limit:** The context window refers to the maximum amount of text the model can use when generating a response. The output token limit determines the response length the model can produce. The limits on these parameters are essential to consider, especially when working with long documents or complex prompts. For instance, if you need to summarize a lengthy legal contract, you'll need a model with a large enough context window to process the entire document. When you choose JSON as an output format, the JSON format itself might eat up half of your output tokens, so the output token limit is equally important. ## Fine-Tuning Your LLM: It's Not Just About the Model Once you pick the correct model for your use case, you must tinker with the various configurations of an LLM, such as the output length and sampling controls, such as temperature or Top-K/Top-P. Most LLMs come with multiple configuration options that control the LLM's output. Effective, prompt engineering requires setting these configurations optimally for your task. ## Output Token Length One of the key settings is the output token length. This controls how many tokens (roughly words) your LLM spits out in its response. Now, here's the thing: more tokens mean more computing power, which translates to higher costs and potentially slower response times. And guess what? Making the output shorter doesn't magically make your LLM more concise. It just causes the LLM to stop predicting more tokens once the limit is reached. TIP: If you're dealing with JSON output, be extra careful with the token limit. The JSON formatting itself can eat up a lot of tokens, so you don't want to end up with a broken response, which makes the JSON invalid (and therefore, you can't chain API calls). ## Sampling Controls: Let's Get Creative (or Not) LLMs don't just predict one word at a time. They actually calculate probabilities for all the words in their vocabulary and then sample from those probabilities to choose the next word. This is where things like temperature, Top-K, and Top-P come in. They control how random and creative (or not) your LLM gets. ## Temperature Temperature controls the degree of randomness in token selection. Higher temperature means more random and unexpected results, while lower temperature makes your LLM stick closer to the expected output. Think of it like this: crank up the temperature if you want your LLM to write a wild marketing blog post. But if you need it to extract medical info from a patient report, keep it low and factual. NOTE: Don't go overboard with the temperature. Above 1, things start to get weird and nonsensical. As the temperature increases, all tokens become equally likely to be the next predicted token. ## Top-K and Top-P Top-K and Top-P (also known as nucleus sampling) are two sampling settings used in LLMs to restrict the predicted next token from tokens with the top predicted probabilities. Like temperature, these sampling settings control the randomness and diversity of generated text. Top-K picks the top (K) most likely words, while Top-P picks the words whose combined probability doesn't exceed a certain value (P). The best way to choose between Top-K and Top-P is to experiment with both methods (or both together) and see which one produces the results you are looking for. A low temperature (e.g., 0.1, works best with a high Top-P: 0.95) ## Safety Settings Many large language models have safety settings or content-filtering controls. For instance, Gemini comes equipped with safety settings designed to filter model output, preventing the generation of harmful, unsafe, biased, or unfair content. These settings can be configured to align with your specific requirements and risk tolerance. They can be turned off so no filtering is applied, they can be set to moderate to mostly remove unsafe content, but potentially harmful content might still be present, or strict, which filters rigorously to minimize the risk of unsafe content. NOTE: Safety settings depend on the model, are not foolproof, and might not catch all instances of unsafe content. Human oversight and additional safeguards are still necessary. Coming up next in our series, we're diving into a topic that's often overlooked but very important: documenting your prompts. I know, it might not sound as exciting as playing around with LLMs, but trust me, it's a total game-changer. So stay tuned for our next post where I'll spill all the details to save yourself from future headaches! --- ## The Foundation Understanding LLMs and Prompt Engineering, And Why It All Matters *Published: 2024-06-16* *Categories: Prompt Engineering* *Tags: Large Language Models, Prompt Engineering, Gemini, ChatGPT, LLM, Enterprise, Real-world* > Build a strong foundation in LLMs and prompt engineering for business success. Learn the core concepts and how to apply them to real-world use cases. --- Let's get down to basics and talk about how Large Language Models (LLMs) actually work. Think of them like prediction machines. There's nothing factual; everything is statistical. It generates text, one word after another (well, technically it's not a word, but it's a token; multiple tokens may form a word), and then tries to guess what the next word should be. They're trained on massive amounts of data, so they get pretty good at figuring out how words relate to each other. *This is the 2nd blog of the series: Prompt engineering for business applications. Prompt Engineering is complex and requires careful planning and refinement to achieve desired results from AI models. As a software engineer @Google with experience in prompt engineering for major businesses, I will share practical learnings in a blog series to help others unlock the power of AI beyond simple tasks.* * Blog 1: [Demystifying Prompt Engineering for the Enterprise](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide1/) The Foundation: Understanding LLMs and Prompt Engineering, And Why It All Matters When you give an LLM or AI Agent a prompt, you're basically giving it instructions on how to do this word prediction thing. Good prompt engineering is all about crafting those instructions really well. It's like giving someone directions - the more precise and more specific you are, the better the chances they'll end up where you want them to. In the world of GenAI and natural language processing, a prompt is the input you give to the model to get a response. You can use these prompts to make the LLM do all sorts of actions: * Summarizing large documents * Extracting key information from a speech * Answering and reasoning your questions from a contract * Classifying stuff (like, is this email spam or not?) * Translating languages of a document * Generating code or explaining code ## The Challenges: It's Not Always Simple Anyone can write a prompt, but writing a good prompt? That's where things get tricky. You've gotta consider a bunch of factors to get a top-notch response from your model: 1. **Teamwork:** Get a subject matter expert on board. They know the ins and outs of your topic and can help you evaluate and rate generated answers or provide examples of what "perfect" looks like. 2. **Make configurations:** You need to pick the right AI model and tweak its settings. Things like how creative (Temperature) it should be, how safe its answers need to be (Safety an filtering Settings), and how it samples which words to use (Top-K / Top-P), it all plays a role. 3. **Prompt Perfection:** The way you write your prompt matters - the words you choose, the order you put them in, how you phrase things, how you provide instructions, the role and style that you take, the context you pass in, the examples you give and the constraints and output expectations that you set. It influences the LLM response. **Plus, even with the same prompt**, the response can sometimes be different. It's different from a calculator, where you always get the same answer. So, you can't just string compare the text of two responses to see if they're the same. **And let's not forget the technical challenges.** * Sometimes, the LLM's answer is too long and, therefore, breaks up the formatting. (This happens a lot when you work with an output format like JSON) * Responses might get blocked if they try to say something harmful, copyrighted, or inappropriate. * You can run into quota issues. * And all kinds of security challenges! So yeah, there are many reasons why your output isn't what you expected. For a consumer using a chat interface like Gemini or ChatGPT, that's fine; they just type another single line question. For a business application, this can be a severe issue. A food ordering bot that takes your order wrong likely won't be used a 2nd time. Worse, a medical summary wrongly summarized or a legal contract wrongly explained has serious consequences. And this is why it's super important to keep track of your prompts, test them thoroughly, and get feedback from real people like subject matter experts, other prompt engineers in your team or even another automated LLM. Don't worry; it's not all doom and gloom. You can fine-tune your AI model, tweak your prompts, or even try a different model altogether. This guide is all about helping you master the art of prompt engineering, so stick around, and we'll dive into the nitty-gritty details! --- ## Demystifying Prompt Engineering for the Enterprise *Published: 2024-06-10* *Categories: Prompt Engineering* *Tags: Large Language Models, Prompt Engineering, Gemini, ChatGPT, LLM, Enterprise, Real-world* > Solve complex business problems with effective prompt engineering. This blog post explores the challenges and key learnings from Google software engineer Lee Boonstra. --- Prompt engineering for AI business applications isn't as simple as asking a question. It's a complex process that requires careful planning and continuous refinement, especially when you want your model to do things like summarize reports, extract insights from conversations, or answer questions based on a large knowledge base. Over the past couple of years, as a Software Engineer for the innovation factory within Google Cloud Office of the CTO, I've been knee-deep in prompt engineering, AI and Machine Learning for major business use cases, working on large innovation projects for a selection of clients, including automating drive-thru orders at Wendy's. Demystifying Prompt Engineering for the Enterprise Through these projects, I've learned that crafting effective prompts is an iterative journey, not a one-and-done task. Writing instructions and constraints that work together seamlessly takes time and effort, especially when dealing with unexpected scenarios. But the payoff is huge. In this blog series, I'm sharing my practical learnings with you. We'll dive into the challenges of prompt engineering, explore different models and configurations, and learn how to write prompts that get the job done. Whether you're a seasoned pro or just starting out, I hope this guide helps you unlock the power of AI for a use case beyond the generation of a to-do list. ## It's Way More Than Just Asking a Question! Alright, let's spill the tea about prompt engineering. There's this misconception floating around that it's a piece of cake, just like asking your LLM a simple question. If you're chatting with Gemini or ChatGPT through their public interfaces for fun, yeah, then maybe that's true. But when it comes to serious business applications, like summarizing hefty reports, extracting insights from conversations, or getting answers from massive knowledge bases, prompt engineering gets complex. Seriously! We are not generating ideas for your next trip or a to-do list. One prompt, reused in a business application, needs to handle many different inputs, and a single wrong answer could cause real problems. We're talking here about generated answers in specific formats, chained JSON outputs, and the potential for errors like hallucinations (when the model makes stuff up) or misinterpretations. That's a whole different ball game than casual GenAI use. And when something goes wrong, like hallucinations, incorrect reasoning, style issues, false precision, API errors, or formatting mistakes, it can seriously do damage to your business or brand. ## What Large Enterprise Prompting Projects Taught Me In my last two years as a software engineer, I've been neck-deep in prompt engineering for major business projects at Google. From automating drive-thru orders at Wendy's to generating medical summaries or Q&A on large legal documents, I've learned that crafting effective prompts is an ongoing process. It takes time, effort, and a lot of tweaking to get those instructions and constraints working together smoothly. All these projects had the same things in common: * Constantly evolving prompts * Growing prompt size and complexity * Defining output formats (like JSON) * Using examples to guide the LLM * Carefully balancing instructions and constraints * Documenting everything So, if you're working with clients, helping them use GenAI effectively, or just curious about how I use LLMs in the real world, this guide is for you. Over the next 6 weeks, I'm sharing my practical learnings, not just generating creative poems stuff. Remember, what works for one project might not work for another - it's all about adapting and experimenting. Read the next blog post in this series: [The Foundation Understanding LLMs and Prompt Engineering, and Why It All Matters](https://www.leeboonstra.dev/prompt-engineering/prompt_engineering_guide2/) --- ## Podcast Shaping the future Generative AI and Large Language Models *Published: 2023-06-07* *Categories: Chatbots* *Tags: Generative AI, LLM, Large Language Models, PaLM, Bard, ChatGPT* > Podcast Shaping the future Generative AI and Large Language Models (LLMs), such as ChatGPT and Bard. None other than Lee Boonstra (Software Engineer Tech Lead & Advocate at Google) and Saskia Nijs (Chief Human & Technology Officer) led by Jimmy de Vreede (Springbok Agency and DDMA Committee DDE) discussed what the technology means and will mean for the market and the work of marketers. --- Podcast Shaping the future Generative AI and Large Language Models (LLMs), such as ChatGPT and Bard. In this interview, I discussed with Saskia Nijs (Chief Human & Technology Officer) and Jimmy de Vreede (Springbok Agency and DDMA Committee) how LLMs like ChatGPT and Bard work, the benefits and drawbacks of this new technology, and the future for marketeers. ![Podcast Shaping the Future on Generative AI](podcast1.jpg) ![Podcast Shaping the Future on Generative AI](podcast4.jpg) Read more --- ## Dialogflow CX Competition — Learn Dialogflow CX & design open-source components *Published: 2021-12-01* *Categories: Chatbots* *Tags: Dialogflow CX, Dialogflow CX for beginners, Dialogflow CX vs. Dialogflow Essentials, Dialogflow CX vs. Dialogflow ES, Dialogflow Customer Experience, Contest, Dialogflow Competition, Developer Competition, Hackathon, Dialogflow t-shirt, Dialogflow Merchandise, Dialogflow training course, Qwiklabs Dialogflow* > Who doesn't love a contest? Join the Dialogflow CX competition to win cool prizes! Enroll to a free Dialogflow training course and get a free Dialogflow t-shirt. --- Since we can't run any Dialogflow community in-person events this year, we came up with another great community activity! A designer & developer (learning) competition! Get trained in Dialogflow CX, get a free t-shirt and work on open-source components. Google Cloud recently released Dialogflow CX, a bot-building platform for building chatbots and telephony agents. It empowers your team to accelerate creating enterprise-level conversational experiences through a visual bot builder and an advanced NLU. To have a reason to play around with the tool, you can join the competition. Developers and UX Designers can register from July the 12th, by visiting the event website:  [https://events.withgoogle.com/dialogflow-cx-competition-global/](https://events.withgoogle.com/dialogflow-cx-competition-global/?utm_source=leeboonstra_blog&utm_medium=site&utm_campaign=cx_competition) ## Free Dialogflow CX Training Course Everyone that signs up, will be enrolled in a free Dialogflow CX Qwiklabs course. You will be able to learn how to design conversational flows and how you can build integrations. This online course has been developed by Google, and is highly recommended as a training resource. ## Competition From the 27th of July, you will be invited back to the event website, here you will be able to enter your registration details. The first 200 participants that enter their information will receive a free Dialogflow CX t-shirt! ![Competition Schedule](https://leeboonstra.dev/images/dialogflow-competition.png) As part of the competition, you will pick one of the following tracks:
  • UX Designers: Design a conversational flow

    Design a Dialogflow CX flow/conversation for building a text chatbot for COVID-19. It could be a vaccination scheduler, could be an FAQ, Could be a bot on where you are allowed to travel to, etc.

  • Bot Developers: Build an integration

    Use the Dialogflow CX API (REST, gRPC, or Client libraries) to build a cool integration. For example, to integrate a chatbot in a website or Blog, WhatsApp, CRM, CMS, Mobile, etc...

Lastly, you will write a short proposal, between 300 to 600 words on what your idea is, to build for the open-source community. For example, share why you think your idea is great or unique. In case of writing a flow, what kind of questions end-users could ask, and in case of building an integration, how you would build it. Will this idea run in a text chatbot, smart speaker or contact center? Etc. The Google judges team will shortlist the 50 best entries, so please make sure your idea is detailed enough. ## Nominations From the 9th of August, Google will nominate the 50 best ideas. We will contact you, and ask you to start developing your flow or integration. You will have to submit an URL to your Github repository, which should contain [an Apache 2 open-source license](https://www.apache.org/licenses/LICENSE-2.0). Your Github repo is where you will upload your code or export your flow (blob export) before August the 31st. As a new Dialogflow CX user you will receive [a $600 credit](https://cloud.google.com/dialogflow/pricing?utm_source=ext&utm_medium=partner&utm_campaign=CDR_lee_aiml_dialogflow_contest_cx_&utm_content=-#cx-trial) for a free trial of Dialogflow CX, which is more than enough to start developing your solutions for the competition. **All open-source contributions will be shared with the community via the Dialogflow Docs website, and promoted by Google through social media. This is your chance to share your solution with the world!** ## Judging A team of Google engineers will judge your working code and pick a total of 10 winners. We will look to see if your solution runs, if it contains setup instructions, how well it's executed, how useful, and how creative and unique your idea is. ## Win cool prizes! You can win cool prizes (like Lego boxes and Google Assistant devices) and every winner will receive the book: [The Definitive Guide to Conversational AI with Dialogflow and Google Cloud](https://www.amazon.com/Definitive-Guide-Conversational-Dialogflow-Google/dp/1484270134/ref=sr_1_1?dchild=1&keywords=the+definitive+guide+to+conversational+ai&qid=1625732877&sr=8-1) written by me. The winners will be contacted and featured on the Google event and Dialogflow Docs website. _Always wanted to dive into Dialogflow CX? Register now on the event website, you will be enrolled in the free Dialogflow CX training course, and from the 27th of July you can start submitting your chatbot / voice bot proposal._ Good luck and have fun! [https://events.withgoogle.com/dialogflow-cx-competition-global/](https://events.withgoogle.com/dialogflow-cx-competition-global/?utm_source=leeboonstra_blog&utm_medium=site&utm_campaign=cx_competition) ![Prizes Track 1: Design a Conversational Flow](https://leeboonstra.dev/images/prizes1.png) ![Prizes Track 2: Build a Dialogflow CX integration](https://leeboonstra.dev/images/prizes2.png) --- ## The Definitive Guide to Conversational AI With Dialogflow & Google Cloud. For building complex chatbots, voicebots and telephony agents. *Published: 2021-07-01* *Categories: Chatbots* *Tags: Dialogflow Essentials, Dialogflow CX, Contact Center AI, Google Assistant, Chatbots, Voicebots, Conversational AI, Apress Book* > It covers all the conversational AI technology at Google. Like Dialogflow Essentials, Dialogflow CX, Google Assistant, Contact Center AI. It's a book that I wrote for the conversational AI chatbot & voice community with the focus on complex enterprise use-cases. --- After I wrote my first book (Hands-on Sencha Touch 2 — O’Reilly), people always asked me if I would ever write another book. Especially, when I started to get expertise in conversational AI. I never said ‘never’, but I knew it would probably eat a lot of my time. Fast forward to February 2020, the whole world looked a lot different, and due to COVID-19, I had to cancel all my trips. Suddenly, I had a lot of extra time, plans for a book about chatbots became real! So I started to write... There aren’t that many books about conversational AI for technical practitioners on the market, and I wanted to write this especially for enterprise users. Thus UX designers/linguists, web/conversational bot engineers, chatbot architects but also back-end developers, project managers and business decision makers that are working on complex conversational projects. Far beyond the ‘hello world examples’, as those are the real-world use cases that I see in my day to day life, while working for Google as a developer advocate and applied AI engineer focussing on Conversational AI. Today, I am excited to announce with you that my book: **[The Definitive Guide to Conversational AI With Dialogflow and Google Cloud ](https://www.amazon.com/Definitive-Guide-Conversational-Dialogflow-Google/dp/1484270134/)** _(Build Advanced Enterprise Chatbots, Voice, and Telephony Agents on Google Cloud)_ has been released by [Apress](https://www.apress.com/gp/book/9781484270134) / [Springer](https://www.springer.com/gp/book/9781484270134) this week! ISBN 978–1–4842–7014–1 & ISBN 978–1–4842–7013–4 ![The Definitive Guide to Conversational AI with Dialogflow and Google Cloud](https://leeboonstra.dev/images/0_dialogflow-book.png) It's a book that covers all the conversational AI technology at Google Cloud & Google. Like, **Dialogflow Essentials**, **Dialogflow CX**, **Google Assistant** and **Contact Center AI**. But it also covers Google Cloud technology such as BigQuery for advanced Chatbot analytics, how to build your own integration channels with **Cloud Run**, **GKE**, **Compute Engine** or **Cloud Functions**. And it covers other Machine Learning APIs such **Natural Language Processing**. While reading this book, readers will learn the following: * What's Dialogflow, Dialogflow Essentials, Dialogflow CX, and how machine learning is used * How to create Dialogflow projects for individuals and enterprise usage * Learn Dialogflow Essentials Concepts like Intents, Entities, Custom Entities, System Entities, Composites and how to track context * How to build bots quickly using prebuilt agents, small talk modules, and FAQ knowledge bases * How Dialogflow can give an out of the box agent review * How to deploy text conversational UIs for web & social media channels * How to build voice agents for voice assistants (Google Assistant) & phone gateways/contact centers with Contact Center AI * How to build multilingual chatbots * Learn how to orchestrate many (sub) chatbots to build a bigger conversational platform * How to use Chatbot analytics and how to test the quality of your Dialogflow agent (machine learning model) * Learn how Dialogflow CX fits in, what’s different in Dialogflow CX and new Dialogflow CX concepts These topics are more targeted to developers & engineers and contain more advanced use cases: * Learn how to create fulfillment to connect to a web service in various ways * How to run back-end code from a local/development machine * How to secure your chatbots * How to integrate your chatbots in a website (Angular) or native mobile (Flutter) app by creating your own custom integrations * How to create an omnichannel bot platform architecture * How to create rich responses in custom integrations * How to stream your voice UIs in IoT voice applications * Using BigQuery for advanced chatbot analytics It contains tips and tricks and code examples to work on complex use-cases. It's a book that I often use to quickly look up things like: _"Oh, how would I build a Google Assistant action with Dialogflow CX again?"_, _"How can I test the underlying bot model to get the best performance?"_, _"How does mTLS work to secure my bot fulfillments?"_. With other words, it's a book that I wrote for the conversational chatbot & voice community! As a few of you might know, I wrote this book during my pregnancy, when the world went into lock down. To come full circle, I picked a non profit organization to donate all my book royalties to, which is the [“Meer dan Gewenst“](https://www.meerdangewenst.nl/about-mdg/), foundation in the Netherlands, an NGO that helps the LGBTQ+ community who have a wish to become parents. They do this by raising awareness (contacts with media, politicians and businesses), education and connections. As an LGBTQ+ parent myself, this charity is close to my heart, as I think having a wish to become a parent should be possible for everyone. You can find my book as a paperback and ebook on [Amazon](https://www.amazon.com/Definitive-Guide-Conversational-Dialogflow-Google/dp/1484270134/ref=sr_1_1?dchild=1&keywords=Definitive+Guide+to+Conversational+AI&qid=1624873031&sr=8-1&asin=B097XX9B3R&revisionId=&format=2&depth=1). _Feel free to connect with me on social media if you have comments or feedback about the book. Do you like my work? I would love to receive book reviews. As a developer advocate, I enjoy speaking about anything related to chat and voice bot technology. I am available for speaking engagements._ --- ## How to integrate Dialogflow with your Flutter app *Published: 2021-03-14* *Categories: APIs* *Tags: gRPC, Using gRPC APIs in Google Cloud, Dart on GCP, Flutter on GCP, Dialogflow gRPC, Dialogflow Flutter package, grpc vs rest, grpc advantages and disadvantages* > The Hidden Manual for building a gRPC Flutter Dart integration. Learn how to use the gRPC API in Google Cloud. E.g., Dart & Flutter on Google Cloud, to stream audio data with Dialogflow on native mobile apps. --- The Hidden Manual for building a gRPC Flutter Dart integration. Learn how to use the gRPC API in Google Cloud. My usecase is to use Dart & Flutter on Google Cloud, to stream audio data with Dialogflow for native mobile Android & iOS apps. ## Flutter & Dart on GCP to build streaming audio apps with Dialogflow Are you a developer, and want to integrate one of the Google Cloud products in your web app or software? You are probably using the client-side GCP libraries. I guess the majority of developers on Google Cloud are using these. When you are a data scientist, you will likely use the Python library; when you are an enterprise developer, chances are you rather use the client-side library for Java. Web developers (like me) choose Node.js, and Go is also very popular. We also support C#, PHP, and Ruby. This week, I wanted to build a native mobile app that integrates Dialogflow to build a chatbot. It needs to use the device microphone so that I can do some audio streaming. I want to support Android and iOS devices and additionally Web, macOS, and Windows as a nice to have. **Flutter 2** is the language of choice, as I only need to write my code once, and I don’t need to learn new programming languages. That said, I am actually new to Flutter, but as an ex [Sencha](https://www.sencha.com) engineer & technical trainer, I’ve noticed that the component & OOP concepts are very similar, so it was easy to use. What’s the first thing that you do when you generated a fresh new project? Right, you will look on the [pub.dev](https://pub.dev/) (Dart & Flutter package management) website to see which Google Cloud & Dialogflow libraries you can use. And that’s where I discovered that (until now) there were no official Google packages available. Okay, then let’s dive into some of the open-source packages for Dialogflow that are available. I quickly discovered that no one of these packages supports audio streaming (the Dialogflow streamingDetectIntent call). The reason for this is simple, all the Dialogflow packages on pub.dev are making use of the underlying REST APIs. REST doesn’t support streaming. (You make a call over HTTP, and you are waiting for the request to complete. The browser won’t communicate progress, you will either get a success/error response, or you can expect a time-out if it takes too long. - When you use audio streaming, you basically keep the connection open to stream the audio chunks. This is something REST can’t do.) Fine, so that brings me to a conclusion, that I will have to build a Dialogflow package myself using gRPC. I have always been staring at the [gRPC reference docs](https://cloud.google.com/dialogflow/es/docs/reference/rpc) in Google Cloud. I have done this a lot, as all the client-side APIs use gRPC, and often their documentation points to the Google Cloud docs. But I always wondered how you would implement the gRPC calls yourself. I couldn’t find any documentation on how to do so. Am I the only one who doesn’t know how this works, or are these docs just there to support the client-side libraries? Well, it seems there is some documentation missing. After researching, I figured out how this works, and I managed to build [my own Dialogflow gRPC package for Dart & Flutter](https://pub.dev/packages/dialogflow_grpc). Dialogflow Flutter Let me explain to you how you can build your own integrations with gRPC on Google Cloud. ## About gRPC In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services. In my case, the mobile Flutter app is the client, and Google Cloud the server. As in many RPC systems, gRPC is based on defining a service, specifying the methods that can be called remotely with their parameters and return types. Google Cloud (Dialogflow) implements this interface and runs a gRPC server to handle client calls. On the client-side, the client has a stub that provides the same methods as the server. ![How gRPC works](images/grpc.png "How gRPC works") By default, gRPC uses [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview), Google’s mature open source mechanism for serializing structured data (although it can be used with other data formats such as JSON). When working with protocol buffers, the first step is to define the structure for the data you want to serialize in a _proto file_: this is an ordinary text file with a .proto extension. [GoogleApis](https://github.com/googleapis/googleapis) provides proto files for all the Google and Google Cloud APIs. Protocol buffer data is structured as _messages_. Each message is a small logical record of information containing a series of name-value pairs called _fields_, which contain a field name (Dialogflow property) and a tag number. Let’s have a look at the Dialogflow [session.proto](https://github.com/googleapis/googleapis/blob/master/google/cloud/dialogflow/v2/session.proto) file to understand: ``` // The message returned from the DetectIntent method. message DetectIntentResponse { string response_id = 1; QueryResult query_result = 2; google.rpc.Status webhook_status = 3; bytes output_audio = 4; OutputAudioConfig output_audio_config = 6; } ``` With a .proto file, you can use a protocol buffer compiler called: **protoc** to generate data access classes in your preferred language(s) from your proto definition. These provide simple properties like queryResult and methods to retrieve the value .getField(int tagNumber) or to serialize/parse the whole structure to/from raw bytes. .toProto3Json(); So for my Flutter app, I will need to compile the Dialogflow protos to .dart files. Let’s give it a try! ## Working with gRPC and Dart/Flutter in Google Cloud Create a folder **_proto** on your hard drive, in this folder, clone the following repositories. These are the repositories you will use each time you generate code: ``` git clone https://github.com/googleapis/googleapis git clone https://github.com/protocolbuffers/protobuf ``` Also, create a **lib/src/generated** folder inside the **_proto** folder. ### **Install the tools** For this example, you will obviously need Dart & Flutter on your machine. I am using Flutter 2. and Dart 2.12.1 And as explained before, you will need the **protoc protobuf compiler**. Follow the steps from [https://grpc.io/docs/protoc-installation/](https://grpc.io/docs/protoc-installation/) **Note: On macOS, this will install protoc to /usr/local/bin/protoc. Make sure you have version 3.15 or higher installed. (run protoc --version). Else remove protoc and re-install.** ``` brew reinstall protobuf ``` By default this will work for Java, Python, JS, PHP, Ruby, C#, and Objective C. To do this for Dart, you will need an additional plugin (**[protoc-gen-dart)](https://pub.dev/packages/protoc_plugin)**, which you can install using the following command: ``` $ pub global activate protoc_plugin ``` Make sure you are downloading version 20.0.0 or higher. Update your PATH so that the protoc compiler can find the plugin: ``` $ export PATH="$PATH":"$HOME/.pub-cache/bin" ``` ## Making use of the Dialogflow gRPC APIs First, make sure you have the Dialogflow API enabled: ``` gcloud services enable dialogflow.googleapis.com ``` We will compile the Dialogflow protos to Dart files, from the googleapis repo. Therefore, first set the following environment variables, where you point to the location on the two cloned repositories: ``` export PROTOBUF="$HOME/Documents/GitHub/_proto/protobuf" export GOOGLEAPIS="$HOME/Documents/GitHub/_proto/googleapis" ``` Then save the below **generate-protos.sh** bash script in the **_proto** folder: [GitHub Gist](https://gist.github.com/19fcdc735ecd613faaaf13e5a2a5864e) and execute it: ``` . generate-protos.sh ``` Then copy the **generated**folder to your Dart/Flutter projects **src/lib folder.** Once the generated files are in your Android project. You can start using the RPC API. You can find the RPC reference documentation online: [https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.Sessions](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.Sessions) For example, it's possible now to make use of SessionsClient(), it will import the following dart files: ``` import 'package:flutter_dialogflow_agent/generated/google/cloud/dialogflow/v2/audio_config.pb.dart'; import 'package:flutter_dialogflow_agent/generated/google/cloud/dialogflow/v2/session.pb.dart' ``` When you get started, you will probably be pleased just to see text messages going back and forth. But eventually, you will probably want to add rich responses such as buttons, clickable hyperlinks, cards, and other such rich responses. The general approach for detecting intents in Dialogflow is a follows: 1. You will need a service account, which the Dialogflow gRPC API will need so your application can access APIs on behalf of a user or using a service account. 2. Choose which version of the API you want to make use of, V2, V2beta1, CX, or CX beta. 3. The Dialogflow [SessionClient](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.Sessions) lets you detectIntents, so you will need to create a SessionClient, which contains a sessionPath, containing the Dialogflow Project Id (so it talks to your chatbot) and a unique session string for each session. (This is also important when you want to log and find chats per session at some point.) 4. Make the [detectIntent](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.DetectIntentRequest) calls; it needs at least the session path and the query input, such as text and a language code. 5. You can get the fulfillment text out of the queryResult from the [detectIntentResponse](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.DetectIntentResponse). ## Conclusion: when to use the client-side library vs. REST. vs. gRPC? You can access the Google Cloud APIs via REST, gRPC, or one of the provided client libraries (built on gRPC). ### **Client-side library** If the client libraries support your desired programming language (see the ones listed above), you should use this option. These libraries are maintained by Google, have built-in authentication and retries, and make efficient HTTP request bodies. Else, you have to choose between REST or gRPC. ### **REST** The advantage of **REST** is that you can create a simple JSON interface. Rest stands for “Representational State Transfer”. It is a set of rules that developers follow when they create their API. One of these rules states that you should be able to get a response when making a request. A request is made up of four things: the endpoint, the method (GET, POST, PUT, PATCH or DELETE), the headers, and the data (body). The language of your choice might already have a [googleapis](https://github.com/dart-lang/googleapis/tree/master/generated/googleapis) package, which can be used to make request requests. Here’s an example that I have created to make detectIntent calls with the googleapis package, which is making use of REST under the hood: [https://gist.github.com/savelee/7068e6cea695088bcf06031d9e435b48](https://gist.github.com/savelee/7068e6cea695088bcf06031d9e435b48) I’ve used the [googleapis_auth](https://pub.dev/packages/googleapis_auth) package to do the authentication. ### **gRPC** gRPC is faster than REST, and as you have seen in my example, if the choice is between REST and gRPC, gRPC is the only streaming solution. But you will have to generate your own client from the Google-supplied protocol buffers, and you will also need to implement authentication. I’ve built my own Dialogflow gRPC Dart / Flutter package this way. When you want to build native Android or iOS applications, you can start using my package too! [https://pub.dev/packages/dialogflow_grpc](https://pub.dev/packages/dialogflow_grpc) --- ## An introduction to bot builder platform Dialogflow CX *Published: 2021-02-26* *Categories: Chatbots* *Tags: Dialogflow CX, Dialogflow CX for beginners, Dialogflow CX vs. Dialogflow Essentials, Dialogflow CX vs. Dialogflow ES, Dialogflow Customer Experience, Dialogflow CX pricing, Bot builder, Chatbot development suite* > Dialogflow CX is a separate product that will coexist with Dialogflow Essentials. Developers and businesses can choose which bot building platform is the right tool for them. Dialogflow CX addresses the need for businesses to build more complex chat & contact center voice bots. Dialogflow Essentials is often chosen for its simplicity. --- According to Gartner, this is the year that 50% of the enterprises will spend more money on bot development than traditional mobile app development! At Google, we have made a similar observation. Through Google Cloud, we work a lot with enterprise customers, we get lots of questions about our conversational AI tools for building chatbots and voice bots, and we help companies build these. This includes bots for the apparent channels such as websites, iOS, Android mobile apps, and social media like Facebook Messenger, Twitter, Slack, Whatsapp, Line etc. but also: building virtual agents in contact centers! Dialogflow recently had a name change; it's now called **Dialogflow Essentials** (**Dialogflow ES**) to make room for a new Google Cloud Conversational AI tool: **Dialogflow Customer Experience** (**Dialogflow CX**). Dialogflow CX will be an alternative development suite for building conversational UIs and will exist next Dialogflow ES. Google will continue to support Dialogflow ES, as we have a huge user base. To understand why Google created another bot builder, let's first understand how Dialogflow ES works.

50% of enterprises will spend more on bots than traditional mobile app development by 2021

Gartner
Jump directly to relevant parts of this article: ![Dialogflow consumption overtime](dialogflow-usage.png "Dialogflow consumption over time")

Note this figure, you will see how popular the bot builder platform Dialogflow Essentials is. It has a user base of more than 1.6M, stated at the beginning of 2021. Dialogflow was previously called API.AI; Google acquired API.AI in September 2016 and renamed it to Dialogflow, making it part of Google Cloud. Dialogflow became so popular because of the outstanding underlying NLU machine learning models (like Natural Language Understanding, intent classification, and entity extraction) similar to the Google Assistant.

How Dialogflow Essentials works

Dialogflow Essentials is a development suite for building conversational UIs. Thus chatbots, voice bots, phone gateways. You can all build it with the same tool, and you can even support multiple channels in over 20 different languages. Dialogflow UX designers (agent modelers, linguists) or developers create intents by specifying training phrases to train an underlying machine learning model. An **intent** categorizes a user's intention. For each Dialogflow ES agent, you can define many intents, where your combined intents can handle a complete conversation. Each intent can contain parameters and responses. Matching an intent is also known as **intent classification** or **intent matching**. This is the main concept in Dialogflow ES. Once an intent is matched it can return a response, gather parameters (entity extraction) or trigger webhook code (fulfillment), for example, to fetch data from a database. When an end-user writes or says something in a chatbot, referred to as a **user expression** or **utterance**, Dialogflow ES matches the expression to your Dialogflow agent's best intent, based on the training phrases. The under the hood Dialogflow ES Machine Learning model was trained on those training phrases. Dialogflow ES works with a concept called **context**. Just like a human, Dialogflow ES can remember the context in a 2nd and 3rd turn-taking turn. This is how it can keep track of previous user utterances.

How the industry is changing its complexity

Large enterprises have been using Dialogflow ES over the past years. Here's an observation I see at Google: At the beginning (2016), most conversational AIs were simple chatbots. (voice assistant bots, FAQ bots…), for web or voice bots, like the Google Assistant. It typically uses 1 or 2 turn-taking turns. For example: "Hey Google, what's currently playing on ABC?" - "The Bachelor started at 8 p.m, an episode you have never seen before!" As we all know, building conversational UIs is an ongoing process. When you capture the right chatbot insights, you will see what your customers are asking for. When you continue to build conversations on top of an existing agent, that bot becomes more complex over the years. So **conversation complexity** is one observation. The other perception I made is that **businesses want to be where their customers are**. And thus, their chatbots will need to connect to more channels to create **omnichannel** experiences. Instead of building a single chatbot, companies now want to develop complete conversational platforms fed by a data lake and automate processes with RPA. Think of complex use cases such as replacing your customer care or HR department by robots. Having overloaded call centers and employees burned out by undervalued monotonous tasks, automation through chatbots and virtual (voice) agents can trim huge business costs. Through conversational AI in contact centers, businesses can reduce call time, on-hold time and offer 24/7 availability while improving our processes by capturing analytics.

Chatbots are expected to help cut business costs by more than $8 billion per year by the year 2022

Juniper Research

Where Dialogflow CX fits in

Dialogflow Essentials has been praised for its simplicity. You can build a chatbot or voice bot quickly. These are chat and voice applications, where a short utterance matches one intent, with a few turn-taking turns. For example, a retail chat or voice app, where you can say: "_Put milk on my shopping list._" Now imagine you are building a voice robot for the telephony helpdesk of a grocery store. This time, customers are not speaking a few sentences, no instead they speak with whole stories: "_Yesterday, right at opening time, I bought milk at So-And-So Store together with my 4-month-old baby, and when I came home, and I wanted to put the milk in the refrigerator, I noticed that the date is past the expiration date. I opened the carton and noticed the odd smell, but the taste was fine_." Suddenly it becomes much more tricky to match the intent. For a human, it can become challenging to understand the intention; for an AI, this is difficult too! Because are we talking about _buying milk_, _bad products_, or _requesting a refund_? The conversation is long-running, the dialogue is large and can have many turn-taking turns, where we need to remember the context—branched off in hundreds of possible outcomes. And look, I am not even mentioning the technical complexity of dealing with multiple speakers, interruptions, background noises, etc. ![Advanced dialogues](advanced-dialogues.png "Advanced dialogues contain reusable intents and flows, and can branch of in many outcomes") Creating a virtual (voice) agent for a contact center or creating an automated bot platform is far more complicated where it reaches the borders of Dialogflow Essentials. Sure, when you have a large team of developers, they can tailor-made a solution on top of Dialogflow ES, as users have been doing so in the past. But that means that you have to develop and maintain additional software rather than focussing on designing conversations. Until now... ...with the release of Dialogflow CX.

Dialogflow CX product explained

Dialogflow CX empowers your team to accelerate creating enterprise-level conversational experiences through visual bot builders, reusable intents, and the ability to address multi-turn conversations. It allows you to quickly create agents with: * Large and complex flows. Think of giant agent implementations with hundreds or thousands of intents. * More than three turn-taking turns (keeping context) and conversations that branch off from each other in various outcomes. * Repeatable dialogue parts in the flow (Think of a login feature, saying yes/no to questions, etc.) * Understanding the intent and context of a long utterances. * Working with teams collaborating on large implementations. * Native Contact Center features such as DTMF, one-click telephony partner integration, barge-in, live agent hand-off. * Agents were additional languages, and regionalization (for example GDPR) are important. * Flows with various outcomes and repeatable parts. For example, filing taxes. Usually this requires you to fill out lots of forms, where questions jump to each other. - If you would build a chatbot for this use-case, Dialogflow CX would be great for this, because of the reusable flows, intents and branching of answers. ![Dialogflow CX Flow](dialogflow-cx-flows.png "A Dialogflow CX Flow in the Visual Flow Builder") Dialogflow CX has advanced their NLU. Even though Dialogflow ES is popular because of the outstanding NLU results, we saw a notable quality improvement by basing our NLU on the BERT language model. It introduces new concepts such as **Pages** and **Flows** for creating reusable flows and branching, and on top of that, it comes with a **visual flow builder** to quickly preview and understand the flow of dialogues.

Dialogflow pricing explained

The [Dialogflow pricing page explains the pricing between both products Essentials vs. Customer Experience](https://cloud.google.com/dialogflow/pricing?utm\_source=blog&utm\_medium=partner&utm\_campaign=CDR\_lee\_aiml\_leedialogflowblog\_personal\_). Dialogflow Essentials has a **free trial**. The **pay-as-you-go version is based on the number of requests** you make, and you will have to pay extra for additional features such as sentiment analysis, knowledge bases, phone gateways, etc. A request is defined as any call to the Dialogflow service, whether direct with API usage or indirect with integration or console usage. With Dialogflow CX, you **pay per session**. A session is a conversation between an end-user and a Dialogflow agent. A session remains active, and its data is stored for 30 minutes after the last request is sent for the session. A session can be either a chat session or a voice session. The pricing seems expensive, but if you consider that you don't need a team of developers solving complex problems in fulfillment webhooks to facilitate large enterprise-scale bots plus, you don't need to pay extra for all additional features; Enterprise customers actually might be better off by using Dialogflow CX. Besides, each new user will receive a **$600 credit for a free** trial of Dialogflow CX. This credit is automatically activated upon using Dialogflow CX for the first time and expires after 12 months. This is a Dialogflow-specific extension of the Google Cloud [free trial](https://cloud.google.com/free/docs/gcp-free-tier#free-trial?utm\_source=blog&utm\_medium=partner&utm\_campaign=CDR\_lee\_aiml\_leedialogflowblog\_personal\_); this should be more than enough to play around with the tool and get started!

Conclusion

Dialogflow CX is a separate product that will coexist with Dialogflow Essentials. Developers and businesses can choose which bot building suite is the right tool for them.

Dialogflow CX vs. Dialogflow Essentials: when to use which tool

When you are a freelancer, start-up, or small business, when you are building a single chat or voice bot, for example, for the Google Assistant or building an FAQ bot, Dialogflow Essentials might be a better choice for you. **This tool is often chosen for its simplicity**. When you are an enterprise customer building a large & complex chatbot platform or contact center customer experience, when data regionalization is crucial for you (for example because of GDPR) or your conversation requires lots of turn-taking turns and dialogue branches, Dialogflow CX is the tool to use. **This tool addresses the need for businesses to build more complex chat & contact center voice bots.**

Try out the new Dialogflow CX Console

--- ## Getting Audio Data from Text (Text to Speech) and play it in your browser. (part IV) *Published: 2021-01-04* *Categories: Chatbots* *Tags: Dialogflow ES, Dialogflow, Voice AI, Google Speech to Text, Google Assistant, Chatbots, Best practice, TTS, Text to Speech* > A best practice for streaming audio from a browser microphone to Dialogflow & Speech To Text. Your own conversational voice AI in a web application. --- This is the fourth blog in the series: A best practice for streaming audio from a browser microphone to Dialogflow & Google Cloud Speech To Text. In case you haven’t read the other blogs, I recommend to browse back to these blogs: * Blog 1: [Introduction to the GCP conversational AI components, and integrating your own voice AI in a web app](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-1/). * Blog 2: [Building a client-side web application which streams audio from a browser microphone to a server.](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-2/) * Blog 3: [Building a web server which receives a browser microphone stream and uses Dialogflow or the Speech to Text API for retrieving text results.](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-3/) In the next blog of this series, I will take text (or Dialogflow QueryResult text data) that’s currently available on the server-side, pass it to the Text to Speech API (to synthesize the text) and return the audio bytes back to the client app, to play it in the browser. It has to play the audio bytes automatically. These blogs [contain simple code snippets](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples), and a demo application; [the Airport Self Service Kiosk](https://github.com/dialogflow/selfservicekiosk-audio-streaming), which will be used as a reference architecture. ## Architecture When you make a Text to Speech call, either with Text to Speech or by using the built-in speech return from Dialogflow, it will return audio byte data. Both TTS and Dialogflow can be called from server-side code. In order to stream and play this in a browser, you could make use of websockets. Once the AudioBuffer (ArrayBuffer in browser JavaScript code) is returned to the client, it can be played by using WebRTC methods. Here’s an example of a browser flow when using the Text to Speech API. In this example a user types text of which the synthesized speech will be played in the browser: ![TTS Architecture](tts-architecture.png) Here’s an example of a browser flow by using Dialogflow. In this example a user speaks in the microphone (similar as the examples above), but Dialogflow returns an AudioBuffer as the result. ![Dialogflow Speech Architecture](dialogflow-architecture.png) ## Client-side Code to play the audio The JavaScript code which runs in your browser will look like this: * [Client-Side Code: Play TTS output in the browser ](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/examples/example6.html)— TTS I’m loading Socket.io and Socket.io with stream support (for bidirectional binary data transfer), from the CDN: Socket.IO is a real time, bidirectional event-based communication library. One of the transports that it uses are websockets, but it also provides other transports (XHR/JSONP), not just as a fallback but also for situations where websockets aren’t supported/required/wanted. ``` ``` I created the Socket.IO object, and made sure it connects. I’ve created an on **‘results’** listener, which will run once the data from the server-side is retrieved in the browser. This will call my **playOutput** method, which I will show later: ``` JavaScript const socketio = io(); const socket = socketio.on('connect', function() {}); socketio.on('results’', function (data) { console.log(data); playOutput(data); }); ``` In my simple demo, I’ve create a textarea field, and a JavaScript method, which will be called on a button click , that takes the value from the field, and emits this via Socket.IO to the back-end: ``` JavaScript const inputTextEl = document.getElementById('inputText'); function submitTTSCall(){ var input = inputTextEl.value; if (input) ss(socket).emit('tts', input, {}); } ``` Now this is just a simple demo. But in a real-world application, a text prompt, or an incoming chatbot answer could trigger TTS to read it out loud in the browser. I’m doing this in the [Airport Self Service Kiosk application](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/client/src/app/dialogflow/dialogflow.component.ts), which you can try out on this URL: [http://selfservicedesk.appspot.com/](http://selfservicedesk.appspot.com/) Here’s the code for playing the output in your browser from your device speakers: [GitHub Gist](https://gist.github.com/e794f97dc7210942330c066f90e9a541)
  1. Here’s the playOutput function, which takes the arrayBuffer that I retrieved from the back-end code that calls the Text to Speech API. Here, I can create a new AudioContext object. The AudioContext interface represents an audio-processing graph built from audio modules linked together, each represented by an AudioNode. An audio context controls both the creation of audio nodes it contains and the execution of the audio processing, or decoding.
  2. Now, let’s create an audio source for Web Audio API from an ArrayBuffer. The decoded AudioBuffer is resampled to the AudioContext’s sampling rate, then passed to a callback.
  3. A user agent could block autoplay, hence why I run audioContext.resume as a trick first. Afterwards, create a new AudioBufferSourceNode to connect to the audioContext destination, which are in our case the device speakers. The buffer property of the AudioBufferSourceNode interface provides the ability to play back audio using an AudioBuffer as the source of the sound data. Finally, let’s play the audio.
## Server-side code to convert text to an AudioBuffer Since I’m writing JavaScript code on the back-end for Node.js, I can make use of a Google Cloud client-side SDK for [TTS](https://www.npmjs.com/package/@google-cloud/text-to-speech). Run npm install @google-cloud/text-to-speech to install the latest package in your project. Once you downloaded the package, you can require the package in the top of your code: const textToSpeech = require(‘@google-cloud/text-to-speech’); First, I instantiate the **TextToSpeechClient()** from the textToSpeech npm package. Then create a request object, which contains settings such as the voice language, voice gender and the audioEncoding. [Here’s an overview of all the settings](https://cloud.google.com/text-to-speech/docs/reference/rest/v1/text/synthesize?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_). ``` JavaScript let ttsClient, requestTTS; ttsClient = new textToSpeech.TextToSpeechClient(); requestTTS = { voice: { languageCode: 'en-US', //https://www.rfc-editor.org/rfc/bcp/bcp47.txt ssmlGender: 'NEUTRAL' // ‘MALE|FEMALE|NEUTRAL’ }, audioConfig: { audioEncoding: encoding, //’LINEAR16|MP3|AUDIO_ENCODING_UNSPECIFIED/OGG_OPUS’ } }; ``` This part finally makes the **synthesizeSpeech** call, which is asynchronous, the await operator is used to wait for a Promise, from the response, I return the audioContent that contains the audio buffer: ``` JavaScript async function textToAudioBuffer(text) { requestTTS.input = { text: text }; // text or SSML const response = await ttsClient.synthesizeSpeech(requestTTS); return response[0].audioContent; } ``` ## TTS in Dialogflow Dialogflow, the tool to create chat agents, can also return AudioBuffers once it detected the intent. You would only need to specify an **outputAudioConfig** in the Dialogflow [DetectIntentRequest](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#detectintentrequest), in order to also get an AudioBuffer as part of the response: ``` JavaScript outputAudioConfig: { audioEncoding: 'OUTPUT_AUDIO_ENCODING_LINEAR_16', }, ``` [You can follow this guide, for the full code.](https://cloud.google.com/dialogflow/docs/detect-intent-tts?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) To play it in the browser, you can use the same instructions as I showed, when working with the Text to Speech API directly. The back-end listens to the ‘tts’ event, which was fired from the client-side. You can find the full creation of the [Express server code here](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/examples/simpleserver.js). In case you want to run this yourself. Call the method: **textToAudioBuffer**() it will pass the string text as a parameter, and it returns a Promise to chain a function that passes the response (which eventually will be the AudioBuffer), to the client-side via Socket.IO emit: ``` JavaScript ss(client).on('tts', function(text) { textToAudioBuffer(text).then(function(results){ console.log(results); client.emit('results', results); }).catch(function(e){ console.log(e); }); }); ``` **Caution**: Be aware of using Dialogflow detect intent on streaming audio. When you use simple detectIntent calls without streaming, you stop the microphone and you will play the TTS audio buffer. However, when you do streaming, you keep your microphone open. You don’t want to end-up in an endless loop, where the speech synthesizer records new streams based on the TTS response, through your microphone. :-) The [AudioBufferSourceNode](https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode) has an onended event handler. Which will run once the AudioBufferSourceNode stopped playing the audio. In case you want to solve the above problem, you could set a **boolean flag; isPlaying**, which should block the recorder from sending the stream to the back-end when it’s set to true. Congratulations! By reading this blog series, you now know how to build an end-to-end solution for streaming audio from a microphone to a server, and stream & play the audio results back in the browser! Do you want to play around with these examples? I am [hosting a web demo online](http://selfservicedesk.appspot.com/). Also I’ve [a video recording of one of my conference talks](https://youtu.be/6JD8WC1LV7g)! ![Lee presenting this content](conference.png) --- ## Building your own conversational voice AI which streams audio from a browser microphone to a server (part III) *Published: 2021-01-03* *Categories: Chatbots* *Tags: Dialogflow ES, Dialogflow, Voice AI, Google Speech to Text, Google Assistant, Chatbots, Best practice, STT, Speech to Text* > A best practice for streaming audio from a browser microphone to Dialogflow & Speech To Text. Your own conversational voice AI in a web application. --- This is the third blog in the series: A best practice for streaming audio from a browser microphone to Dialogflow & Google Cloud Speech To Text. In case you haven’t read the other blogs, I recommend to browse back to these blogs: * Blog 1: [Introduction to the GCP conversational AI components, and integrating your own voice AI in a web app](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-1/). * Blog 2: [Building a client-side web application which streams audio from a browser microphone to a server.](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-2/) In the next blog of this series, I will receive the audio bytes from the browser microphone on the server-side, so I can use this to make Dialogflow Detect Intent or Speech to Text transcribe calls! These blogs [contain simple code snippets](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples), and a demo application; [the Airport Self Service Kiosk](https://github.com/dialogflow/selfservicekiosk-audio-streaming), which will be used as a reference architecture. ## Server-side Implementation ![App Architecture](architecture2.png) Below are the steps for creating a Node.js Express application which integrates with the Google APIs such as Dialogflow, Speech to Text and Text to Speech. You will need a working front-end application as described in the previous blog, in order to get AudioBuffers live from an HTML5 microphone. Make sure you have read Blog 2, before you continue. Before writing any Node.js server code, let’s quickly preview the configuration and the NPM libraries that I am using: ### My application configuration: .env In my code base; for the simple examples, and for the end-to-end Airport Self Service Kiosk, I’m storing all my speech configurations outside the project. So it will be easy to play around with settings, without browsing through all the code. This is why I’ve created an **.env** system environments file. Later in my application code, I can make use of the npm library [dotenv](https://www.npmjs.com/package/dotenv). It loads environment variables from a .env file into [process.env](https://nodejs.org/docs/latest/api/process.html#process_process_env). In case I deploy my application later in a container, or with App Engine Flexible Environments, I can specify these environment variables in a **GKE configmap** or in the **app.yaml.** Here’s how my **.env** file looks like: ``` PROJECT_ID=gcp-project-id LANGUAGE_CODE=en-US ENCODING=AUDIO_ENCODING_LINEAR_16 SAMPLE_RATE_HERZ=16000 SINGLE_UTTERANCE=false SPEECH_ENCODING=LINEAR16 SSML_GENDER=FEMALE ``` For Dialogflow, an AudioConfig as part of the [DetectIntentRequest](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#detectintentrequest), is essential. It instructs the speech recognizer how to process the speech audio. Check the RPC reference for all the possible [configurations](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.dialogflow.v2.InputAudioConfig). For Speech to Text it’s important to pass an [RecognitionConfig](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1#google.cloud.speech.v1.RecognitionConfig) and [RecognitionAudio](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1#google.cloud.speech.v1.RecognitionAudio) to the [RecognizeRequest](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1#google.cloud.speech.v1.RecognizeRequest). **RecognitionAudio** contains audio data in the encoding specified in the **RecognitionConfig**. Either **content** or **uri** must be supplied. The **RecognitionConfig** provides information to the recognizer that specifies how to process the request. For Text to Speech the following configurations are important to make a [SynthesizeSpeechRequest](https://cloud.google.com/text-to-speech/docs/reference/rpc/google.cloud.texttospeech.v1#synthesizespeechrequest) call. [SynthesisInput](https://cloud.google.com/text-to-speech/docs/reference/rpc/google.cloud.texttospeech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.texttospeech.v1.SynthesisInput) (which can be text or SSML), [VoiceSelectionParams](https://cloud.google.com/text-to-speech/docs/reference/rpc/google.cloud.texttospeech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.texttospeech.v1.VoiceSelectionParams) (to describe which voice to use) and an [AudioConfig](https://cloud.google.com/text-to-speech/docs/reference/rpc/google.cloud.texttospeech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.texttospeech.v1.AudioConfig) (to describe the Audio data to be synthesized). ### The NPM libraries that I am using: package.json Since my example application makes use of Node.js and NPM, I will need to download external Node libraries. Here you can find [my package.json](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/examples/package.json). The npm packages which are important to build speech integration: * [dialogflow](https://www.npmjs.com/package/dialogflow): to interact with Dialogflow and do intent matching (on speech) * [@google-cloud/speech](https://www.npmjs.com/package/@google-cloud/speech): to interact with STT and to transcribe speech * [@google-cloud/text-to-speech](https://www.npmjs.com/package/@google-cloud/text-to-speech): to interact with TTS and to synthesize text The following packages came in handy too: * [pb-util](https://www.npmjs.com/package/pb-util): Utilities for working with common protobuf types. It can be used with the Dialogflow intent response. * [stream](https://www.npmjs.com/package/stream), [util](https://www.npmjs.com/package/util), [through2](https://www.npmjs.com/package/through2): For working with streams. To pipe streams together and destroy all of them if one of them closes. * [recordrtc](https://www.npmjs.com/package/recordrtc): WebRTC JavaScript Library for Audio+Video+Screen+Canvas Recording. I don’t need it on the server-side, but it’s in my package.json file, so I can host the library locally (instead from a CDN). * [socket.io](https://www.npmjs.com/package/socket.io): Socket.IO enables real-time bidirectional event-based communication. * [socket.io-stream](https://www.npmjs.com/package/socket.io-stream): This is the module for bidirectional binary data transfer with Stream API through Socket.io * [uuid](https://www.npmjs.com/package/uuid): to generate Universally Unique IDentifiers ### Setup Dialogflow Navigate to: [http://console.dialogflow.com](http://console.dialogflow.com/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) and create a new agent. Make sure you have enabled **Beta features** in settings, because we will make use of **Knowledge Base connectors** to import web-based FAQs and this feature is currently in beta. Once, it’s enabled we can create a new **knowledge base FAQ**, with **text/html** as a **mime-type**. For the Airport Self Service Kiosk demo, I am loading San Francisco Airport Questions and answers into my agent from a live website: [https://www.flysfo.com/faqs](https://www.flysfo.com/faqs) As soon as the FAQs are imported, you will see all the questions and answers listed in Dialogflow. We will now need to specify the answer to the Text and SSML response: **$Knowledge.Answer[1]** Dialogflow will use this response (the first answer from the Knowledge Base Q and A) to synthesize as an AudioBuffer. ![Knowledge Base Connector](knowledgebase.png) **Note:** In my Airport Self Service Kiosk demo, I am also showing the Question and Answer as readable text in my Angular web app. How did I do that? By creating a **custom payload** next to the Text & SSML response: ``` JSON { "knowledgebase": true, "QUESTION": "$Knowledge.Question[1]", "ANSWER": "$Knowledge.Answer[1]" } ``` ### Writing the server code Typically, the server-side code will exist of the following parts: * Importing all the required libraries * Loading the environment vars * Setting up the Express server with Socket.IO listeners * Google Cloud API Calls: Dialogflow Audio DetectIntent & DetectStream calls, Speech to Text Recognize & StreamingRecognize calls, Text to Speech synthesize calls For demo purposes, I won’t discuss how to setup a Node.js application with an express server. But as a reference, you can have a look into my [simple server code](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/examples/simpleserver.js), which has been used for the simple [client-side examples](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples). And you can also have a look into [the code of the Airport Self Service Kiosk](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/server), an end-to-end example. This example makes use of Cloud Speech to Text [StreamingRecognize](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.Speech.StreamingRecognize), Dialogflow [DetectIntent](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.Sessions.DetectIntent), and Text to Speech [SynthesizeSpeech](https://cloud.google.com/text-to-speech/docs/reference/rpc/google.cloud.texttospeech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.texttospeech.v1.TextToSpeech.SynthesizeSpeech). When you browse through these code snippets, you will be able to see the Express server. They all communicate via Socket.io like this: [GitHub Gist](https://gist.github.com/ed39c217b8346a2eb67cff4b41fd3560) 1. With Socket.io instantiated, I can listen to the connect emit. As soon as a Socket.io client connects to the server, this code will execute. 2. When connected to a socket, and the ‘message’ event was fired by the client, execute this code. It will retrieve the data which was set when stopping the WebRTC recorder. To recall from my previous blog, I have created an object with a child object, which contains the mime-type (**audio/webm**), and the **audioDataURL**, which is the Base64 string containing the audio recording. Let’s take that Base64 string and convert it to a Node.js file Buffer. 3. With that **fileBuffer** I could call my custom Dialogflow **DetectIntent** implementation, explained later in this article: ``` JavaScript const results = await detectIntent(fileBuffer); client.emit('results', results); ``` Or I could call my custom Speech to Text Recognize implementation, explained later in this article: ``` JavaScript const results = await transcribeAudio(fileBuffer); client.emit('results', results); ``` Both calls are asynchronous and return a Promise with the results. These results will be sent to the client-side app. The client could listen to the socket emit like this: ``` JavaScript socketio.on('results', function (data) { console.log(data); }); ``` 4. Here’s an example of a 2nd event fired by the client. In this case a streaming event. Now, I will retrieve the data when the WebRTC recorder streams chunks of audio data in the **ondataavailable** listener. Note, that the client socket is wrapped with **socket.io-stream **for streaming binary data transfers. I am retrieving the audio chunks, plus additional data, such as the stream name (a string). This can be used to store a temporary audio file on the server, in which I can pipe the incoming audio stream. It’s used as a holder, to activate my custom Dialogflow or Speech to Text implementations. 5. Like the DetectIntentStreaming implementation, explained later in this article: ``` JavaScript detectIntentStream(stream, function(results){ client.emit('results', results); }); ``` Or the Speech to Text StreamingRecognize implementation, explained later in this article: ``` JavaScript transcribeAudioStream(stream, function(results){ client.emit('results', results); }); ``` Both calls, pass in the stream, and a callback function to execute once the results are in. These results will be sent to the client-side app. The client could listen to the socket emit like this: ``` JavaScript socketio.on('results', function (data) { console.log(data); }); ``` ### API Calls to Dialogflow Dialogflow is an AI-powered tool for building text and voice-based conversational interfaces such as chatbots and voice apps. It uses Machine Learning models such as Natural Language Understanding to detect the intentions of a conversation. The way how Dialogflow intent detection works is, it first tries to understand the user utterance. Then, it will check the Dialogflow agent, which contains intents (chat flows), based on the training phrases. The intent with the best match (highest confidence score), will return the answer, which could be a text response, audio response or a response from a system through a fulfillment. I will use the Dialogflow Node.js client SDK to manually detect the intent, based on finished audio buffers and incoming audio streams. `const df = require(‘dialogflow’);` Let’s first prepare the client and the request. Later I can modify the request by adding the audio input: [GitHub Gist](https://gist.github.com/fcd1c22b55e2b64420a10938584dacba) 1. Dialogflow will need a session ID. Let’s use UUID to generate a random [https://www.ietf.org/rfc/rfc4122.txt](https://www.ietf.org/rfc/rfc4122.txt) RFC4122 id, in a format like: ‘1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed’. 2. Afterwards let’s create a Dialogflow Session Path. The session path can be created from a Dialogflow Session Client object. It needs a session ID to make each Dialogflow session unique. And it needs the GCP project id, which points to a GCP project that has a working Dialogflow agent. **Note: **Each Google Cloud Platform project can have only one Dialogflow agent. In case your Dialogflow Agent needs a test and development version. You either can make use of the [versions](https://cloud.google.com/dialogflow/docs/agents-versions?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) feature in Dialogflow. Or you could create more GCP projects, one for the test agent and one for the development agent. 3. Let’s already setup a request object, which will be used for each Dialogflow API call.In case this request will be used when streaming audio, this request will be used as the initial request. Which means it first connects to the SDK without the audio stream, but prepares the API with audio configurations it can use. Afterwards the chunks of audio will stream in. It needs to have a sessionPath (which now will point to a client session and a particular Dialogflow agent). Even without the audio input I can already setup the **queryInput**. 4. Since my application works with speech, I will need to set the [**audioConfig](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.dialogflow.v2.InputAudioConfig)** object. The **audioConfig** object requires a sample rate hertz, (this number has to be the same as the **desiredSampleRateHerz** from your client-side code). It requires a languageCode which contains the language of the spoken text, and it should be a language that was set in Dialogflow. It will need to have an encoding, which also needs to be the same as the encoding used in the client. In my code demos, I am using the configurations from the **.env **file. Now let’s have a look into both calls, [DetectIntent](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.dialogflow.v2.Sessions.DetectIntent) and [StreamingDetectIntent](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.dialogflow.v2.Sessions.StreamingDetectIntent). ### DetectIntent DetectIntent it receives the intent match results after all audio has been sent and processed. I’m creating an asynchronous function, which takes the AudioBuffer and adds it to the request. Next I’m calling detectIntent, by passing in the request. It returns a promise, which will be chainable: ``` JavaScript async function detectIntent(audio){ request.inputAudio = audio; const responses = await sessionClient.detectIntent(request); return responses; } ``` Here’s how the response would look like: [DetectIntentResponse](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.dialogflow.v2.DetectIntentResponse). You will likely be interested in the **queryResult**. In case you passed in the **DetectIntentRequest** an output audio config, you will be able to retrieve audio data bytes generated based on the values of the default platform text responses found in the **queryResult.fulfillmentMessages** field. If multiple default text responses exist, they will be concatenated when generating audio. If no default platform text responses exist, the generated audio content will be empty. ### StreamingDetectIntent The StreamingDetectIntent performs bidirectional streaming intent detection: receive results while sending audio. This method is only available via the gRPC API (not REST). [GitHub Gist](https://gist.github.com/4e8d03d74d6928daf8c52cd9703e5ece) 1. I’m creating an asynchronous function, which takes the AudioBuffer and adds it to the request, and the name of the callback function which will execute with the results once the API fetches the results. 2. Execute the **streamingDetectIntent()** call. 3. There’s an **on(‘data’)** event listener, which executes once audio chunks are streamed in. You could create some conditional logic here, in case there’s a **data.recognitionResult **in the response, then the intermediate transcript is recognized. Otherwise, likely the intent has been detected (or a fallback intent was triggered, in case there wasn’t a match). I’m returning the results by executing the callback function. 4. You could also listen to **error** events, when something went wrong with the request. Or you could listen to **end** events, when streaming to Dialogflow stopped. 5. The way how this works, is first we will let the Dialogflow API know that there will be a **streamingDetectIntent** call with all the **queryInput** and **audioConfigs** that can be retrieved from the requests. Afterwards, all the other messages that will come in, will contain the audio stream via **inputAudio**. 6. Let’s use a small node module called **pump**, which pipes the streams together and destroys all of them if one closes. 7. Here, I will transform the stream, so the request will now also contain **inputAudio** with the audioBuffer streaming in. Here’s how the response would look like. [StreamingDetectIntentResponse](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.dialogflow.v2.StreamingDetectIntentResponse). You will likely be interested in the queryResult. In case you passed in the **StreamingDetectIntentRequest** and output audio config, you will be able to retrieve audio data bytes generated based on the values of the default platform text responses found in the **queryResult.fulfillmentMessages** field. If multiple default text responses exist, they will be concatenated when generating audio. If no default platform text responses exist, the generated audio content will be empty. Here’s how it would look like in a real production application, using TypeScript: [https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/server/dialogflow.ts](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/server/dialogflow.ts) ## API Calls to Speech to Text The Speech to Text API transcribes spoken words to written text. This is great for when you want to generate subtitles in a video, generate text transcripts from meetings, etc. You could also combine it with chatbots (detect intent from texts) to synthesize the chatbot answers. Speech to Text is very powerful, as the API call response will return the written transcript with the highest confidence score, also return an array with alternative transcript options. It’s also possible to bias the recognizer, by sending phrase hints to the API. Here, I will use the Speech Node.js client SDK to transcribe voices to written text. const speech = require('@google-cloud/speech'); Let’s first prepare the client and the request. Later I can modify the request by adding the audio input: [GitHub Gist](https://gist.github.com/7d78feb8f956cb981dfd034ab7acc151) 1. First, let’s instantiate the Speech Client. 2. Here I will describe the request object. Since our application works with speech, we will need to set the recognition [**config](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.RecognitionConfig)** object. The **config** object requires a sample rate hertz, (this number has to be the same as the desiredSampleRateHerz from your client-side code). It requires a languageCode which contains the language of the spoken text. And it will need to have an encoding, which also needs to be the same as the encoding used in the client. In my code demos, I am using the configurations from the **.env **file. Now let’s have a look into both calls, [Recognize](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.Speech.Recognize) and [StreamingRecognize](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.Speech.StreamingRecognize). ### Recognize The Recognize call performs synchronous speech recognition. It receives results after all audio has been sent and processed. I’m creating an asynchronous function, which takes the AudioBuffer and adds it to the [request](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.RecognitionAudio). Next; I’m calling the **recognize** method from the speech client, by passing in the request. It returns a promise, which will be chainable: ``` JavaScript async function transcribeAudio(audio){ request.audio = { content: audio }; const responses = await speechClient.recognize(request); return responses; } ``` Here’s how the response would look like: [RecognizeResponse](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.RecognizeResponse). It will return [SpeechRecognitionResults](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.SpeechRecognitionResult), which will contain an array with [alternatives](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.SpeechRecognitionAlternative). Each alternative contains the transcript, confidence score (The confidence estimate between 0.0 and 1.0. A higher number indicates an estimated greater likelihood that the recognized words are correct.) and an array with all the words. Alternatives are sorted on confidence level, with the highest confidence first in the array. ### StreamingRecognize StreamingRecognize performs bidirectional streaming speech recognition: receive results while sending audio. This method is only available via the gRPC API (not REST). [GitHub Gist](https://gist.github.com/c8f5646ed3cf63ea397cc92f11ab052b) 1. I’m creating an asynchronous function, which takes the AudioBuffer and adds it to the request, and the name of the callback function which needs to return with the results. 2. Execute the streamingRecognize() call by passing in the speech request. 3. There’s an on(‘data’) event listener, which executes once audio chunks are streamed in. I’m returning the results by executing the callback function. 4. You could also listen to **error** events, when something went wrong with the request. Or you could listen to **end** events, when streaming to Dialogflow stopped. 5. Finally we pipe the recognizeStream together with the incoming audio. Here’s how the response would look like: [StreamingRecognizeResponse](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.StreamingRecognizeResponse). It will return [StreamingRecognitionResult](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.StreamingRecognitionResult), which will contain an array with [alternatives](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.speech.v1.SpeechRecognitionAlternative). Each alternative contains the transcript, confidence score (The confidence estimate between 0.0 and 1.0. A higher number indicates an estimated greater likelihood that the recognized words are correct.) and an array with all the words. Alternatives are sorted on confidence level, with the highest confidence first in the array. By now, you have seen how to build a web application which streams audio from a microphone on your local device through your browser to a back-end application, and fetches results from Google Cloud Speech to Text or Dialogflow and displays it in the User Interface. It would be even nicer, when the browser could play the audio streams. [This is what the next blog is all about!](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-3/) --- ## Building your own conversational voice AI which streams audio from a browser microphone to a server (part II) *Published: 2021-01-02* *Categories: Chatbots* *Tags: Dialogflow ES, Dialogflow, Voice AI, Google Speech to Text, Google Assistant, Chatbots, Best practice* > A best practice for streaming audio from a browser microphone to Dialogflow & Speech To Text. Your own conversational voice AI in a web application. --- This is the second blog in the series: A best practice for streaming audio from a browser microphone to Dialogflow & Google Cloud Speech To Text. [In this first blog, I have introduced all the conversational components, and I addressed why customers would integrate their own conversational AI compared to building for the Google Assistant](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-1/). Today, I will make a start by building a client-side web application which uses a HTML5 Microphone with WebRTC, streaming the audio bytes to a Node.js backend. Later in this blog series, I will show you how to use the Google Cloud Conversational AI APIs to transcribe texts or to return answers with Text to Speech. These blogs [contain simple code snippets](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples), and a demo application; [the Airport Self Service Kiosk](https://github.com/dialogflow/selfservicekiosk-audio-streaming/), which will be used as a reference architecture. Let’s start with the creation of the client-side HTML & JavaScript code. Whether you want to transcribe voice to written text (STT), or use voice to trigger a chatbot agent to answer (Dialogflow), the client-side code base is very similar. ![Architecture](architecture2.png) ## Client-side WebRTC Implementation Here are some code snippets, which you can run to play around with this. You can run these examples, by cloning this repository. [The instructions are written here](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples). * [Client-Side Code: DetectIntent](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples/example1.html) — Dialogflow * [Client-Side Code: Transcribe ](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples/example4.html)— STT These examples make use of the following JavaScript libraries: * [Socket.io](https://www.npmjs.com/package/socket.io) — Socket.IO enables real-time bidirectional event-based communication. * [Socket.io-Stream](https://www.npmjs.com/package/socket.io-stream) — for binary stream transfers through Socket.io * [RecordRTC](https://github.com/muaz-khan/RecordRTC) — RecordRTC is WebRTC JavaScript library for audio/video as well as screen activity recording. In these demos I am using 2 buttons, a start recording and a stop recording button. Also I have created a textarea field, which later will display the results. ```
``` In case you want to see an end-to-end example, please have a look into the [Airport Self Service Kiosk demo](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/client/src). It’s an Angular web app, which contains one record button which needs to be pressed once, to record audio streams. It will display the results in the middle of the screen. ![Front-end Code: Airport Self Service Kiosk demo](selfservicedesk.png) I wrote some JavaScript code, which is embedded in the HTML simple example: [GitHub Gist](https://gist.github.com/42da2831f9451dd9c5c54e11a89a9179) 1. First I will create some pointers to the start & stop buttons. 2. Next, I am instantiating socket.io, and I am opening a connection. 3. I’ve created 2 event listeners for starting and stopping the recording. The start button onclick event, will disable the start button, so you can’t press the button twice and therefore record audio twice. 4. [navigator.getUserMedia()](https://www.html5rocks.com/en/tutorials/getusermedia/intro/) is an important part of the code. It’s part of a set of WebRTC APIs which provides the means to access the user’s local camera/microphone stream. In our case, we only use the microphone (**audio: true**). This gives us access to the stream. 5. Now, I am making use of the library RecordRTC. I could have chosen to write this part of code by myself. But RecordRTC solves a lot of complicated stuff. Such as converting buffers (from Float32 to Int16), cross browser support, etc. 6. RecordRTC takes 2 arguments. The first argument is the MediaStream from the getUserMedia() call. The second argument is a configuration object, with settings to optimize the stream.There are a couple of important settings that I am making, which should be in line with your settings, later in the server side code (Documentation for [InputAudioConfig](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#google.cloud.dialogflow.v2beta1.InputAudioConfig) in Dialogflow or [RecognitionConfig](https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_#recognitionconfig) in STT): * The mimetype is set to **audio/webm** — which would be a good setting when using **AUDIO_ENCODING_LINEAR_16** or **LINEAR16** as an AudioEncoding config in Dialogflow or STT. * The sampleRate is the input sampling frequency in hertz. I am resampling it to 16000Hz (desiredSampleRate) so the size of the messages over the network will be smaller, and match the sample herz setting in my Dialogflow or STT calls. * Also Dialogflow & STT require mono sound, which means, I should set the numberOfAudioChannels to 1. RecorderType StereoAudioRecorder allows me to change the number of audio channels from 2 to 1. ## Record Single Utterances Short utterances / detect intent. This means your end-user presses a record button, speaks, and when they press stop, we collect the audio stream to return results. In your code, this means once the client web app collects the full audio recording, it sends it to the server, so the server can do a call to Dialogflow or the Speech to Text API. For this use case, the magic will be in the stop button onclick event listener: [GitHub Gist](https://gist.github.com/954802a2afaf64d6a2b5d637f0a40410) 1. When you click stop, it will first reset the buttons, then stop the recording. And while stopping the recording, in a callback function, it will request the audioDataURL which is part of the [RecordRTC API](https://recordrtc.org/). This will return a string dataURL, with a Base64 string that contains your audio stream. This long string looks like this: **data:audio/wav;base64,UklGRiRgAgBXQVZFZm10IBAAAAABAAEARKwAA** 2. We can create an object from it, which also sets the audio type, and then we are sending it to the server, with socket io: **socketio.emit(‘message’, files);** We will set a name. Once the server makes a connection to this socket, it will look for the ‘message’ event name, to respond on. And it will receive the files object. 3. The last part of this script will run once the server made a call to Dialogflow / Speech API and made a websockets call back to the server to return the results. In this example, I am just printing the results in a textarea box. For Dialogflow, the **fulfillmentText** is part of the queryResult. When using STT, you want to print the **transcript** string from the **alternatives** array. ## Record Audio Streams Recording streams means your end-user presses the record button, speaks, and will see the results on the fly. When detecting intents with Dialogflow, it could mean that it will detect better matches once you have spoken more, or it could collect multiple results. In your code, this means the client starts making a bi-directional stream and streams chunks to the server so the server can make a call with event listeners on incoming data and thus it’s real-time. You might choose this approach because the audio you are expecting is long. Or in the case of Dialogflow, you might want to show intermediate results on the screen, in real time, while speaking. In this case, you don’t need the stopRecording callback function, which sends the base64 URL string to the server. Instead it will send the stream to the server in real-time! Have a look into the following examples: Client-Side Code: [DetectStreamingIntent](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/examples/example2.html) — you can run these examples, by cloning this repository. [The instructions are written here](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples). [GitHub Gist](https://gist.github.com/9283fd2abea90bae44d5dc2fce0bfe29) The magic in this case, is in the RecordRTC object and the **ondataavailable** event listener: 1. First, you will need to set a **timeSlice.** The timeSlice sets intervals for creating audio chunks. In the case of Dialogflow, you likely might not want to detect the intents each second (as you might not be finished speaking a sentence), and rather build in a timer. timeSlice is set to **milliseconds**, so I am using 4000 (4 seconds). 2. Then there is the **ondataavailable** event listener, which gets triggered once there is data, and will contain chunks of blobs (audio buffers), in my case every 4 seconds. 3. Here’s where **socketio-stream** comes in. I am making use of bi-directional streams (i’m sending a stream with chunks every 4 seconds, but I also might want to receive results from the server in between). So I am creating the stream, which will be temporarily stored on my local drive. with **ss(socket).emit()** I am streaming it to the server, and while I do so, I am piping the audio buffer into the stream. The purpose of **stream.pipe()** is to limit the buffering of data to acceptable levels such that sources and destinations of differing speeds will not overwhelm the available memory. In case you want to see an end-to-end example, please have a look into the [Airport Self Service Kiosk demo microphone class](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/client/src/app/microphone/microphone.component.ts). It’s written in TypeScript, which implements the HTML5 microphone. ## Running your app on iOS When running your application on iOS devices, you might run into various problems. First of all, iOS doesn’t support the JavaScript **getUserMedia** and WebRTC methods in any other mobile browser than Safari. In my application, [I’m showing a pop-up when it’s opened on an iOS browser other than mobile Safari](https://github.com/dialogflow/selfservicekiosk-audio-streaming/blob/master/client/src/app/app.component.ts). To make use of the getUserMedia() WebRTC method, you will need to allow the permissions popup, which only shows up once running from **HTTPS**. One important limitation remains on iOS: Web Audio is effectively muted until user activation. To play and record audio in iOS, it requires a user interaction (such as touch start). That’s it for now. [In the next blog of this series, I will receive the audio bytes on the server-side, so I can use this to make Dialogflow Detect Intent or Speech to Text transcribe calls!](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-3/) --- ## Building your own conversational voice AI with Dialogflow & Speech To Text in web apps. (part I) *Published: 2021-01-01* *Categories: Chatbots* *Tags: Dialogflow ES, Dialogflow, Voice AI, Google Speech to Text, Google Assistant, Chatbots, Best practice* > A best practice for streaming audio from a browser microphone to Dialogflow & Speech To Text. Your own conversational voice AI in a web application. --- This is the first blog in the series: A best practice for streaming audio from a browser microphone to Dialogflow & Google Cloud Speech To Text. In this first blog, I will address why customers would integrate their own conversational AI compared to building for the Google Assistant. I will introduce all the conversational AI components in Google Cloud and where you would use each component for. Later in this blog series, I will show you how to integrate an HTML5 microphone in your web application. How to stream audio streams to a (Node.js) back-end. How to use the Dialogflow API for audio streaming. How to use the Speech API. And, lastly, how to return audio (Text to Speech) to a client to play this in a browser. These blogs contain [simple code snippets](https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples), and a demo application; the [Airport Self Service Kiosk](https://github.com/dialogflow/selfservicekiosk-audio-streaming/), which will be used as a reference architecture. ## Google Assistant vs. a custom conversational AI I often speak with customers and their wish to include the Google Assistant in their business web apps. Unless you are a manufacturer for tv setup boxes or headphones, I always answer; “Is this really what you want? Or do you mean you want to extend your own app with a conversational AI?” **- “Eh?”** If you have one or more of the below requirements, you probably want to make direct use of the Google Cloud Speech and Dialogflow APIs, instead of [packing your voice AI as an action in the Google Assistant](https://developers.google.com/assistant?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) or [wrapping the Google Assistant in your app](https://developers.google.com/assistant/sdk/overview?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_). * This application shouldn’t be public available. * This application doesn’t need to be available on the Google Assistant / Nest Home. * You don’t want to start your app with the wake words: “Hey Google, talk to my app”. * The application doesn’t need to answer native Google Assistant questions, such as: “what’s the weather in Amsterdam”. * The application can only make use of the Google Cloud terms & conditions, instead of combining it with the consumer terms & conditions of the Google Assistant. Convinced that you want to extend your own (mobile) web app by integrating voice AI capabilities? Here’s the ultimate developer guide, on implementing voice streaming from a web application to Google Cloud Speech and Dialogflow. ## Dialogflow versus Text-to-Speech API versus Speech-to-Text API **Dialogflow** is an AI-powered tool for building text and voice-based conversational interfaces such as chatbots and voice apps. It uses Machine Learning models such as Natural Language Understanding to detect the intentions of a conversation. The way how Dialogflow intent detection works is, it first tries to understand the user utterance. Then, it will check the Dialogflow agent, which contains intents (or chat flows), based on the training phrases. The intent with the best match (highest confidence score), will return the answer, which could be a text response or a response from a system through a fulfillment. Although many of us will use Dialogflow with text input, for web or social media chatbots, it is also possible to do intent matching with your voice as audio input, and it can even return spoken text (TTS) as an audio result. Dialogflow speech detection & output will have some overlap with Cloud Speech to Text API (STT) and Cloud Text to Speech (TTS). Even the API calls look similar! However those services are different, and they have been used in separate use cases. **Speech to Text (STT)** transcribes spoken words to written text. This is great for when you want to generate subtitles in a video, generate text transcripts from meetings, etc. You could also combine it with Dialogflow chatbots (detect intent from text transcripts) to synthesize the chatbot answers, however STT doesn’t do intent detection like Dialogflow does. STT is very powerful, as the API call response will return the written transcript with the highest confidence score, and it will return an array with alternative transcript options. With **Text to Speech (TTS)**, you can send text or SSML (text with voice markup) input and it will return audio bytes, which you can use to create an mp3 file or directly stream to an audio player (in your browser). Compared to the **Google Assistant**, by extending your apps with a conversational AI manually with the above tools, you no longer are part of the Google Assistant ecosystem. That ecosystem is nice if you are building consumer or campaign apps (voice actions), that everyone can find by invoking it through the *Hey Google, talk to my app* wake phrase. But when you are an enterprise, that whole ecosystem might be overkill. ![Actions on Google ecosystem](actions-on-google-space.png) For an enterprise who wants to integrate a voice AI in their own apps, the full Google Assistant ecosystem might be an overkill. ## Google Cloud Contact Center AI There’s another Google solution, which is called **Google Cloud Contact Center AI **(CCAI). This solution is for enterprises that want to deploy a voice AI in their existing telephone contact center (IVR). Dialogflow and Cloud Speech APIs are the key pieces in that architecture OEMed by a telephony partner (such as Genesys, Avaya, Cisco etc.) Since Contact Center AI is an out of the box solution, you don’t need to implement these APIs yourself. ## About the demo application; Airport Self Service Kiosk Now that you know the differences between all the conversational GCP components, let’s see how we can implement these in an end-to-end web application. For this guide, I will make use of a demo app, which is a self service kiosk for an airport. (Self Service Kiosks are also common in retail or the finance sectors.) You can ask the Self Service Kiosk; if’t okay to bring a lighter in your handbag, or what time boarding is. The results will be presented on the screen, and it will also be spoken out: ![Here’s a screenshot from my demo app: The Airport Self Service Kiosk](selfservicedesk.png) Let me show you the best practice for streaming audio from your microphone through the browser to Dialogflow and then out through the speaker. All the code is available on Github: [https://github.com/dialogflow/selfservicekiosk-audio-streaming](https://github.com/dialogflow/selfservicekiosk-audio-streaming) And the final solution has been deployed with App Engine Flex: [http://selfservicedesk.appspot.com](http://selfservicedesk.appspot.com) Building the demo application requires the following tools: * NodeJS * Dialogflow client SDK * STT Node.js client SDK * TTS Node.js client SDK * [Socket.io](https://www.npmjs.com/package/socket.io) & [Socket.io-Stream](https://www.npmjs.com/package/socket.io-stream) * [RecordRTC](https://github.com/muaz-khan/RecordRTC) * AppEngine Flexible Environment (with websockets support & HTTPS) ## Architecture Here’s the architecture that I have been using: ![The architecture I’ve used.](architecture1.png) * Client website / app. For demo purposes I will show you two versions. A simple HTML page, and an example of a full web application in Angular, [such as the self service kiosk demo](https://github.com/dialogflow/selfservicekiosk-audio-streaming). It contains the **getUserMedia() WebRTC** call wrapped by the **RecordRTC** library, to record the audio streams from the browser microphone. * A NodeJS server which will serve the static content (such as the HTML page) and connect to the GCP libraries, like Dialogflow, STT and TTS. * You could use any other programming language as well. All GCP services have various client SDKs (such as Node.js, Java, Python, Go etc), and also Rest and GRPC libraries. * The Dialogflow Agent, which contains intents, entities, and FAQ Knowledge bases. The client app talks to the backend server via websockets. This is a common approach when building chatbots or chat applications because they can respond in real-time, without any page refreshes. I am using the socket.io framework with the socket.io-stream plugin, since it’s easy to use and I need to make use of bi-directional streaming. ***Note:*** *I’ve seen solutions online where the microphone is directly streamed to the Dialogflow, without a server in between. The REST calls were made directly in the web client with JavaScript. I would consider this as an anti-pattern. You will likely expose your service account / private key in your client-side code. Anyone who is handy with Chrome Dev tools could steal your key and make (paid) API calls via your account. It’s a better approach to always let a server handle the Google Cloud authentication. This way the service account won’t be exposed to the public.* ## Short utterance vs. Streaming There are typically 2 approaches on how to integrate voice in your application. 1. Short utterances / detect intent. This means your end-user presses a record button, speaks, and when they press stop, we collect the audio stream to return results. In your code, this means once the client web app collects the full audio recording, it sends it to the server, so the server can do a call to Dialogflow or the Speech to Text API. 2. Streaming of long utterances / detect intents in a stream. This means your end-user presses the record button, speaks, and will see the results on the fly. When detecting intents, it could mean that it will detect better matches once you have spoken more, or it could collect multiple results. In your code, this means the client starts making a bi-directional stream and streams chunks to the server so the server can make a call with event listeners on incoming data and thus it’s real-time. 3. When there is an intent match, we can either show the results on screen by presenting the text, or we can synthesize (read out) the results by streaming an audio buffer back to the client, which will played via the WebRTC AudioBufferSourceNode (or audio player). Stay tuned for my next blog. [In this blog I will make a start by building a client-side web application which uses a HTML5 Microphone with WebRTC, streaming the audio bytes to a Node.js backend.](https://www.leeboonstra.dev/Chatbots/building-your-own-voice-ai-2/) --- ## Orchestrate multiple sub chatbots from one chat interface by using the Mega Agent feature in Dialogflow *Published: 2020-12-01* *Categories: Chatbots* *Tags: Dialogflow ES, Dialogflow, Chat Orchestrator, Mega Agent, Flows in Dialogflow ES, Sub Agents* > Dialogflow has the Mega Agent feature. (At the time of writing, this feature is still in beta but ready to use.) This feature allows you to connect various sub Dialogflow agents to one single… --- Dialogflow has the Mega Agent feature. (At the time of writing, this feature is still in beta but ready to use.) This feature allows you to connect various sub Dialogflow agents to one single Dialogflow agent which is connected to your integration channels, so your users can interact with one chatbot interface instead of many. When you are building a chatbot with Dialogflow, you might notice that at some moment you will reach the point that your chatbot performs less and becomes hard to maintain. If you have 1500 intents in one agent, likely you will have intents with training phrases that are quite similar to each other. The way you can solve this is in the Dialogflow settings panel, the **ML Settings** tab, to change the Machine Learning threshold for the confidence score. If the returned value is less than the threshold value, then a fallback intent will be triggered, or if there is no fallback intents defined, no intent will be triggered. This might solve your problem for then, but what if your agent has more than 2000 intents? And how would you maintain that? Especially when working with large teams. Imagine you would work for a large enterprise, such as a bank. The chatbot needs to address Bank Accounts/Cards, Mortgages and General Banking questions. When you would add all these intents to one Dialogflow agent, you will likely have intents colliding with each other. In fact, at a traditional bank people work in teams. There is a team working on chatbots for Bank Accounts vs. Mortgages etc. — These teams don’t know about the existence of each other’s Dialogflow intents. Data governance is very important. Mega agents could solve these problems. As each team can work on their own chatbot. This means rapid changes without breaking the training phrases from other teams, better debugging, version control and agent reviews. But for the end user, it looks like one single chatbot which can address many features through one interface. ![Mega Agent Architecture](mega-agent-architecture.png) As with all Dialogflow agents, you can only create only one agent per GCP project, so each sub-agent and mega agent will be associated with its own GCP project. The small talk feature won’t work, but you can include Knowledge Base articles per sub agent. A mega agent can have at most 10 sub-agents. Each Dialogflow agent can have 2000 intents. So this will give you a maximum of 20k intents. When you detect an intent via the SDK, you would make a detectIntent call and use the mega agent’s project ID. Dialogflow will consider all of the sub-agents, and the best response from the sub-agents is returned. However, it’s also possible to specify one or more sub-agents for a detect intent request by setting the subAgents field of QueryParameters. ``` JavaScript { "queryInput": { "text": { "text”: “How can I open a new bank account?", "languageCode": "en-US" } }, "queryParams": { "subAgents": [ {"project": "projects/dialogflow-mortgages"}, {"project”: “projects/dialogflow-bankaccounts"} ] } } ``` ## Configure a mega agent Imagine you are a media retailer. Your shop sells video games and movies. The chatbot users can ask questions about video games which will under the hood be answered by the video games chatbot, or about movies, which will be answered under the hood by a movie chatbot. 1. To set up the Mega Agent feature, you will need to have (or create), “normal” Dialogflow agents first, which later will become a sub agent. 2. Next, you will **create a new agent**, from the drop down menu, under the logo. 3. You will give the new agent a name for example: *mega-agent-media-retailer *and then you will flip the switch: **Set as Mega Agent** then hit **save**. ![Mega Agent settings in Dialogflow](mega-agent.png) After you have created the Mega Agent, you will see a new menu item: *Sub Agents*, in your Dialogflow menu. Additionally, if your mega agent name doesn’t take too many characters, you will see the word: “*MEGA*” next to the agent name. ![Mega Agent settings in Dialogflow](mega-agent-2.png) When it’s your first time setting up the Mega agent, you will need to assign the sub agents to it. Click No sub-agents yet. **Add the first one**. You will get in a new screen and you can start to select other agents (from GCP projects) that are available to your Dialogflow user account (Google identity). While doing so, you can also see the **GCP project name**, select the **Dialogflow environment** or **include/exclude the knowledge base feature**. Hit **save**. ![Sub Agents in Dialogflow](sub-agents.png) 4. The Mega Agent will need to get access to each sub agent, and we can do this via the IAM roles page in the GCP console, by adding the service account email address to each sub agent. Here’s how we do this: While in Dialogflow, make sure the Mega Agent is still active. click the **settings cog** icon next to mega agent name. Which will open the settings panel. When you scroll down in the **General** tab, you will see a service account like: *dialogflow-@.iam.gserviceaccount.com* ![Mega Agent settings in Dialogflow](mega-agent-3.png) Copy this email address to your Notepad/Notes app, so you can use this one later to assign it to the other projects in the steps below. 5. You will need to browse back to the GCP console: [http://console.cloud.google.com](http://console.cloud.google.com?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) and make sure that the **Dialogflow API** for each project id (the subs and the mega agent) is enabled. If it’s correct, it’s already enabled by default but it’s better to double check this. First you select the project in the blue top bar, and use the search form to search for *Dialogflow API. *When you see *the blue Disable API link*, you will know it’s enabled. ![Dialogflow API in Google Cloud Platform Console](dialogflow-api-gcp.png) 1. In the GCP console load one of the sub agent projects, and navigate to **IAM & Admin**. Click the **Add** button at the top. In the next slide out screen you can **paste the service account email address** (from step 4) as a member. As a role, use the filter to search for **Dialogflow API Client**. (*Project Owner, Project Editor or Dialogflow Admin* roles would work as well, but for security reasons it’s always best to give the least / absolute necessary permissions to your service accounts.) Repeat this step for each sub agent and hit **Save**. 7. From here you can test your mega agent in the Dialogflow simulator. However, if you want to integrate this in a real application, you will likely need to download the service account to your local drive. To test the mega agent feature in Dialogflow, make sure you are still in the Mega Agent project. You can start typing questions (based on training phrases of each sub agent) in the simulator like: * “Which games will be released on Nintendo Switch*” — it will return the response from the video games sub agent. * “What movies are out on DVD?*” It would return an answer from the movies sub agent. ## Billing Now, how does this work for billing? The pricing is based on the number of intents used to fulfill a user request. If a request explicitly specifies sub-agents, this is the sum of all intents for the supplied sub-agents. If a request does not specify sub-agents, this is the sum of all intents for all sub-agents of the mega agent. Thus, when any request is made with a potential for any one of N intents as a match, all N intents need to be examined. Dialogflow has to search through all N of those intents, so when N is large, the price will be increased. For more information see the Dialogflow Documentation on Mega Agents: [https://cloud.google.com/dialogflow/docs/agents-mega](https://cloud.google.com/dialogflow/docs/agents-meg?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) --- ## Create high-quality chatbots by making use of agent validation, an out of the box review feature. *Published: 2020-03-30* *Categories: Chatbots* *Tags: Dialogflow, Chatbots, Machine Learning, AI* > Dialogflow provides a validation feature. Agent validation results are available automatically whenever agent training is performed and completed. You can access the results of validation from either… --- Dialogflow provides a validation feature. Agent validation results are available automatically whenever agent training is performed and completed. You can access the results of validation from either the Dialogflow Console or the API. When you are building a chatbot which becomes a little bit more advanced, you will easily end up with lots of intents and entities. Maybe, you are even working with a team within your Dialogflow project. Various ux designers working on various intents. Where people work, mistakes are made. This can become a problem when you are training your Dialogflow model. Common mistakes that can be made in Dialogflow are: * No, or not enough training phrases in an intent. * The intent has training phrases which are too similar to each other, or to training phrases of other intents. * Variations of entities are not used enough in training phrases. * Text is annotated within some training phrases but not in others. * No negative examples for the fallback intents. What's good to know is that Dialogflow has an automatic validation feature built-in the Dialogflow console or API. The results are available whenever the agent training is completed. The agent validation feature is by default enabled. Should you not be able to find it, you can enable the Agent Validation toggle setting in the **Settings > ML Settings** tab. Once you click **Validation** in the Dialogflow menu (and the agent has been trained), you will see the results of the validation: The validation results provide a list of warnings and errors that you should correct to improve the quality and performance of your agent. It can find issues on (global) agent level, in intents or in entities. If your agent has warnings or errors, you can choose to ignore them and launch your agent. It's for informational purposes only, but you would rob yourself from Agent Quality by not using this feature, basically this is a Dialogflow Agent Review out of the box, for free! Issues can represent various levels of severity. Only Info, Warning and Error messages are shown in the Validation screen. The settings Critical and Unspecified are available in the SDK (or on the specific intent / entity pages). ``` INFO - The agent doesn't follow the best practice WARNING - The agent might not behave as expected. ERROR - The agent may experience partial failures. CRITICAL - The agent may completely fail. SEVERITY_UNSPECIFIED - Not specified. This value should never be used. ``` Here are some examples: In this example there are 6 warnings in various intents. ![Dialogflow Validation](https://leeboonstra.dev/images/0_t74TgSdQI5E7eQ8i.png) * The **estimate-yes-agent-count-calls-per-month** intent, annotated 'Around 70000' as text, but the parameter expects a @sys.number. Just annotating the number would in the training phrase, would fix this warning. * The **estimate-yes-agent-count-invalid-calls-per-month** intent has no training phrases. Oops, I likely forgot to provide those. In this example, you can see there is an error in my entity FirstName where it uses duplicated aliases (values). I would likely need to delete those. ![Dialogflow Validation](https://leeboonstra.dev/images/0_66XRF8wjABXchNfO.png) **NOTE: At most 5000 issues are shown at a time. If you have over 5000 issues, you may not see a count reduction until less than 5000 remain.** Besides the validation screen, when you visit either the intents list or the entities list pages, any intents or entities with validation errors show an error outline indicator next to the name. ![Dialogflow Validation](https://leeboonstra.dev/images/0_HTGbZX-u3mEN7vIj.png) Also, when you visit a page for a specific intent or entity that has validation errors, an error outline indicator is shown near the **Save** button. ![Dialogflow Validation](https://leeboonstra.dev/images/0_zUjiOI8Dm5QOhulo.png) Clicking this indicator shows a list of errors for the intent or entity. By default, only errors with a severity of *CRITICAL* or *ERROR* are shown. It's also possible to run the validation from your code, through the SDK. ([ValidationResult](https://medium.com/r/?url=https%3A%2F%2Fcloud.google.com%2Fdialogflow%2Fdocs%2Freference%2Frpc%2Fgoogle.cloud.dialogflow.v2beta1%23google.cloud.dialogflow.v2beta1.ValidationResult)). This might be handy, in case you are building your own CI/CD pipelines, and before bringing your agent to production, you might want to run the agent validation first. Here's an example on how you could implement this for Node.js: [GitHub Gist](https://gist.github.com/620084b4806ad6c4a5d105235dd88262) The [ValidationResult](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1#google.cloud.dialogflow.v2beta1.ValidationResult) response could look like this: ``` JSON { "validationErrors": [ { "severity": "ERROR", "entries": [ "projects/my-project/agent/intents/58b44b2d-4967–4a81-b017– 12623dcd5d28/parameters/bf6fdf55-b862–4101-b5b1–36f1423629d0" ], "errorMessage": "Parameter 'test' has an empty value." }, { "severity": "WARNING", "entries": [ "projects/my-project/agent/intents/271e3808–3c91–4e6b-89e8–47951abcec8d" ], "errorMessage": "Intent 'app.current.update' does not have enough unique training phrases. Consider adding more different examples." }, { "severity": "ERROR", "entries": [ "projects/my-project/agent/intents/26e64b1b-eaa7–4ce2-be46–631a501fccbe/trainingPhrases/a650375e-083c-4bb5–9794-ba9453e51282", "projects/my-project/agent/intents/58b44b2d-4967–4a81-b017–12623dcd5d28/trainingPhrases/1d947780–22d3–4f80–8d7a-3f86efbf0be3" ], "errorMessage": "Multiple intents share training phrases which are too similar:\n - Intent 'app.notifications.open': training phrase 'open allo notifications settings'\n - Intent 'app.current.notifications.open': training phrase 'open notifications settings'" } ]} ``` ## Conclusion As you have seen, Dialogflow provides an out of the box agent review & validation feature. It's enabled by default and you can access it via the Dialogflow console or through the API. This will give you an overview of errors and warnings, which you should fix to improve your agent quality. When a large number of issues are found you should consider fixing the issues in small batches, based on similarity. It might be that fixing one issue may solve similar issues after you retrain the agent. So, now you know: Before bringing your chatbots to production, always check the validation page! --- ## Mastering auto speech adaptation in Dialogflow for voice agents *Published: 2020-03-23* *Tags: Speech Adaptation, ML Bias, Dialogflow, Voicebots, Callbots* > Auto speech adaptation improves the speech recognition accuracy of your Dialogflow voice agent by effectively adding context to your conversations. In the Netherlands, we have this radio show called… --- Auto speech adaptation improves the speech recognition accuracy of your Dialogflow voice agent by effectively adding context to your conversations. In the Netherlands, we have this radio show called: "Mama Appelsap*". It makes fun of mishearing and misinterpreting phrases in lyrics. Another word for this phenomenon is a **mondegreen**. Mondegreens are most often created by a person listening to a poem or a song; the listener, being unable to clearly hear a lyric, substitutes words that sound similar and make some kind of sense. You can find on Youtube all kinds of mondegreens, such as the *Creedence Clearwater Revival song Bad moon rising*. You might have heard of it. Instead of hearing "there's a bad moon on the rise", you could also hear: "there's a bathroom on the right". (In fact, after hearing this once, it's hard to unhear this anymore.) When we talk about Speech Machine Learning models, speech to text models have been trained by large datasets. Based on the audio examples it heard before, it tries to match text phrases out of it. With that being said, mishearing speech is not only hard for humans, it can be difficult for machines as well. However, we can bias the machine learning model by providing tips. In Dialogflow, when building voice agents, it's actually so easy; you just need to enable the **auto speech adaptation switch** which can be found in the **settings > speech** tab. Intents and (marked) entities will be used as hints. ![Dialogflow Speech Settings](https://leeboonstra.dev/images/0_UZklk43Hek7Drmgu.png) At the time of writing, this feature is beta. In case you can't find the speech tab in the settings panel, you will need to enable the beta features first. This can be found in the **Settings > General** tab: ![Settings > General](https://leeboonstra.dev/images/0_mVVqNNDmLqAJrWyR.png) Once you have enabled auto speech adaptation, you can build your Dialogflow voice agents in ways to take advantage of it. You can either test it in the simulator by using your microphone, or if you are a developer, I would suggest you will use my Dialogflow Speech recognition example, because it will give you access to more debug information. For best results, you'll also want to enable enhanced models: https://cloud.google.com/dialogflow/docs/data-logging **Note: Auto speech adaptation does not work for Actions on Google (Google Assistant), because with the AoG framework, speech recognition is performed by Actions on Google before sending data to Dialogflow.** ## Setting up a Speech Intent Detection example, to use your browser's microphone and run a Dialogflow script from the server. I've created some code snippets which you can use in your browser to use your laptop microphone. You can speak out a user utterance, and it will match the intent. When you open the debug console, you can browse through the results and see the **query text**, the spoken text Dialogflow captured' and transformed to text. Follow the [readme steps](https://github.com/dialogflow/selfservicekiosk-audio-streaming), to download a service account key, setup Dialogflow and run an `npm install` from the examples folder. Once done, you can run the following command to run the script: ``` npm --EXAMPLE=1 --PORT=8080 --PROJECT_ID=[your-gcp-id] run start ``` When you browse to localhost:8080, you will see a screen like the one below and you can play around by creating examples in Dialogflow, which are similar to the examples in this blog. ![Demo](https://leeboonstra.dev/images/0_pBJieu9ZjA-CLga5.png) ## Speech Adaptation Examples The following examples show how speech recognition may be improved with certain changes to your agent: ### Custom Entities Hints If you define entities for product or service names offered by your company, and the end user mentions these terms in an utterance, they are more likely to be recognized. I have created an airlines voice AI; http://selfservicedesk.appspot.com/ Without Speech Adaptation enabled, when I mention airlines such as **Corendon Airlines** or **Flybe**, it won't interpret the correct names: ![Demo](https://leeboonstra.dev/images/0_CIkyTopAoUic6cdL.png) But when the speech adaptation is enabled, I can create a custom entity which points to the various airlines: ![Dialogflow](https://leeboonstra.dev/images/0_kWM96gkUW4bT87o7.png) Then I can create intents that use the **@Airlines** entity. Next, when I test in the simulator (by using the microphone) "Corendon Airlines", it will understand which airline I meant. ![Dialogflow](https://leeboonstra.dev/images/0_uMGN-7lPGHUDJh4r.png) ### System Entities Hints The previous example makes use of **custom entities**. But I will work similar with system entities such as `@sys.number`. Think about user speech expressions when the user says "two", it may be recognized as "to", "too", "2" or "two". Or "for 4 to 5", "425", "four four two five" etc. The system entities will be used as hints. ### Intent Hints If you define training phrases with a phrase like "We are going to Ibiza", a similar sounding user utterance is reliably recognized as "We are going to Ibiza" and not "We are going to eat pizza". ### Overriding speech hints in your code You can also tweak the speech adaptation in your code. That's handy when you are implementing integrations through the SDK. Providing explicit **speech_contexts** in the **inputAudioConfig** will override the implicit speech context hints generated by auto speech adaptation for input audio (speech-to-text) configuration in the Dialogflow console. The **speechContexts** takes an object with an array of phrases[] containing words and phrases that the speech recognizer should recognize with a higher likelihood. See the [Cloud Speech documentation](https://medium.com/r/?url=https%3A%2F%2Fcloud.google.com%2Fspeech-to-text%2Fquotas) for usage limits. The speechContexts object can also take an optional boost (float) property. Boost for this context compared to other contexts: * If the boost is positive, Dialogflow will increase the probability that the phrases in this context are recognized over similar sounding phrases. * If the boost is unspecified or non-positive, Dialogflow will not apply any boost. Dialogflow recommends that you use boosts in the range (0, 20) and that you find a value that fits your use case with binary search. **Note: phraseHints[] will be deprecated.** Here you can find an example. Consider the following chat flow: ``` "What's my PNR for my flight to Rome?" > I can send it to you per mail or per text message. "Mail" ``` ![Dialogflow](https://leeboonstra.dev/images/0_wZ3O7K46c2LRLesg.png) To make sure that the agent understands "Mail" and not "Male" or "Nail", I have provided **speechContexts** in the code: [GitHub Gist](https://gist.github.com/73af0549a2cbdc4bd945b90ea2fcfc7c) ### SpeechContext code snippetSpeech Adaptation and supporting regular expressions Currently speech recognition doesn't have built-in support for the built-in [regex entities](https://medium.com/r/?url=https%3A%2F%2Fcloud.google.com%2Fdialogflow%2Fdocs%2Fentities-regexp). The entity regex rules have no effect on auto speech adaptation. You might need alphanumeric sequences in your user expressions, for example to speak out an account id. With auto speech adaptation you can work around this limitation. This example shows how to recognize an account id in a user utterance. An account id is a 8 character string of alpha and numeric characters. For example: AA12BB34 Create the following 2 entities with the following entries: *Character* * A, A * B, B * … * Z, Z *Digit* * 0, 0 * 1, 1 * … * 9, 9 *AccountId* * @character @character @digit @digit @character @character @digit @digit Use **@AccountId** as a parameter in a training phrase. **Note: The @AccountId entity entry requires whitespace between consecutive entities in order to be a valid entity definition. Because of this, a user utterance of "AB12CD34" is not matched, but a user utterance of "A B 1 2 C D 3 4" is matched. This is not usually a problem, because a spoken user utterance is processed with whitespace padding for alphanumeric characters. This means, once the back-end receives the AccountId parameter, you will have to trim off the whitespace. As always, proper validation should be done in your webhook.** In case of a flight-number (PNR) which is a 6 alphanumeric combination. We can make use of the system entity **@sys.flight-number** in an intent: ![Dialogflow](https://leeboonstra.dev/images/0_j4fV4sj00UZdRY5g.png) You can customize the **@sys.flightnumber** from the **entities > system** tab. ![Dialogflow System Entity](https://leeboonstra.dev/images/0_dmUx8T_8ZtmMr2Q7.png) Here we will add a regex to capture the right number of characters: Which in our case will be 6 or 7 characters, which can be uppercase or lowercase characters or numbers: **([A-Zaz0–9]){6,7}** ![Dialogflow Entity](https://leeboonstra.dev/images/0_eq5MO4U2Lr-EL8up.png) ## Conclusion Auto speech adaptation improves the speech recognition accuracy of your Dialogflow voice agent by effectively adding context to your conversations. In this article I've shown some examples within voice bots. This feature can improve the accuracy of the agent by more than 40%! You can imagine that this will be a game changer, when you use voice agents in your IVR systems / contact centers. Up until now, IVRs were pretty basic and the user experience was such that people just shout trigger phrases like "speak to a 'representative'" to escape the flow as soon as possible. Dialogflow & CCAI want to help build experiences that help people get a high-quality service which doesn't require them to repeat themselves. With SpeechContexts, developers and conversational UX designers can provide context and boost it to bias the model. Therefore fine-tune the likelihood that conversations will include wrong phrases. Lastly, Google has expanded the number of phrase hints per API request from 500 to 5,000. *In case you wonder what the name of the radio show means, it's a mondegreen for the Michael Jackson song - Gonna be Startin'. Which has a phrase 'Ma ma se ma ma sa, ma ma coo sa.' Not the Dutch words 'Mama Appelsap' which is Dutch for: Mother Apple Juice.* --- ## A simple solution for fetching data from a headless CMS in a Dialogflow Chatbot *Published: 2020-03-03* *Categories: Chatbots* *Tags: Cloud Functions, Dialogflow, Node JS, Sanity CMS, Chatbots* > How to integrate Dialogflow with a headless CMS such as Sanity? So non-techy people can modify webhook fulfillment answers, without having to train models or use the Dialogflow console. --- I often get the question, why does Dialogflow not have a CMS? So non-techy people can modify webhook fulfillment answers, without having to train models or use the Dialogflow console. The thing is, Dialogflow can be integrated with anything you like. In this tutorial I will make use of a headless CMS; [Sanity](https://www.sanity.io/). You can use any CMS, even Wordpress, but it makes things easier when there are (out of the box) REST APIs to use. This is what a headless CMS can do really well. It detaches the content from a webpage in order to reuse it in various contexts such as Mobile Apps or Chatbots. In [my Dialogflow, Django, Angular example](https://github.com/savelee/kube-django-ng/) I am using Django as a CMS to import user accounts. In fact, I am using the Django Rest module, to communicate with Django via a Rest API. In this tutorial I will make use of a headless CMS; [Sanity](https://www.sanity.io/). ### **Dialogflow** To prepare Dialogflow with dynamic content, let’s create some entities and intents. This example, creates a multilingual help desk chatbot. In the Dialogflow settings panel I have enabled the following two languages: **English** and **French**. In the entities screen I have created 2 entities: **systems** and **useractions**. **Systems** holds a bunch of computer system names, that all map to a key. For example **InsidePayments** belongs to key **ibb** in entity **systems**. ![Dialogflow Entities](https://leeboonstra.dev/images/0_oiY4oK9y_2upL2lR.png) Dialogflow Entities The entity **useractions** contains user actions such as **delete, add, edit**. For every content item in my CMS, I will need to create an intent with training phrases. Here’s an example of intent name: **333**. It has a bunch of training phrases, such as: **“How do I edit a new account in TopMortgage”.** This intent holds 2 required parameters, which needs to be filled, in order to filter the CMS content items. **paramUserActions** & **paramSystems**. ![A Dialogflow Intent with required parameters](https://leeboonstra.dev/images/0_4lt6bqiz6j81uF1X.png) A Dialogflow Intent with required parameters You will need to make sure that all your intents that need to fetch information from a CMS, have the **webhook fulfillment** enabled: ![enable webhooks](https://leeboonstra.dev/images/0_-Oo3aWlQp7JKiXHj.png) And that’s it for the Dialogflow part. Now let’s move on to Sanity. ### Sanity _I’m installing Sanity on my localhost, since I will only use the Sanity Studio to write content items. In case you want to deliver this environment to your (non techy) customers, you could install Sanity on a Compute Engine Node.js VM._ 1. Install Sanity `sudo npm install -g @sanity/cli` `sanity init` Login with a Google account, Github or your own email account. Click on the link which is shown on the command line, and accept the terms & conditions: ![Sanity Terms & Conditions](https://leeboonstra.dev/images/0_acDbZhajXGBi7aPa.png) 2\. Back on the command line: **Provide a Project name**: Chatbot CMS **Default Dataset configuration**: Y **Output Path**: /home/admin/chatbotcms **Schema**: None **Add Samples**: No 3\. Let’s modify the Sanity schema: ``` cd /home/admin/chatbotcms/schemas nano schema.js ``` Paste & Save the contents of: Schema.js [GitHub Gist](https://gist.github.com/688ac7837f58dfe3561d4e56d835d5f7) ``` nano simpleresponse.js ``` Paste & Save the contents of: simpleresponse.js [GitHub Gist](https://gist.github.com/9ef13f33f8eb7b2d2ff41ef363a4f4be) 4\. Start Sanity ``` cd chatbotcms sanity start ``` 5\. Navigate to localhost:3333 Click on **Intent > +** Add the content pages: Here it’s important that you stick to the Dialogflow mappings. * Note the Intent name (id) which is the same as the intent name **333** in Dialogflow! * System name (paramSystem) maps to the Dialogflow entity key **ibb** in **systems** * User Action (paramUserAction) maps to the Dialogflow entity key **add** in **useractions** For the rest this item needs to have a readable title which will be visible in Sanity. And 2 chatbot responses. One in English and one in French: ![Sanity CMS](https://leeboonstra.dev/images/0_SEKa233K93G83k92.png) You can create multiple content items. For each user action and system combination its own item. Now let’s query our data: 6\. Click on **Vision** in the Sanity menu Use the following [GROQ](https://www.sanity.io/docs/groq) query to get all simple response items: `\*\[\_type == ‘simpleresponse’\]` ![GROQ results](https://leeboonstra.dev/images/0_dLBO3mSyPJ-kMebL.png) Use the following query to filter the items on the specific intent name (id) and parameters **paramSystem** & **paramUserAction**. As an output let’s just show the response in English language: ``` \*\[\_type == ‘simpleresponse’ && id == $id && paramUserAction == $paramUserAction && paramSystem == $paramSystem\]{ responseEn } ``` Params: ``` { “id”: “333”, “paramUserAction”: “add”, “paramSystem”: “ibb” } ``` And notice the result: ![GROQ results](https://leeboonstra.dev/images/0_nvvnUYJOEKCqwxHA.png) Alright so the content is present. We can filter for the contents through **GROQ** queries. The only thing that’s left to do is to implement webhook fulfillment code in Dialogflow, which can run this query, but uses the parameters provided by Dialogflow through slot filling. ### **Dialogflow** In the Dialogflow inline editor, we can use the following code: Use the following **package.json**: package.json [GitHub Gist](https://gist.github.com/39583d17e88be59d3918560d656c0ed4) And the following **index.js**: index.js [GitHub Gist](https://gist.github.com/0453fa5b009de4b18eaa9ab16bf2a112) This code implements the Sanity NPM client. You will need to specify the Sanity project ID and dataset name. (In case you lost these, you can find it in the Sanity installation folder, **sanity.json**). This way it knows to query the content items that belong to your project. The Sanity client method: **client.fetch()** takes the GROQ query with the parameters provided by the Dialogflow agent. Within the Firebase Cloud function, it implements the Dialogflow agent, and retrieves the parameters and agent locale from the Dialogflow agent. The correct intent will be matched by these lines of code: ``` JavaScript let intentMap = new Map(); intentMap.set(‘333’, responses); agent.handleRequest(intentMap); ``` The only important thing to remember, is that these lines will need to be inside the resolved promise of **client.fetch** otherwise you will likely get a timing issue and thus Dialogflow returns with a matched intent, without the CMS results. Now you can test your expression in the Dialogflow console: ![Dialogflow Simulator](https://leeboonstra.dev/images/0_bPoId4OOj4MjcSEX.png) Based on the Agent locale, it will return the language item of choice, from the data which is inside of the headless Sanity CMS. And there you go, here’s a simple solution for fetching data from a headless CMS (Sanity) in a Dialogflow Chatbot! --- ## How to build chatbots for Hangouts / Google Chat with Dialogflow by using custom payloads *Published: 2020-02-21* *Categories: Chatbots* *Tags: Cloud Functions, Dialogflow, Node JS, Firebase, Hangouts Chat, Chatbots, Custom Payloads* > The Dialogflow Hangouts Google Chat integration lets you create bots you can include in --- The Dialogflow Hangouts integration lets you create bots you can include in one-on-one chats as well as chat rooms. From the Integrations menu on the left, you can enable this feature and select your agent environment. You can follow this guide for more information: [https://cloud.google.com/dialogflow/docs/integrations/hangouts](https://cloud.google.com/dialogflow/docs/integrations/hangouts) But you are probably here, to figure out how you can integrate this with rich messages such as cards, formatted texts, images or custom payloads. Maybe you are even trying to enable this via your fulfillment code. Well I got you covered! ### Rich Messages When Hangouts Chat sends an event to a bot, it includes certain data in the event payload; the exact payload depends on the event type. This event and payload are contained in the HTTP call sent to the bot’s registered URL. For some events, the bot may send a message to the chat in response to the event. This message is a JSON object, whose contents depend on what kind of message it is: ### Google Hangouts integration in Dialogflow UI Console Out of the box, in the Dialogflow UI console, you could choose custom Hangout responses by clicking on the + button in the **Responses** block: ![Dialogflow](https://leeboonstra.dev/images/0_YaAyVGrY9A3jYPaW.png) This allows you to create 4 different types of responses: * Formatted Text Responses * Images * Cards * Custom Payloads ![Dialogflow Responses](https://leeboonstra.dev/images/0_2U3Q_AjG5VK8K22S.png) The Hangouts **Text Response**, visually looks the same as the **Default Text Response** in the Dialogflow UI console. However the raw api response will look a bit different as it also sets the platform config to “GOOGLE\_HANGOUTS”, which could be interesting when building multi channel agents. ``` JSON "fulfillmentMessages": \[ { "text": { "text": \[ "This is a test." \] }, "platform": "GOOGLE\_HANGOUTS" }, ``` ... On top of that, you can change the text formatting. See [SimpleText](https://developers.google.com/hangouts/chat/reference/message-formats/basic) for example you can make text **bold** or _italics_ by wrapping the text in certain (markdown light) symbols. The Hangouts **Image**, will as the name implies insert an image in Hangouts. You can specify an image url. The Hangouts **Card** allows you to create a basic Simple Card. You can set a **title**, a **subtitle**, an **image** **url** and **multiple** **buttons** (a label and an url): ![Dialogflow Cards](https://leeboonstra.dev/images/0_lP4zxoRxsU6enke9.png) Which will look in Google Hangouts like this: ![Hangouts Chat](https://leeboonstra.dev/images/0_T_aRue9LmbXn5AYX.png) Lastly, there is the option to specify a Hangouts **Custom Payload**, _now this is where the magic happens in order to create advanced cards_. One card can have one or many sections. Each section could have a header. You can have a look into the hangouts message formats cards reference guide, to see some of the combinations you can create with this: [https://developers.google.com/hangouts/chat/reference/message-formats/cards](https://developers.google.com/hangouts/chat/reference/message-formats/cards) However, using custom payloads means that you will have to provide the JSON format. Now if you copy and paste the examples of the reference guide to the custom payload box in Dialogflow, you will need to be aware of the following: * The first key can’t be called **cards**, but it has to be named: **hangouts** * The **hangouts** key points to an object, not an array (of cards) * Make sure the editor doesn’t contain any linting errors * You can’t test the results in the Dialogflow _Try it now Console_, you will have to test it directly in Hangouts Chat Here’s a working example: [GitHub Gist](https://gist.github.com/612450be03f64a69cc2d4b2b4a5b9d32) Dialogflow custom payload for Hangouts advanced rich cards ### Google Hangouts integration in Dialogflow fulfillment code Unless you create static FAQ chat agents for Hangouts chat, you will likely fetch results from a server and display it in a component. For this you will need back-end fulfillment code (either in a cloud function (for example provided by the inline editor), or by specifying a webhook url. Either Way, incase you use JavaScript/Node, the simplest way to integrate Hangouts cards in your chats is by using the **dialogflow-fulfillment** npm library: [https://www.npmjs.com/package/dialogflow-fulfillment](https://www.npmjs.com/package/dialogflow-fulfillment) _(Note: You can’t use the actions-on-google npm package, since that library is meant for Google Assistant agents.)_ Make sure you use version 0.6.1 (or higher). Although version 0.6.1 doesn’t have specific Google Hangouts platform recognition, the default Dialogflow Card, Image and Payload responses will work out of the box: ``` JavaScript var card = new Card({ title: 'hello', text: 'test', imageUrl: 'https://goo.gl/aeDtrS', buttonText: 'Details', buttonUrl: 'https://assistant.google.com/' }); var image = new Image({ imageUrl: 'https://goo.gl/aeDtrS', }); ``` _(The Suggestion chips won’t work in Hangouts, as they don’t exist in Hangouts Chat. … but you probably could use buttons for this.)_ But assuming that you want a little more complicated cards than these, you will have to work with custom payloads. Let’s assume we have the following cloud function written in the Dialogflow fulfillment editor: ``` JavaScript const { WebhookClient, Payload } = require('dialogflow-fulfillment'); exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { let agent = new WebhookClient({ request, response }); let intentMap = new Map(); intentMap.set('test', test); agent.handleRequest(intentMap); }); ``` In Dialogflow we have created an intent called ‘**test**’, which has the fulfillment webhook enabled. You can use the [rich card example](https://gist.github.com/savelee/612450be03f64a69cc2d4b2b4a5b9d32), I have used earlier in this article. Instead a function **getCard**, returns this custom payload object. The test method would look something like this: ``` JavaScript function test(agent){ let json = getCard(); let payload = new Payload( 'hangouts', json, { rawPayload: true, sendAsMessage: true} ); agent.add(payload); } ``` We are creating a **Payload** object. It’s important to specify the platform as a string: **hangouts** to make sure it will showup in Google Hangouts. The second argument is the custom payload as mentioned before. And we will need to specify a 3rd object, which sets the **rawPayload** to **true**, and make sure it will be **sent as a message**. Go ahead, test it. It should work! --- ## Build a conditional template in Dialogflow for Google Assistant with custom payloads *Published: 2018-05-20* *Categories: Chatbots* *Tags: Actions on Google, Conditionals, Dialogflow, Google Assistant, Pug, Templating, Chatbots, Dialogflow Enterprise, Custom Payloads* > With the use of custom payloads, webhook fulfillments and templating system, you can use conditionals from the Dialogflow console. --- What I like most about Dialogflow, (the tool to create Chatbots and Smart Assistants / Google Assistant apps), is that you can maintain your conversations within the Dialogflow UI. Many users write their complete FAQ content in intents and responses. You no longer need a developer to tweak your conversations or deploy your agent which makes it very scalable for large organizations. It's also possible to load data from external systems. In that case, you are hosting parts of the conversation elsewhere. You could store these parts in language files or databases. Some organizations prefer to write the full conversation in the Dialogflow UI. That way your copywriters can maintain the full conversation. It's good to know that it is possible to use templates and conditionals within the Dialogflow UI. The trick here, is to make use of the **custom payload** response setting, which you can find in the Intent > Responses section. Just like how you would include Rich Cards, you can choose custom payload, and provide your own JSON. Since JSON is just JavaScript, my first try was to use `if else` conditions directly in the code: ``` JSON { "web": { "type": "text", "fn": "var today = new Date(); var curHr = today.getHours(); var greet = ""; if (curHr < 12) { greet = 'Good morning!'; } else if (curHr < 18) { greet = 'Good afternoon!'; } else { greet = 'Good evening!'; } return greet;" } } ``` In my SDK back-end code or fulfillment app, I could convert the string to executable JavaScript code. Although this works. It kinda feels dirty, since I need to use evil `eval()` or equivalent code in my back-end. On top of that I expect the person who maintains the conversation, to have JavaScript skills. The next solution, came from a customer of mine. Instead, let make use of a templating library, so you can provide readable templates, and variables that an be injected. Think Jinja (for Python or Java developers), Smarty (PHP) or Jade/Pug, Handlebars and Mustache (for JavaScript developers). I tried this with [PugJS](http://www.pugjs.org) (formerly known as Jade). It works really nice. Let's take this intent: **Intent Name**: [templating] example **Training Phrases**: * Greet me * Greet Lee **Parameters**: * username - @sys.given-name - $username **Fulfillment**: Enable webhook call **Response**: Here's an example **custom payload**: ``` JSON { "custom": { "locals": { "username": "$username" }, "pug": [ "if usernamen", " | Hello $usernamen", "elsen", " | Hello stranger" ] } } ``` The values of the `locals` object, are the parameter values. In the `pug` object, I wrote a multi-line string template, with an if else branche. When using PugJS, the line indenting is importing. Pay attention to the newline `n` code, and the `|` for using plain text. In my fulfillment Cloud Function, I will take this template, and compile it, together with the local template variables. Your code will look like this: [GitHub Gist](https://gist.github.com/52b9c84c035b2e1ef4f8a84051a2803b) --- ## Build a dynamic welcome message with Dialogflow webhook fulfillment and Actions on Google. *Published: 2018-05-20* *Categories: Chatbots* *Tags: Custom Welcome Intent, Dialogflow, Google Assistant, Chatbots, Actions on Google, Greeting, Custom Payload, Fulfillment, Webhook* > Create a dynamic welcome messages for the Google Assistant with Dialogflow --- Let's have a look into how you can create custom dynamic **Welcome messages** for your Google Assistant with Dialogflow and Actions on Google. 1. First open your Dialogflow Console. 2. Create a new Intent with the following settings: **Intent name**: [bot-first-greeting] 3. **Events**: Choose *Google Assistant Welcome* 4. **Training Phrases**: Empty 5. **Fulfillment**: Enable Webhook call for intent Click *Save*. 6. For the code I am using Google Cloud functions. [Please see also my previous post](https://www.leeboonstra.com/developer/actions-on-google-for-google-cloud-functions/). Your Google Cloud Function could like this: ``` JavaScript 'use strict'; const { dialogflow } = require('actions-on-google'); //npm actions-on-google 2.1.1 const welcomeHandler = (conv) => { var today = new Date(); var curHr = today.getHours(); var greet = ""; if( curHr < 12 ) { greet = "Good morning!"; } else if (curHr < 18) { greet = "Good afternoon!"; } else { greet = "Good evening!"; } conv.ask(greet); }; const app = dialogflow(); app.intent('[bot-first-greeting]', welcomeHandler); exports.index = app; ``` Click the *Fulfillment* menu item, and make sure the URL points to your Google Cloud function. Assuming that you linked the Actions on Google already in the *Integrations* tab; move back to your Actions on Google simulator, and refresh your app. When you start your Google Assistant app, it will greet you, based on the time of the day. Obviously, this is a simple example, but this can become more interesting, when loading profile information or previous contexts before starting your app. --- ## Actions on Google with Google Cloud Functions *Published: 2018-05-19* *Categories: Chatbots* *Tags: Actions on Google, Cloud Functions, Dialogflow, Node JS, Google Assistant, Dialogflow Enterprise, Chatbots* > Learn how to invoke the Google Assistant with Cloud Functions --- When building Google Assistant apps (actions) with Dialogflow, you likely will have to write some logics. The most common way in developing this logics layer is by using a webhook and a Cloud Function. The webhook requires a URL. So technically you can use any web server and program language you like, Cloud Functions are just easy. It's serverless, which means you don't need to worry on setting up and maintaining an environment, and it scales out of the box. Dialogflow integrates with Firebase Cloud Functions. There's an easy inline editor you can use, which creates the Cloud Function within Firebase. (Which under the hood uses the infrastructure of Google Cloud.) For Dialogflow Enterprise customers (the Dialogflow version which is compliant and better for large organizations), Firebase Functions don't make much sense. (And in fact, are not been created within your current GCP project). You rather use the Google Cloud Functions. Both use functions can make use of HTTP triggers. The way of invoking is different: *GCP*: ``` JavaScript exports.helloWorld = function helloWorld (request, response) { res.send(`Hello from GCP!`); }; ``` *Firebase:* ``` JavaScript exports.helloWorld = functions.https.onRequest((request, response) => { response.send("Hello from Firebase!"); }); ``` The [Actions on Google Node JS library](https://www.npmjs.com/package/actions-on-google) (for creating Dialogflow agents with Google Assistant), explains how you can integrate the library within a Firebase Cloud function. Unfortunately, it doesn't explain to you how to integrate it with a GCP Cloud Function. So here's how you would do this: [GitHub Gist](https://gist.github.com/7485763b67e6ee3a5d00aa07c8678f94) As you can see, you can get the request and response headers from the `conv` object, in the conversation handler function. NOTE: This example was written for the 2.1.1 version of the Actions on Google NPM package: *package.json* ``` JSON { "name": "Demo", "description": "Google Assistant with Dialogflow Enterprise", "version": "1.0.0", "license": "Apache-2.0", "author": "Lee Boonstra", "engines": { "node": "^6.11.5" }, "dependencies": { "actions-on-google": "2.1.1" } } ``` --- ## Introduction to the Video Intelligence API *Published: 2017-04-10* *Categories: Machine Learning* *Tags: Pre-trained Model, Machine Learning, Video Intelligence Api, ML, AI, Google Cloud* > Learn how to use machine learning on videos and get insights with Google Cloud --- ## Search and discover your media content with powerful Cloud Video Intelligence API Google Cloud Video Intelligence API makes videos searchable, and discoverable, by extracting metadata with an easy to use REST API. You can now search every moment of every video file in your catalog and find every occurrence as well as its significance. It quickly annotates videos stored in Google [Cloud Storage](https://cloud.google.com/storage/), and helps you identify key nouns entities of your video, and when they occur within the video. Separate signal from noise, by retrieving relevant information at the video, shot or per frame. You can try this out, yourself: 1. Open: [https://cloud.google.com/video-intelligence/#demo](https://cloud.google.com/video-intelligence/#demo?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) 2. Select a sample video from the list, for example: **Volleyball Court**, (which is a video made at the Google Mountain View office). Notice the labels. 3. Select another sample video from the list: **Google Work**. 4. Click on the **Shots** tab. Notice all the keywords detected from the video, which are being renewed per video shot! 5. Click on the **API** tab. Notice how the JSON response would look like. 6. Now, lets try it with one of my own videos, which I’ve uploaded as a public available video in Cloud Storage: **gs://leeboonstra-videos/mov_bbb.mp4** ## Write metadata on video upload Machine Learning for videos, can be super interesting, in case you want to implement it within your own systems. Let’s say you host a lot of videos on your website. Instead of manually writing meta per video; you could create an ETL job, (for example through Cloud Functions), which listens to the upload event of Google Cloud Storage, runs the Video Intelligence API, and writes the metadata in a database. This looks like a great use case! Let’s see if we can build this! ### Getting Started To get started, open the GCP console, and create a new project. Go to: [https://console.cloud.google.com](https://console.cloud.google.com?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) and click: [Create new project](https://cloud.google.com/resource-manager/docs/creating-managing-projects), and enter an easy to remember project name, which will be used as the *project id*. You will have to remember this. The project id will have to be unique. For this workshop, it might be a good practice to prefix it with your **[firstname][lastname]** ### Enable the Cloud Functions API Click on the menu button. **Cloud Functions > Enable API**. ### Enable the Video Intelligence API Click on the menu button. **API Manager**. Search for: **Google Cloud Video Intelligence**. **API > Enable** Click **Credentials > Create Credentials > Service Account** Choose **New Service Account** Service Account Name: **demoapp** Role: **Project Owner** Choose **JSON** Click **Create** Click **Manage Service Accounts** Click the menu button (the 3 vertical dots), and select **Create Key** This will download the key on your local machine. Create a folder on your machine called: **cloudfunctions-videoapi**, and move the file over. ### Create storage buckets When you write the JavaScript code for the cloud function, you will need to upload it somewhere. Under the hood, GCP will create a container with a Node environment for you, so you can execute your function. You can upload function code, in a bucket of the Google Storage. Let’s create a bucket which contains function code. You can create the bucket via the Cloud Console (menu > **Storage > Create Bucket**), or from the command-line, on your local machine (if you have the gcloud sdk installed), or from your online terminal in the Cloud Console: `gsutil mb -p [PROJECT_ID] gs://[BUCKET_NAME]` *Create two buckets, with the name of your [project id] + **-functions-src**, and one [project-id]**-videos**. (This way, your bucket will be unique.)* After you’ve created the videos bucket, you will need to make this bucket public, so your video’s get gs:// public urls. You can do this by clicking on the menu button of the bucket (the button with the 3 vertical dots). Click **Add Item**: *User - allAuthenticatedUsers - Reader* Click **Save**. ![Screenshot](https://leeboonstra.dev/images/bucket-permissions.png) ### Client Library Since the client node js library is still in Alpha at the time of writing, we will download the alpha version and host it locally. Once the library gets publically available, you can ignore the next step, and instead link to the online version of the client library. (See: [https://www.npmjs.com/package/google-cloud](https://www.npmjs.com/package/google-cloud)) Download the alpha node client library: [https://storage.googleapis.com/videointelligence-alpha/videointelligence-nodejs.tar.gz](https://storage.googleapis.com/videointelligence-alpha/videointelligence-nodejs.tar.gz) Unzip the package. Create a new folder inside **cloudfunctions-video**, called: **local_modules**, and move the **videointelligence** folder into it. Create a cloudfunctions-video/**package.json** file. It should contain the following contents: ``` { "name": "videoapi", "description": "Webservice VideoAPI", "version": "0.0.1", "private": true, "license": "Apache-2.0", "author": "Lee Boonstra", "engines": { "node": "~4.2" }, "dependencies": { "@google-cloud/videointelligence": "file:local_modules/videointelligence", "dotenv": "^4.0.0" } } ``` ### Create a Cloud Function Create a new hidden **.env** file, and give it the following content: ``` ENV=production GCLOUD_PROJECT=[project-id] GCLOUD_KEY_FILE=./[path to json service key file] ``` Afterwards, create the **index.js** file with these contents: https://gist.github.com/savelee/48d8642ffe0a99348cfeca56485181c2 ### Deploy your Cloud Function On the command-line, enter the following: ``` $ gcloud beta functions deploy [name-function] --stage-bucket [bucket name of the function-src bucket] --trigger-bucket [bucket name of the videos bucket] ``` For example: ``` $ gcloud beta functions deploy videoapi --stage-bucket leeboonstra-functions-src --trigger-bucket leeboonstra-videoapi ``` ### TimeOuts The way how the video api works, is that it first will read in the video to the memory. The machine learning under the hood, is similar to Google’s Cloud Vision API, which can detect what’s on an image. But for the Video Intelligence API this works per frame. Cloud functions can timeout. You will need to specify a timeout in seconds. By default it’s set to 60s. A number between 1 and 540 (9min) is required. A video with a long duration, will likely make that the cloud function will timeout. So becareful. You can either setup, the timeout in the **Cloud Functions / **Logging** ### Conclusion By now, you managed to test the Video Intelligence API with your own JavaScript code, within a cloud function based on file upload in a bucket. The power of this cloud function, is that you could easily build an application around this, which makes use of microservices (cloud functions). It wouldn’t be much work to create an interface (for example with Sencha Ext JS, or just with plain HTML, CSS and JavaScript), which shows a list of video’s and the tags. I’m logging the tags in the logs of Stack Driver. But instead I could save it in the data store. I could create another cloud function, based on an HTTP trigger which loads the data of the datastore and displays it in the front-end list. Another idea could be, to pass in the results of the Video Intelligence API into another Machine Learning API, such a translate. To translate the keywords to a different language, before saving it in the database. *TIP: In case you don’t want English meta data, it’s also possible to put the Translate API right after the Video Intelligence API call!*

Video Intelligence API from Ladysign on Vimeo.

### Resources * [Cloud Functions Docs](https://cloud.google.com/functions/docs/reference) * [Video Intelligence Demo](https://cloud-ml-video.appspot.com/index_v1beta1.html) --- ## Analyzing data with BigQuery and Machine Learning APIs *Published: 2017-03-18* *Categories: Machine Learning* *Tags: BigQuery, NLP, NLU, Compute Engine, GCP, Natural Language API, Translate API* > Analyzing social media tweets with Natural Language Processing (NLP) about political parties. Storing it in BigQuery and publish data in Dashboards. --- ## Forecast the Dutch elections based on social media presence. Wednesday the 15th of March 2017, was a big day for the Netherlands, at that day it was the Dutch general election, where the Dutch elected members of the house of representatives. The House of Representatives (the Second Chamber) is composed of 150 seats elected by proportional representation in a single nationwide constituency. There are 28 political parties, where the Dutch can vote the members from. Since I recently started working with Google Cloud Platform, I thought it would be a great idea, to predict the winning political party based on social media presence and sentiment. This idea is based on the blog post of Sara Robinson: [Comparing tweets about Trump & Hillary with natural language processing.](https://medium.com/google-cloud/comparing-tweets-about-trump-hillary-with-natural-language-processing-a0064e949666) This blog post will contain the following technical pieces: * **Compute Engine** (1) - To deploy our data scraping script on a VM. * **Machine Learning APIs** - To use Natural Language API (4) to understand the context of the data, and since our data is in Dutch (and the Natural Language API doesn’t support the Dutch language yet), we will need the Translate API (2) to translate. * **BigQuery** (5) - To collect a lot of data. To analyze this data we use BigQuery and run some queries on it. * **DataStudio** - To visualize our result set, we will make use of Google’s Data Studio (6). We can use some nice charts! To do the social media data scraping, we use the **Twitter Streaming API**. The application code is written in JavaScript for Node.js. Let’s get started with this tutorial, and see if we can predict which Dutch political party will win the elections! ![Application Architecture with the various Google Cloud solutions](https://leeboonstra.dev/images/analyze-bq-ml-1-500x300.png) *NOTE: This blog post won’t contain any political opinions from me, neither from Google. This is a blog post to demonstrate the various solutions of the Google Cloud Platform, on how you can be creative with data. Please see the [disclaimer](#disclaimer), at the end of this article.* ### Prerequisites * [Node JS](https://nodejs.org/en/) * Node [Express](http://expressjs.com/) framework ( `$ npm install express --save `) * A GCP Account (or create a [free trial account](https://cloud.google.com/free-trial/)) ## Getting Started To get started, open the GCP console, and create a new project. Go to: [https://console.cloud.google.com](https://console.cloud.google.com?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) and click: [Create new project](https://cloud.google.com/resource-manager/docs/creating-managing-projects), and enter an easy to remember project name, which will be used as the project id. You will have to remember this. Next, on your local machine, generate an empty Node Express application: `$ express` *NOTE: Technically, for this demo, you don’t need Express, since Express is a Web framework for Node.js. I choose it though, since I prefer working from this folder structure. My code will be future proof, should I ever plan to port it to a web app.* Once, the generation is done, you can open the **package.json** file, and add the following lines of code dependencies: ```json "dotenv": "~4.0.0", "google-cloud": "~0.47.0", "user-stream": "~0.0.8", "nodemon": "~1.11.0", ``` After, you are done; run the following command in your terminal: `$ npm install` Go back to the [GCP console](https://console.cloud.google.com?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) and click the hamburger menu icon, and select: **IAM > Service Accounts > Create Service Account** Service account name: **nodeapp** Role: **Project > Owner** Click: **Create > Close** Click the **button with the 3 dots** next to nodeapp, to open an extra menu. Select: **Create key**. Choose **JSON**. Click **Create**. A key has been saved to your local machine. Rename this -.json file, as: **cloudkey.json**, and copy it into your freshly generated Express app, root folder. Also, create an **.env** file in the root of your Express app folder. It should have the following contents: `GCLOUD_PROJECT= GCLOUD_KEY_FILE=./cloudkey.json`. Later in this tutorial, you will also need to add the keys for the Twitter Streaming API in here. ## Twitter Streaming API The [Twitter Streaming APIs](https://dev.twitter.com/streaming/overview) give developers low latency access to Twitter’s global stream of Tweet data. A streaming client that pushes tweets, without any of the overhead associated with polling a REST endpoint. Exactly, what we need, to monitor the Twitter newsfeed during election day, so we can see which party is popular on Twitter. We will need to create a [Twitter API account](https://dev.twitter.com/resources/signup). (and if you don’t have Twitter, also a Twitter account). With the Twitter API account, you can create an application, and generate the tokens. These tokens, you will need to copy to the **.env** file in the root of your Node project. It has the following contents: `CONSUMER_KEY= CONSUMER_SECRET= ACCESS_TOKEN_KEY= ACCESS_TOKEN_SECRET=` Create a folder called: **lib** Create the following file in the **lib** folder: **twitter.js** The **twitter.js** file will contain the following content: [GitHub Gist](https://gist.github.com/bf0afb23c5a6606c8aa3b1afbe49f873) Now you can start testing your twitter stream. In your terminal run the following command from your Express root folder: `$ node bin/www` Now, look in your terminal, you should see the data based on your search terms streaming in. To close the data stream hit **CTRL + c**. ## Machine Learning APIs Nice all that data, but how will we know that people are talking good or bad about a certain political party? Just counting all the tweets, wouldn’t be the correct thing to do, we actually need to understand the context of the data. Hmm, how can we automate this? For this, you can actually use machine learning. Machine learning is not only for data scientists and experts, you and I can make use of it as well. The Google Cloud platform provides a bunch of APIs which are easy to use. Think about machine learning APIs such as: *Speech API* (which can convert speech to text), *Vision API* (which can get insights from pictures), *Translate API* (to translate text to a certain language), *Video Intelligence API* (to retrieve metadata from videos) and *[Natural Language API](https://cloud.google.com/natural-language/)* (to derive insights from unstructured text). Google is planning to release much more machine learning APIs to the cloud platform, and on top of that; if you are a data scientists you could write your own machine learning models. The last mentioned API is what we need to understand pieces of our Twitter data. However... Unfortunately, the NLP API won’t understand the Dutch language yet. So here’s the test case: Would it work if we translate the text from Dutch to English first? Afterall, the only thing we need to know is if the context of the tweet is positive or negative. Let’s give it a try. We make use of the [Translate API](https://cloud.google.com/translate/) first, afterwards the Natural Language API. Later, we will store in BigQuery the original tweet, but with a ranking total score column. Go to the [cloud console](https://console.cloud.google.com?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) and click on the **menu** (hamburger) button. Click **API Manager > Dashboard** and click **Enable APIs**. Click **Natural Language API** from the Google Cloud Machine Learning API section. Click **Enable**. Go back to the previous screen, and select **Translation API** and hit **Enable** again. In the **lib** folder, create the following file: **ml.js** which contains these contents: (see the comments in the JavaScript code for further explanation). [GitHub Gist](https://gist.github.com/fb2c637e981ae582aeae520d01c4a3d3) In **twitter.js**, add the following line somewhere in the top of the file: ``` JavaScript var path = require('path'); var machinelearning = require( path.resolve( __dirname, "ml.js" ) ); ``` You can test the MachineLearning APIs by adding the following example code lines to the end of the file: ``` JavaScript getSentiment('I love Milkshakes', function(results){ console.log(results.sentences); }); getTranslation('Ik houd van Milkshakes.', function(results){ console.log(results); }); ``` The sentiment score is a number between -1.0 (negative sentiment) and 1.0 (positive sentiment). The magnitude is a non-negative number in the [0, +inf) range, which represents the absolute magnitude of sentiment regardless of score (positive or negative). To change the language, change the `from` input parameter on line 11, to another language, such as `es` for Spanish, or `fr` for French. Feel free to play arround with it, and pass in different text strings, or language API settings. From the terminal run: `$ node bin/www` Now, you will have to add these pieces of code, within the `stream.on()` function, right after the `console.log` line. (let’s say line 42). This will be a callback, in a callback, in a callback... *pffff*. ...and we also need to bind to the original scope! So I solved it this way: [GitHub Gist](https://gist.github.com/89d57616b54474134106db24211f3ae5) Once done with it, start the node app again. You should see the Twitter lines, streaming in, with translations and sentiment detection. The next step will be to push these data into BigQuery! ## BigQuery The next challenge is to store this data in the BigQuery storage in the Google cloud. BigQuery is a fast, economical and fully-managed enterprise data warehouse for large-scale data analytics. BigQuery can query terabytes of data in seconds and petabytes in minutes! The code to save the data in BigQuery is not so difficult. Please have a look: [GitHub Gist](https://gist.github.com/11490d86a942f0b0acb83b55009cac95) In **twitter.js** you will need to require the file: ``` JavaScript var bigquery = require( path.resolve( __dirname, "bigQuery.js" ) ); ``` And within the most inner callback (on line 127), you can enable the `insertBq()` method: ``` JavaScript bigquery.insertInBq(row); ``` Run the node app for a while. This will load new content into BigQuery. We can now run a bunch of queries to analyze our data. Click **Compose Query**, write: ``` sql SELECT SUM(score) as totalscore, party, COUNT(party) as totalparties FROM dutchelectionstweets.dutchelections GROUP BY party ORDER BY totalscore DESC ``` This query, will sum the *total sentiment score* per party. Also, it counts and it groups the parties. Click **Run**, to run the query. You will see the results in the table below. When the results look ok to you, you can save the view. This will create a new view (which is like a table, but it includes the query). It contains the unique result set. We will need this later for Data Studio. Let’s create a few more views. Such as the 3 most positive parties: ``` sql SELECT SUM(score) as totalscore, party FROM dutchelectionstweets.dutchelections GROUP BY party ORDER BY totalscore DESC LIMIT 3 ``` The total amount of tweets per party: ``` sql SELECT party, COUNT(party) as totalparties FROM dutchelectionstweets.dutchelections GROUP BY party ORDER BY totalparties DESC ``` And the total amount of tweets: ``` sql SELECT COUNT(text) as totaltweets FROM dutchelectionstweets.dutchelections ``` In case you want to browse through all the tweets you can use: ``` sql SELECT text, party, score FROM dutchelectionstweets.dutchelections ``` ### Data Studio Instead of displaying the results in *"boring"* tables, we could very easily display the result sets in charts. I’ve used [Google Data Studio](https://datastudio.google.com) for this. ![Image](https://leeboonstra.dev/images/analyze-bq-ml-2-500x250.png) When you drop a chart on the stage, you will need to **Create a new Data Source**. Which you can set to **BigQuery Connector**. From there, you select your project, your dataset, and then your table set or table views. From that moment, you can start using all your data within the Studio. You can easily refresh the data, by clicking on the **Refresh Data** button: ![Refresh data in Data Studio](https://leeboonstra.dev/images/analyze-bq-ml-3.png) With Google Data Studio you can drag and drop charts, images and visualizations on the stage. It turns your data into informative dashboards and reports which are easy to read and share. Google Data Studio is currently in beta. It’s not part of Google Cloud Platform, it’s a Google Analytics solution. You can read more about this tool on: [https://www.google.com/analytics/data-studio/] ## Compute Engine / Cloud Launcher Ok, so we’re done with writing the code to fetch and analyze our data! Although you could keep your terminal open on your local machine for a full day, it’s not ideal. At the end, this process will take some processing power. A better use case would be to move it to a virtual machine. ![Image](https://leeboonstra.dev/images/analyze-bq-ml-4-500x166.png) Let’s create a virtual machine. Open the [Cloud console](https://console.cloud.google.com/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) in your browser. You could create a virtual machine, manually yourself by clicking the **Compute Engine** menu option. But since we need an image which requires Node.js installed, we will do it the easy way, instead make use of **Cloud Launcher**! 1. Click [Cloud Console Launcher](https://console.cloud.google.com/launcher/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) 2. Search for **Node.js** 3. Choose the 2nd Node.js image, (the one from **Bitnami**.) 4. Click the **Launch on Compute Engine** button. 5. Specify a name, and a zone (I am in Europe, so I choose **europe-west-1b**) 6. Choose **micro machine** type. 7. Hit the **Deploy** button. Notice, how fast it will launch this virtual machine for you! After you deployed the Bitnami Node.js app, you will see the IP address where your VM will be available. There’s an **SSH** dropdown. Hit it, and click **View SSH command**. It will show you the command that you can paste in your local terminal. For example: `gcloud compute --project "myproject" ssh --zone "europe-west1-b" "nodejs-1-vm"` This will log you in on the virtual machine. When you run this command the first time, you will need to create a passphrase. It will automatically create a public key on your machine. Once you’re logged in, you can verify if nodejs was properly installed. Enter: `$ node -v` It should output the nodejs version number. The next step is to transfer our project on this virtual machine. You can use Github for this, or you can use Google Cloud Development repositories. (I choose Github, since I want to share my public Github repository, with you. When you have your own project running locally on your machine, you can follow the steps from the **Development** screen instead.) Pull the code, from some repository in the root of your vm: `$ git clone https://github.com/savelee/gcloud-bq-election-demo.git` Navigate into the folder: `$ cd gcloud-bq-election-demo/` Install all the nodejs packages: `$ npm install` We aren’t done yet! We need to have all our keys available on the virtual machine. That’s the **.env** file and the **cloudkeys.json** file. Obviously, I didn’t put these files in my public Github repo. Hmm, so how can we move these files to the VM? With AppEngine or ContainerEngine, you probably would work with Docker images, so you can easily put these keys in the Docker file. For this demo, we use Compute Engine, so we should make use of [Google Cloud Storage](https://cloud.google.com/storage/) instead. We can create a bucket, and upload our keys into it. Open the Cloud console in your browser. Click on **Storage > Create Bucket**. Give the bucket a name. (For example *-contents>*). Make sure this bucketname is unique, and remember it, because you will use it later. Select **Regional**, and select the same region as the VM you have choosen before. (so *europe-west1* in my case.) Click **Create**. Once the bucket is created, we upload the files from the command-line. In your terminal on your local development machine enter the following two commands: `$ gsutil cp .env gs://` `$ gsutil cp cloudkey.json gs://` Both files are now in the bucket. The next step will be to download it, from the bucket into your virtual machine. Open the terminal which runs on your VM. Enter the following command, to download everything from the bucket, into the root of your vm: `$ gsutil cp gs:///* .` If you want, you can remove your bucket in the console. (Although, it probably wouldn’t cost much, to save 2 small files.) And now you can start the Node.js service: `$ nodemon bin/www` And now you can start the Node.js service. You might have noticed, that when you start running nodemon, and you close the VM console, that your script stops as well. What you will need is a Node module, which can run your script “forever”. `$ npm install forever -g` And then start your script like: `$ sudo /opt/bitnami/nodejs/bin/forever start bin/www` (with `sudo forever stopall` you can stop the script again) You should see the Tweets coming by. When you query in BigQuery you will see the results! Awesome, now let’s keep this process running for a while! *TIP: In case you rather want to schedule your script to go on, on a certain time, you could use Cron for this.* *First create a symlink to the correct node folder:* `$ sudo ln -s /opt/bitnami/nodejs /usr/bin/node` *Then start a crontab* `$ crontab -e` *Inside the crontab you code probably would look something like:* `SHELL=/bin/shell` `0 0 15 3 * PATH=$PATH:/opt/bitnami/nodejs cd ~/gcloud-bq-election-demo/ && sudo /opt/bitnami/nodejs/bin/forever start bin/www` *This means, at 0:00:00 time, on the 15th of March, navigate to the gcloud-bq-election-demo directory, and start the nodemon script. You can request the VM time by running `date` in the console.* *You can check if it all worked out, by viewing the logs:* `$ tail -f /var/log/syslog` ## Conclusion By now, you have learned how to work with various Google Cloud services. We have been looking into: **Compute Engine** and **Cloud Launcher** to spin off a VM in the cloud, **Machine Learning APIs** like the **Natural Language API** and **Translation API** to understand the data, **BigQuery** to analyze the data and **Data Studio** to visualize the data in nice charts. Did I predict the Dutch elections well? Well, in some extend. The top 2 most positively spoken parties on Twitter, got the 2nd and 3rd place in the final results. So that was as expected. The PvdA didn’t do well at all, and the VVD got the first place in the elections, with the most votes. If I do random checks on the collected data, I can see that the sentiment detection has been calculated correctly. There is just no real direct relation between popularity on social media vs. the voting process. But mind you every person can only vote once, but can post unlimited positive or negative tweets. All my code will be in Github [https://github.com/savelee/gcloud-bq-election-demo]. Feel free to use my code, to run a similar case. I will put the instructions on how to setup, in the readme file. In case you are new to Google Cloud platform. You can create [a free trial account](https://cloud.google.com/free-trial/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_), which includes $300 dollars that can be used to spend on cloud services which expire after 12 months. That should be enough money to try out this demo. ### The costs To run this tutorial for one day in a Compute VM created by Cloud Launcher, with the use of machine learning APIs and streaming and querying within BigQuery, costed me about 14 dollars. This included the VM running for 5 days (which costed me a dollar). My data wasn’t that big, so I didn’t had any costs for executing BigQuery queries. I was worried about the amount of API calls that I would made, but by running the script for a large part of the election day, I managed to still stay mostly within the free Natural Language API call request tier. So most of my money has been used on the large amount of tweets that has been translated. Let’s drill a little more into the costs of GCP. BigQuery storage will cost, $0.02 per GB Streaming data in BigQuery: $0.05 per GB (loading data is free). To execute Queries, will cost you $5.00 per TB but the first TB per month is free. [BigQuery Pricing](https://cloud.google.com/bigquery/pricing). Note, that Data Studio makes use of a BigQuery connector, which basically fires queries in BigQuery. The price for a micro virtual machine will cost less than 5 dollar a month. But for a computing demo like this, you will probably only run it for a bit, and pay for your use. With Compute Engine, you will pay per minute, with a 10 minute minimum. The prices for translation and language detection is about 20 dollars for a million characters. It will become cheaper, once your amount is over the 1.5 billion characters. See [Translate API pricing](https://cloud.google.com/translate/pricing). The Natural Language API has a free tier, if the amount of text records stays under the 5k, for pricing info see: [Natural Language Pricing](https://cloud.google.com/natural-language/pricing). Another great way, for checking and estimating costs, is by making use of the [GCP Pricing calculators](https://cloud.google.com/pricing/calculators). In case you are concerned about the prices, you can setup a billing alert. Which can send an email to you once you reach your quota. From the Cloud console, you can enable this, by clicking: **Billing > Budgets & Alerts**. In this screen, you can select the project, and your quota. - You can get emails once you get closer to your quota. ## Disclaimer This blog post won’t contain any political opinions from me, neither from Google. This is a blog post to demonstrate the various solutions of the Google Cloud Platform, on how you can be creative with data. I have collected a large part of the day all the Dutch political tweets, posted on Twitter. Though, my results can not be considered as the ultimate truth. I use the Natural Language API in combination with the Translation API, translating first from Dutch to English before doing a sentiment analysis. Whether this always results, in the right score is the question. But based on some random tests, the majority of the tweets seem to have a reasonable score. At the end I only need to figure out if the score is positive or negative. Let’s look into an example: Here’s the translation of the `translate.translate()` call with the following text: `Partij XYZ is mijn favoriete partij. Met de meeste stellingen ben ik het eens.`. It will be translated to this: `Party XYZ is my favorite party. With most positions, I agree.` If I would have translated it myself, I probably would have said it a little different: "Party XYZ is my favorite party. I agree with most of the positions they take on issues." However, to detect the sentiment, it doesn’t really matter, the score seems to be positive and correct: `{ magnitude: 0.800000011920929, score: 0.800000011920929 }` There are certain tweets which I left out the *totalscore* calculation for sentiment analysis. For example, tweets which contain multiple opinions for multiple political parties in one single tweet: *Here’s the result of the `language.detectSentiment()` call with the following text: `I love Milkshakes.` : `{ magnitude: 0.699999988079071, score: 0.699999988079071 }`* Here’s the result of the `language.detectSentiment()` call with the following text: `I really hate Whipcream.` : `{ magnitude: 0.5, score: -0.5 }` Here’s the result for the `language.detectSentiment()` call with the following text: `I love Milkshakes but I really hate Whipcream.` : `{ magnitude: 0, score: 0 }` I’m sure there are ways on how you can calculate this more nicely, but for now it’s out of scope for this blog post. (I came to this conclusion after seeing tweets (in single sentences) were people tag 5 different parties, with good and bad opinions about those.) Then there are also tweets, which are very hard for a machine to detect; for example tweets that contain sarcasm: Here’s the result for the `language.detectSentiment()` call with the following text: `Such a great idea, yeah, and monkeys can fly!` : `{ magnitude: 0.5, score: 0.5 }` I left these tweets in my score calculation, assuming that people will use sarcasm for every political party, with both a positive and negative meaning. Last but not least, not everyone who votes, shares their thoughts on Twitter, and some people with strong political opinions might tweet more, to use it as a personal outlet. But in real-life, a person can obviously only vote once. It’s interesting anyway and I was curious to see, if my analysis results come close to the final scores of the Dutch elections. At the end, I had a lot of fun by writing the code, and I learned a lot about the Google Cloud Platform. BTW; do you want to receive more insights about the Dutch Elections? Google has a [Google Trends](https://trends.google.nl/trends/story/NL_cu_l5QQ-lkBAADe7M_en) page. ## Handy Links * [Create a free GCP trial account](https://cloud.google.com/free-trial/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Node JS](https://nodejs.org/en/) * [Express](http://expressjs.com/) * [Twitter Streaming APIs](https://dev.twitter.com/streaming/overview) * [Translate API](https://cloud.google.com/translate/) * [Translate API pricing](https://cloud.google.com/translate/pricing?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Natural Language API](https://cloud.google.com/natural-language/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Natural Language Pricing](https://cloud.google.com/natural-language/pricing?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [BigQuery](https://cloud.google.com/bigquery/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [BigQuery pricing](https://cloud.google.com/bigquery/pricing?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Data Studio](https://www.google.com/analytics/data-studio/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Run Google Data Studio](https://datastudio.google.com?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Cloud Launcher](https://cloud.google.com/launcher/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Cloud Storage](https://cloud.google.com/storage/?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [GCP Pricing calculators](https://cloud.google.com/pricing/calculators?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) * [Comparing tweets about Trump & Hillary with natural language processing.](https://medium.com/google-cloud/comparing-tweets-about-trump-hillary-with-natural-language-processing-a0064e949666?utm_source=blog&utm_medium=partner&utm_campaign=CDR_lee_aiml_leedialogflowblog_personal_) ---