Vineet Ganti
Building my first AI agent with Strands and Groq
All posts

AI & LLMs/Agentic AI · Part 1 of 8

·18 min read

Building my first AI agent with Strands and Groq

What an agent really is, how the Strands SDK and Groq fit together, and a step-by-step first agent with a tool, a prebuilt calculator and a system prompt.

Agentic AILLMsAI Engineering

Huge shoutout to Scaler Academy for the solid resources that got this learning going. This post follows their guide Your first AI agent.

Agentic AI has been around for more than a couple of years, and I finally wanted to try it, not just as a user but as a builder, to understand it at a deeper technical level. By pure coincidence I came across Scaler's AI Bootcamp, which has a guided path from a first agent to production, and I'll be documenting what I learn along the way.

#What is an agent?

In the world of LLMs, the most basic interaction is a chat. You type a prompt, and the LLM replies based on that prompt. Under the hood, the model predicts the response one token at a time, drawing on what it learned during training and on whatever context you put in the prompt. Depending on the model's size and compute power, the quality of the output can vary a lot.

The limitation is that a chat is a single round trip: one prompt in, one answer out. The model can't go and check something, run code, look up fresh data or fix its own mistake. If the task needs five steps, I am the one doing the stitching, copying the output of step one into the prompt for step two, and so on.

An agent takes over that stitching. It automates the repeated prompting and runs multiple jobs behind the scenes, so the user gets a complete solution and not just an answer that may not solve the actual request. In practice, an agent is a loop: the model decides what to do next, calls a tool (a search, a calculator, an API, a function I wrote), reads the result, and decides again, until it judges that the task is done.

#What is the Strands Agents SDK?

Strands Agents is an open-source Python SDK for building AI agents. It was originally released by AWS. Its main idea is that you let the language model do the planning, instead of writing a rigid workflow yourself. You give the model a prompt and a set of tools. It decides which tool to call, reads the result, and keeps going until the task is done. This cycle is called the agent loop.

Why it suits a first agent:

  • Small API. The core is one class, Agent. You can build a working agent in a handful of lines.
  • Model-agnostic. Strands works with Amazon Bedrock, OpenAI, Anthropic, Ollama and others. Any OpenAI-compatible API also works, and that includes Groq, which this post uses because it has a free tier.
  • Tools are plain Python. Decorate a function with @tool and the agent can call it. Strands also ships a ready-made tools package (strands-agents-tools).
  • Grows with you. The same SDK supports memory, multi-agent patterns, MCP and A2A, and deployment to AWS. You don't need to switch frameworks later.

#The whole agent, in about 10 lines

python
import os  # read environment variables, such as the API key
from strands import Agent  # the class that runs the agent loop
from strands.models.openai import OpenAIModel  # adapter for any OpenAI-compatible API

# The model the agent will use, reached through Groq's OpenAI-compatible endpoint
model = OpenAIModel(
    client_args={
        "api_key": os.environ["GROQ_API_KEY"],         # your Groq key, read from the environment
        "base_url": "https://api.groq.com/openai/v1",  # where the requests go
    },
    model_id="openai/gpt-oss-120b",  # which model to call
)

agent = Agent(model=model)  # wrap the model in an agent and its loop
agent("Hello! Tell me a fun fact about AI agents.")  # send a prompt through the loop

What each part does:

  • OpenAIModel tells Strands which model to talk to. Pointing base_url at Groq is the only change needed to swap providers.
  • Agent(model=model) creates the agent and its loop. With no tools, it behaves like a chat model. Adding tools is what makes it act.
  • agent("...") sends your prompt through the loop and returns the response.

DataRequest or acknowledgment

#What is Groq?

Groq is a company that runs open-source LLMs very fast. Groq doesn't train its own frontier models. It hosts models like OpenAI's gpt-oss, Llama and Qwen on custom chips, and sells access through an API called GroqCloud.

