1. All Blogs 2. About Me 3. Contact Me 4. Visit My Website 5. Visit My Venture

HotSeat AI: Pitch Deck Diligence & Agent Skills with Gemma 4

font_size 100%
font
contrast
listen

HotSeat AI: Multimodal Venture Diligence and Agent Skill Compiler Powered by Gemma 4

HotSeat AI: Pitch Deck Diligence & Agent Skills with Gemma 4

When someone reviews a startup pitch deck, the biggest red flags usually aren't on any single slide by itself. They show up in the contradictions between slides—or between what is on the screen and what the founder says out loud.

For example, Slide 1 might claim "FDA Class III clearance across 42 hospital networks", while a footnote on Slide 2 quietly admits "Research Use Only (n = 110 retrospective samples)". Or a founder might show a clinical diagnostics slide while verbally pitching a B2B lending business. Basic PDF-to-text wrappers miss these conflicts because they don't look at the slide pixels alongside a running ledger of what has already been claimed.

For HackDay 2026 at GDG Vishwakarma University (October 22, 2026), I built HotSeat AI—an open-source (Apache-2.0) multimodal pitch-deck auditor powered by Google Gemma 4 (gemma-4-26b-a4b-it and gemma-4-31b-it via the Gemini API, plus local open-weight support via Ollama gemma4:12b and LM Studio).

Project Links

1. How HotSeat AI Works End to End

I designed the app around a 4-stage pipeline that runs identically on local Python FastAPI (backend/app.py, backend/engine.py) and at the edge on Cloudflare Workers (worker.js):

HotSeat AI 4-Stage Diligence Pipeline and Dual-Runtime Architecture

1. Client-Side Slide Rasterization (static/js/decks.js, voice.js, app.js): In the browser, PDF.js v3.11 and an HTML5 <canvas> render uploaded PDFs or slide images into compressed 1280px JPEG frames. We also built 3 instant multi-slide benchmark decks (FinTech, Healthcare/MedTech, and D2C Retail) with intentional financial and regulatory contradictions baked in so judges could test discrepancy detection in one click, plus Web Speech API voice input for answering questions out loud.

2. Gemma 4 Vision Inspection (Gemma4Router): On each turn, the backend sends the current slide's JPEG bytes, the founder's latest answer, and the running claim ledger to Gemma 4 (gemma-4-26b-a4b-it, cascading to gemma-4-31b-it or local Ollama gemma4:12b) and records the exact round-trip latency in milliseconds.

3. Stateful Claim Ledger & Contradiction Alerts: Every extracted number from [Slide X] and every verbal statement from [Founder] is appended to SESSION_STATE["claim_ledger"]. You can switch between 4 interviewer personas (Managing Partner, CFO, Enterprise Procurement Lead, or Technical Diligence Partner). Whenever a new slide or founder answer contradicts an earlier claim, the UI raises a crimson MATERIAL DISCREPANCY IDENTIFIED alert and adjusts the Investment Conviction Index (0–100).

4. IC Memo & Self-Improving SKILL.md Export: At any point, clicking Generate IC Evaluation Memo (POST /api/verdict) compiles a full Investment Committee memo (PROCEED TO TERM SHEET, ADVANCE TO SECOND-STAGE DILIGENCE, or DECLINE TO ADVANCE). In parallel, every contradiction discovered during the session is compiled into a portable Agent Skill file (.claude/skills/pitch-defense/SKILL.md) that you can download and drop into Claude Code or OpenCode.

2. The Tricky Part: Gemma 4's Multi-Turn API Quirks

If you swap a Gemini Flash model ID for gemma-4-26b-a4b-it in the Google GenAI SDK (google-genai) without changing your request payload, your multi-turn requests will fail. While building backend/engine.py and worker.js, I had to handle four specific Gemma API constraints:

  • Stripping unsupported config keys: Gemma models on the Gemini API reject systemInstruction, tools, toolConfig, and responseMimeType inside GenerateContentConfig. We strip those keys before every call and fold the system prompt cleanly into the first user message.
  • Strict user ↔ model alternation: If a previous turn errored out or the user clicked a button twice, having two user messages in a row causes a 400 error. Both _sanitize_gemini_contents() in Python and sanitizeGeminiContents() in the Cloudflare Worker deep-clone the session history, remap assistant to model, drop empty turns, merge consecutive same-role messages, and cap the history to the last 10 turns.
  • Sending only one image per request: Keeping base64 slide images attached to older turns in the chat history bloats the payload and slows down inference. Our sanitizer strips inlineData images from prior turns so only the current slide image is sent on the final user turn.
  • Staying inside Cloudflare's 100-second timeout: Transient 500 INTERNAL responses get a single retry after 350ms, and per-attempt timeouts are budgeted so the primary call, retry, and model fallback (gemma-4-26b-a4b-it → gemma-4-31b-it) always finish before Cloudflare's gateway timeout.
import time
from google import genai

def call_gemma_multimodal(active_api_key, model_id, history, prompt, image_bytes, mime_type):
    client = genai.Client(api_key=active_api_key)

    # Clean up history: assistant -> model, fold system prompt into user[0],
    # strip old slide images, and merge consecutive same-role turns.
    turns = _build_gemma_contents(
        _session_history_to_messages(history),
        prompt,
        image_bytes,
        mime_type,
    )
    contents = _to_genai_contents(turns)  # Slide image goes before text

    start_t = time.time()
    response = client.models.generate_content(
        model=model_id,  # "gemma-4-26b-a4b-it" -> fallback "gemma-4-31b-it"
        contents=contents,
        config=_gemma_generation_config(),
    )
    latency_ms = int((time.time() - start_t) * 1000)
    return response.text, latency_ms

3. Parsing JSON Reliably Without responseMimeType

Because Gemma 4 doesn't support responseMimeType: "application/json"—and often wraps its reasoning in <thought>...</thought> tags or Markdown code fences—we wrote a 3-step parser (extract_json_safely()) that strips <thought> blocks, checks for fenced JSON, falls back to finding the outermost {...} braces while fixing trailing commas, and returns a clean Python dict (or JS object in worker.js):

import json
import re

def extract_json_safely(raw_text: str) -> dict:
    # 1. Strip <thought> blocks from Gemma 4 output
    cleaned = re.sub(r"<thought>.*?</thought>", "", raw_text or "", flags=re.DOTALL).strip()

    try:
        return json.loads(cleaned)
    except json.JSONDecodeError:
        pass

    # 2. Check for ```json ... ``` blocks
    fence_match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", cleaned, re.DOTALL)
    if fence_match:
        try:
            return json.loads(fence_match.group(1))
        except json.JSONDecodeError:
            pass

    # 3. Grab outermost {...} and strip trailing commas
    start, end = cleaned.find("{"), cleaned.rfind("}")
    if start != -1 and end > start:
        candidate = re.sub(r",\s*([}\]])", r"\1", cleaned[start : end + 1])
        return json.loads(candidate)

    raise ValueError("Could not parse JSON from Gemma 4 response.")

Dual-monitor setup building HotSeat AI during HackDay 2026 at GDG Vishwakarma University

14:50 PM during HackDay 2026 at GDG Vishwakarma University—wiring up @app.post("/api/verdict") and the SESSION_STATE contradiction payload in backend/app.py across my MacBook and portable ThinkVision monitor.

4. Indexing the Codebase with Graphify

For the hackathon's sponsor track, we also indexed the repo with Graphify (graphifyy>=0.9.38), which parses the Python and JS abstract syntax trees into a NetworkX knowledge graph (graphify-out/graph.json, graphify-out/graph.html, and graphify-out/GRAPH_REPORT.md) highlighting the core hub functions (Gemma4Router, extract_json_safely(), interrogate(), and verdict()).

Takeaways

  • Don't assume open-weight models accept the exact same API flags as Gemini Pro/Flash: Stripping systemInstruction and responseMimeType and sanitizing role alternation saved hours of debugging.
  • Keep a structured ledger alongside vision calls: Asking a vision model to critique a slide on its own gives generic feedback; passing a compact list of every claim made on Slides 1–3 is what lets it catch real contradictions on Slide 4.
  • Dual FastAPI + Cloudflare Workers parity is worth the effort: Having backend/app.py for local Ollama testing and worker.js for zero-cold-start edge hosting meant judges could test the live URL immediately without spinning up a server.

Try It Out: Open hotseat-ai.ojasdeshpande.in, check out the code in OjasD07/HotSeatAI-HackDay-2026-GDG_VU, or continue in the Hackathon Engineering & AI Systems series: Next → ZeroCode at Techfest 2026: Building SPLITZZ! with Google AI Studio & Gemini Flash | Browse All Articles

← Back to all articles