Tag: python

  • Build a Game Generator with AI

    Build a Game Generator with AI

    Introduction

    Why use AI for game development?

    Because it’s fast, fun, and wildly creative. You go from idea to game in seconds. Great for prototyping, learning, or impressing your friends at brunch.

    Imagine typing “a Flappy Bird clone” and watching it pop open in your browser — ready to play. No design. No dev work. Just vibes and velocity.

    What You’ll Need

    Prerequisites

    • Python 3.9+
    • An OpenAI API key
    • Curiosity

    Setting up your environment

    pip install openai python-dotenv

    Create a .env file and drop in your key:

    OPENAI_API_KEY=your-key-goes-here

    Tutorial

    Step 1 — Import required Python libraries

    from openai import OpenAI
    import ast
    import webbrowser
    import dotenv
    import pathlib

    These do the heavy lifting: API calls, browser opening, env loading, and safe data parsing.

    Step 2 — Load environment variables with dotenv

    dotenv.load_dotenv()

    Keeps your API key safe and tidy. No need to hardcode secrets.

    Step 3 — Set up the OpenAI client

    client = OpenAI()

    Boom. You’re connected to OpenAI’s LLMs.

    Step 4 — Create a function to call the LLM

    def call_llm(system_prompt: str, user_prompt: str) -> str:
    response = client.chat.completions.create(
    model=”o3-mini”,
    messages=[
    {“role”: “system”, “content”: system_prompt},
    {“role”: “user”, “content”: user_prompt},
    ],
    temperature=1,
    top_p=1,
    response_format={“type”: “json_object”},
    )
    return ast.literal_eval(response.choices[0].message.content.strip())

    The importance of system vs. user prompts

    • System = the brain’s role.
    • User = the actual task.

    Use both. Be specific.

    How to parse JSON safely with ast.literal_eval

    Don’t just eval. That’s dangerous. ast.literal_eval is safer and stricter.

    Step 5 — Generate the game code using your prompt

    def create_game_code(game_name: str) -> str:
    prompt = f”””
    You are a game developer.
    You are given a game name.
    Create code for that game in JavaScript, HTML, and CSS (all in one file).
    The game should be a simple game that can be played in the browser.
    It should be a single page game.
    Follow a json schema for the response: {{“game_code”: “game code”}}
    By default, use the html extension.
    “””
    response = call_llm(prompt, game_name)
    return response[“game_code”]

    Crafting the right system prompt

    Talk to the LLM like it’s a dev on your team. Clear, structured, and friendly.

    Step 6 — Save the generated game as HTML

    def create_game_html(game_code: str):
    with open(“game.html”, “w”) as file:
    file.write(game_code)

    Simple write-to-file. Now it exists on your machine.

    Step 7 — Automatically open the game in the browser

    def open_game():
    path = pathlib.Path().resolve() / “game.html”
    webbrowser.open(f”file://{path}”)

    No need to hunt for the file. It just opens.

    Step 8 — Tie it all together in one function

    def play_game():
    request = input(“Enter a game name: “)
    game_code = create_game_code(game_name=request)
    create_game_html(game_code)
    open_game()

    if __name__ == "__main__":
    play_game()

    Just run it. Type something fun like “Zombie Runner.” Boom. You’re playing it.

    Test it out

    Suggested prompts to try

    • “Snake but it gets faster over time”
    • “Tetris in grayscale”
    • “A ghost catching game”
    • “Mouse maze challenge”

    Try weird stuff too. The model gets creative.

    Final thoughts

    This isn’t just a coding shortcut — it’s a creative launchpad. You can brainstorm, prototype, and even teach kids how code becomes experience.

    The combo of Python + OpenAI is like a magic wand for your imagination.

    So next time someone says “Let’s build a game!”, just smile and say “Give me 30 seconds.”

    Feel free to reach out to me if you would like to discuss further, it would be a pleasure (honestly):

  • How to Fine-Tune an LLM with Hugging Face + LoRA

    How to Fine-Tune an LLM with Hugging Face + LoRA

    Fine-tuning is the process of taking a pre-trained model and adjusting it on a specific dataset to specialize it for a particular task.

    Instead of training a model from scratch (which is costly and time-consuming), you leverage the general knowledge the model already has and teach it your domain-specific patterns.

    It’s like giving a well-read intern a crash course in your company’s workflow — faster, cheaper, and surprisingly effective.

    LoRA (Low-Rank Adaptation) is a clever trick that makes fine-tuning large models much more efficient.

    Instead of updating the entire model (millions or billions of parameters), LoRA inserts a few small trainable matrices into the model and only updates those during training.

    Think of it like attaching a lightweight lens to a heavy camera — you adjust the lens, not the whole system, to get the shot you want.

    Under the hood, LoRA works by decomposing weight updates into two smaller matrices with a much lower rank (hence the name).

    This dramatically reduces the number of parameters you need to train — without sacrificing performance.

    It’s a powerful way to customize large models on modest hardware, and it’s part of why AI is becoming more accessible beyond big tech labs.

    The dataset

    For this tutorial, I’ve decided to use Paul Graham’s blog to build a dataset with his essays.

    I really like his style of writing, and thought it’d be cool to have a fine-tuned model that mimics it.

    To build the dataset, I scraped his blog, then reverse-engineered the prompts that could have been used to write his essays.

    This means I gave each of his essays to ChatGPT and asked what prompt could have been used to generate it.

    This resulted in a dataset containing a prompt and an essay, which we’ll use to fine-tune our model.

    Now, let’s build!

    Tutorial

    Start by installing stuff:

    !pip install bitsandbytes
    !pip install peft
    !pip install trl
    !pip install tensorboardX
    !pip install wandb
    • bitsandbytes: efficient 8-bit optimizers for reducing memory usage during training
    • peft: lightweight fine-tuning methods like LoRA for large language models
    • trl: tools for training LLMs with reinforcement learning (e.g. PPO, DPO)
    • tensorboardX: TensorBoard support for PyTorch logging and visualization
    • wandb: experiment tracking and model monitoring with Weights & Biases

    Next, let’s preprocess our data:

    from enum import Enum
    from functools import partial
    import pandas as pd
    import torch
    import json

    from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
    from datasets import load_dataset
    from trl import SFTConfig, SFTTrainer
    from peft import LoraConfig, TaskType
    import os

    seed = 42
    set_seed(seed)

    # Put your HF Token here
    os.environ['HF_TOKEN']="<your HF token here>" # the token should have write access

    model_name = "google/gemma-3-1b-it"
    dataset_name = "arthurmello/paul-graham-essays"
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    tokenizer.chat_template = "{{ bos_token }}{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{{ '<start_of_turn>' + message['role'] + '\n' + message['content'] | trim + '<end_of_turn><eos>\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\n'}}{% endif %}"
    def preprocess(sample):
    prompt = sample["prompt"]
    response = sample["response"]

    messages = [
    {"role": "user", "content": prompt},
    {"role": "assistant", "content": response}
    ]

    return {"text": tokenizer.apply_chat_template(messages, tokenize=False)}

    dataset = load_dataset(dataset_name)
    dataset = dataset.map(preprocess, remove_columns=["prompt", "response"])
    dataset = dataset["train"].train_test_split(0.1)

    Here, we set up the environment for fine-tuning a chat-style language model using LoRA and Google’s Gemma model.

    We then format the answers to have a “text” field, containing both the prompts and the responses.

    The result is a train/test split of the dataset, ready for supervised fine-tuning.

    Now, we define our tokenizer:

    model = AutoModelForCausalLM.from_pretrained(model_name,
    attn_implementation='eager',
    device_map="auto")
    model.config.use_cache = False
    model.to(torch.bfloat16)

    Here, we:

    • Load the model with attn_implementation='eager', which uses a more compatible (though sometimes slower) attention mechanism useful for certain hardware or debugging.
    • Map the model to available devices (device_map="auto"), which automatically spreads the model across CPUs/GPUs as needed based on memory availability.
    • Cast the model to bfloat16, a memory-efficient format that speeds up training/inference on supported hardware (like recent NVIDIA/TPU chips).

    Next, we set up our LoRA parameters:

    rank_dimension = 16
    lora_alpha = 64
    lora_dropout = 0.1

    peft_config = LoraConfig(r=rank_dimension,
    lora_alpha=lora_alpha,
    lora_dropout=lora_dropout,
    target_modules=[
    "q_proj", "k_proj", "v_proj",
    "o_proj", "gate_proj", "up_proj",
    "down_proj"
    ],
    task_type=TaskType.CAUSAL_LM)
    • r: rank dimension for LoRA update matrices (smaller = more compression)
    • lora_alpha: scaling factor for LoRA layers (higher = stronger adaptation)
    • lora_dropout: dropout probability for LoRA layers (helps prevent overfitting)
    • target_modules : which layers we target. You don’t need to specify those individually, you can just set it to “all_linear”. However, it can be a good exercise to experiment with different layers (to check all the available layers, run print(model))

    Next, we set up our training arguments:

    username = "arthurmello" # replace with your Hugging Face username
    output_dir = "gemma-3-1b-it-paul-graham"
    per_device_train_batch_size = 1
    per_device_eval_batch_size = 1
    gradient_accumulation_steps = 4
    learning_rate = 1e-4

    num_train_epochs=10
    warmup_ratio = 0.1
    lr_scheduler_type = "cosine"
    max_seq_length = 1500

    training_arguments = SFTConfig(
    output_dir=output_dir,
    per_device_train_batch_size=per_device_train_batch_size,
    per_device_eval_batch_size=per_device_eval_batch_size,
    gradient_accumulation_steps=gradient_accumulation_steps,
    save_strategy="no",
    eval_strategy="epoch",
    logging_steps=logging_steps,
    learning_rate=learning_rate,
    max_grad_norm=max_grad_norm,
    weight_decay=0.1,
    warmup_ratio=warmup_ratio,
    lr_scheduler_type=lr_scheduler_type,
    report_to="tensorboard",
    bf16=True,
    hub_private_repo=False,
    push_to_hub=True,
    num_train_epochs=num_train_epochs,
    gradient_checkpointing=True,
    gradient_checkpointing_kwargs={"use_reentrant": False},
    packing=False,
    max_seq_length=max_seq_length,
    )

    Here, we set:

    • per_device_train_batch_size and per_device_eval_batch_size set how many samples are processed per device at each step for training and evaluation, respectively.
    • gradient_accumulation_steps allows effective batch sizes larger than memory limits by accumulating gradients over multiple steps.
    • learning_rate sets the starting learning rate for model optimization.
    • num_train_epochs defines how many times the model will see the full training dataset.
    • warmup_ratio gradually increases the learning rate during the first part of training to help stabilize early learning.
    • lr_scheduler_type="cosine" uses a cosine decay schedule to adjust the learning rate over time.
    • max_seq_length defines the maximum number of tokens per training sequence.

    Finally, we train our model:

    trainer = SFTTrainer(
    model=model,
    args=training_arguments,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    processing_class=tokenizer,
    peft_config=peft_config,
    )

    trainer.train()

    Here, you should see something that looks like this:

    This shows the training and validation loss for each epoch.

    If training loss decreases and validation loss increases, this indicates overfitting (which we can see here around epoch 3).

    Some strategies to adress overfitting include:

    • reducing learning_rate
    • increasing lora_dropout
    • reducing num_train_epochs

    Once you’re satisfied with the training results, you can compare your model’s output with the base model’s:

    base_model = AutoModelForCausalLM.from_pretrained(model_name).to(torch.bfloat16)
    base_tokenizer = AutoTokenizer.from_pretrained(model_name)

    fine_tuned_model = model
    fine_tuned_tokenizer = tokenizer

    # Example input prompt
    prompt = "<start_of_turn>user\Write an essay on the future of AI<end_of_turn><eos>\n<start_of_turn>model\n"

    # Inference helper
    def generate(model, tokenizer, prompt):
    device=model.device
    inputs = tokenizer(prompt, return_tensors="pt").to(device)
    output = model.generate(**inputs)
    return tokenizer.decode(output[0], skip_special_tokens=True)

    print("=== Base Model Output ===")
    print(generate(base_model, base_tokenizer, prompt))

    print("\n=== Fine-Tuned Model Output ===")
    print(generate(fine_tuned_model, fine_tuned_tokenizer, prompt))

    There you go, now you have your own fine-tuned model to replicate Paul Graham’s style!

    If you set push_to_hub=True in SFTConfig , you can call your fine-tuned model anytime, using your own username and output_id :

    model = AutoModelForCausalLM.from_pretrained(
    "arthurmello/gemma-3-1b-it-paul-graham")

    And, of course, you can adapt this approach to fine-tune LLMs for other use cases!

    A video version of this tutorial is available here:


    Feel free to reach out to me if you would like to discuss further, it would be a pleasure (honestly):

  • Build a Neural Network From Scratch – in Less Than 5 minutes

    Build a Neural Network From Scratch – in Less Than 5 minutes

    No TensorFlow. No PyTorch. Just you, NumPy, and 20-ish lines of code.

    We’re going straight to the core: how a neural network actually learns — and we’ll teach it the classic XOR problem.

    The Problem: XOR

    We want this network to learn the XOR rule:

    0 XOR 0 = 0  
    0 XOR 1 = 1  
    1 XOR 0 = 1  
    1 XOR 1 = 0

    If A or B are equal to 1, then the output is equal to 1… unless they are both equal to 1, in which case the output is 0.

    It’s a simple pattern… that isn’t linearly separable. A single-layer perceptron fails here. But with one hidden layer, it works.

    Step 1: Setup and architecture

    Let’s define our data and our tiny network.

    import numpy as np
    
    # XOR input and labels
    X = np.array([[0,0],[0,1],[1,0],[1,1]])
    y = np.array([[0],[1],[1],[0]])
    
    # Define network architecture
    input_size = 2
    hidden_size = 4
    output_size = 1
    

    We’ve got:

    • 2 input features (x1, x2)
    • 4 neurons in the hidden layer
    • 1 output (for binary classification)

    Step 2: Initialize weights

    Random weights, zero biases. Simple and effective.

    np.random.seed(1)
    W1 = np.random.randn(input_size, hidden_size)
    b1 = np.zeros((1, hidden_size))
    W2 = np.random.randn(hidden_size, output_size)
    b2 = np.zeros((1, output_size))
    

    We’ll learn these weights as we train.

    Step 3: Activation functions

    We’ll use sigmoid for both layers — good enough for this toy example.

    def sigmoid(z): return 1 / (1 + np.exp(-z))
    def sigmoid_deriv(a): return a * (1 - a)
    

    sigmoid_deriv is the derivative — it tells us how much to adjust during backprop.

    Step 4: Train it

    Here’s the full training loop. Forward pass, backprop, and gradient descent.

    learning_rate = 0.1
    epochs = 1000
    
    for epoch in range(epochs):
        # Forward pass
        A1 = sigmoid(X @ W1 + b1)      # hidden layer
        A2 = sigmoid(A1 @ W2 + b2)     # output layer
    
        # Backpropagation (compute gradients)
        dA2 = (A2 - y) * sigmoid_deriv(A2)
        dA1 = dA2 @ W2.T * sigmoid_deriv(A1)
    
        # Gradient descent (update weights and biases)
        W2 -= learning_rate * A1.T @ dA2
        b2 -= learning_rate * np.sum(dA2, axis=0, keepdims=True)
        W1 -= learning_rate * X.T @ dA1
        b1 -= learning_rate * np.sum(dA1, axis=0, keepdims=True)
    

    This is the heart of every neural net:

    • Forward pass: make a guess
    • Backward pass: see how wrong you were
    • Update: adjust weights to do better next time

    Step 5: Make predictions

    Let’s see if it learned XOR.

    preds = sigmoid(sigmoid(X @ W1 + b1) @ W2 + b2) > 0.5
    
    print("Predictions:\n", preds.astype(int))
    

    Output:

    [[0]
     [1]
     [1]
     [0]]

    It works!

    Where to go from here

    You just built a functioning neural network from scratch.

    Here’s what you can try next:

    • Replace sigmoid with ReLU in the hidden layer
    • Add a second hidden layer
    • Swap out the loss function for cross-entropy
    • Wrap this into a class and build your own mini framework

    Final words

    Learning how this stuff works under the hood is powerful.

    You’ll never look at TensorFlow or PyTorch the same again.

    No magic.
    Just math.
    Just code.

    Here’s a video version of this tutorial:

  • Why You’re Struggling to Break into Data Science

    Why You’re Struggling to Break into Data Science

    I see many people having a hard time transitioning from other fields into Data Science, even though there’s more open jobs every day. Many companies end up going for people who already have domain experience or who are fresh out of college, with a degree in Statistics or Computer Science.

    Although it can be hard to make this transition, I did it, and I think you can do it too, as long as you have the right strategy.

    Let’s take a look at some tips to help you land your first job.

    Stop doing MOOCs

    Don’t get me wrong, some online courses are great, but I feel that beginners tend to think that doing ten of those in a year will somehow help landing a job.

    First, if you just do them for the sake of getting a certificate and putting it on LinkedIn, you won’t learn much. Plus, so many people have so many of those that most recruiters don’t care much about it.

    Instead, do a few good ones at first, to have some understanding of the field and to be able to answer interview questions. The one I recommend is the famous Machine Learning specialization by DeepLearning.AI and Stanford, on Coursera. This will keep you busy and give you a good theoretical basis.

    That next book will not help

    The same logic goes for books: many people think that reading hundreds of books will make them magically absorb all that content and become machine learning experts.

    Instead, use books as tools to gather specific knowledge that you need right now. Working on a Time Series project? Reading a book on the subject while you do it might help.

    But again, don’t do it just to cross that item off your list, recruiters don’t care about how many books you read last year.

    Choose the right side projects

    Side projects help you in two ways: building skills and showing off your work. If your first project is doing logistic regression on the Titanic dataset, fine, you are warming up. But that’s not a great project for display.

    Once you know the basics, try working on 2 or 3 projects that will actually display your skills to recruiters, such as deploying a model in production via a WebApp that you can show during an interview, creating a public dashboard or doing a deep analysis on some interesting dataset.

    Some certifications help, others don’t

    There are tons of certifications out there, so choose wisely. Usually, the hard ones also have the better payoffs: GCP, AWS, Azure and IBM certifications can be quite valuable. Tableau and Power BI too. The ones you get from just watching videos on Coursera, not so much.

    If you are doing one of those I mentioned, check what are the most used cloud providers and dashboarding tools in your region, and focus on those.

    Don’t be picky (at first)

    If you are transitioning and haven’t been able to land a great job at first, don’t be picky. If you work in logistics and want to do Machine Learning, maybe a first job as a Data Analyst for a year will get you closer to your goal. Even if you are just doing Excel and dataviz, you are now closer than you were before, so look at it as a transitory move.

    You might need to accept a lower salary at a not-so-great company too.

    Choose a smooth transition

    Let’s say you work in HR and want to transition to Data Science or Data Analysis. Focusing on data jobs related to HR analytics will make the transition smoother to you, and your set of skills will be valuable to your employer. They will be much more likely to accept your lack of data skills if you can make up for it with domain expertise.

    Even if you don’t want to work with HR analytics forever, see this as a transitory move.

    Start with consulting companies

    There are many consulting companies out there who outsource data scientists and data analysts to other companies. They tend to pay less, but the bar might be lower, since they are currently hiring like crazy.

    Do this for a couple of years and you will have enough experience to land a better paying job in the future.

    Focus on your coding skills

    Everyone will say during an interview how awesome they are, and how they have a unique skill set that differentiates them from competition.

    Trust me, you are not the only one who knows how to “approach problems from a business perspective to get insights from data and generate actual value”.

    Instead, build hard skills like Python and SQL, which will likely be tested during interviews, and can actually differentiate you from other candidates.

    If you would like to discuss further, feel free to reach out to me on other platforms, it would be a pleasure (honestly):

  • How to Build a Chatbot with Python

    How to Build a Chatbot with Python


    A no-BS guide for complete beginners

    Chatbots are becoming more powerful and accessible than ever. In this tutorial, you’ll learn how to build a simple chatbot using Streamlit and OpenAI’s API in just a few minutes.

    Prerequisites

    Before we start coding, make sure you have the following:

    • Python installed on your computer
    • A code editor (I recommend Cursor, but you can use VS Code, PyCharm, etc.)
    • An OpenAI API key (we’ll generate one shortly)
    • A GitHub account (for deployment)

    Step 1: Setting Up the Project

    We’ll use Poetry for dependency management. It simplifies package installation and versioning.

    Initialize the Project

    Open your terminal and run:

    # Initialize a new Poetry project
    poetry init
    
    # Create a virtual environment and activate it
    poetry shell

    Install Dependencies

    Next, install the required packages:

    poetry add streamlit openai

    Set Up OpenAI API Key

    Go to OpenAI and get your API key. Then, create a .streamlit/secrets.toml file and add:

    OPENAI_API_KEY="your-openai-api-key"
    

    Make sure to never expose this key in public repositories!

    Step 2: Creating the Chat Interface

    Now, let’s build our chatbot’s UI. Create a new folder: streamlit-chatbot, and add a file to it, called app.py with the following code:

    import streamlit as st
    from openai import OpenAI
    
    # Access the API key from Streamlit secrets
    api_key = st.secrets["OPENAI_API_KEY"]
    client = OpenAI(api_key=api_key)
    st.title("Simple Chatbot")
    
    # Initialize chat history
    if "messages" not in st.session_state:
        st.session_state.messages = []
    
    # Display previous chat messages
    for message in st.session_state.messages:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])
    
    # Chat input
    if prompt := st.chat_input("What's on your mind?"):
        st.session_state.messages.append(
           {"role": "user", "content": prompt}
        )
        with st.chat_message("user"):
            st.markdown(prompt)
    

    This creates a simple UI where:

    • The chatbot maintains a conversation history.
    • Users can type their messages into an input field.
    • Messages are displayed dynamically.

    Step 3: Integrating OpenAI API

    Now, let’s add the AI response logic:

    # Get assistant response
        with st.chat_message("assistant"):
            response = client.chat.completions.create(
                model="gpt-3.5-turbo",
                messages=[
                  {"role": m["role"],
                   "content": m["content"]} for m in st.session_state.messages
                ])
            assistant_response = response.choices[0].message.content
            st.markdown(assistant_response)
    
        # Add assistant response to chat history
        st.session_state.messages.append({"role": "assistant", "content": assistant_response})
    

    This code:

    • Sends the conversation history to OpenAI’s GPT-3.5-Turbo model.
    • Retrieves and displays the assistant’s response.
    • Saves the response in the chat history.

    Step 4: Deploying the Chatbot

    Let’s make our chatbot accessible online by deploying it to Streamlit Cloud.

    Initialize Git and Push to GitHub

    Run these commands in your project folder:

    git init
    git add .
    git commit -m "Initial commit"

    Create a new repository on GitHub and do not initialize it with a README. Then, push your code:

    git remote add origin <https://github.com/your-username/your-repo.git>
    
    git push -u origin master

    Deploy on Streamlit Cloud

    1. Go to Streamlit Cloud.
    2. Click New app, connect your GitHub repository, and select app.py.
    3. In Advanced settings, add your OpenAI API key in Secrets.
    4. Click Deploy and your chatbot will be live! 🚀

    Conclusion

    Congratulations, you’ve built and deployed a chatbot using Streamlit and OpenAI. This is just the beginning — here are some ideas to improve it:

    • Add error handling for API failures.
    • Use different GPT models for varied responses.
    • Allow users to clear chat history.
    • Integrate RAG into it

    I hope you enjoyed this tutorial! If you found it helpful, feel free to share it.

    The full code is available here.


    Feel free to reach out to me if you would like to discuss further, it would be a pleasure (honestly):

    LinkedIn

  • More Than Words: How AI Agents Are Changing Automation

    More Than Words: How AI Agents Are Changing Automation

    Memory, reasoning, and the future of intelligent systems

    AI is shifting from passive assistants to active problem solvers. Instead of just generating text based on a prompt, AI agents retrieve live information, use external tools, and execute actions.

    Think of the difference between a search engine and a research assistant. One provides a list of links. The other finds, summarizes, and cross-checks relevant sources before presenting a well-formed answer. That’s what AI agents do.

    Let’s break down how they work, why they matter, and how you can build one yourself.

    Why AI Agents?

    Traditional GenAI applications can generate convincing answers but lack:

    • Tools: They can’t fetch real-time data or perform actions.
    • Reasoning structure: They sometimes jump to conclusions without checking their work.

    AI agents solve these issues by integrating tool usage, and structured reasoning.

    Take a financial analyst, for example. Instead of manually searching for Apple’s stock performance, reading reports, and comparing it to recent IPOs, she could use an agent to:

    1. Retrieve live stock data from an API.

    2. Pull market news from a financial database.

    3. Run calculations on trends and generate a summary.

    No wasted clicks. No sifting through search results. Just a concise, actionable report.

    How AI Agents Work

    AI agents combine three essential components:

    1. The model (Language understanding & reasoning)

    This is the core AI system, typically based on an LLM like GPT-4, Gemini, or Llama. It handles natural language understanding, reasoning, and decision-making.

    2. Tools (external data & action execution)

    Unlike standalone models, agents don’t rely solely on their training data.

    They use APIs, databases, and function calls to retrieve real-time information or perform actions.

    Common tools include:

    • Search engines for fetching up-to-date information.
    • Financial APIs for stock prices, economic reports, or currency exchange rates.
    • Weather APIs for real-time forecasts.
    • Company databases for business insights.

    3. The Orchestration Layer (Planning & Execution)

    This is what makes an agent more than just a chatbot. The orchestration layer manages:

    • Memory: Keeping track of previous interactions.
    • Decision-making: Deciding when to retrieve information vs. generating a response.
    • Multi-step execution: Breaking down complex tasks into logical steps.

    It ensures that the agent follows structured reasoning instead of blindly generating an answer.

    Thinking Before Acting: The ReAct Approach

    One of the biggest improvements in AI agent design is ReAct (Reason + Act). Instead of immediately answering a question, the agent first:

    1. Thinks through the problem, breaking it into smaller steps.

    2. Calls a tool (if needed) to gather relevant information.

    3. Refines its answer based on the retrieved data.

    Without this structure, models can confidently hallucinate — generating incorrect information with complete certainty.

    ReAct reduces that risk by enforcing a step-by-step thought process.

    Example

    Without ReAct:

    Q: What’s the tallest building in Paris?

    A: The Eiffel Tower.

    (Sounds reasonable, but wrong. The Montparnasse Tower is taller if you exclude antennas.)

    With ReAct:

    Q: What’s the tallest building in Paris?

    Agent:

    1. “First, let me check the list of tall buildings in Paris.” (Calls search tool)

    2. “The tallest building is Tour Montparnasse at 210 meters.” (Provides correct answer)

    This approach ensures accuracy by retrieving data when necessary rather than relying on training data alone.

    AI Agents in Action: Real-World Examples

    Let’s explore some concrete applications with the smolagents framework, by HuggingFace.

    from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModel
    
    model = HfApiModel()
    agent = CodeAgent(tools=[DuckDuckGoSearchTool()], model=model)
    
    query = "Compare Apple's stock performance this week to major tech IPOs."
    response = agent.run(query)
    print(response)
    

    What happens here?

    1. The agent searches for stock performance data using DuckDuckGo’s API.

    2. It retrieves relevant comparisons between Apple and newly public companies.

    3. If needed, it could summarize key financial trends.

    Instead of giving a vague answer like “Apple’s stock is up”, the agent provides a structured comparison, making it more useful.

    This example uses an existing search tool, but the smolagents framework allows you to build your own: it could be calling an API or writing in a database, sending an email.

    Any Python function, really.

    The Future of AI Agents

    AI agents are shifting how we interact with AI.

    Instead of just responding to prompts, they make decisions based on logic, and call external tools.

    Where Are We Headed?

    1. Multi-Agent Systems — Teams of specialized AI agents working together.

    2. Self-Improving Agents — Agents that refine their own strategies based on past interactions.

    3. Embedded AI — Assistants woven into workflows that anticipate problems before they arise.

    AI isn’t just answering questions anymore — it’s solving problems.

    Final Thoughts

    The difference between an AI model and an AI agent is the difference between knowing and doing.

    A model like ChatGPT is an information engine. It predicts words based on patterns.

    An agent is an action engine. It retrieves data, runs calculations, and executes tasks.

    This shift — from static responses to dynamic, tool-enabled intelligence — is where AI is headed.

    The real challenge now isn’t just improving models, but designing intelligent, adaptive systems that can reason, act, and learn over time.

    AI agents will augment human decision-making, making us faster, more informed, and better equipped to navigate an increasingly complex world.

    And that’s a future worth paying attention to.

    Sources

  • Book Summary: Statistics — A Very Short Introduction

    Book Summary: Statistics — A Very Short Introduction

    How can we apply statistical methods to real-world problems?

    Who should read this book?

    You either work with data or want to start to, but come from a tech background or just don’t remember much from Stats 101, and need a refresh on the basics of statistics.

    One-paragraph summary

    It’s a good starting point to understanding statistics: it approaches a broad range of topics, from basic probability to random forests, without going too deep in any of them, so no previous mathematical background is required.

    Full summary

    Introduction

    The author starts by giving a series of possible definitions for statistics, one of them being “the technology of extracting meaning from data”.

    “Statistics is hocupocus with numbers” — Audrey Habera and Richard Runyon

    He then gives a glance of the many different applications that are possible with statistics, from public policy to marketing to spam filtering, and mentions some of the issues that can arise from misusing it. The most notable example is the Sally Clark case: in 1999, a young British lawyer was sentenced for life for killing her two baby children who she claimed had died from cot death. The sentence was based on the testimony of Sir Roy Meadow, the prosecution’s paediatrician, that said that it was nearly impossible that this was the actual cause, since the chances of this happening to two children was of 1 in 73 million. The verdict was then that the mother was guilty. The probability calculated by the doctor was, however, flawed: he did it by multiplying the probability of one cot death two times. This method, however, needs the two events to be independent, which they are not, considering that, given that one of the children died from it might indicate genetic conditions that will also manifest in the second child.

    This, and many other examples, show that statistics has an important role in society: providing evidence. Without it, we cannot subject our opinions to test, and they remain mere speculations.

    Statistics began on the end of the 19th century only as discursive explorations of data. In the first half of the 20th century it evolved and became a more mathematics-oriented field. Only in the second half of the 20th century it faced its latest revolution with the use of computers, which allowed the field to develop its methods and apply heavy-computational algorithms.

    Descriptions

    In statistics, we analyse objects and their attributes usually in the shape of observations and variables. This information can, sometimes, be overwhelming, so we might want to aggregate it by doing simple summary statistics: average, dispersion, skewness and quantiles, for example.

    The concept of average can comprise many formal concepts, but the most used case is the arithmetical mean: the sum of all values, divided by the number of observations. For example, if we wanted to understand the attribute “age” for a given classroom of college students, instead of looking at all the students’ ages, we sum them all, divide by the number of students, and get 22 years. It doesn’t mean all students are 22 years old, but it gives us an overall picture: some are older, some are younger, but we can imagine it is not a classroom full of kids, for example.

    However, let’s take a second example: there’s five people, four of them earn $5,000 a month and one of them earns $100,000 a month. In average, these people make $24,000 a month. However, this does not fully describe their real situation, since it is not a group of people where everyone earns more or less $24,000. From here we can add the concept of dispersion: how far from the average are the values in this group? One measure of dispersion is the variance, calculated by taking the square of the difference between all the numbers and the mean, and then calculating their averages. Wouldn’t it be simpler to just take the mean without the square part? Yes, but then positive and negative values would reduce each other’s effects, cancelling out the whole purpose of measuring dispersion. We can take the square root ofthe variance as another measure, called the standard deviation.

    Ok, so we know the average and whether the dispersion is high or low, but how exactly is the shape of this dispersion? For this, we can look at skewness and quantiles. Skewness measures the lack of symmetry in the population: if it’s very asymmetric, there are many more values higher or lower than the average. Quantiles tell you what value you should take if you want a certain percentage of the population below this value, and there are a few types of quantiles. One of the most common is the percentile: if you are in the 90th percentile of your classroom’s grades, it means you have better grades than 90% of the your classroom.

    Collecting good data

    “Garbage in, garbage out” — Everyone data science article out there

    When collecting data for analysis, it is very important to pay attention to its quality: no matter how sophisticated are your models, if you put bad data in, your outcome will also be poor.

    Pay special attention to missing data: sometimes it’s random but sometimes it can also reveal an underlying pattern. For example, when asking people for their income, people who get really good (or really bad) salaries may prefer not to answer, generating missing data that can actually give you some information. To deal with missing data, you can ignore it, remove those observations/variables or you can try to input it by replacing them by something simple such as the sample mean or by something a lot more complex, using prediction algorithms. It will depend mostly on your data and your goals. When data is incorrect, on the other hand, most of the time there’s not much that can be done a posteriori so avoid making these mistakes when fetching data.

    When it comes to data sources, they can basically be of two types: observational or experimental. The first one comes from real-life observations whereas the latter comes from controlled experiments. Experimental studies are better for isolating variables and causation effects but they are usually harder to do. When conducting experiments, we should plan very well our experiment design: choosing the best groups for measuring the impact of each variable, taking into account the effect of interactions. For example, if we want to test the effect of a new drug, we should have a control group and a test group, sampled randomly from the population, ideally with similar characteristics. If the test group has only men and the control group has only women, we won’t be able to know if the observed results were the effect of the drug or of the subject’s gender.

    For this kind of procedure, we can apply techniques from a statistics domain called survey sampling, which can help us the best methods of sampling individuals within a population.

    Probability

    Another definition of statistics is “the science of handling uncertainty”, which is what the study of probability tries to address. A lot of its utility is based on the Law of Large Numbers, which roughly means that, if when you toss of a coin you have 50% chances of getting heads, then the more you throw the coin, the closer the overall proportion of heads will be to 50%.

    This leads us to the two main approaches when it comes to probability: frequentist and Bayesian. Roughly, frequentists see probabilities as the proportion of times the event would occur if the exact same circumstances were repeated infinitely. The Bayesian approach takes into account the amount of information available: probability is subject to how much we know, and thus it changes as we gather new information.

    Whatever approach you take, you will encounter the idea of independence between events. Basically, two events being independent means that the occurrence of one of them does not affect the probability of the other one occurring. If we throw two coins separately, the fact that we got heads in one does not change the probability of getting heads in the second one.

    To look at dependent events, we often use the Bayes theorem, which is given by the formula below:

    Skymind’s beginner’s guide to the Bayes theorem

    Ok, that’s very useful, but how do we know these probabilities? In basic exercises, usually we have probabilities that are easy to calculate, with things such as coins and dice. But how do we deal with more complicated probabilities? We work with cumulative distribution functions, which give us the probabilities of finding a value smaller (or greater) than another value we set. For example, if we knew the distribution of people’s heights in our town, we could calculate the probability of finding someone shorter (or taller) than 1.80m. From this function, we can derive the probability distribution, that gives us the probability that a value will fall within a certain range (we could know the probability of someone being between 1.70m and 1.80m tall, for example).

    Some distributions are particularly important since they are often found in many real-life phenomena: BernoulliBinomialPoisson and Gaussian, to mention a few. The Gaussian distribution is particularly important because of the Central Limit Theorem that states that, for any given distribution, when we sample the population many times, the means of those samples will follow a Gaussian distribution with the same mean as the original population.

    Gaussian distribution. Source: Wikipedia

    Probability distributions are a huge subject, and there’s a lot of content out there on it. It is out of the scope of the book to go into the details of each of them but it’s an interesting subject to study further.

    Estimation and inference

    Once we have our probability distribution, we want to be able to make estimations from a given sample. For example, let’s say we sample a few students in a school, get their ages and want to estimate the average age in the school. There are mainly two approaches to it: Maximum Likelihood and Least Squares. The first one reasons that our estimation of the average age in the population should be the one that makes the sampled result the most likely. The latter tries to find the estimation that will yield the smaller difference between estimated values and observations. And how do we choose an estimator? Ideally, we want an unbiased estimator, such that it is expected to give us a true result, but also one that doesn’t vary too much depending on the sample we take.

    What if we want to estimate an interval, instead of a single point? It is also possible, due to something called confidence interval. A confidence interval can be calculated from the distribution we have, and will allow us to make a statement more or less like “I’m 95% confident that the average age in this school is between 10 and 12”, which can be quite useful for decision-making.

    Another important statistical method is called hypothesis testing, which is used to test if your parameter takes a specific value or lies within a specific range. Let’s say we want to know if men and women earn the same. We sample a group of men and a group of women, calculate their average wages and find out that men earn in average $35,000 a year and women make $33,000. Ok, can we really say that those populations are essentially different? What if women earned $34,999, could we also reach the same conclusion? How big should this difference be so that we can say its statistically significant? We set a level of confidence we want to have (say 95%) and test our hypothesis. There are many ways of doing it, depending on what we are testing and on the population distribution but, if we do it right, our test will indicate us if our hypothesis holds or not.

    Statistical models

    A statistical model is some simple representation or description of the system we are studying. Since it is a simplification, we’ll necessarily lose information in this process, so we try not to lose the most important bits.

    “All models are wrong, some models are useful” — George Box

    Models can be mechanistic, based in a solid underlying theory (such as gravity) that allows us to predict some behaviour (an object falling, for example) or empirical, more common in the social sciences, where we try to infer the theory from observed data.

    They can also be exploratory, where we try to find relationships and patterns (ex.: looking at demographic data to see if there are characteristics that are correlated) or confirmation models, where we test our conjectures to see if they are supported by data.

    Finally, they can be split into descriptive models, where we try to characterise our data, calculating means, standard deviations, etc., or predictive models, were we try to infer some variable’s behaviour based on the other variables.

    Predictive models are quite useful and they can be very simple or very complicated, usually depending on the number of explanatory variables we use. However, more complicated models do not always yield better predictions. Sometimes, adding more information makes models so specific for our sample that they do not generalise well for the whole population. This phenomenon is called overfitting.

    Statistical models are often based on the idea of correlation: when two variables are correlated, it means that observing a value for one of them gives us a hint on the value of the other. For example, height and weight: tall people tend to be heavier and heavy people tend to be taller. Obviously, tall people can be light and heavy people can be short, but there’s still an overall trend. Correlation can also be negative, for example temperature and hot chocolate sales: the higher the temperature, the less people buy hot chocolate. Correlation is usually represented by a correlation coefficient that goes from -1 (perfect negative correlation) to 0 (no correlation at all) to 1 (perfect positive correlation). It is very important to keep in mind that correlation does not mean causation. For example, ice cream sales and deaths by drowning are correlated, but one does not cause the other, it’s just that in warmer days people buy more ice cream and swim more, so usually when ice cream sales go up it’s because it’s a warm day, meaning more people will swim (and drown).

    In the end, the author briefly goes through some important statistical methods that are worth checking in more detail:

    Regression analysis: it allows us to say “someone who weights 83kg is expected to be 1.83m tall”, based on a sample, even if we haven’t sampled anyone who’s 83kg. The most basic type of regression is linear regression, which supposes a linear relationship between two variables, as per the example below:

    Trying to predict muscle strength based on lean body mass. Taken from http://www.jerrydallal.com

    In the plot above, we can see our sample data (the dots) and the estimated regression line that will allow us to make estimations.

    Analysis of variance (ANOVA): it allows us to compare means from many different populations and test if they are significantly different or not.

    Clustering: used for finding groups of observations that are very similar. We just set the number of groups we want in the end and the algorithm gives us the best partitions.

    Linear Discriminant Analysis (LDA): technique for finding the best linear combination of features in order to characterise different observations. Roughly, it helps us find attributes that are good at differentiating observations.

    K-nearest neighbours (KNN): method used to estimate an attribute of a specific observation, based on the K observations that are the most similar to it.

    Decision tree: it is a very intuitive model used to estimate a certain characteristic (numeric or not) for a given variable, based on decision rules:

    Simple decision tree taken from this article: https://bit.ly/2vwM2fp

    Time series: there is a whole domain in statistics dedicated to studying how certain variables fluctuate on time, based on concepts like trend and seasonality.

    Factor analysis: in summary, it tries to find factors that are responsible for the shared variance between the observed variables.

    Cross-validation: to avoid overfitting, we should not test our models on the same data they were trained on. There are many different methods that allow us to do that, such as splitting our sample data into two groups, one for training and one for testing.

    Bootstrapping: it’s a good technique for getting better models, by sampling observations and replacing them within the actual sample.

    Survival analysis: for example, imagine studying impacts of a disease in people’s lifetimes. After 20 years of study, some people have died, some haven’t. How do you deal with those who didn’t die, since you do not know their total lifetime yet? If you remove them from the study, you remove everyone who survived, and you will estimate a lifetime shorter than it actually is. Survival analysis deals with this sort of specificity.

    Statistical computing

    With the advent of computers, most of the calculations needed for statistical analysis can be done within seconds with softwares such as R, which really helped this field to grow, and made statistician’s work a lot easier and more productive. On the other hand, it made it easier to apply methods without mastering how they actually work, leading sometimes to wrong results.

    Conclusion

    The book really covers a broad range of subjects, so of course it is not possible to go too deep in any of them. However, it’s a very good introductory book, specially for those who come from a non-mathematical background. It is important, though, to pick some subjects that seem more relevant to you and study them in more depth. I’ll give it 7/10.