TermWhat it is
LPU (Language Processing Unit)Groq's custom chip, built for inference, meaning running a trained model, not training one. It keeps model weights in on-chip memory, which is why responses come back so quickly. Speed is the selling point.
GroqCloudThe hosted service. You call models over HTTP after creating an API key.
OpenAI-compatible APIGroq's endpoint speaks the same protocol as OpenAI's. That's why the Strands code only needs a different base_url and api_key. It's also why swapping providers later is easy.
Free tierNo credit card is needed, and you are never charged on it. If you go over the limits, you get an HTTP 429 error. Limits apply per organisation and per model.

The "OpenAI-compatible" part is what makes the model swappable. The Strands code stays the same, and only two settings change:

Metadata

Why it fits a first agent:

  • Agents call the model many times per task: plan, call a tool, read the result, repeat.
  • Fast inference makes that loop feel responsive, and the free tier lets us experiment without spending money.
  • The catch is the free tier limits. A looping agent can hit them quickly, so expect a 429 or two while we learn.

#Build it step by step

#Step 1: Install dependencies

Install the packages in a single notebook cell. %pip installs into the notebook's own Python, -q keeps the output quiet, and the backslashes continue the command on the next line. The quotes around each package matter, because < and > would otherwise be read by the shell as redirects.

python
%pip install -q \
  "strands-agents[openai]>=1.60.0,<2.0.0" \
  "strands-agents-tools>=0.8.9,<0.9.0" \
  "langchain>=1.4.0,<2.0.0" \
  "langchain-groq>=1.1.0,<2.0.0" \
  "langgraph>=1.2.0,<2.0.0" \
  "python-dotenv>=1.0.0" \
  "rich<14" \
  "opentelemetry-api<=1.42.1" \
  "opentelemetry-sdk<=1.42.1"
PackageWhat it is for
strands-agents[openai]The Strands SDK, plus the extra that adds the OpenAI-compatible model adapter
strands-agents-toolsReady-made tools, such as a calculator
langchain, langchain-groq, langgraphNot used in this post. They appear later in the series, in How AI agents remember.
python-dotenvLoads your API key from a .env file
richTerminal formatting, used for the coloured tool output; pinned below version 14
opentelemetry-api, opentelemetry-sdkTracing libraries that Strands can use for telemetry; pinned to a tested version

The version pins (>=1.60.0,<2.0.0) mean "this version or newer, but not the next major release", which keeps the code from breaking when a library changes its API.

#Step 2: Choose the model

The next cell picks the Groq model and checks that the API key is in place. Generate a key at Groq keys, then set it as GROQ_API_KEY in your environment or in a .env file (in this folder or the repo root).

python
import os

from dotenv import load_dotenv  # loads KEY=value lines from a .env file

load_dotenv()  # reads GROQ_API_KEY from a .env file, if present

# Which Groq model to use. Falls back to gpt-oss-120b if GROQ_MODEL_ID isn't set.
MODEL_ID = os.getenv("GROQ_MODEL_ID", "openai/gpt-oss-120b")

print(f"Model  : {MODEL_ID}")
# Prints whether the key exists, without ever printing the key itself
print(f"API key: {'set' if os.getenv('GROQ_API_KEY') else 'MISSING - set GROQ_API_KEY'}")

The last line is a handy habit: it tells you whether the key is set without printing the secret into your notebook.

#Step 3: The first agent

Now the real thing: a model, wrapped in an agent, answering a prompt.

python
from strands import Agent
from strands.models.openai import OpenAIModel

model = OpenAIModel(
    client_args={
        "api_key": os.environ["GROQ_API_KEY"],  # raises an error straight away if the key is missing
        "base_url": os.getenv("GROQ_BASE_URL", "https://api.groq.com/openai/v1"),  # or a local OpenAI-compatible server
        "max_retries": 5,  # retry dropped connections (the client default is 2)
    },
    model_id=MODEL_ID,
    # Groq reasoning models stream their thinking by default; hide it so only the answer prints.
    params={"extra_body": {"include_reasoning": False}},
)

