JJoeven

Projects/Weather Tool Agent/Part 1

Overview and Architecture

Define the weather agent as a loop: user goal, three tools, JSON actions, stop conditions, and a fake model you can later swap for an API.

A weather tool-agent is the smallest system that still looks like production: a user goal, a tiny action space, a JSON protocol, and a loop that can fail. You will not call a live meteorological API in this project. You will simulate geocoding and forecasts with dictionaries so the lesson is the control plane, not HTTP.

This is the right first project because chatbots hide the hard parts. The moment you add two tools, you must answer: who chooses the next call, how you parse the model's output, what happens when the city does not exist, and when you stop. Those questions are the industry. A weather bot is small enough to hold in your head and large enough to need retries, schemas, and tests.

The product

A user types a goal such as What is the weather in Paris?. The agent may not invent a temperature from training data. It must geocode the place to coordinates, fetch weather for those coordinates, then finish with a one-sentence report. If geocoding fails, it retries with a cleaner city name or it finishes with a refusal. That is more honest than hallucinating 22°C.

PieceIn this projectNot in this project
GoalA string the user would typeA chat UI
EnvironmentDicts of cities and forecastsReal HTTP, API keys, GPS
Toolsgeocode, weather, finishBrowser, SQL, shell
PolicyA fake model (script / rules), later an LLMFine-tuning
Stopfinish, max steps, parse budgetKubernetes

The four boxes on the whiteboard

Draw this before you write a line of Python. If a teammate cannot point at each box, you are building a demo, not an agent.

  1. Goal buffer — the original user string. You never overwrite it. Later parts will parse a city name out of it, but the raw goal stays in the transcript.
  2. Tool registry — a dict from name to callable. The model is not allowed to invent a fourth tool. Unknown names are errors, not "creative autonomy".
  3. JSON action protocol — every model turn must be an object {"tool": name, "args": {...}}. Free prose is a parse error. Parse errors consume a retry, not a tool call.
  4. Loop — assemble transcript → ask policy → parse → execute or retry → append observation → check stop.

That is the same loop you saw in Getting Started. This project makes it testable.

Why JSON actions, not "just ask the model"

Natural language is a terrible wire format. "Call weather for Paris" might mean the city, the hotel, or the perfume. JSON with a schema is a contract: the tool name is a key in your registry; arguments are types you can validate. When the contract breaks, you retry or you stop. You do not eval a sentence.

In production the model still writes JSON (or a vendor's tool-call object, which is JSON underneath). Your loop does not care whether the bytes came from GPT, Claude, or a 20-line fake. That swap is the whole point of part 3.

Stop conditions (write them first)

An agent that cannot stop is a denial-of-service against your wallet. Before the loop, write predicates:

  • Success: finish was called with a non-empty answer after a successful weather observation in the transcript.
  • Refusal: finish was called with an answer that starts with cannot: (unknown city, missing forecast).
  • Budget: more than max_steps policy calls, or more than max_parse_retries consecutive JSON failures.
  • Protocol error: the model named a tool that does not exist after one retry.

You will encode these as functions in part 4. For now, treat them as product requirements.

Fake model vs real LLM

Until part 3, the "model" can be a function that inspects the transcript and returns a dict. That is not cheating. The agent is the loop. Intelligence is a plug. If you cannot ship the loop with a fake model, you cannot ship it with a real one — you will just spend money while you debug JSON.

Live Pythonpython
Output
Run to execute this in your browser. Nothing is sent to a server.

Data flow for one successful run

Walk this on paper. Paris is in the catalog. The fake model has not seen the forecast yet.

  1. User goal lands in the transcript.
  2. Policy emits {"tool": "geocode", "args": {"city": "Paris"}}.
  3. Tool returns {"lat": 48.86, "lon": 2.35}.
  4. Policy emits {"tool": "weather", "args": {"lat": 48.86, "lon": 2.35}}.
  5. Tool returns {"temp_c": 18, "conditions": "cloudy"}.
  6. Policy emits {"tool": "finish", "args": {"answer": "Paris is 18°C and cloudy."}}.
  7. Loop stops. The answer is the product.

If step 2 returns {"error": "unknown_city"}, step 6 should be a refusal, not a guessed forecast. That branch is as important as the happy path. You will test both.

Failure modes this architecture must survive

FailureWhat you will do
Model writes markdown around JSONRetry with a "JSON only" reminder in the transcript
City not in the catalogfinish with cannot: unknown city
Weather dict missing that lat/lonfinish with cannot: no forecast
Model calls weather before geocodeTool can reject missing coords; policy should geocode first
Model calls explode_serverRegistry miss → error observation, then stop if repeated
Loop never calls finishmax_steps returns a budget error to the user
Tip:If you cannot name the failure, you cannot write the test. List failures before tools.

What you will build across the five parts

Part 2 registers simulated APIs as tools with stable error shapes. Part 3 implements the JSON loop and retries. Part 4 puts a tiny test runner around tools, parser, and goal predicates. Part 5 hardens: unknown tools, parse budgets, argument validation, and a rate-limit stub.

You will keep stdlib only. Cities are a dict. Forecasts are a dict keyed by rounded coordinates. Files are dicts. Search is not needed. That is enough to learn the shape used by every vendor SDK.

Exercise

On paper, write the transcript for the goal Weather in Lyon? assuming Lyon is in the catalog, and a second transcript assuming it is not. Each turn is one JSON action plus one observation. Bring both to part 2.

Check your understanding

What is the weather agent's policy in this project?