{"slug": "touch-grass-natural-language-outdoor-discovery", "title": "Touch Grass: Natural Language Outdoor Discovery", "summary": "A developer built Touch Grass, an open-source AI-powered outdoor discovery app that lets users search for trails, parks, and running clubs using natural language. The application uses an LLM-powered Text-to-SQL agent, routed through LiteLLM with Gemini or open-weight Llama models, to translate queries into SQLite statements, with the generated SQL shown in the interface for debugging. The backend mitigates hallucinated schemas by injecting the SQLite schema and query-generation rules into the agent's system prompt.", "body_md": "*Submission for the [Hacktoberfest Open-Source AI Challenge – Week 1: Touch Grass](https://dev.to/challenges/hacktoberfest-week1-2026-10-05)*\n\nFinding somewhere to go outdoors shouldn't require browsing through multiple websites, filtering endless map results, or scrolling through hiking forums.\n\n**Touch Grass** is an open-source, AI-powered outdoor discovery application that lets users search for trails, parks, and running clubs using natural language.\n\nInstead of navigating multiple filters, users can simply ask:\n\nThe application uses an LLM-powered Text-to-SQL agent to translate these questions into SQLite queries, retrieve matching records, and display the results in a responsive dashboard.\n\nThe idea is simple: spend less time searching for outdoor activities and more time actually doing them.\n\n**1. Natural Language Search**\n\nUsers can enter questions directly into the search bar or choose from predefined example queries to get started.\n\n**2. SQL Query Inspector**\n\nThe application displays the SQL generated by the model, allowing users to see how their natural language request was translated into a database query.\n\n**3. Dynamic Results Table**\n\nResults are displayed in a responsive table with formatting based on the returned data:\n\n**4. Loading and Error States**\n\nThe interface handles loading, empty results, and invalid queries with appropriate feedback.\n\n**User input:**\n\n\"Show me easy trails under 3 miles with shade.\"\n\n**Generated SQL:**\n\n```\nSELECT\n    name,\n    length_miles,\n    has_shade,\n    location\nFROM trails\nWHERE LOWER(difficulty) = 'easy'\n    AND length_miles < 3\n    AND has_shade = 1\nLIMIT 50;\n```\n\n**Example Results:**\n\n| Trail | Distance | Shade | Location | \n|---|---|---|---|\n| Sunset Ridge Loop | 1.8 mi | ✅ Yes | Austin, TX | \n| Lady Bird Lake | 1.3 mi | ✅ Yes | Austin, TX | \n| Piedmont Park Loop | 2.4 mi | ✅ Yes | Atlanta, GA | \n\nThe generated SQL is also visible in the interface, making the application's behavior easier to understand and debug.\n\nThe project is open-source and organized as a full-stack monorepo.\n\n**GitHub Repository:** ([https://github.com/radhikaaa25/touch-grass](https://github.com/radhikaaa25/touch-grass))\n\n| Layer | Technologies | Purpose | \n|---|---|---|\n| Backend | Python 3.11+, FastAPI, Uvicorn | REST API | \n| AI Agent | LiteLLM, Gemini / Open-weight Llama | Natural Language to SQL | \n| Database | SQLite3 | Outdoor activity storage | \n| Frontend | React 19, Vite, Tailwind CSS v4 | User interface | \n| Tooling | Pydantic v2, TypeScript | Data validation and type safety | \n\nThe application follows a straightforward request-response pipeline:\n\n```\n             User\n               |\n               v\n      React Frontend\n               |\n               v\n        FastAPI Backend\n               |\n               v\n       Text-to-SQL Agent\n               |\n               v\n      LiteLLM Model Router\n               |\n               v\n       Generated SQL Query\n               |\n               v\n        Query Validation\n               |\n               v\n         SQLite Database\n               |\n               v\n         JSON Response\n               |\n               v\n       Dynamic Results UI\n```\n\nOne of the main challenges was making sure the language model generated queries using actual database tables and columns rather than inventing its own structure.\n\nTo address this, the backend retrieves the SQLite schema and includes it in the agent's system prompt.\n\nThe model receives both the database structure and a set of query-generation rules.\n\n```\nSYSTEM_PROMPT = f\"\"\"\nYou are a SQL query generator for an outdoor activities SQLite database.\n\nDATABASE SCHEMA:\n{get_schema_string()}\n\nRULES:\n1. Output ONLY a single valid SQLite SELECT query.\n2. Do not include markdown, code fences, or explanations.\n3. Never generate INSERT, UPDATE, DELETE, DROP, ALTER, or CREATE statements.\n4. Use only the tables and columns provided in the schema.\n5. Boolean columns (has_shade, has_restrooms, allows_dogs)\n   use 1 for true and 0 for false.\n6. Use LOWER() for case-insensitive string comparisons.\n7. Always include 'name' and 'location' in the results.\n8. Limit results to a maximum of 50 rows.\n\"\"\"\n```\n\nThis helps constrain the model's output and makes the generated queries more consistent with the available data.\n\nI used `litellm` to keep the application independent of a single LLM provider.\n\nThe backend can be configured to use Google Gemini, local open-weight models through Ollama, or supported cloud-hosted models through providers such as Groq.\n\nThe model can be changed through an environment variable without rewriting the agent logic.\n\n```\nresponse = litellm.completion(\n    model=self.model,\n    messages=[\n        {\n            \"role\": \"system\",\n            \"content\": SYSTEM_PROMPT\n        },\n        {\n            \"role\": \"user\",\n            \"content\": question\n        },\n    ],\n    temperature=0.0,\n    max_tokens=300,\n)\n```\n\nThis also makes it possible to experiment with different models and compare their Text-to-SQL performance.\n\nSince the SQL is generated by a language model, it needs to be checked before execution.\n\nThe backend includes a validation layer that:\n\n`SELECT`.` try/except sqlite3.Error`.\nThese checks provide an initial safeguard against unintended queries. For a production deployment, I would strengthen this further with SQLite read-only connections, single-statement enforcement, and stricter SQL validation rather than relying on prompt instructions or prefix checks alone.\n\nI wanted the interface to feel like an outdoor discovery tool rather than a conventional database search application.\n\nThe frontend uses React, Vite, and Tailwind CSS with an earth-toned visual theme.\n\n**Design choices:**\n\n`#1d3818` to `#8dba7e`), bark browns, and soft sky accents.\nThe frontend does not need to know which database table the user is querying beforehand. It builds the results table from the returned column names and values.\n\nThis allows the same interface to display trails, parks, and running clubs without separate result components for every category.\n\nI wanted Touch Grass to be easy to run, modify, and extend without depending entirely on proprietary services.\n\nThree aspects of open innovation influenced the project.\n\nOutdoor discovery applications can involve location information and personal activity preferences.\n\nBy supporting local models through Ollama, Touch Grass provides an option to process natural-language queries locally instead of sending them to a cloud-hosted LLM.\n\nThis gives developers more control over where their data is processed.\n\nUsing LiteLLM allows developers to experiment with different supported models without changing the application's core architecture.\n\nSomeone running the project locally can choose an open-weight model, while another developer can configure a hosted provider.\n\nThe application is not tied to one model or vendor.\n\nSQLite provides a lightweight starting point for maintaining a curated collection of trails, parks, and running clubs.\n\nThe project can be extended with additional locations, activity categories, and community-contributed datasets.\n\nThe goal is to make outdoor discovery accessible and adaptable to different cities and communities.\n\nI used **Google DeepMind Antigravity** during development to assist with scaffolding, implementation, and debugging.\n\nThe development process involved:\n\nAntigravity was useful for iterating across the backend and frontend while keeping the application architecture consistent.\n\nThe most interesting part of building Touch Grass was connecting natural-language input to structured database operations.\n\nA few key takeaways:\n\n**Schema context matters.** Providing the model with the actual database structure makes Text-to-SQL generation more reliable than relying on generic instructions.\n\n**LLM output still needs validation.** Even with explicit prompting, generated SQL should be treated as untrusted input.\n\n**Model flexibility is useful.** Separating the model provider from the application logic makes experimenting with local and hosted models significantly easier.\n\n**A simple interface can hide a fairly interesting backend.** From the user's perspective, the entire workflow is just asking a question and viewing matching outdoor activities.\n\nSome improvements I'd like to explore:\n\n**Primary Challenge:** Hacktoberfest Week 1 – Touch Grass\n\n**Featured Categories:**\n\nBuilt with Python, React, SQLite, and LiteLLM.\n\n**Less scrolling. More strolling. 🌿**\n\n*Source code: [GitHub – Touch Grass](https://github.com/radhikaaa25/touch-grass)*", "url": "https://wpnews.pro/news/touch-grass-natural-language-outdoor-discovery", "canonical_source": "https://dev.to/radhikaaa25/touch-grass-natural-language-outdoor-discovery-101h", "published_at": "2026-10-11 19:57:21+00:00", "updated_at": "2026-10-11 20:02:06.514204+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "large-language-models", "developer-tools"], "entities": ["Touch Grass", "LiteLLM", "Gemini", "Llama", "FastAPI", "SQLite", "React", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/touch-grass-natural-language-outdoor-discovery", "markdown": "https://wpnews.pro/news/touch-grass-natural-language-outdoor-discovery.md", "text": "https://wpnews.pro/news/touch-grass-natural-language-outdoor-discovery.txt", "jsonld": "https://wpnews.pro/news/touch-grass-natural-language-outdoor-discovery.jsonld"}}