# Touch Grass: Natural Language Outdoor Discovery

> Source: <https://dev.to/radhikaaa25/touch-grass-natural-language-outdoor-discovery-101h>
> Published: 2026-10-11 19:57:21+00:00

*Submission for the [Hacktoberfest Open-Source AI Challenge – Week 1: Touch Grass](https://dev.to/challenges/hacktoberfest-week1-2026-10-05)*

Finding somewhere to go outdoors shouldn't require browsing through multiple websites, filtering endless map results, or scrolling through hiking forums.

**Touch Grass** is an open-source, AI-powered outdoor discovery application that lets users search for trails, parks, and running clubs using natural language.

Instead of navigating multiple filters, users can simply ask:

The 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.

The idea is simple: spend less time searching for outdoor activities and more time actually doing them.

**1. Natural Language Search**

Users can enter questions directly into the search bar or choose from predefined example queries to get started.

**2. SQL Query Inspector**

The application displays the SQL generated by the model, allowing users to see how their natural language request was translated into a database query.

**3. Dynamic Results Table**

Results are displayed in a responsive table with formatting based on the returned data:

**4. Loading and Error States**

The interface handles loading, empty results, and invalid queries with appropriate feedback.

**User input:**

"Show me easy trails under 3 miles with shade."

**Generated SQL:**

```
SELECT
    name,
    length_miles,
    has_shade,
    location
FROM trails
WHERE LOWER(difficulty) = 'easy'
    AND length_miles < 3
    AND has_shade = 1
LIMIT 50;
```

**Example Results:**

| Trail | Distance | Shade | Location | 
|---|---|---|---|
| Sunset Ridge Loop | 1.8 mi | ✅ Yes | Austin, TX | 
| Lady Bird Lake | 1.3 mi | ✅ Yes | Austin, TX | 
| Piedmont Park Loop | 2.4 mi | ✅ Yes | Atlanta, GA | 

The generated SQL is also visible in the interface, making the application's behavior easier to understand and debug.

The project is open-source and organized as a full-stack monorepo.

**GitHub Repository:** ([https://github.com/radhikaaa25/touch-grass](https://github.com/radhikaaa25/touch-grass))

| Layer | Technologies | Purpose | 
|---|---|---|
| Backend | Python 3.11+, FastAPI, Uvicorn | REST API | 
| AI Agent | LiteLLM, Gemini / Open-weight Llama | Natural Language to SQL | 
| Database | SQLite3 | Outdoor activity storage | 
| Frontend | React 19, Vite, Tailwind CSS v4 | User interface | 
| Tooling | Pydantic v2, TypeScript | Data validation and type safety | 

The application follows a straightforward request-response pipeline:

```
             User
               |
               v
      React Frontend
               |
               v
        FastAPI Backend
               |
               v
       Text-to-SQL Agent
               |
               v
      LiteLLM Model Router
               |
               v
       Generated SQL Query
               |
               v
        Query Validation
               |
               v
         SQLite Database
               |
               v
         JSON Response
               |
               v
       Dynamic Results UI
```

One of the main challenges was making sure the language model generated queries using actual database tables and columns rather than inventing its own structure.

To address this, the backend retrieves the SQLite schema and includes it in the agent's system prompt.

The model receives both the database structure and a set of query-generation rules.

```
SYSTEM_PROMPT = f"""
You are a SQL query generator for an outdoor activities SQLite database.

DATABASE SCHEMA:
{get_schema_string()}

RULES:
1. Output ONLY a single valid SQLite SELECT query.
2. Do not include markdown, code fences, or explanations.
3. Never generate INSERT, UPDATE, DELETE, DROP, ALTER, or CREATE statements.
4. Use only the tables and columns provided in the schema.
5. Boolean columns (has_shade, has_restrooms, allows_dogs)
   use 1 for true and 0 for false.
6. Use LOWER() for case-insensitive string comparisons.
7. Always include 'name' and 'location' in the results.
8. Limit results to a maximum of 50 rows.
"""
```

This helps constrain the model's output and makes the generated queries more consistent with the available data.

I used `litellm` to keep the application independent of a single LLM provider.

The backend can be configured to use Google Gemini, local open-weight models through Ollama, or supported cloud-hosted models through providers such as Groq.

The model can be changed through an environment variable without rewriting the agent logic.

```
response = litellm.completion(
    model=self.model,
    messages=[
        {
            "role": "system",
            "content": SYSTEM_PROMPT
        },
        {
            "role": "user",
            "content": question
        },
    ],
    temperature=0.0,
    max_tokens=300,
)
```

This also makes it possible to experiment with different models and compare their Text-to-SQL performance.

Since the SQL is generated by a language model, it needs to be checked before execution.

The backend includes a validation layer that:

`SELECT`.` try/except sqlite3.Error`.
These 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.

I wanted the interface to feel like an outdoor discovery tool rather than a conventional database search application.

The frontend uses React, Vite, and Tailwind CSS with an earth-toned visual theme.

**Design choices:**

`#1d3818` to `#8dba7e`), bark browns, and soft sky accents.
The 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.

This allows the same interface to display trails, parks, and running clubs without separate result components for every category.

I wanted Touch Grass to be easy to run, modify, and extend without depending entirely on proprietary services.

Three aspects of open innovation influenced the project.

Outdoor discovery applications can involve location information and personal activity preferences.

By 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.

This gives developers more control over where their data is processed.

Using LiteLLM allows developers to experiment with different supported models without changing the application's core architecture.

Someone running the project locally can choose an open-weight model, while another developer can configure a hosted provider.

The application is not tied to one model or vendor.

SQLite provides a lightweight starting point for maintaining a curated collection of trails, parks, and running clubs.

The project can be extended with additional locations, activity categories, and community-contributed datasets.

The goal is to make outdoor discovery accessible and adaptable to different cities and communities.

I used **Google DeepMind Antigravity** during development to assist with scaffolding, implementation, and debugging.

The development process involved:

Antigravity was useful for iterating across the backend and frontend while keeping the application architecture consistent.

The most interesting part of building Touch Grass was connecting natural-language input to structured database operations.

A few key takeaways:

**Schema context matters.** Providing the model with the actual database structure makes Text-to-SQL generation more reliable than relying on generic instructions.

**LLM output still needs validation.** Even with explicit prompting, generated SQL should be treated as untrusted input.

**Model flexibility is useful.** Separating the model provider from the application logic makes experimenting with local and hosted models significantly easier.

**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.

Some improvements I'd like to explore:

**Primary Challenge:** Hacktoberfest Week 1 – Touch Grass

**Featured Categories:**

Built with Python, React, SQLite, and LiteLLM.

**Less scrolling. More strolling. 🌿**

*Source code: [GitHub – Touch Grass](https://github.com/radhikaaa25/touch-grass)*
