Skip to content
AI Agents

Building Your First AI Agent with Python

Build a read-only lookup agent with the Responses API, validation, timeouts, and a bounded loop.

Overview

For learners who want to complete this practical exercise with inspectable sample data and explicit checks.

  • Declare a tool with JSON Schema and execute it in the application.
  • Return tool output with the correct call_id and preserve response history.
  • Stop at a final answer or when the budget is exhausted.

You will build: agent.py answers from fictional inventory, handles unknown SKUs, and stops after at most four model turns.

Prerequisites

  • Python 3.10+, a virtual environment, and basic dict/function knowledge.
  • Locally configured OPENAI_API_KEY and OPENAI_MODEL; choose a model supporting Responses and function calling.

The main example makes billed API calls when run live. A ChatGPT subscription does not by itself establish API credits. Testing execute with fixtures requires no key.

Step-by-step tutorial

Conceptual practice workflow; not measured results.

Step 1. Install the SDK in a separate environment

SHELL
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install openai

The activation command above is for macOS/Linux; PowerShell uses .venv\Scripts\Activate.ps1. Set the key through your local environment-management mechanism, not in source or the browser. Set OPENAI_MODEL to an actual accessible model supporting tool calling; do not leave a placeholder ID.

Step 2. Write the complete agent loop

Save this as agent.py. The SDK is imported only when the file is run directly, so execute can be tested without installing it. The history preserves response.output items needed by later turns, and each tool result carries the matching call_id.

PYTHON
import json
import os

TOOLS = [{"type": "function", "name": "lookup_stock",
    "description": "Read fictional training inventory, never live stock.",
    "strict": True, "parameters": {"type": "object",
    "properties": {"sku": {"type": "string"}},
    "required": ["sku"], "additionalProperties": False}}]

def execute(name, raw):
    if name != "lookup_stock":
        raise ValueError("unknown tool")
    args = json.loads(raw)
    if not isinstance(args, dict) or set(args) != {"sku"}:
        raise ValueError("invalid arguments")
    if not isinstance(args["sku"], str) or not 1 <= len(args["sku"]) <= 40:
        raise ValueError("invalid sku")
    stock = {"DEMO-PEN": 7, "DEMO-BOOK": 0}
    sku = args["sku"]
    return {"sku": sku, "units": stock.get(sku), "found": sku in stock}

def run(client, model, question):
    history = [{"role": "user", "content": question}]
    for turn in range(4):
        response = client.responses.create(model=model, input=history,
            tools=TOOLS, max_output_tokens=500,
            instructions="Use lookup_stock before stating quantities. "
            "Label all inventory as fictional. Unknown means unknown.")
        history.extend(response.output)
        calls = [item for item in response.output if item.type == "function_call"]
        if not calls:
            if not response.output_text.strip():
                raise RuntimeError("no final answer")
            return response.output_text
        if len(calls) > 3:
            raise RuntimeError("too many tool calls")
        for call in calls:
            try:
                result = execute(call.name, call.arguments)
            except (ValueError, TypeError) as error:
                result = {"error": str(error)}
            history.append({"type": "function_call_output",
                "call_id": call.call_id, "output": json.dumps(result)})
    raise RuntimeError("turn limit reached")

if __name__ == "__main__":
    from openai import OpenAI
    client = OpenAI(timeout=20, max_retries=1)
    print(run(client, os.environ["OPENAI_MODEL"],
              "How many DEMO-PEN units are in the training inventory?"))

Step 3. Test the executor offline first

PYTHON
from agent import execute
assert execute("lookup_stock", '{"sku":"DEMO-PEN"}')["units"] == 7
assert execute("lookup_stock", '{"sku":"MISSING"}')["found"] is False
try:
    execute("delete_all", "{}")
except ValueError:
    pass
else:
    raise AssertionError("unknown tool accepted")
print("executor checks passed")

Save the checks as check_agent.py beside agent.py and run python3 check_agent.py. This deterministic executor test does not establish that a model will always choose the tool or answer correctly.

Step 4. Run a live trial and check the answer

SHELL
python3 agent.py

After configuring API access, run once with fictional data and inspect account usage. The answer should report 7 units in the training inventory; wording will vary. Try DEMO-BOOK and MISSING next. A tool schema does not replace execute validation, and instructions cannot guarantee factual grounding.

Step 5. Exercise limits and network failures

For loop tests, replace the client with a fake that always returns tool calls; it must reach turn limit reached. In live use, observe authentication failures and timeouts rather than retrying indefinitely. Log tool names, call IDs, and status, never keys.

Verification

  • The executor rejects unknown tools and preserves found=false for missing SKUs.
  • Live output is compared with the fixture, and the four-turn limit remains effective.

Troubleshooting

401 or model not found

Cause: The key, access permission, or OPENAI_MODEL is incorrect.

Fix: Check account configuration and official model IDs without logging the key.

The model keeps calling the tool

Cause: There is no final answer or evidence is insufficient.

Fix: Retain the turn limit and report incompletion rather than increasing it indefinitely.

Exercises & extensions

  • Add a two-turn fake client to verify call_id handling.
  • Continue to tool calling to design another tool and validate its arguments.

Do not connect a real inventory. Offline checks do not verify account billing, model access, or answer quality; live API validation needs separate credentials.

References

  1. OpenAI function calling
  2. OpenAI text generation
  3. Python virtual environments

Keep practicing