# An agent = a model + (optional) tools + (optional) system prompt
agent = Agent(model=model)

response = agent("Explain Amazon Bedrock AgentCore in three sentences.")

This gives us an answer:

text
Amazon Bedrock AgentCore is the underlying runtime that powers **agents**—self‑directed AI workflows you can build on the Bedrock platform. It orchestrates calls to multiple foundation models (e.g., Claude, Titan, Llama 3), manages tool integrations, maintains conversation state, and handles error recovery, all while abstracting the infrastructure so developers focus on logic rather than scaling or networking. By exposing a simple SDK and API, AgentCore lets you define prompts, actions, and branching rules, then executes them reliably at scale within a secure, fully managed AWS environment.

The agent streams its answer to the screen as it generates it. The object it returns, response, holds the full result: the final text, the token usage and the stop reason.

python
print("Stop reason:", response.stop_reason)
print("Tokens used:", response.metrics.accumulated_usage)
text
Stop reason: end_turn
Tokens used: {'inputTokens': 81, 'outputTokens': 145, 'totalTokens': 226}
FieldValueMeaning
stop_reasonend_turnThe model finished its answer normally. It wasn't cut off by a token limit.
inputTokens81Tokens sent to the model: the prompt, plus anything Strands adds
outputTokens145Tokens the model wrote back
totalTokens226The two added together, which is what usage limits and bills count

#Step 4: Give the agent a tool

#What a tool is

  • A tool is an ordinary Python function that we hand to the agent.
  • The model can't run code itself. It can only produce text.
  • When it wants to act, it asks for a tool call, our code runs the function, and the result goes back to the model as new context.
  • The model never sees the function body. It only sees a description of the function.
  • Function name: the tool's name. Pick a clear verb phrase like get_weather.
  • Docstring: the tool's description. This is how the model decides when to use it.
  • Type hints and Args:: the input schema. They tell the model what arguments to pass and what types they are.

Here is an example that fetches the current time:

python
from datetime import datetime   # the current date and time
from zoneinfo import ZoneInfo   # time zones by IANA name, such as "Asia/Kolkata"
from strands import Agent, tool # tool turns a function into something the agent can call


@tool
def current_time(timezone: str = "Asia/Kolkata") -> str:
    """Get the current date and time in a given IANA timezone.

    Args:
        timezone: IANA timezone name, for example "Asia/Kolkata" or "America/New_York".
    """
    return datetime.now(ZoneInfo(timezone)).strftime("%A, %d %B %Y %I:%M %p %Z")


agent = Agent(model=model, tools=[current_time])
agent("What time is it right now in Bangalore and in New York?")

An LLM has no clock. Its knowledge is frozen at training time, so it can't answer this question by itself. A tool fixes that, because it gives the model access to things it can't know or do on its own.

#The function, line by line

  • @tool registers the function with Strands and builds the description the model reads.
  • timezone: str = "Asia/Kolkata": the type hint tells the model to pass a string. The default makes the argument optional, so a call with no argument gives IST.
  • The docstring: the summary line tells the model when to use the tool (it needs the current date or time). The Args: line tells it what to pass. The examples inside it matter most here. They show the model the exact format wanted, an IANA name like America/New_York, and not "NYC" or "EST".
  • The body: ZoneInfo(timezone) loads the zone, datetime.now(...) gets the current time in it, and strftime formats it as text such as Sunday, 25 October 2026 06:30 PM IST. Returning a readable string means the model can quote it directly.

#What happens when you run the agent

python
agent = Agent(model=model, tools=[current_time])
agent("What time is it right now in Bangalore and in New York?")
text
Tool #1: current_time

Tool #2: current_time
- **Bangalore (India – Asia/Kolkata):** Sunday, 25 October 2026 09:16 PM IST
- **New York (USA – America/New_York):** Sunday, 25 October 2026 11:46 AM EDT

