Your Agentic AI - Part 1: Getting Started

Table of Contents
Agents are revolutionizing automation and AI workflows. In this series, we will explore how to build your own production-ready Agentic AI systems.
What is an Agent?
An agent is not just a script, a function, or a wrapper around an API. An agent is a self-directed, goal-oriented program that can perceive its environment, make decisions, and take actions — often autonomously — based on those perceptions and goals.
In practical terms, an agent:
- Receives input from a user, another system, or the environment.
- Decides what to do using logic, rules, or even machine learning.
- Acts by calling APIs, manipulating data, or triggering workflows.
- Observes the result and can adapt its future actions accordingly.
What makes agents different from traditional automation?
- Autonomy: Agents can operate without constant human oversight. They can make decisions, handle errors, and adapt to changing conditions.
- Goal-driven: Instead of following a fixed script, agents pursue objectives. They can choose different strategies or tools to achieve their goals.
- Context-awareness: Agents can maintain state, remember past actions, and use context to inform decisions.
Why does this matter? Because the world is messy. APIs fail, data is incomplete, and requirements change. Agents are designed to handle this messiness by being robust, adaptive, and persistent in pursuing their goals.
In the context of AI, agents are the bridge between raw model capabilities (like LLMs) and real-world outcomes. They turn predictions into actions, automate complex workflows, and enable systems that can reason, learn, and improve over time.
Why Agents? Why Now
Most automation today is fragile. You write a script: it breaks when the API changes. You build a workflow: it fails on the first unexpected input. You glue together SaaS tools: you’re one schema change away from a 3am incident call or an unhappy customer.
The world is moving too fast for brittle, static automation. Data is messy, APIs are unreliable, and business requirements change weekly. Agentic architectures are exploding right now because they provide:
- Resilience: Agents can act, recover from errors, retry, and adapt their strategy. They don’t just crash and burn on the first failure.
- Autonomy: Agents can make decisions on the fly, handle ambiguity, and operate with minimal human intervention.
- Composability: Agents can use tools, call other agents, and build complex behaviors from simple blocks.
- Scalability: You can deploy hundreds of agents — each pursuing goals, learning, and improving — without hand-holding.
The rise of LLMs and advanced AI models has made it possible to build agents that are not just rule-based, but can reason, plan, and act in open-ended environments. But raw model power isn’t enough — you need structure, validation, and guardrails. That’s where Pydantic AI comes in: it gives you the rigor of strong data models, the flexibility of Python, and the power to build agents that don’t just hallucinate — they deliver.
Defining Your First Agent
Here’s an example for defining a simple agent using Pydantic AI. First, make sure you have the right dependencies:
uv add pydantic pydantic-ai
Then define a basic agent that takes a user’s name and returns a greeting, using a system prompt to drive the agent’s behavior and Gemini 2.5 Flash as the LLM backend.
import os
from pydantic import BaseModel, Field, ValidationError
from pydantic_ai.agent import Agent
from datetime import datetime
system_prompt = """
You are a friendly assistant. When given a user's name, respond with a personalized greeting.
"""
agent = Agent(
system_prompt=system_prompt,
model="gemini-2.5-flash", # Specify the Gemini 2.5 Flash model
api_key=os.environ["GEMINI_API_KEY"] # Set your Gemini API key in the environment
)
if __name__ == "__main__":
user_input = {"name": "Conor"}
result = agent.run(user_input)
What’s happening here?
- The agent is defined with a system prompt, and is configured to use Gemini 2.5 Flash.
- The
api_key
parameter provides authentication for the Gemini API from environment variables.
Typing Inputs with Pydantic Field
Pydantic’s Field
is more than just a type annotation — it’s a way to make an agent’s interface self-documenting and robust. By using Field
, you ensure that your code not only validates and constrains the data, but also provides clear descriptions and examples that help both you (as a developer) and the agent (as an LLM) understand what each input means and what kind of output is expected. This is critical for both catching errors early and for giving the LLM the context it needs to generate accurate, relevant responses.
Here’s how you can update your code to enforce type safety:
from pydantic import BaseModel, Field
class GreetInput(BaseModel):
name: str = Field(..., description="The user's name to greet", min_length=1, max_length=50, example="Conor")
feedback: str = Field("", description="Optional feedback for the agent to self-correct", max_length=200)
class GreetOutput(BaseModel):
message: str
agent = Agent(
input_model=GreetInput,
output_model=GreetOutput,
system_prompt=system_prompt
)
What’s happening here?
description
helps both humans and LLMs understand what each field is for (this is especially useful if you’re auto-generating prompts or API docs).min / max length
ensures the name is neither empty nor absurdly long.example
can be used by tools or UIs to show sample input.- The
feedback
field is optional, with a default empty string and a max length for safety. - The agent has been told what the input and output structure needs to look like.
Why does this matter?
- Strong typing and validation at the boundary of an agent means fewer bugs, clearer contracts, and better user experience.
Validating Response Logic
To keep your agent contextually accurate and avoid embarrassing mistakes, such as hallucinations, you want to add a governance or evaluation layer. For example: the agent should not say “good morning” if it’s actually the afternoon, and vice versa.
from pydantic import BaseModel, ValidationError
from pydantic_ai.agent import Agent
from datetime import datetime
class GreetInput(BaseModel):
name: str
feedback: str
class GreetOutput(BaseModel):
message: str
def governance_layer(output: GreetOutput):
hour = datetime.now().hour
message = output.message.lower()
if "morning" in message and hour >= 12:
raise ValueError("Response includes 'morning' but it's actually afternoon.")
if "afternoon" in message and hour < 12:
raise ValueError("Response includes 'afternoon' but it's actually morning.")
return output
system_prompt = """
You are a friendly assistant. When given a user's name, respond with a personalized greeting.
"""
agent = Agent(
input_model=GreetInput,
output_model=GreetOutput,
system_prompt=system_prompt
)
if __name__ == "__main__":
user_input = {"name": "Conor"}
max_attempts = 3
attempt = 0
last_error = None
feedback = None
while attempt < max_attempts:
try:
result = agent.run(user_input)
result = governance_layer(result)
print(result)
break
except (ValidationError, ValueError) as e:
last_error = str(e)
# we update the feedback here so that the agent can self-improve.
user_input["feedback"] = f"Your last response failed: {last_error}. Please try again."
attempt += 1
else:
print(f"Agent failed governance after {max_attempts} attempts: {last_error}")
What’s happening here?
- The agent is given up to 3 attempts to generate a valid, contextually-appropriate response.
- After each failed governance check (e.g., saying “morning” in the afternoon), the code updates the
feedback
field in the input. This feedback is passed back to the agent, giving it a chance to self-correct based on explicit instructions about what went wrong. - If the agent produces a valid response, the loop breaks and the result is printed. If it fails all attempts, the error is reported and the process stops.
- By including a
feedback
field in your input schema, you can create a feedback loop to help LLM-based agents learn from their mistakes in real time — without human intervention.
Why does this matter?
- LLMs are powerful, but they’re not infallible. They can hallucinate, misunderstand context, or simply get things wrong. If you want to build agents that are robust and production-ready, you need to design for self-correction and governance from the start.
- This pattern — governance, feedback, retry — lets you catch and correct errors before they reach your users or downstream systems. It’s a foundation for building trustworthy, autonomous agents that can handle real-world complexity.
What’s Missing From This Example
This post gives you a solid foundation for building your first agent, but at the moment it is little more than a thin wrapper over Gemini - and you would probably get more value from using something else, but in my follow up posts I will explore:
- Tool Usage: How to instrument your agent with tools and functions it needs to be more than a simple chatbot.
- Observability: How to integrate with tools like OpenTelemetry, or your cloud provider’s monitoring stack, to understand how your agent is working and to manage costs by monitoring token usage.
- Testing: You need automated tests for both the happy path and all the ways things can go wrong — especially around governance and LLM edge cases.
- Versioning: As your agent evolves, you’ll want to version your API and your agent logic to avoid breaking clients.