An AI-powered San Francisco tour guide and itinerary planning platform built with a fully local AI stack using NVIDIA Nemotron models, RAG (Retrieval Augmented Generation), and real-time voice processing.
Live Site: https://touristy.urbantech.dev
This entire application was coded and developed using OpenCode with Ollama running NVIDIA Nemotron 30B locally. No cloud AI APIs were used during development - the entire codebase was generated, debugged, and refined using a fully local AI coding assistant.
| Tool | Purpose |
|---|---|
| OpenCode | AI-powered coding assistant (local) |
| Ollama | Local LLM inference runtime |
| NVIDIA Nemotron 30B | Large language model for code generation |
| Claude Code | Additional AI pair programming |
# Install Ollama
curl -fsSL https://ollama.com/install.sh | sh
# Pull Nemotron 30B for code generation
ollama pull nemotron:30b
# or the smaller variant
ollama pull nemotron-3-nano-64k
# OpenCode automatically connects to local Ollama
# Configure in ~/.config/opencode/config.yaml:
# provider: ollama
# model: nemotron:30b
# endpoint: http://localhost:11434
The Nemotron 30B model excels at:
Touristy is built as a microservices architecture with all AI inference running locally:
┌─────────────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────────┐ │
│ │ React 19 │ │ Leaflet Maps │ │ WebSocket Audio (VoiceMicrophone)│ │
│ │ Next.js 16 │ │ react-leaflet│ │ Web Audio API │ │
│ └─────────────┘ └──────────────┘ └─────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ NEXT.JS API ROUTES (Port 3000) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌───────────────┐ │
│ │ /api/chat │ │ /api/walking │ │ /api/events │ │ /api/itinerary│ │
│ │ LangGraph │ │ -tour/generate│ │ CRUD + Scrape│ │ CRUD + Social │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ └───────────────┘ │
└───────────────────────────────┬─────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ FASTAPI │ │ FASTAPI STS │ │ POSTGRESQL │
│ RAG AGENT │ │ Voice Pipeline │ │ + PRISMA ORM │
│ (Port 8001) │ │ (Port 8000) │ │ (Port 5432) │
│ │ │ │ │ │
│ ┌───────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │
│ │ FAISS │ │ │ │ Whisper STT │ │ │ │ 20+ Models │ │
│ │ VectorDB │ │ │ │ VITS TTS │ │ │ │ Users, POIs, │ │
│ │ │ │ │ │ WebSocket │ │ │ │ Itineraries │ │
│ └───────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │
└───────┬───────┘ └────────┬─────────┘ └──────────────────┘
│ │
└──────────┬──────────┘
▼
┌──────────────────────┐
│ OLLAMA LOCAL LLM │
│ (Port 11434) │
│ │
│ ┌──────────────────┐ │
│ │ nemotron-3-nano │ │
│ │ -64k │ │
│ │ NVIDIA Nemotron │ │
│ │ 3B Parameters │ │
│ └──────────────────┘ │
└──────────────────────┘
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 16.1.4 | React meta-framework with App Router |
| React | 19.2.3 | UI library with JSX |
| TypeScript | 5.x | Type-safe JavaScript |
| Tailwind CSS | 4.x | Utility-first CSS framework |
| Leaflet | 1.9.4 | Interactive mapping library |
| react-leaflet | 5.0.0 | React wrapper for Leaflet |
| react-markdown | 10.1.0 | Markdown rendering |
| SuperTokens Auth React | 0.51.1 | Frontend authentication |
| supertokens-web-js | 0.16.0 | Web SDK for authentication |
| Technology | Version | Purpose |
|---|---|---|
| Next.js API Routes | 16.1.4 | Serverless backend functions (42+ routes) |
| Prisma ORM | 5.22.0 | Database ORM with type-safe queries |
| PostgreSQL | Latest | Primary relational database |
| SuperTokens Node | 24.0.0 | Backend session management |
| bcryptjs | 3.0.3 | Password hashing |
| FastAPI | Latest | Python async web framework |
| uvicorn | Latest | ASGI server |
| Technology | Purpose |
|---|---|
| Ollama | Local LLM runtime/inference engine |
| LangChain | LLM orchestration framework |
| langchain-community | Community integrations |
| langchain-core | Core abstractions |
| langchain_huggingface | HuggingFace embeddings |
| FAISS | Facebook AI Similarity Search (Vector DB) |
| PyTorch | Deep learning framework |
| transformers | HuggingFace Transformers |
| sentence-transformers | Sentence embeddings for RAG |
| DSPy | Declarative Self-Improving prompts |
| accelerate | Distributed inference utilities |
The application uses NVIDIA Nemotron 3 Nano models running entirely locally through Ollama:
# Primary model for tour generation and content
Model: nemotron-3-nano-64k:latest
Context Window: 64,000 tokens
Parameters: ~3B
Runtime: Ollama (http://localhost:11434)
# Model capabilities:
- Walking tour narrative generation
- Conversational AI responses
- POI description generation
- Itinerary planning
- Natural language understanding
Why Nemotron?
The codebase supports the NVIDIA NeMo Toolkit for advanced model capabilities:
# Available in requirements
nemo-toolkit[all]
# Provides:
- Advanced speech recognition models
- Neural text-to-speech
- Speaker diarization
- Language models
# Start Ollama service
ollama serve
# Pull the Nemotron model
ollama pull nemotron-3-nano-64k
# Verify model
ollama list
The AI chat assistant uses LangGraph for tool-augmented conversations:
# Agent Tools Available:
- POI search and recommendations
- Restaurant finder
- Neighborhood guide
- Route planning (walking, cycling, transit)
- Event discovery
- Weather information
- Distance calculations
The RAG (Retrieval Augmented Generation) system provides contextual recommendations by searching a vector database of 1,000+ San Francisco landmarks.
┌─────────────────────────────────────────────────────────────┐
│ RAG PIPELINE │
│ │
│ User Query │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ QUERY EMBEDDING │ │
│ │ HuggingFace Sentence Transformers │ │
│ │ Model: all-MiniLM-L6-v2 (or similar) │ │
│ │ Dimensions: 384 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ FAISS VECTOR SEARCH │ │
│ │ Index: /agent/vectorstore/index.faiss │ │
│ │ Metadata: /agent/vectorstore/index.pkl │ │
│ │ Algorithm: Approximate Nearest Neighbors │ │
│ │ Top-K: 5-10 relevant documents │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CONTEXT AUGMENTATION │ │
│ │ Retrieved POIs + User Query → Prompt │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ NEMOTRON GENERATION │ │
│ │ Model: nemotron-3-nano-64k │ │
│ │ Context: Query + Retrieved Documents │ │
│ │ Output: Contextual recommendations │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
# Location: /agent/vectorstore/
# Files:
# - index.faiss (Vector embeddings)
# - index.pkl (Document metadata)
# Embedding Model
from langchain_huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings()
# Vector Store Loading
from langchain.vectorstores import FAISS
vectorstore = FAISS.load_local("vectorstore", embeddings)
# Similarity Search
docs = vectorstore.similarity_search(query, k=5)
| File | Purpose |
|---|---|
agent/rag_agent.py | Main RAG agent with vector search |
agent/location_extractor.py | Constrained entity extraction (prevents hallucination) |
agent/location_service.py | Unified POI lookup from Prisma database |
agent/restaurant_service.py | POI-based restaurant recommendations |
agent/neighborhood_guide.py | Curated SF neighborhood data |
agent/routing.py | Turn-by-turn navigation via ORS/OSRM |
To prevent LLM hallucinations, the system uses constrained extraction:
# location_extractor.py
# Only returns POIs that exist in the database
# Normalizes queries for fuzzy matching
# Falls back to vector search for unknown queries
┌─────────────────────────────────────────────────────────────────────┐
│ REAL-TIME VOICE PIPELINE │
│ │
│ Browser (VoiceMicrophone.tsx) │
│ │ │
│ │ WebSocket: wss://touristyvoice.urbantech.dev │
│ │ (or ws://localhost:8000) │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ FASTAPI STS SERVICE (Port 8000) ││
│ │ ││
│ │ ┌──────────────────────────────────────────────────────────┐ ││
│ │ │ 1. SPEECH-TO-TEXT (STT) │ ││
│ │ │ Model: openai/whisper-small │ ││
│ │ │ Library: transformers (WhisperForConditionalGeneration)│ ││
│ │ │ Input: Raw audio bytes │ ││
│ │ │ Output: Transcribed text │ ││
│ │ └──────────────────────────────────────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌──────────────────────────────────────────────────────────┐ ││
│ │ │ 2. RAG AGENT API CALL │ ││
│ │ │ Endpoint: http://localhost:8001/api/chat │ ││
│ │ │ Payload: { "message": transcribed_text } │ ││
│ │ │ Response: AI-generated tour guide response │ ││
│ │ └──────────────────────────────────────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌──────────────────────────────────────────────────────────┐ ││
│ │ │ 3. TEXT-TO-SPEECH (TTS) │ ││
│ │ │ Model: Baghdad99/english_voice_tts │ ││
│ │ │ Library: transformers (VitsModel) │ ││
│ │ │ Input: Agent response text │ ││
│ │ │ Output: WAV audio stream │ ││
│ │ └──────────────────────────────────────────────────────────┘ ││
│ │ │ ││
│ └───────────────────────────┼──────────────────────────────────────┘│
│ ▼ │
│ WebSocket Audio Stream → Browser AudioContext → Speaker │
└─────────────────────────────────────────────────────────────────────┘
# Model: OpenAI Whisper Small
# Library: HuggingFace Transformers
from transformers import WhisperProcessor, WhisperForConditionalGeneration
model_name = "openai/whisper-small"
processor = WhisperProcessor.from_pretrained(model_name)
model = WhisperForConditionalGeneration.from_pretrained(model_name)
# Features:
# - Multi-language support
# - Real-time streaming
# - Voice Activity Detection (webrtcvad)
# Model: Baghdad99/english_voice_tts
# Library: HuggingFace Transformers (VITS)
from transformers import VitsModel, VitsTokenizer
model_name = "Baghdad99/english_voice_tts"
tokenizer = VitsTokenizer.from_pretrained(model_name)
model = VitsModel.from_pretrained(model_name)
# Features:
# - Natural sounding English voice
# - 16kHz WAV output
# - Real-time streaming via WebSocket
| Library | Purpose |
|---|---|
| librosa | Audio analysis and feature extraction |
| soundfile | WAV file I/O |
| pyaudio | Audio input/output |
| webrtcvad-wheels | Voice Activity Detection |
| scipy | Signal processing |
| numpy | Numerical operations |
The system uses DSPy for declarative, self-improving prompts:
# DSPy Configuration
import dspy
# Define structured signatures
class TourGuideSignature(dspy.Signature):
"""Generate helpful tour guide responses"""
query: str = dspy.InputField()
context: str = dspy.InputField()
response: str = dspy.OutputField()
# Create predictor
predictor = dspy.Predict(TourGuideSignature)
# batch_evaluator.py
# Automated prompt optimization pipeline:
# 1. Fetch recent conversations from PostgreSQL
# 2. Sample 10% for evaluation
# 3. Calculate GEPA (Graph-based Efficiency-Preserving Automated) scores
# 4. Compare against baseline
# 5. Auto-update prompts when 5%+ improvement detected
# 6. Track prompt versions in database
| Metric | Description |
|---|---|
| GEPA Score | Graph-based efficiency metric for prompt quality |
| Response Relevance | How well the response matches the query |
| Factual Accuracy | Grounding in retrieved documents |
| User Satisfaction | Implicit feedback from conversation flow |
// Core Models
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
createdAt DateTime @default(now())
superTokensId String? @unique
// Relations
savedRoutes SavedRoute[]
trips Trip[]
following Follow[] @relation("follower")
followers Follow[] @relation("following")
}
model POI {
id String @id @default(uuid())
name String
description String?
latitude Float
longitude Float
address String?
rating Float?
priceLevel Int?
categoryId String?
// Relations
category Category?
routeStops RouteStop[]
likes POILike[]
comments POIComment[]
}
model Category {
id String @id @default(uuid())
name String @unique
icon String?
color String?
pois POI[]
}
model Route {
id String @id @default(uuid())
name String
description String?
geojson Json?
createdAt DateTime @default(now())
stops RouteStop[]
}
model Event {
id String @id @default(uuid())
title String
description String?
startTime DateTime
endTime DateTime?
venue String?
latitude Float?
longitude Float?
price Float?
category String?
sourceUrl String?
imageUrl String?
}
model Message {
id String @id @default(uuid())
conversationId String
role String
content String
gepaScore Float? // For prompt optimization
createdAt DateTime @default(now())
}
model PromptVersion {
id String @id @default(uuid())
name String
content String
version Int
score Float?
isActive Boolean @default(false)
createdAt DateTime @default(now())
}
| Endpoint | Method | Description |
|---|---|---|
/api/auth/[...path] | ALL | SuperTokens auth routes |
/api/user | GET/PUT | User profile |
| Endpoint | Method | Description |
|---|---|---|
/api/chat | POST | LangGraph agent conversation |
/api/conversations/[id] | GET/DELETE | Conversation management |
/api/conversations/[id]/messages | GET | Message history |
| Endpoint | Method | Description |
|---|---|---|
/api/walking-tour/generate | POST | Generate tour with Nemotron |
/api/walking-tour/tts | POST | Text-to-speech for tour |
/api/walking-tour/generate-image | POST | Cover image generation |
| Endpoint | Method | Description |
|---|---|---|
/api/pois | GET | List all POIs |
/api/categories | GET | POI categories |
/api/routes | GET/POST | Route CRUD |
/api/route | GET/POST/DELETE | Single route ops |
| Endpoint | Method | Description |
|---|---|---|
/api/events | GET | Event listing |
/api/events/[id] | GET | Event details |
/api/events/scrape | POST | Trigger event scraping |
/api/events/today | GET | Today's events |
| Endpoint | Method | Description |
|---|---|---|
/api/itineraries | GET/POST | Itinerary CRUD |
/api/itineraries/[id] | GET/PUT/DELETE | Single itinerary |
/api/itineraries/[id]/like | POST | Like itinerary |
/api/itineraries/[id]/comments | GET/POST | Comments |
| Service | Endpoint | Description |
|---|---|---|
| RAG Agent | http://localhost:8001/api/chat | Tour guide RAG |
| TTS | http://localhost:8000/v1/audio/tts | Text-to-speech |
| STT WebSocket | ws://localhost:8000/v1/audio/speech_to_speech/realtime | Real-time voice |
| Metrics | http://localhost:8000/v1/metrics | Token usage stats |
# Clone repository
git clone https://github.com/yourusername/touristy.git
cd touristy
# Install Node.js dependencies
npm install
# Create Python virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
# Install Python dependencies
pip install -r requirements.txt
pip install -r agent/requirements.txt
pip install -r sts/requirements.txt
# Install Ollama (Linux)
curl -fsSL https://ollama.com/install.sh | sh
# Start Ollama service
ollama serve
# Pull Nemotron model
ollama pull nemotron-3-nano-64k
# Verify
ollama list
# Create PostgreSQL database
createdb touristy
# Run migrations
npx prisma migrate dev
# Seed data
npm run db:seed
# Copy environment template
cp .env.example .env.local
# Edit with your values
nano .env.local
# Terminal 1: Next.js
npm run dev
# Terminal 2: RAG Agent
cd agent && python main.py
# Terminal 3: Voice Service
cd sts && python Touristy.py
# Terminal 4: Ollama (if not running as service)
ollama serve
# Database
DATABASE_URL=postgresql://touristy:touristy123@localhost:5432/touristy
# Authentication (SuperTokens)
NEXT_PUBLIC_API_DOMAIN=http://localhost:3000
NEXT_PUBLIC_WEBSITE_DOMAIN=http://localhost:3000
# Ollama (Local LLM)
OLLAMA_URL=http://localhost:11434/api/generate
OLLAMA_MODEL=nemotron-3-nano-64k:latest
# Text-to-Speech Service
LOCAL_TTS_URL=http://localhost:8000/v1/audio/tts
# RAG Agent Service
AGENT_API_URL=http://localhost:8001/api/chat
# Image Generation (Optional - Stable Diffusion)
IMAGE_API_URL=http://localhost:7860/sdapi/v1/txt2img
USE_PLACEHOLDER_IMAGES=true
# NVIDIA API (Optional - for cloud inference)
NVIDIA_API_KEY=your_key_here
# Offline Mode (disable external network)
OFFLINE_MODE=false
touristy/
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── explore/ # Main exploration hub
│ │ ├── events/ # Event discovery
│ │ ├── itinerary/ # Itinerary viewing/editing
│ │ ├── landmarks/ # SF landmarks browser
│ │ └── api/ # API routes (42+ endpoints)
│ │ ├── chat/ # LangGraph agent
│ │ ├── walking-tour/ # Tour generation
│ │ ├── events/ # Event CRUD
│ │ ├── itineraries/ # Itinerary CRUD
│ │ └── ...
│ ├── components/ # React components
│ │ ├── MapView.tsx # Interactive Leaflet map
│ │ ├── VoiceMicrophone.tsx # Voice interaction
│ │ ├── WalkingTourPresentation.tsx
│ │ ├── MapSearchBar.tsx # Search with autocomplete
│ │ └── ChatSidebar.tsx # AI chat interface
│ ├── lib/ # Utilities and helpers
│ └── services/ # External service integrations
│
├── agent/ # Python RAG Agent Service
│ ├── main.py # FastAPI entry point
│ ├── rag_agent.py # RAG agent implementation
│ ├── location_extractor.py # Constrained extraction
│ ├── location_service.py # POI database service
│ ├── restaurant_service.py # Restaurant recommendations
│ ├── neighborhood_guide.py # SF neighborhoods data
│ ├── routing.py # ORS/OSRM routing
│ ├── batch_evaluator.py # DSPy prompt optimization
│ ├── vectorstore/ # FAISS vector database
│ │ ├── index.faiss
│ │ └── index.pkl
│ └── requirements.txt
│
├── sts/ # Speech-to-Speech Service
│ ├── Touristy.py # FastAPI voice server
│ ├── generated_audio/ # Audio cache
│ └── requirements.txt
│
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/ # Migration history
│
├── scripts/
│ ├── run-agent.sh # Start agent service
│ ├── setup-agent.sh # Setup agent environment
│ └── scrape-events.sh # Event scraping
│
├── public/ # Static assets
├── ecosystem.config.js # PM2 configuration
├── Caddyfile # Caddy reverse proxy
├── vercel.json # Vercel cron jobs
├── package.json
├── tsconfig.json
└── .env.local # Environment variables
npm run dev # Start development server (port 3000)
npm run build # Build for production
npm run start # Run production build
npm run lint # Run ESLint
npm run db:seed # Seed database with POIs
# RAG Agent
cd agent
python main.py # Start on port 8001
# Voice Service
cd sts
python Touristy.py # Start on port 8000
./scripts/run-agent.sh # Start agent with venv
./scripts/setup-agent.sh # Setup Python environment
./scripts/scrape-events.sh # Trigger event scraping
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-website',
script: 'npm',
args: 'start',
cwd: '/home/dell/my-website',
env: {
NODE_ENV: 'production',
PORT: 3000
},
max_memory_restart: '500M'
}]
};
# Caddyfile
touristy.urbantech.dev {
reverse_proxy localhost:3000
}
touristyvoice.urbantech.dev {
reverse_proxy localhost:8000
}
{
"crons": [
{
"path": "/api/events/scrape",
"schedule": "0 6 * * *"
}
]
}
| Service | Purpose | Local/Remote |
|---|---|---|
| Ollama | LLM inference | Local |
| FAISS | Vector similarity search | Local |
| Whisper | Speech-to-text | Local |
| VITS TTS | Text-to-speech | Local |
| Nominatim | Geocoding | Remote (OSM) |
| OpenRouteService | Routing | Remote (or self-hosted) |
| OSRM | Routing fallback | Remote (or self-hosted) |
| SuperTokens | Auth sessions | Remote (free tier) |
~2GB - nemotron-3-nano-64k (Ollama)
~500MB - whisper-small (HuggingFace)
~200MB - VITS TTS model (HuggingFace)
~100MB - Sentence embeddings (HuggingFace)
This project demonstrates a fully local AI-powered development workflow where the entire codebase was written using local LLMs without any cloud API dependencies.
┌─────────────────────────────────────────────────────────────────┐
│ LOCAL AI DEVELOPMENT STACK │
│ │
│ Developer │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ OPENCODE │ │
│ │ AI-Powered Coding Assistant │ │
│ │ - Code generation & completion │ │
│ │ - Debugging & error fixing │ │
│ │ - Refactoring suggestions │ │
│ │ - Documentation generation │ │
│ │ - Architecture planning │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ OLLAMA │ │
│ │ Local LLM Runtime (http://localhost:11434) │ │
│ │ - Model management │ │
│ │ - GPU acceleration (CUDA/ROCm) │ │
│ │ - Context window management │ │
│ │ - Streaming responses │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ NVIDIA NEMOTRON 30B │ │
│ │ Large Language Model for Code Generation │ │
│ │ - 30 billion parameters │ │
│ │ - Optimized for code understanding │ │
│ │ - Multi-language support (TS, Python, SQL, etc.) │ │
│ │ - Long context for full-file analysis │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| Model | Size | Purpose |
|---|---|---|
| nemotron:30b | ~18GB | Primary code generation, complex logic |
| nemotron:70b | ~40GB | Architecture decisions, large refactors |
| nemotron-3-nano-64k | ~2GB | Quick edits, documentation, simple fixes |
# ~/.config/opencode/config.yaml
provider: ollama
model: nemotron:30b
endpoint: http://localhost:11434
# Ollama-specific settings
options:
num_ctx: 32768 # Context window
num_gpu: 99 # GPU layers (all)
temperature: 0.1 # Low for consistent code
top_p: 0.9
repeat_penalty: 1.1
# 1. Start Ollama with Nemotron
ollama serve
# 2. Verify model is loaded
ollama list
# NAME SIZE
# nemotron:30b 18GB
# 3. Launch OpenCode in project directory
cd /path/to/touristy
opencode .
# 4. OpenCode connects to local Ollama
# All code generation happens locally
# No API keys needed, no data sent to cloud
The following components were entirely generated using OpenCode + Nemotron 30B:
Frontend (Next.js/React)
Backend (Next.js API Routes)
Python AI Services
Database
DevOps
| Benefit | Description |
|---|---|
| Zero API Costs | No OpenAI/Anthropic/Google API fees |
| Complete Privacy | Code never leaves your machine |
| No Rate Limits | Generate as much code as needed |
| Offline Development | Work anywhere without internet |
| Customizable | Fine-tune models for your codebase |
| Fast Iteration | Low latency local inference |
| Reproducible | Same model version, same results |
Development Machine:
- GPU: NVIDIA RTX 4090 (24GB VRAM)
- CPU: AMD Ryzen 9 7950X
- RAM: 64GB DDR5
- Storage: 2TB NVMe SSD
Inference Performance:
- nemotron:30b: ~30 tokens/sec
- nemotron-3-nano-64k: ~100 tokens/sec
# 1. Install Ollama
curl -fsSL https://ollama.com/install.sh | sh
# 2. Pull Nemotron models
ollama pull nemotron:30b # Primary development
ollama pull nemotron-3-nano-64k # Quick tasks
# 3. Install OpenCode
# Follow instructions at: https://github.com/opencode-ai/opencode
# 4. Configure OpenCode for Ollama
mkdir -p ~/.config/opencode
cat > ~/.config/opencode/config.yaml << 'EOF'
provider: ollama
model: nemotron:30b
endpoint: http://localhost:11434
options:
num_ctx: 32768
temperature: 0.1
EOF
# 5. Start coding!
opencode /path/to/your/project
MIT
npm run lintTypeScript
63.6%
Python
35.6%
An AI-powered San Francisco tour guide and itinerary planning platform built with a fully local AI stack using NVIDIA Nemotron models, RAG (Retrieval Augmented Generation), and real-time voice processing.
Live Site: https://touristy.urbantech.dev
This entire application was coded and developed using OpenCode with Ollama running NVIDIA Nemotron 30B locally. No cloud AI APIs were used during development - the entire codebase was generated, debugged, and refined using a fully local AI coding assistant.
| Tool | Purpose |
|---|---|
| OpenCode | AI-powered coding assistant (local) |
| Ollama | Local LLM inference runtime |
| NVIDIA Nemotron 30B | Large language model for code generation |
| Claude Code | Additional AI pair programming |
# Install Ollama
curl -fsSL https://ollama.com/install.sh | sh
# Pull Nemotron 30B for code generation
ollama pull nemotron:30b
# or the smaller variant
ollama pull nemotron-3-nano-64k
# OpenCode automatically connects to local Ollama
# Configure in ~/.config/opencode/config.yaml:
# provider: ollama
# model: nemotron:30b
# endpoint: http://localhost:11434
The Nemotron 30B model excels at:
Touristy is built as a microservices architecture with all AI inference running locally:
┌─────────────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────────┐ │
│ │ React 19 │ │ Leaflet Maps │ │ WebSocket Audio (VoiceMicrophone)│ │
│ │ Next.js 16 │ │ react-leaflet│ │ Web Audio API │ │
│ └─────────────┘ └──────────────┘ └─────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ NEXT.JS API ROUTES (Port 3000) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌───────────────┐ │
│ │ /api/chat │ │ /api/walking │ │ /api/events │ │ /api/itinerary│ │
│ │ LangGraph │ │ -tour/generate│ │ CRUD + Scrape│ │ CRUD + Social │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ └───────────────┘ │
└───────────────────────────────┬─────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ FASTAPI │ │ FASTAPI STS │ │ POSTGRESQL │
│ RAG AGENT │ │ Voice Pipeline │ │ + PRISMA ORM │
│ (Port 8001) │ │ (Port 8000) │ │ (Port 5432) │
│ │ │ │ │ │
│ ┌───────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │
│ │ FAISS │ │ │ │ Whisper STT │ │ │ │ 20+ Models │ │
│ │ VectorDB │ │ │ │ VITS TTS │ │ │ │ Users, POIs, │ │
│ │ │ │ │ │ WebSocket │ │ │ │ Itineraries │ │
│ └───────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │
└───────┬───────┘ └────────┬─────────┘ └──────────────────┘
│ │
└──────────┬──────────┘
▼
┌──────────────────────┐
│ OLLAMA LOCAL LLM │
│ (Port 11434) │
│ │
│ ┌──────────────────┐ │
│ │ nemotron-3-nano │ │
│ │ -64k │ │
│ │ NVIDIA Nemotron │ │
│ │ 3B Parameters │ │
│ └──────────────────┘ │
└──────────────────────┘
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 16.1.4 | React meta-framework with App Router |
| React | 19.2.3 | UI library with JSX |
| TypeScript | 5.x | Type-safe JavaScript |
| Tailwind CSS | 4.x | Utility-first CSS framework |
| Leaflet | 1.9.4 | Interactive mapping library |
| react-leaflet | 5.0.0 | React wrapper for Leaflet |
| react-markdown | 10.1.0 | Markdown rendering |
| SuperTokens Auth React | 0.51.1 | Frontend authentication |
| supertokens-web-js | 0.16.0 | Web SDK for authentication |
| Technology | Version | Purpose |
|---|---|---|
| Next.js API Routes | 16.1.4 | Serverless backend functions (42+ routes) |
| Prisma ORM | 5.22.0 | Database ORM with type-safe queries |
| PostgreSQL | Latest | Primary relational database |
| SuperTokens Node | 24.0.0 | Backend session management |
| bcryptjs | 3.0.3 | Password hashing |
| FastAPI | Latest | Python async web framework |
| uvicorn | Latest | ASGI server |
| Technology | Purpose |
|---|---|
| Ollama | Local LLM runtime/inference engine |
| LangChain | LLM orchestration framework |
| langchain-community | Community integrations |
| langchain-core | Core abstractions |
| langchain_huggingface | HuggingFace embeddings |
| FAISS | Facebook AI Similarity Search (Vector DB) |
| PyTorch | Deep learning framework |
| transformers | HuggingFace Transformers |
| sentence-transformers | Sentence embeddings for RAG |
| DSPy | Declarative Self-Improving prompts |
| accelerate | Distributed inference utilities |
The application uses NVIDIA Nemotron 3 Nano models running entirely locally through Ollama:
# Primary model for tour generation and content
Model: nemotron-3-nano-64k:latest
Context Window: 64,000 tokens
Parameters: ~3B
Runtime: Ollama (http://localhost:11434)
# Model capabilities:
- Walking tour narrative generation
- Conversational AI responses
- POI description generation
- Itinerary planning
- Natural language understanding
Why Nemotron?
The codebase supports the NVIDIA NeMo Toolkit for advanced model capabilities:
# Available in requirements
nemo-toolkit[all]
# Provides:
- Advanced speech recognition models
- Neural text-to-speech
- Speaker diarization
- Language models
# Start Ollama service
ollama serve
# Pull the Nemotron model
ollama pull nemotron-3-nano-64k
# Verify model
ollama list
The AI chat assistant uses LangGraph for tool-augmented conversations:
# Agent Tools Available:
- POI search and recommendations
- Restaurant finder
- Neighborhood guide
- Route planning (walking, cycling, transit)
- Event discovery
- Weather information
- Distance calculations
The RAG (Retrieval Augmented Generation) system provides contextual recommendations by searching a vector database of 1,000+ San Francisco landmarks.
┌─────────────────────────────────────────────────────────────┐
│ RAG PIPELINE │
│ │
│ User Query │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ QUERY EMBEDDING │ │
│ │ HuggingFace Sentence Transformers │ │
│ │ Model: all-MiniLM-L6-v2 (or similar) │ │
│ │ Dimensions: 384 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ FAISS VECTOR SEARCH │ │
│ │ Index: /agent/vectorstore/index.faiss │ │
│ │ Metadata: /agent/vectorstore/index.pkl │ │
│ │ Algorithm: Approximate Nearest Neighbors │ │
│ │ Top-K: 5-10 relevant documents │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CONTEXT AUGMENTATION │ │
│ │ Retrieved POIs + User Query → Prompt │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ NEMOTRON GENERATION │ │
│ │ Model: nemotron-3-nano-64k │ │
│ │ Context: Query + Retrieved Documents │ │
│ │ Output: Contextual recommendations │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
# Location: /agent/vectorstore/
# Files:
# - index.faiss (Vector embeddings)
# - index.pkl (Document metadata)
# Embedding Model
from langchain_huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings()
# Vector Store Loading
from langchain.vectorstores import FAISS
vectorstore = FAISS.load_local("vectorstore", embeddings)
# Similarity Search
docs = vectorstore.similarity_search(query, k=5)
| File | Purpose |
|---|---|
agent/rag_agent.py | Main RAG agent with vector search |
agent/location_extractor.py | Constrained entity extraction (prevents hallucination) |
agent/location_service.py | Unified POI lookup from Prisma database |
agent/restaurant_service.py | POI-based restaurant recommendations |
agent/neighborhood_guide.py | Curated SF neighborhood data |
agent/routing.py | Turn-by-turn navigation via ORS/OSRM |
To prevent LLM hallucinations, the system uses constrained extraction:
# location_extractor.py
# Only returns POIs that exist in the database
# Normalizes queries for fuzzy matching
# Falls back to vector search for unknown queries
┌─────────────────────────────────────────────────────────────────────┐
│ REAL-TIME VOICE PIPELINE │
│ │
│ Browser (VoiceMicrophone.tsx) │
│ │ │
│ │ WebSocket: wss://touristyvoice.urbantech.dev │
│ │ (or ws://localhost:8000) │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ FASTAPI STS SERVICE (Port 8000) ││
│ │ ││
│ │ ┌──────────────────────────────────────────────────────────┐ ││
│ │ │ 1. SPEECH-TO-TEXT (STT) │ ││
│ │ │ Model: openai/whisper-small │ ││
│ │ │ Library: transformers (WhisperForConditionalGeneration)│ ││
│ │ │ Input: Raw audio bytes │ ││
│ │ │ Output: Transcribed text │ ││
│ │ └──────────────────────────────────────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌──────────────────────────────────────────────────────────┐ ││
│ │ │ 2. RAG AGENT API CALL │ ││
│ │ │ Endpoint: http://localhost:8001/api/chat │ ││
│ │ │ Payload: { "message": transcribed_text } │ ││
│ │ │ Response: AI-generated tour guide response │ ││
│ │ └──────────────────────────────────────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌──────────────────────────────────────────────────────────┐ ││
│ │ │ 3. TEXT-TO-SPEECH (TTS) │ ││
│ │ │ Model: Baghdad99/english_voice_tts │ ││
│ │ │ Library: transformers (VitsModel) │ ││
│ │ │ Input: Agent response text │ ││
│ │ │ Output: WAV audio stream │ ││
│ │ └──────────────────────────────────────────────────────────┘ ││
│ │ │ ││
│ └───────────────────────────┼──────────────────────────────────────┘│
│ ▼ │
│ WebSocket Audio Stream → Browser AudioContext → Speaker │
└─────────────────────────────────────────────────────────────────────┘
# Model: OpenAI Whisper Small
# Library: HuggingFace Transformers
from transformers import WhisperProcessor, WhisperForConditionalGeneration
model_name = "openai/whisper-small"
processor = WhisperProcessor.from_pretrained(model_name)
model = WhisperForConditionalGeneration.from_pretrained(model_name)
# Features:
# - Multi-language support
# - Real-time streaming
# - Voice Activity Detection (webrtcvad)
# Model: Baghdad99/english_voice_tts
# Library: HuggingFace Transformers (VITS)
from transformers import VitsModel, VitsTokenizer
model_name = "Baghdad99/english_voice_tts"
tokenizer = VitsTokenizer.from_pretrained(model_name)
model = VitsModel.from_pretrained(model_name)
# Features:
# - Natural sounding English voice
# - 16kHz WAV output
# - Real-time streaming via WebSocket
| Library | Purpose |
|---|---|
| librosa | Audio analysis and feature extraction |
| soundfile | WAV file I/O |
| pyaudio | Audio input/output |
| webrtcvad-wheels | Voice Activity Detection |
| scipy | Signal processing |
| numpy | Numerical operations |
The system uses DSPy for declarative, self-improving prompts:
# DSPy Configuration
import dspy
# Define structured signatures
class TourGuideSignature(dspy.Signature):
"""Generate helpful tour guide responses"""
query: str = dspy.InputField()
context: str = dspy.InputField()
response: str = dspy.OutputField()
# Create predictor
predictor = dspy.Predict(TourGuideSignature)
# batch_evaluator.py
# Automated prompt optimization pipeline:
# 1. Fetch recent conversations from PostgreSQL
# 2. Sample 10% for evaluation
# 3. Calculate GEPA (Graph-based Efficiency-Preserving Automated) scores
# 4. Compare against baseline
# 5. Auto-update prompts when 5%+ improvement detected
# 6. Track prompt versions in database
| Metric | Description |
|---|---|
| GEPA Score | Graph-based efficiency metric for prompt quality |
| Response Relevance | How well the response matches the query |
| Factual Accuracy | Grounding in retrieved documents |
| User Satisfaction | Implicit feedback from conversation flow |
// Core Models
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
createdAt DateTime @default(now())
superTokensId String? @unique
// Relations
savedRoutes SavedRoute[]
trips Trip[]
following Follow[] @relation("follower")
followers Follow[] @relation("following")
}
model POI {
id String @id @default(uuid())
name String
description String?
latitude Float
longitude Float
address String?
rating Float?
priceLevel Int?
categoryId String?
// Relations
category Category?
routeStops RouteStop[]
likes POILike[]
comments POIComment[]
}
model Category {
id String @id @default(uuid())
name String @unique
icon String?
color String?
pois POI[]
}
model Route {
id String @id @default(uuid())
name String
description String?
geojson Json?
createdAt DateTime @default(now())
stops RouteStop[]
}
model Event {
id String @id @default(uuid())
title String
description String?
startTime DateTime
endTime DateTime?
venue String?
latitude Float?
longitude Float?
price Float?
category String?
sourceUrl String?
imageUrl String?
}
model Message {
id String @id @default(uuid())
conversationId String
role String
content String
gepaScore Float? // For prompt optimization
createdAt DateTime @default(now())
}
model PromptVersion {
id String @id @default(uuid())
name String
content String
version Int
score Float?
isActive Boolean @default(false)
createdAt DateTime @default(now())
}
| Endpoint | Method | Description |
|---|---|---|
/api/auth/[...path] | ALL | SuperTokens auth routes |
/api/user | GET/PUT | User profile |
| Endpoint | Method | Description |
|---|---|---|
/api/chat | POST | LangGraph agent conversation |
/api/conversations/[id] | GET/DELETE | Conversation management |
/api/conversations/[id]/messages | GET | Message history |
| Endpoint | Method | Description |
|---|---|---|
/api/walking-tour/generate | POST | Generate tour with Nemotron |
/api/walking-tour/tts | POST | Text-to-speech for tour |
/api/walking-tour/generate-image | POST | Cover image generation |
| Endpoint | Method | Description |
|---|---|---|
/api/pois | GET | List all POIs |
/api/categories | GET | POI categories |
/api/routes | GET/POST | Route CRUD |
/api/route | GET/POST/DELETE | Single route ops |
| Endpoint | Method | Description |
|---|---|---|
/api/events | GET | Event listing |
/api/events/[id] | GET | Event details |
/api/events/scrape | POST | Trigger event scraping |
/api/events/today | GET | Today's events |
| Endpoint | Method | Description |
|---|---|---|
/api/itineraries | GET/POST | Itinerary CRUD |
/api/itineraries/[id] | GET/PUT/DELETE | Single itinerary |
/api/itineraries/[id]/like | POST | Like itinerary |
/api/itineraries/[id]/comments | GET/POST | Comments |
| Service | Endpoint | Description |
|---|---|---|
| RAG Agent | http://localhost:8001/api/chat | Tour guide RAG |
| TTS | http://localhost:8000/v1/audio/tts | Text-to-speech |
| STT WebSocket | ws://localhost:8000/v1/audio/speech_to_speech/realtime | Real-time voice |
| Metrics | http://localhost:8000/v1/metrics | Token usage stats |
# Clone repository
git clone https://github.com/yourusername/touristy.git
cd touristy
# Install Node.js dependencies
npm install
# Create Python virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
# Install Python dependencies
pip install -r requirements.txt
pip install -r agent/requirements.txt
pip install -r sts/requirements.txt
# Install Ollama (Linux)
curl -fsSL https://ollama.com/install.sh | sh
# Start Ollama service
ollama serve
# Pull Nemotron model
ollama pull nemotron-3-nano-64k
# Verify
ollama list
# Create PostgreSQL database
createdb touristy
# Run migrations
npx prisma migrate dev
# Seed data
npm run db:seed
# Copy environment template
cp .env.example .env.local
# Edit with your values
nano .env.local
# Terminal 1: Next.js
npm run dev
# Terminal 2: RAG Agent
cd agent && python main.py
# Terminal 3: Voice Service
cd sts && python Touristy.py
# Terminal 4: Ollama (if not running as service)
ollama serve
# Database
DATABASE_URL=postgresql://touristy:touristy123@localhost:5432/touristy
# Authentication (SuperTokens)
NEXT_PUBLIC_API_DOMAIN=http://localhost:3000
NEXT_PUBLIC_WEBSITE_DOMAIN=http://localhost:3000
# Ollama (Local LLM)
OLLAMA_URL=http://localhost:11434/api/generate
OLLAMA_MODEL=nemotron-3-nano-64k:latest
# Text-to-Speech Service
LOCAL_TTS_URL=http://localhost:8000/v1/audio/tts
# RAG Agent Service
AGENT_API_URL=http://localhost:8001/api/chat
# Image Generation (Optional - Stable Diffusion)
IMAGE_API_URL=http://localhost:7860/sdapi/v1/txt2img
USE_PLACEHOLDER_IMAGES=true
# NVIDIA API (Optional - for cloud inference)
NVIDIA_API_KEY=your_key_here
# Offline Mode (disable external network)
OFFLINE_MODE=false
touristy/
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── explore/ # Main exploration hub
│ │ ├── events/ # Event discovery
│ │ ├── itinerary/ # Itinerary viewing/editing
│ │ ├── landmarks/ # SF landmarks browser
│ │ └── api/ # API routes (42+ endpoints)
│ │ ├── chat/ # LangGraph agent
│ │ ├── walking-tour/ # Tour generation
│ │ ├── events/ # Event CRUD
│ │ ├── itineraries/ # Itinerary CRUD
│ │ └── ...
│ ├── components/ # React components
│ │ ├── MapView.tsx # Interactive Leaflet map
│ │ ├── VoiceMicrophone.tsx # Voice interaction
│ │ ├── WalkingTourPresentation.tsx
│ │ ├── MapSearchBar.tsx # Search with autocomplete
│ │ └── ChatSidebar.tsx # AI chat interface
│ ├── lib/ # Utilities and helpers
│ └── services/ # External service integrations
│
├── agent/ # Python RAG Agent Service
│ ├── main.py # FastAPI entry point
│ ├── rag_agent.py # RAG agent implementation
│ ├── location_extractor.py # Constrained extraction
│ ├── location_service.py # POI database service
│ ├── restaurant_service.py # Restaurant recommendations
│ ├── neighborhood_guide.py # SF neighborhoods data
│ ├── routing.py # ORS/OSRM routing
│ ├── batch_evaluator.py # DSPy prompt optimization
│ ├── vectorstore/ # FAISS vector database
│ │ ├── index.faiss
│ │ └── index.pkl
│ └── requirements.txt
│
├── sts/ # Speech-to-Speech Service
│ ├── Touristy.py # FastAPI voice server
│ ├── generated_audio/ # Audio cache
│ └── requirements.txt
│
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/ # Migration history
│
├── scripts/
│ ├── run-agent.sh # Start agent service
│ ├── setup-agent.sh # Setup agent environment
│ └── scrape-events.sh # Event scraping
│
├── public/ # Static assets
├── ecosystem.config.js # PM2 configuration
├── Caddyfile # Caddy reverse proxy
├── vercel.json # Vercel cron jobs
├── package.json
├── tsconfig.json
└── .env.local # Environment variables
npm run dev # Start development server (port 3000)
npm run build # Build for production
npm run start # Run production build
npm run lint # Run ESLint
npm run db:seed # Seed database with POIs
# RAG Agent
cd agent
python main.py # Start on port 8001
# Voice Service
cd sts
python Touristy.py # Start on port 8000
./scripts/run-agent.sh # Start agent with venv
./scripts/setup-agent.sh # Setup Python environment
./scripts/scrape-events.sh # Trigger event scraping
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-website',
script: 'npm',
args: 'start',
cwd: '/home/dell/my-website',
env: {
NODE_ENV: 'production',
PORT: 3000
},
max_memory_restart: '500M'
}]
};
# Caddyfile
touristy.urbantech.dev {
reverse_proxy localhost:3000
}
touristyvoice.urbantech.dev {
reverse_proxy localhost:8000
}
{
"crons": [
{
"path": "/api/events/scrape",
"schedule": "0 6 * * *"
}
]
}
| Service | Purpose | Local/Remote |
|---|---|---|
| Ollama | LLM inference | Local |
| FAISS | Vector similarity search | Local |
| Whisper | Speech-to-text | Local |
| VITS TTS | Text-to-speech | Local |
| Nominatim | Geocoding | Remote (OSM) |
| OpenRouteService | Routing | Remote (or self-hosted) |
| OSRM | Routing fallback | Remote (or self-hosted) |
| SuperTokens | Auth sessions | Remote (free tier) |
~2GB - nemotron-3-nano-64k (Ollama)
~500MB - whisper-small (HuggingFace)
~200MB - VITS TTS model (HuggingFace)
~100MB - Sentence embeddings (HuggingFace)
This project demonstrates a fully local AI-powered development workflow where the entire codebase was written using local LLMs without any cloud API dependencies.
┌─────────────────────────────────────────────────────────────────┐
│ LOCAL AI DEVELOPMENT STACK │
│ │
│ Developer │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ OPENCODE │ │
│ │ AI-Powered Coding Assistant │ │
│ │ - Code generation & completion │ │
│ │ - Debugging & error fixing │ │
│ │ - Refactoring suggestions │ │
│ │ - Documentation generation │ │
│ │ - Architecture planning │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ OLLAMA │ │
│ │ Local LLM Runtime (http://localhost:11434) │ │
│ │ - Model management │ │
│ │ - GPU acceleration (CUDA/ROCm) │ │
│ │ - Context window management │ │
│ │ - Streaming responses │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ NVIDIA NEMOTRON 30B │ │
│ │ Large Language Model for Code Generation │ │
│ │ - 30 billion parameters │ │
│ │ - Optimized for code understanding │ │
│ │ - Multi-language support (TS, Python, SQL, etc.) │ │
│ │ - Long context for full-file analysis │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| Model | Size | Purpose |
|---|---|---|
| nemotron:30b | ~18GB | Primary code generation, complex logic |
| nemotron:70b | ~40GB | Architecture decisions, large refactors |
| nemotron-3-nano-64k | ~2GB | Quick edits, documentation, simple fixes |
# ~/.config/opencode/config.yaml
provider: ollama
model: nemotron:30b
endpoint: http://localhost:11434
# Ollama-specific settings
options:
num_ctx: 32768 # Context window
num_gpu: 99 # GPU layers (all)
temperature: 0.1 # Low for consistent code
top_p: 0.9
repeat_penalty: 1.1
# 1. Start Ollama with Nemotron
ollama serve
# 2. Verify model is loaded
ollama list
# NAME SIZE
# nemotron:30b 18GB
# 3. Launch OpenCode in project directory
cd /path/to/touristy
opencode .
# 4. OpenCode connects to local Ollama
# All code generation happens locally
# No API keys needed, no data sent to cloud
The following components were entirely generated using OpenCode + Nemotron 30B:
Frontend (Next.js/React)
Backend (Next.js API Routes)
Python AI Services
Database
DevOps
| Benefit | Description |
|---|---|
| Zero API Costs | No OpenAI/Anthropic/Google API fees |
| Complete Privacy | Code never leaves your machine |
| No Rate Limits | Generate as much code as needed |
| Offline Development | Work anywhere without internet |
| Customizable | Fine-tune models for your codebase |
| Fast Iteration | Low latency local inference |
| Reproducible | Same model version, same results |
Development Machine:
- GPU: NVIDIA RTX 4090 (24GB VRAM)
- CPU: AMD Ryzen 9 7950X
- RAM: 64GB DDR5
- Storage: 2TB NVMe SSD
Inference Performance:
- nemotron:30b: ~30 tokens/sec
- nemotron-3-nano-64k: ~100 tokens/sec
# 1. Install Ollama
curl -fsSL https://ollama.com/install.sh | sh
# 2. Pull Nemotron models
ollama pull nemotron:30b # Primary development
ollama pull nemotron-3-nano-64k # Quick tasks
# 3. Install OpenCode
# Follow instructions at: https://github.com/opencode-ai/opencode
# 4. Configure OpenCode for Ollama
mkdir -p ~/.config/opencode
cat > ~/.config/opencode/config.yaml << 'EOF'
provider: ollama
model: nemotron:30b
endpoint: http://localhost:11434
options:
num_ctx: 32768
temperature: 0.1
EOF
# 5. Start coding!
opencode /path/to/your/project
MIT
npm run lintTypeScript
63.6%
Python
35.6%