Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Weather Agent

A small terminal weather assistant. It talks to a language model through litellmOpenRouter, and gets its weather tools from an external MCP server (weather-mcp, backed by free public data sources — NOAA, Open-Meteo, USGS — global, no weather API key).

The chat interface shows each step the agent takes — every tool call, its arguments, the raw tool result, and the final answer — so you can watch it work. It's a Textual app, so the noisy panels are collapsed by default and expand when you click them.

How it works

you › what's the weather in Paris?
 ▶ reasoning                                             # chain-of-thought, folded
   → call search_location(query='Paris')                 # resolve to lat/long first
 ▶ result · search_location
   → call get_current_conditions(latitude=48.85, longitude=2.35)
 ▶ result · get_current_conditions
 ▼ weather
   It's partly cloudy in Paris, 20°C (68°F)…             # model's final answer
2 tool calls · 412 tokens (350 in / 62 out, 18 reasoning) · $0.0002

Reasoning and tool-result panels start folded so the transcript stays scannable; click a title to unfold one and inspect what the MCP server actually returned. The final answer starts unfolded. Only one turn runs at a time — the prompt is disabled while the agent works, so steps always appear in order.

After the final answer, a summary line shows the cost of that turn: how many tool calls were made and the token/cost breakdown (prompt vs. completion vs. reasoning tokens), using the provider-reported cost when available.

The agent loop lives in agent.py and is deliberately UI-agnostic: run_turn is an async generator that yields a typed event per step, and the UI (cli.py) renders each one as a widget. Adding the Textual front-end needed no changes to agent.py at all. Tool definitions are discovered at runtime from the MCP server (litellm.experimental_mcp_client.load_mcp_tools), not hand-written.

The default model is a reasoning ("thinking") variant, so its chain-of-thought (reasoning_content) is shown too, in a separate panel from tool calls and the final answer.

The server's tool set is enabled via ENABLED_TOOLS=all,-summary (set in config.py): current conditions, forecast, alerts, air quality, marine, historical, lightning, wildfire, river conditions, imagery, and saved locations. get_weather_summary is deliberately excluded — it's a composite tool that bundles several of the above into one call, which would hide steps from the per-step display. Excluding it forces the agent to make the individual calls. Some sources are region-limited — e.g. NOAA weather alerts cover the US only, so the agent will report when alerts aren't available for a foreign location.

Setup

The weather MCP server runs via npx, so you need Node.js (which provides npx) on your PATH. The first run downloads and caches the server package.

Install Python dependencies:

uv sync

Set your OpenRouter API key:

export OPENROUTER_API_KEY=sk-or-...

Usage

Start the chat:

uv run weather-agent

Pick a different model (any OpenRouter model id that supports tool calling; pick a "thinking"/reasoning variant to see its chain-of-thought too):

uv run weather-agent --model openrouter/meta-llama/llama-3.3-70b-instruct

or set a default via the environment:

export WEATHER_AGENT_MODEL=openrouter/qwen/qwen3-235b-a22b-thinking-2507

Press Ctrl-C to quit.

Configuration

Variable Default Purpose
OPENROUTER_API_KEY (required) OpenRouter credential
WEATHER_AGENT_MODEL openrouter/qwen/qwen3-235b-a22b-thinking-2507 Default model id

The default is an affordable open-weights reasoning model with reliable tool calling.

Observability

Tracing to Langfuse and/or Arize is opt-in and turns on automatically once the relevant env vars are set — each backend is independent, so you can enable any combination.

Arize AX (space-based) and Arize Phoenix are separate products with separate litellm callbacks; use whichever env vars your Arize dashboard gave you.

Install the extra dependencies first:

uv sync --extra observability

Then set the env vars for whichever backend(s) you want:

Backend Required env vars Optional
Langfuse LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY LANGFUSE_HOST
Arize AX ARIZE_SPACE_ID, ARIZE_API_KEY ARIZE_PROJECT_NAME
Arize Phoenix PHOENIX_API_KEY, PHOENIX_COLLECTOR_ENDPOINT PHOENIX_PROJECT_NAME

The header lines at the top of the transcript show which backend(s) are active (observability: langfuse, none, etc.). This is wired up via litellm.callbacks (config.ENABLED_CALLBACKS) — no changes to the agent loop were needed, since litellm logs each completion call automatically.

Sessions: each CLI run generates one session_id (shown in the startup transcript header as session: ... when observability is on) that's attached to every completion call in the conversation, so a Langfuse/Arize session groups all of that run's turns. Each turn additionally gets its own trace_id — litellm defaults to using session_id as the trace id too when no trace_id is set, which collapses every turn in the conversation into one trace instead of one trace per turn, so we set it explicitly.

Note: the langfuse package is pinned to <3 — this litellm version's integration targets the v2 SDK API and errors on import with v3+.

Development

uv run pytest              # tests
uv run black .             # format
uv run isort .             # sort imports

About

Simple AI agent for accessing weather information

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages