// projects / building-a-multi-provider-ai-weather-agent-in-node-js

Building a Multi-Provider AI Weather Agent in Node.js

How I built a minimal AI agent with a tool-calling loop that runs on Anthropic Claude, OpenAI, Gemini, DeepSeek, and Kimi — all sharing one weather tool, with provider switching from the CLI.

ai-agentstool-callingnodejsjavascriptllmanthropicopenaigeminideepseekfunction-calling

Building a Multi-Provider AI Weather Agent in Node.js

Most AI agent tutorials lock you into a single provider. I wanted to understand what an agent's tool-calling loop actually looks like under the hood — and how much of it changes (or doesn't) when you swap the model behind it. So I built weather-ai-agent: a small Node.js project where the same weather-lookup agent runs on five different LLM providers.

Repo: github.com/marsab14/weather-ai-agent

What it does

You ask a natural-language question like "What's the weather like in Pune right now?" and the agent:

  1. Sends your message to the LLM along with a get_weather tool definition

  2. The model decides it needs the tool and returns a structured tool call

  3. The agent executes the tool locally and feeds the result back to the model

  4. The model composes a final natural-language answer

The loop keeps running until the model stops asking for tools — that's the whole "agent" pattern in its simplest form.

Five providers, one agent

The project supports Anthropic (Claude), OpenAI (GPT), Google Gemini, DeepSeek, and Kimi (Moonshot). You pick the provider with a CLI flag or an environment variable:

bash

node index.js                        # defaults to Anthropic
node index.js --provider openai
node index.js --provider deepseek
node index.js --provider kimi
AI_PROVIDER=gemini node index.js

A nice trick here: DeepSeek and Kimi both expose OpenAI-compatible APIs, so they reuse the OpenAI adapter with just a different baseURL and API key. That means five providers are covered by only three adapters.

How it's structured

The codebase is deliberately small and readable:

  • index.js — entry point; picks a provider and runs the agent with a sample question

  • cli.js — parses the --provider flag / AI_PROVIDER env var

  • providers.js — one config object per provider (model name, SDK client factory, API kind)

  • tools.js — a single source of truth for the get_weather tool, exported in each provider's native schema format (Anthropic input_schema, OpenAI function, Gemini functionDeclarations)

  • agent.js — a thin router that dispatches to the right adapter

  • adapters/ — the actual tool-calling loops for Anthropic, OpenAI-compatible, and Gemini APIs

The most interesting file is tools.js. Every provider wants tool definitions in a slightly different shape, but the underlying schema — name, description, JSON Schema parameters — is identical. Defining it once and re-exporting it per provider keeps things DRY and makes the API differences obvious at a glance.

The tool-calling loop

Here's the core of the Anthropic adapter — the other adapters follow the same rhythm with provider-specific message shapes:

javascript

while (true) {
  const response = await client.messages.create({
    model: config.model,
    max_tokens: 1024,
    system: SYSTEM_PROMPT,
    tools: ANTHROPIC_TOOLS,
    messages,
  });

  messages.push({ role: "assistant", content: response.content });

  if (response.stop_reason !== "tool_use") {
    return response.content
      .filter((b) => b.type === "text")
      .map((b) => b.text)
      .join("");
  }

  // Execute each requested tool and send the results back
  const toolResults = [];
  for (const block of response.content) {
    if (block.type === "tool_use") {
      toolResults.push({
        type: "tool_result",
        tool_use_id: block.id,
        content: runTool(block.name, block.input),
      });
    }
  }
  messages.push({ role: "user", content: toolResults });
}

That's it. No framework, no LangChain — just the raw request/response cycle, which makes it a great way to actually understand what agent frameworks abstract away.

A note on the weather tool

The get_weather implementation is currently a mock — it returns a fixed sunny-and-31°C payload for any city. That's intentional for a learning project: the focus is the agent loop and multi-provider plumbing, not the weather API. Swapping in a real API like Open-Meteo or OpenWeatherMap is a one-function change, and it's the obvious next step.

Getting started

bash

git clone https://github.com/marsab14/weather-ai-agent
cd weather-ai-agent
npm install
cp .env.example .env   # add your API keys
node index.js --provider anthropic

You only need the API key for whichever provider you want to run.

Takeaways

  • The agent loop is the same everywhere: call model → check for tool requests → run tools → feed results back → repeat

  • Provider differences live almost entirely in message/tool schemas, not in the logic

  • OpenAI-compatible APIs (DeepSeek, Kimi) make multi-provider support surprisingly cheap

  • Building this without a framework is the fastest way to demystify how agents work

Check out the full source on GitHub