HotSeat AI: Pitch Deck Diligence & Agent Skills with 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
- Live Cloudflare Workers Deployment: https://hotseat-ai.ojasdeshpande.in/
- GitHub Repository: https://github.com/OjasD07/HotSeatAI-HackDay-2026-GDG_VU
- Generated Agent Skill (
SKILL.md): .claude/skills/pitch-defense/SKILL.md - Graphify AST Knowledge Graph Report: graphify-out/GRAPH_REPORT.md
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):
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, andresponseMimeTypeinsideGenerateContentConfig. We strip those keys before every call and fold the system prompt cleanly into the firstusermessage. - Strict
user↔modelalternation: If a previous turn errored out or the user clicked a button twice, having twousermessages in a row causes a 400 error. Both_sanitize_gemini_contents()in Python andsanitizeGeminiContents()in the Cloudflare Worker deep-clone the session history, remapassistanttomodel, 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
inlineDataimages from prior turns so only the current slide image is sent on the finaluserturn. - Staying inside Cloudflare's 100-second timeout: Transient
500 INTERNALresponses get a single retry after350ms, 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.")

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
systemInstructionandresponseMimeTypeand 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.pyfor local Ollama testing andworker.jsfor 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