The two Tool # lines are log messages: the agent called current_time twice. Behind them, these steps happened:

  1. The prompt and the tool list go to the model. It sees the question and the description of current_time.
  2. The model reasons about it. It decides it needs the clock, and that the question has two places, so it needs two lookups.
  3. The model translates place names. We said "Bangalore" and "New York", but the tool wants IANA names. The model maps them itself, which is why the docstring examples matter:
    • current_time(timezone="Asia/Kolkata")
    • current_time(timezone="America/New_York")
  4. Strands runs the Python. Each call executes the real function and returns a string like Sunday, 25 October 2026 06:30 PM IST.
  5. The results go back to the model. It now has both times and writes the final answer in plain English.

Steps 2 to 5 are the agent loop. The model may make the two calls one after the other or together, and the log doesn't say which. Either way, we wrote none of the control flow. The model chose which tool to call, how many times, and with what arguments.

#Step 5: Use a prebuilt tool

You don't have to write every tool yourself. strands-agents-tools ships ready-made ones, such as a calculator.

python
from strands_tools import calculator  # a ready-made math tool from strands-agents-tools

# Give the agent two tools; it will pick whichever the question needs
agent = Agent(model=model, tools=[calculator, current_time])
agent("What is 18% GST on a laptop that costs Rs 84,999? Give me the final price too.")
text
The GST (18 % of ₹84,999) is **₹15,299.82**.

Adding this to the base price gives a **final price of ₹100,298.82**.

The agent had two tools, and the question only needed the calculator, so it left current_time alone. That is the model choosing a tool from the descriptions, which is the same decision as before, just with a bigger menu. The arithmetic checks out: 84,999 × 0.18 is 15,299.82, and adding it to the price gives 100,298.82.

#Step 6: Shape behaviour with a system prompt

The system prompt sets the agent's role, tone and rules for every turn. It is the standing instruction that sits in front of whatever the user types.

python
agent = Agent(
    model=model,
    # A system prompt applies to every message in the conversation
    system_prompt=(
        "You are a patient tutor for engineers who are new to AI agents. "
        "Explain ideas with one real-world analogy and keep answers under 120 words."
    ),
)
agent("What is the difference between an LLM and an AI agent?")

With that prompt, the agent should answer as a short tutor's explanation with a single analogy, instead of a long generic one. Change the system prompt and the same question gets a different kind of answer.

#Summary

An agent is a loop: the model decides, calls a tool, reads the result and decides again until the task is done. Strands gives you that loop in one Agent class and works with any OpenAI-compatible API, including Groq. A tool is a plain Python function that the decorator, the type hints and the docstring describe to the model. Prebuilt tools save you from writing common ones, and a system prompt shapes the role and tone, as guidance and not a guarantee.

#Concepts at a glance

ConceptIn one lineWhy it matters
AgentA model in a loop that can call toolsIt can act, not just answer
Strands AgentOne class: model, tools, system promptThe loop is built in
GroqFast hosted models behind an OpenAI-compatible APISwap base_url to change provider
ToolA Python function with a decorator, type hints and a docstringThe model reads those, never the body
Prebuilt toolA ready-made tool such as a calculatorNo need to rewrite what exists
System promptStanding instructions for role and toneGuidance only: enforce hard rules in code

#Test yourself

What is the difference between a chat and an agent?

A chat is one prompt in and one answer out. An agent loops: it decides, calls a tool, reads the result and decides again until the task is done.

What tells the model what a tool does?

The function's name, type hints and docstring. The model never sees the body.

How do you point the same code at a different provider?

Change base_url and api_key, as long as the provider is OpenAI-compatible.

Can a system prompt guarantee a rule?

No. It is guidance. When a rule must hold, enforce it in code.

Next: Giving an AI agent tools, where a plain function becomes a tool and we build a tip calculator, a sales assistant and an inventory checker.