Close Menu
AI News TodayAI News Today

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Impactful scheduling for GPU clusters

    Microsoft barred from sponsoring foreign workers for US residency

    Where Does the Money Go Across Long-Running Coding Agents?

    Facebook X (Twitter) Instagram
    • About Us
    • Contact Us
    Facebook X (Twitter) Instagram Pinterest Vimeo
    AI News TodayAI News Today
    • Home
    • AI News
    • AI Reviews
    • AI Tools
    • AI Tutorials
    • Chatbots
    • Free AI Tools
    • Artificial Intelligence
    AI News TodayAI News Today
    Home»AI Tools»Build Your First AI Agent with One Tool Call
    AI Tools

    Build Your First AI Agent with One Tool Call

    By No Comments13 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Reddit Telegram Email
    Build Your First AI Agent with One Tool Call
    Share
    Facebook Twitter LinkedIn Pinterest Email

    If you’re embedded in the world of software development, AI, and Large Language Models (LLMs), you probably talk about and use AI agents all the time. If you want to go beyond using agents and start to create your own, this article is for you.

    But before we build our first one, have you ever stopped to think about what an AI agent is? Let’s pin down exactly what we mean by the term — agent.

    As you might expect, there are as many answers to this question as there are LLMs, but I think a broad definition that most agree with is that …

    An AI agent is a software system that uses an LLM that calls one or more tools in a loop to reach a goal.

    The “goal” an agent reaches can be anything from answering a simple question to controlling your browser to book an airline ticket or developing highly complex software.

    Learn this step by step with the interactive AI Agents roadmap.

    Bear in mind that in agentic systems, the loop describes the cycle between the model, the application and its tools. It doesn’t require a literal while or for statement. The example I’ll use makes one pass through that cycle: the model requests a tool, Python runs it, and the model uses the result to answer. Because one turn is enough, the steps are written out explicitly.

    In the rest of this article, I’ll show you how to build your first AI agent. It won’t do anything complex, but that’s by design to keep things simple to begin with, not because it can’t.

    Table of contents

    1. The problem our AI agent will solve
    2. What you’ll need
    3. Create the project
    4. Creating our test database
    5. Creating our AI agent
    6. How the agent code works
    7. Running the agent
    8. Testing the failure paths
    9. See the schema the model receives
    10. Debugging the output if the model gets it wrong
    11. Using a different model
    12. Summary

    The problem our AI agent will solve

    Suppose you run a small online shop and want a support assistant that can answer questions about orders. The order data sits in a database, so a language model can’t answer those questions on its own. It needs a controlled way to ask your application for the facts.

    We’ll build that controlled route using a fictional shop and a small SQLite database. The database has two tables. The orders table stores the order number, customer and date. The order_items table stores the products, quantities, and prices for each order.

    Our sample database contains order 1001 for Sarah Jones. She bought one mechanical keyboard for GBP 79.95 and two USB-C cables at GBP 8.50 each. The correct order total is therefore GBP 96.95.

    Rather than making the user look up the order and write SQL, we’ll let them ask an LLM:

    What is the total value of order 1001?

    The model will recognise that it needs an order total and request a Python function called get_order_total. That function will query SQLite and return the real value. The model can then turn the result into an ordinary sentence.

    The model can ask for an operation, but Python decides whether it’s allowed and performs it.

    Our first version has four moving parts:

    1. A language model receives a question and a description of a Python function.
    2. The model asks for that function to be called with some arguments.
    3. Python checks the requested function name, calls the permitted function and sends its result back.
    4. The model writes an answer using the result.

    The model doesn’t run the function or connect to SQLite. Your Python program still controls both. We’ll run the model locally through Ollama, so the example doesn’t need a cloud account or an API key. The program will also print the requested function, its arguments and the database result, making the exchange visible rather than hiding it inside an agent framework.

    The code in this article uses Ollama’s Python library, but the general client-side tool pattern is shared by other LLM APIs, including OpenAI and Anthropic. SDK classes, field names, and message formats differ by provider, so this script isn’t portable as-is. The parts worth carrying across are the allowlist, argument validation, function dispatch, returned tool result and bounded loop.

    What you’ll need

    You’ll need Windows 10 or 11, enough free disk space for a local model, and PowerShell. The example also works on macOS and Linux, although the installation commands differ.

    We’ll use uv to install Python and manage the project. If uv isn’t already installed, open PowerShell and run:

    PS D:projects> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

    Close PowerShell, open a fresh window and check it:

    PS D:projects> uv --versionuv 0.12.5 (210d1f678 2026-08-14 x86_64-pc-windows-msvc)

    Next, install Ollama. If you’ve never heard of Ollama, it’s a versatile, powerful tool that lets you run large language models directly on your local machine, as long as you have enough RAM to accommodate your chosen model. Download the Windows installer from https://ollama.com/download/windows, run it, then open a new PowerShell window and run the command shown below.

    PS D:projects> ollama pull qwen3.5:4bpulling manifestpulling 81fb60c7daa8: 100% ▕██████████████████████████████████████████████████████████▏ 3.4 GBpulling 7339fa418c9a: 100% ▕██████████████████████████████████████████████████████████▏  11 KBpulling 9371364b27a5: 100% ▕██████████████████████████████████████████████████████████▏   65 Bpulling de9fed2251b3: 100% ▕██████████████████████████████████████████████████████████▏  475 Bverifying sha256 digestwriting manifestsuccess

    The download is about 3.4 GB as of this writing. Model availability and sizes can change, so check for errors when you run the command above, and make sure the model fits in your memory.

    Create the project

    Create a new directory and add the Ollama Python library:

    uv init local-shop-agentcd local-shop-agentuv python pin 3.14uv add ollama

    uv will download Python 3.14 if it can’t find a suitable copy. You don’t need to activate the virtual environment; uv run will use it automatically.

    We’ll build the database before involving the model. That gives us something deterministic to check: if the database contains the wrong data, there’s no point in debugging the agent.

    The database will sit beside the Python scripts and can be recreated whenever you want to reset the example. 

    Creating our test database

    Create the file build_shop_database.py with this code:

    import sqlite3from contextlib import closingfrom pathlib import PathDATABASE_PATH = Path(__file__).with_name("shop_agent.db")DATABASE_PATH.unlink(missing_ok=True)with closing(sqlite3.connect(DATABASE_PATH)) as connection:    connection.executescript(        """        CREATE TABLE orders (            order_id INTEGER PRIMARY KEY,            customer_name TEXT NOT NULL,            order_date TEXT NOT NULL        );        CREATE TABLE order_items (            order_id INTEGER NOT NULL REFERENCES orders(order_id),            product_name TEXT NOT NULL,            quantity INTEGER NOT NULL,            unit_price REAL NOT NULL        );        """    )    connection.execute(        "INSERT INTO orders VALUES (?, ?, ?)",        (1001, "Sarah Jones", "2026-08-03"),    )    connection.executemany(        "INSERT INTO order_items VALUES (?, ?, ?, ?)",        [            (1001, "Mechanical keyboard", 1, 79.95),            (1001, "USB-C cable", 2, 8.50),        ],    )    connection.commit()print(f"Created {DATABASE_PATH}")

    Run it like this:

    PS D:projectslocal-shop-agent> uv run python build_shop_database.pyCreated D:projectslocal-shop-agentshop_agent.db

    SQLite is part of Python’s standard library, so you don’t need to install a database server. The closing(…) wrapper is required because, although sqlite3.Connection used in a plain with block handles transactions, it doesn’t close the connection when the block ends. Explicitly closing it avoids a locked database file on Windows

    Creating our AI agent

    Create the file single_tool.py and insert the following code. 

    import jsonimport osimport sqlite3from contextlib import closingfrom pathlib import Pathfrom ollama import chatMODEL = os.getenv("OLLAMA_MODEL", "qwen3.5:4b")DATABASE_PATH = Path(__file__).with_name("shop_agent.db")def get_order_total(order_id: int) -> str:    """Calculate the total value of an order.    Args:        order_id: The numeric order ID.    Returns:        JSON containing the order ID, item count and total in pounds.    """    uri = f"file:{DATABASE_PATH.as_posix()}?mode=ro"    with closing(sqlite3.connect(uri, uri=True)) as connection:        connection.row_factory = sqlite3.Row        row = connection.execute(            """            SELECT                o.order_id,                COALESCE(SUM(oi.quantity), 0) AS item_count,                ROUND(                    COALESCE(SUM(oi.quantity * oi.unit_price), 0),                    2                ) AS total_gbp            FROM orders AS o            LEFT JOIN order_items AS oi ON oi.order_id = o.order_id            WHERE o.order_id = ?            GROUP BY o.order_id            """,            (order_id,),        ).fetchone()    if row is None:        return json.dumps({"error": f"Order {order_id} wasn't found"})    return json.dumps(dict(row))messages = [    {        "role": "system",        "content": (            "Use tools for shop facts. Write currency as GBP, "            "without a currency symbol."        ),    },    {        "role": "user",        "content": "What is the total value of order 1001?",    },]response = chat(    model=MODEL,    messages=messages,    tools=[get_order_total],)messages.append(response.message)if not response.message.tool_calls:    raise RuntimeError("The model answered without calling get_order_total")for call in response.message.tool_calls:    if call.function.name != "get_order_total":        raise RuntimeError(            f"The model requested an unknown tool: {call.function.name}"        )    arguments = call.function.arguments    print(f"Model requested: get_order_total({arguments})")    result = get_order_total(**arguments)    print(f"Python returned: {result}")    messages.append(        {            "role": "tool",            "tool_name": call.function.name,            "content": result,        }    )final_response = chat(    model=MODEL,    messages=messages,    tools=[get_order_total],)print(f"Agent: {final_response.message.content}")

    How the agent code works

    The MODEL variable contains the Ollama model name. Reading it from the OLLAMA_MODEL environment lets you try another installed model without editing the script. DATABASE_PATH points to the database file shop_agent.db in the same directory as single_tool.py, so the command works regardless of your current drive or user folder.

    get_order_total() is ordinary application code. It opens SQLite in read-only mode, runs a parameterised query and returns JSON. The model never sees the SQL and can’t connect to the database itself. 

    The function’s name, type annotation and docstring also describe the tool to Ollama. When the script passes tools=[get_order_total], the Ollama library converts that information into a JSON schema. It sends the schema to the model, not the Python source code.

    messages is the conversation so far. The system message tells the model how to behave, and the user message contains the question. The first chat() call sends those messages along with the tool schema

    That call doesn’t execute the get_order_total() tool. If the model decides it needs the tool, it returns a request in response.message.tool_calls. For this question, the request is equivalent to:

    The script appends response.message to the history because the next model call needs to see its own tool request. It then checks that a tool was requested and that its name is exactly get_order_total. 

    This line is where the tool actually runs:

    result = get_order_total(**arguments)

    **arguments turns {“order_id”: 1001} into the normal Python call get_order_total(order_id=1001). The result is added to the conversation with the tool role. tool_name tells the model which requested function produced it.

    The second chat() call receives the complete history: the original question, the model’s tool request and the JSON result returned by Python. The model now has all the information it needs and can write the final sentence. 

    Running the agent

    Just run it like you would any other regular Python script, like this

    PS D:projectslocal-shop-agent> uv run python single_tool.py

    The first run may pause while Ollama loads the model into memory. On my test run, the program printed:

    Model requested: get_order_total({'order_id': 1001})Python returned: {"order_id": 1001, "item_count": 3, "total_gbp": 96.95}Agent: The total value of order 1001 is 96.95 GBP.

    The final sentence can vary. Local language models generate text rather than selecting a fixed response, but the value should remain 96.95 because Python calculated it. For example, when I ran it a second time, I got a slightly different output.

    Model requested: get_order_total({'order_id': 1001})Python returned: {"order_id": 1001, "item_count": 3, "total_gbp": 96.95}Agent: The total value of order 1001 is 96.95 GBP. This order contains 3 items with a combined total of 96.95 British Pounds.

    Testing the failure paths

    What happens if we change the question to an order that doesn’t exist:

    "content": "What is the total value of order 9999?"

    This was my output.

    Model requested: get_order_total({'order_id': 9999})Python returned: {"error": "Order 9999 wasn't found"}Agent: I was unable to calculate the total value of order 9999 because the order wasn't found in our system. It's possible there may be a typo or the order ID might need verification.

    The function returns structured error data. The model should explain that the order wasn’t found instead of inventing a total.

    Now to ask something the tool can’t establish:

    "content": "When will order 1001 arrive?"

    My response was:

    Model requested: get_order_total({'order_id': 1001})Python returned: {"order_id": 1001, "item_count": 3, "total_gbp": 96.95}Agent: I'm sorry, but I can only calculate the total value of orders with my available tool. For order 1001, the total amount is £96.95 (3 items), but I don't have access to information about delivery or arrival dates. To find out when your order will arrive, you'll need to check with the shop directly or look at the order confirmation email you received.

    A model may still answer from general knowledge or guess. The system message helps, but it isn’t a hard security boundary. If an answer must come from verified data, your program needs to check that an appropriate tool was called and decide what to do when it wasn’t. Still, the answer we got was pretty good in my opinion.

    See the schema the model receives

    The convenience of passing a Python function directly can hide an important detail. Ollama isn’t sending your source code to the model. The SDK inspects the function and produces a JSON schema similar to this:

    { "type": "function", "function": { "name": "get_order_total", "description": "Calculate the total value of an order.", "parameters": { "type": "object", "required": ["order_id"], "properties": { "order_id": { "type": "integer", "description": "The numeric order ID."     }   }  } }}

    The model sees this alongside the conversation. Its task is to decide whether the function is relevant and, if so, produce arguments matching the schema. That’s why adding a good docstring to your function is important, as the model can make good use of it.

    Schema generation isn’t validation. A model can still send a string where an integer was requested, add a field that doesn’t exist or request a function that wasn’t advertised. We’ll learn how to deal with some of these issues in future parts.

    Debugging the output if the model gets it wrong

    When an agent gives a wrong answer, start with the tool trace. Three different failures may look similar to the user.

    • If no tool_calls were returned, the model decided it could answer without the function. Tighten the system message, improve the tool description or use a model with stronger tool-calling behaviour.

    • Double-check what you sent as an argument to Python by examining the output of the print call.function.arguments, as this script does. The database can’t return order 1001 when the model asked for 1010.

    • If the Python result was correct but the final answer was wrong, the failure happened while the model interpreted the tool output. Keep results short and structured. For example, {“total_gbp”: 96.95} is harder to misread than a paragraph containing several numbers. If all else fails, you may need a stronger model.

    Using a different model

    The script reads the OLLAMA_MODEL environment variable, making it easy to switch models if needed. Use Ollama to pull another tool-capable model, then set the variable for one PowerShell session. For example,

    PS D:projectslocal-shop-agent> ollama pull gpt-oss:20bPS D:projectslocal-shop-agent> $env:OLLAMA_MODEL = "gpt-oss:20b"PS D:projectslocal-shop-agent> uv run python single_tool.py

    A larger model needs more memory and disk space and will usually respond more slowly on the same hardware. Don’t assume that a fluent final sentence means better tool use. Try missing order IDs, irrelevant questions and ambiguous wording, then compare the requested function and arguments.

    Summary

    Starting with a definition of what an AI agent really is, this article showed you how to build a simple agent using Python, Ollama and SQLite, running locally without a cloud account or API key. 

    Using a fictional shop’s order data, it walked through how a language model requests a tool, how Python checks and executes that request, and how the model turns the result into an answer. 

    Along the way, you learned how tool schemas work, and how to diagnose issues when the model gets things wrong.

    This deliberately simple example keeps the mechanics visible and provides a starting point for agents that use multiple tools across several steps.

    That’s the job of the next article.

    agent build call tool
    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Previous ArticleGoogle brings agentic AI to Gemini, starting with businesses
    Next Article Rocket Report: A rare Falcon 9 scrub; is it just about Vulcan’s time to shine?
    • Website

    Related Posts

    AI Tools

    Where Does the Money Go Across Long-Running Coding Agents?

    AI Tools

    Stop Using AI. Start Hiring It.

    AI Tools

    AI Agents Beat PyTorch: Writing Faster CUDA Kernels

    Add A Comment
    Leave A Reply Cancel Reply

    Top Posts

    Impactful scheduling for GPU clusters

    0 Views

    Microsoft barred from sponsoring foreign workers for US residency

    0 Views

    Where Does the Money Go Across Long-Running Coding Agents?

    0 Views
    Stay In Touch
    • Facebook
    • YouTube
    • TikTok
    • WhatsApp
    • Twitter
    • Instagram
    Latest Reviews
    AI Tutorials

    Quantization from the ground up

    AI Tools

    David Sacks is done as AI czar — here’s what he’s doing instead

    AI Reviews

    Judge sides with Anthropic to temporarily block the Pentagon’s ban

    Subscribe to Updates

    Get the latest tech news from FooBar about tech, design and biz.

    Most Popular

    Impactful scheduling for GPU clusters

    0 Views

    Microsoft barred from sponsoring foreign workers for US residency

    0 Views

    Where Does the Money Go Across Long-Running Coding Agents?

    0 Views
    Our Picks

    Quantization from the ground up

    David Sacks is done as AI czar — here’s what he’s doing instead

    Judge sides with Anthropic to temporarily block the Pentagon’s ban

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    Facebook X (Twitter) Instagram Pinterest
    • About Us
    • Contact Us
    • Terms & Conditions
    • Privacy Policy
    • Disclaimer

    © 2026 ainewstoday.co. All rights reserved. Designed by DD.

    Type above and press Enter to search. Press Esc to cancel.