DEV Community

Jurgen Kola
Jurgen Kola

Posted on

Understanding AI Agents: Concepts, Architectures, and Practical Examples for Developers

Understanding AI Agents: Concepts, Architectures, and Practical Examples for Developers

Artificial intelligence agents are everywhere: chatbots that help customers, automation scripts that triage issues, and RL policies that power games and robotics. If you are a developer building intelligent systems, understanding what an AI agent is, how it is structured, and how to build simple agents is invaluable.

This article explains the core ideas behind AI agents, the common architectures, evaluation and safety concerns, and gives practical examples you can run in Python.


What is an AI agent?

An AI agent is an entity that perceives its environment through observations, makes decisions (possibly using memory and models), and acts to affect the environment. Agents can be as simple as a rule-based bot or as advanced as a deep reinforcement learning policy or a tool-enabled LLM agent.

Key terms:

  • Environment: the context or system the agent interacts with.
  • Observation: the input the agent receives from the environment.
  • Action: what the agent does to influence the environment.
  • State: the internal or external representation of the environment.
  • Reward (optional): a scalar signal used in reinforcement learning to guide behavior.

Core agent concepts and the agent loop

Most agents implement a loop like this:

A clean circular diagram showing an AI agent loop: environment → observation → memory/state → decision/brain → action, with arrows closing the loop and a small reward cue on the feedback path.

  1. Observe state or input from environment
  2. Update internal state or memory
  3. Decide on an action using rules, models, or search
  4. Execute the action
  5. Receive feedback (new observation, reward)
  6. Repeat

Pseudocode:

state = initial_state
while not done:
    observation = env.observe()
    state = update_state(state, observation)
    action = agent_policy(state)
    env.step(action)
    reward, done = env.feedback()
Enter fullscreen mode Exit fullscreen mode

This generic loop maps to many concrete types of agents.


Types of agents (high level)

  • Reactive (reflex) agents: map observations or patterns directly to actions using rules. Simple and fast, but no planning.
  • Deliberative (planning) agents: build internal models and plan sequences of actions toward a goal.
  • Goal-based agents: act to achieve explicit goals, often using search or planning.
  • Learning agents: adapt policy/model over time from data or rewards (supervised learning, RL).
  • Model-based vs model-free: model-based agents build an internal model of the environment; model-free learn policies directly.
  • Hybrid agents: combine reactive and deliberative components (common in practical systems).
  • Tool-enabled LLM agents: large language models orchestrating tools (APIs, code execution) to solve tasks.

Architectures and common building blocks

  • Rule engine + scheduler: used for automation and deterministic tasks.
  • Neural policy + replay buffer: common in RL (e.g., DQN, PPO).
  • Planner + simulator: used for robotics and logistics (search, MCTS, optimization).
  • LLM + tool kit (tools, memory, retrievers): used for complex language-driven agents.

Important supporting features:

  • Memory: short-term or long-term storage of past observations or facts.
  • Observers/sensors: data ingestion and pre-processing.
  • Action modules: adapters that convert abstract actions to API calls or commands.
  • Safety/constraint layers: filters that limit harmful or invalid actions.

Practical examples in Python

Below are three approachable examples: a reflex agent, a simple LLM conversational agent, and a tool-enabled LLM agent using LangChain style patterns.

Prerequisites: Python 3.8+, pip

1) Reflex (rule-based) agent

This agent watches inputs and applies rules. Good for simple automations, alerts, or state machines.

# file: reflex_agent.py

class ReflexAgent:
    def __init__(self, rules):
        # rules: list of (predicate_fn, action_fn)
        self.rules = rules

def step(self, observation):
        for pred, action in self.rules:
            if pred(observation):
                return action(observation)
        return None

# Example usage
if __name__ == '__main__':
    def is_error(obs):
        return 'error' in obs.lower()

def notify(obs):
        print('notify ops: ', obs)
        return 'notified'

agent = ReflexAgent(rules=[(is_error, notify)])
    print(agent.step('Disk error on host 12'))
