Walkguide Studio: Local Gemma Tours for Independent Guides A developer built Walkguide Studio, an open-source tool that lets independent walking-tour operators turn their own stop notes into self-guided tour packages using Gemma running locally through Ollama. The tool drafts narration for each stop and applies a grounding check that rejects unsupported names and numbers, retrying once at lower temperature before falling back to a deterministic template built from the source facts. The project, submitted to the Hacktoberfest Open-Source AI Challenge, produces a static tour with a route map, transcripts, optional audio, and an offline ZIP, with no hosted model API or per-narration charge. This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle https://dev.to/challenges/hacktoberfest-week2-2026-10-12 Built during Hacktoberfest Week 2 for the DEV Open-Source AI Challenge: Side Hustle https://dev.to/devteam/join-the-hacktoberfest-open-source-ai-challenge-side-hustle-2550-in-prizes-across-18-winners-55ba?bb=264692 . Click the thumbnail to watch the video. Walkguide Studio helps an independent walking-tour operator turn their own stop notes into a self-guided tour package. The operator enters the route and their local knowledge once; Gemma, running on the operator's computer through Ollama, drafts a short narration for each stop. A grounding check rejects unsupported names and numbers. The result is a static tour with a route map, transcripts, optional audio, and an offline ZIP that the operator can share or sell through their existing channel. The main product flow is built around open-weight AI: Studio requires a local Gemma model before it can create a package. There is no hosted model API in the path and no per-narration API charge. The original command line tools remain available for automation and for the separate… To run the Studio: ollama pull gemma3:1b python studio.py A failure shaped the design: Gemma sometimes returned generic introductions or echoed a stop heading, causing the grounding check to reject its narration. I tightened the prompt, cleaned up echoed headings, and made the Studio show why the guard rejected a draft. Unsupported narration is still blocked rather than quietly packaged as a finished tour. Open-weight AI makes it practical to experiment with a specialized tour product without first building around a closed provider’s API and pricing. The trade-off is that operators need a capable computer and detailed source notes; incomplete notes may lead to rejected narration. Build a stdlib-only Python 3.9+ project called "walkguide" no pip dependencies; optional external tools: Ollama for a local Gemma model, Piper for offline TTS . It has two products that share code: A WALK GUIDE: an offline-first audio guide that narrates places near you while you walk. B TOUR PACK: for small tour operators; turns their stop list + notes into an offline, narrated, self-guided tour a static folder: index.html + audio files . FILES - sources.py: haversine, bearing, compass deg - "north-east" etc. Fetch POIs from OpenStreetMap Overpass two endpoint fallbacks; query named historic / tourism museum|artwork|attraction|viewpoint|gallery / leisure park|garden|nature reserve / place of worship / natural peak|water|spring|wood plus trees with species; "out center tags 80" and Wikipedia geosearch generator=geosearch, extracts+coordinates, intro, plaintext, 4 sentences . Convert OSM tags to plain English sentences "It is a church.", "It dates from 1890." . Skip OSM places with fewer than 2 facts. Merge by case-insensitive name Wikipedia wins, add up to 3 OSM facts . Cache per 0.01-degree cell ~1.1 km, fetch radius 800 m around the cell centre as JSON files; only cache when BOTH sources succeeded. Support offline mode cache only . nearby lat, lon, cache dir, offline, max dist returns POIs with dist and bearing, sorted by distance. - narrator.py: build a prompt Place, Location, up to 8 "Fact:" lines and call Ollama /api/chat stream false, temperature 0.4, num predict 140, default model gemma3:1b . Accept a custom system prompt and max sentence count. GROUNDING GUARD: reject output if any number is not in the source text name + location + facts , or if any capitalised word that is not the first word of a sentence is not in the source allow "I" and compass words . On rejection retry once at temperature 0.1, then fall back to a deterministic template built from the facts. Strip markdown, trim to N sentences. Return text, meta with meta = {source: "llm"|"template", attempts, rejected: ... , latency s}. Never crash if Ollama is down. - speak.py: make speaker piper model returns a blocking speak text using, in order: Piper + aplay/ afplay/paplay/ffplay , termux-tts-speak, macOS say, espeak-ng/espeak, Windows System.Speech; None if nothing is found. synth text, base path, piper model writes