A local, zero-cost hiking day planner built with LangChain 1.x create_agent.
Give it a location and your preferences. It searches the web for trails and current weather, suggests options, and adapts as your constraints change across the conversation. You can also drop in a photo of your gear and it will identify the items and offer general readiness tips.
The planning agent runs fully local at no cost: trails, weather, and memory all work offline. The
optional gear-photo feature is the one part that uses a free cloud vision model. Because every model
is created through a provider-agnostic factory, swapping any of them is a one-line change in .env
with no code touched.
![]() |
![]() |
| Plan a hike in one shot, grounded in live web search. | Interactive chat: follow-ups remember the conversation. |
![]() |
![]() |
--json returns a validated, typed plan. |
--gear identifies gear in a photo and gives tips. |
(Recorded against a cloud model for a snappy demo; the same commands run locally on Ollama.)
One small agent, assembled from a few building blocks:
- a provider-agnostic model (
init_chat_model) - a hiking-guide system prompt
- a web search tool (Tavily) for live trails and weather
- short-term memory (a LangGraph checkpointer + thread id) so follow-ups make sense
- image input for the gear photo, handled by a separate vision model
The chat is wrapped by a single create_agent, which runs the tool-calling loop automatically.
The planning agent runs entirely on a local model. Reading an image is the only step that needs a
vision model, and capable vision models are large and memory-hungry to run locally alongside
everything else, so that single step uses a small free cloud vision model for reliability while the
rest stays local and free. Swapping either side is just a .env edit, thanks to the
provider-agnostic factory.
--json returns a validated schema via response_format. Hosted models emit strict schemas
reliably, so one call is enough; a small local model usually complies but occasionally finishes
without the structured result, so the JSON mode retries a few times before giving up (and never
crashes on a miss). Pointing LLM_* at a cloud model removes the need for the retry.
Trailhead is a handful of small modules instead of a pile of plumbing, because LangChain 1.x does the heavy lifting:
create_agentruns the tool-calling loop for you (decide -> call the tool -> feed the result back -> answer). There is no hand-written loop translating tool calls and re-invoking the model.init_chat_modelis provider-agnostic. You choose a model with a string plus a base URL, so switching between local and cloud is a.envedit. There is no separate provider-routing layer to build and maintain.- The building blocks are explicit: one agent, one tool, one prompt, one checkpointer. The wiring is a few readable lines, not a framework of scripts.
Prerequisites: Python 3.12+, uv, and Ollama: Setup the environment here with a tool-capable model pulled:
ollama pull qwen2.5:7bWeb search needs a free Tavily key. The optional gear-photo feature uses a local vision model served over an OpenAI-compatible endpoint (for example LM Studio running a vision model).
uv sync
cp .env.example .env # set TAVILY_API_KEY (and a Gemini key for the gear photo)
uv run trailhead "easy day hikes near Seattle today" # plan a hike
uv run trailhead --gear samples/gear.jpg # identify gear in a photoAll settings live in .env (copy from .env.example):
| Variable | Purpose | Default |
|---|---|---|
LLM_MODEL |
agent model tag | qwen2.5:7b |
LLM_MODEL_PROVIDER |
dialect the endpoint speaks | openai |
LLM_BASE_URL |
model server URL | http://localhost:11434/v1 |
LLM_API_KEY |
ignored locally; any non-empty value | ollama |
TAVILY_API_KEY |
web search key (free tier) | (yours) |
.env.example also lists free OpenAI-compatible cloud backends you can point LLM_* at for faster
responses.
The same agent (web search + planning) run across a few providers, to show the trade-off behind the "build local, run on a fast free tier when you want speed" design. Small samples on a single machine, so treat these as indicative, not a rigorous benchmark.
| Provider (model) | Median latency/turn | Tool-calls |
|---|---|---|
Local — Ollama qwen2.5:7b (CPU) |
~75 s | 5/5 |
| Gemini 2.5 Flash | ~6 s | 3/3 |
GitHub Models gpt-4o-mini |
~11 s | 2/2 |
Groq llama-3.3-70b-versatile |
~4 s (fastest) | 1/3 |
The local model is free and fully offline but roughly 10x slower on CPU. Groq had the lowest raw
latency but its strict tool-call validation rejected 2 of 3 runs; Gemini and gpt-4o-mini were both
fast and reliable. Method: N=5 local and N=2-3 per cloud provider, one warm-up call excluded, medians
reported; switching providers is a .env change only.
Inspired by Introduction to LangChain from LangChain Academy and the official LangChain documentation, which are where the concepts behind this project came from. Everything here was then built entirely from scratch: the agent, its tools and its prompts are all original work.
The sample gear photo (samples/gear.jpg) is "A backpack with trekking poles and shoes" by
SGrabarczuk (WMF), licensed under CC BY-SA 4.0,
from Wikimedia Commons.
See samples/NOTICE.md.
The code is released under the MIT License. The bundled sample image keeps its own license (see Credits).