Enter fullscreen mode Exit fullscreen mode

Why use a reflex agent: quick to write, easy to test, deterministic. The downsides: brittle, not adaptable.

2) Simple LLM conversational agent (using OpenAI style API)

This example shows a minimal conversational agent that takes a user message, sends a prompt to an LLM, and returns a reply. Replace the API call with your provider of choice.

# file: llm_conversation.py
import os
import openai

openai.api_key = os.environ.get('OPENAI_API_KEY')  # set in environment

class LLMConversationAgent:
    def __init__(self, model='gpt-4'):
        self.model = model
        self.history = []

def ask(self, user_message):
        self.history.append({'role': 'user', 'content': user_message})
        resp = openai.ChatCompletion.create(
            model=self.model,
            messages=self.history,
            max_tokens=500,
            temperature=0.2,
        )
        assistant_msg = resp['choices'][0]['message']['content']
        self.history.append({'role': 'assistant', 'content': assistant_msg})
        return assistant_msg

if __name__ == '__main__':
    agent = LLMConversationAgent(model='gpt-4')
    print(agent.ask('Hello, who are you?'))
Enter fullscreen mode Exit fullscreen mode

Notes:

  • Keep history size bounded to avoid long prompts and high token costs.
  • Use system messages to steer behavior (persona, safety rules).
  • Add telemetry to log latency and API errors.

3) Tool-enabled agent (planner + tools)

Tool-enabled agents combine an LLM with a set of tools (web search, calculator, code runner) and a controller that decides when to call which tool. Frameworks like LangChain implement this pattern; here is a minimal conceptual example.

Install minimal packages for this example (optional):

pip install openai requests
Enter fullscreen mode Exit fullscreen mode
# file: simple_tool_agent.py
import os
import openai
import requests

openai.api_key = os.environ.get('OPENAI_API_KEY')

# Tools

def web_search(query):
    # very simple example hitting a public search API or scraping
    # here we simulate a result
    return f'search results for {query} (simulated)'

def calculator(expr):
    # naive and unsafe eval; for production use a safe math parser
    try:
        return str(eval(expr, {'__builtins__': {}}, {}))
    except Exception as e:
        return f'error: {e}'

TOOLS = {
    'web_search': web_search,
    'calculator': calculator,
}

class ToolAgent:
    def __init__(self, model='gpt-4'):
        self.model = model

def decide(self, user_query):
        # ask the model which tool to call and what args to use
        prompt = (
            'You are a helpful assistant with access to tools. ' 
            'When appropriate, choose one tool and provide the tool name and argument. ' 
            'Tools: web_search(query), calculator(expression).\n'
            f'User: {user_query}\n'
            'Respond in the format: TOOL: <name>\nARG: <argument>\nIf no tool is needed, respond with: ANSWER: <text>'
        )
        resp = openai.ChatCompletion.create(
            model=self.model,
            messages=[{'role': 'user', 'content': prompt}],
            temperature=0,
        )
        return resp['choices'][0]['message']['content']

def handle(self, user_query):
        decision = self.decide(user_query)
        if decision.startswith('TOOL:'):
            lines = [l.strip() for l in decision.splitlines() if l.strip()]
            tool_line = lines[0]
            arg_line = lines[1] if len(lines) > 1 else ''
            tool_name = tool_line.split(':', 1)[1].strip()
            arg = arg_line.split(':', 1)[1].strip() if ':' in arg_line else ''
            if tool_name in TOOLS:
                result = TOOLS[tool_name](arg)
                return f'Tool result:\n{result}'
            else:
                return f'Unknown tool: {tool_name}'
        elif decision.startswith('ANSWER:'):
            return decision.split(':', 1)[1].strip()
        else:
            return 'Could not parse model response.'

# Example
if __name__ == '__main__':
    agent = ToolAgent(model='gpt-4')
    print(agent.handle('What is 13 times 19?'))
    print(agent.handle('Who is the prime minister of canada?'))
Enter fullscreen mode Exit fullscreen mode

Notes:

  • Real implementations need a robust protocol for tool invocation and a verifier for tool outputs.
  • Use frameworks like LangChain or LlamaIndex to avoid reinventing the orchestration layer.

Reinforcement learning agent (brief example)

If your agent needs to learn by interacting with an environment, reinforcement learning (RL) is a common approach. Below is a minimal outline using Stable Baselines3 for an OpenAI Gym environment.

Install packages:

pip install gym stable-baselines3[extra]
Enter fullscreen mode Exit fullscreen mode

Train a PPO agent on CartPole:

from stable_baselines3 import PPO
import gym

env = gym.make('CartPole-v1')
model = PPO('MlpPolicy', env, verbose=1)
model.learn(total_timesteps=10000)
model.save('ppo_cartpole')

# run
obs = env.reset()
for _ in range(500):
    action, _states = model.predict(obs)
    obs, reward, done, info = env.step(action)
    if done:
        obs = env.reset()
Enter fullscreen mode Exit fullscreen mode

RL considerations:

  • Environment design, reward shaping, and stable observation spaces are critical.
  • Logging, evaluation episodes, and reproducible seeds help debugging.

Evaluation and metrics

How do you know an agent is good?

  • Task success rate: fraction of episodes or tasks completed successfully.
  • Reward or score: cumulative return for RL tasks.
  • Latency and throughput: important for real-time or high-volume agents.
  • Accuracy / precision / recall: for classification or information extraction tasks.
  • Human evaluation: for chatbots and creative tasks, human rating matters.
  • Safety metrics: frequency of unsafe outputs, adversarial robustness.

Design unit tests for deterministic parts (rule engines, action adapters), and integration tests for the full agent loop.


Safety, constraints, and guardrails

Agents that act autonomously need safety layers:

  • Input validation and sanitization.
  • Output filtering and content moderation.
  • Permission checks for actions that change external state.
  • Rate limiting and retries for external services.
  • Human-in-the-loop for high risk decisions.

For LLM agents, add system-level constraints and response filters before executing actions.


Debugging and observability

  • Log observations, actions, and decision traces. For LLM agents, log prompts and the model output (masked or hashed if sensitive).
  • Capture metrics: API latency, tokens used, success/failure counts.
  • Create a replay mode that replays stored observations for deterministic debugging.

Deployment tips

  • Isolate tool runners to sandbox dangerous operations.
  • Cache common responses and precompute embeddings for retrieval-augmented generation.
  • Use async IO and batching to improve throughput with remote APIs.
  • Monitor costs (API tokens, compute hours) and set quotas.

Libraries and frameworks to explore

  • LangChain: primitives for chains, agents, tools, and memory.
  • Hugging Face Transformers: run or fine-tune models locally.
  • Stable Baselines3, Ray RLlib: reinforcement learning libraries.
  • OpenAI, Anthropic, Azure OpenAI APIs: hosted LLMs.
  • LlamaIndex (now named something else at times): retrieval and knowledge layers.

Quick checklist for building an agent

  1. Define the environment, observations, actions, and goals clearly.
  2. Choose an architecture: rule-based, planner, learning, or hybrid.
  3. Implement core loop and unit tests for adapters and connectors.
  4. Add memory and tooling if needed (retrieval, calculators, web search).
  5. Instrument logging and metrics, run integration tests.
  6. Add safety checks and human oversight.
  7. Iterate: measure, tune, and retrain as needed.

Further reading and resources

  • Sutton, Barto: Reinforcement Learning: An Introduction (for RL fundamentals)
  • LangChain docs and agent examples
  • OpenAI system and tool use guides
  • Stable Baselines3 tutorials

Conclusion

AI agents are a flexible abstraction that ranges from simple scripts to sophisticated, learning systems. Start small with a clear problem, pick the simplest agent architecture that works, and add learning, memory, and tools as needed. Instrument and test aggressively, and prioritize safety when your agents act on the real world.

If you want, tell me what kind of agent you want to build (chatbot, automation, RL policy, tool-using assistant), and I can provide a tailored starter project.

Top comments (0)