Module 1 · Lesson 10

Build Your First AI Agent in Python

In this lesson we will build a small tool-using agent with the current OpenAI Agents SDK, then map the code back to the agent loop you learned earlier.

Updated August 2026 · Python · OpenAI Agents SDK

What we are building

The agent will answer developer-support questions. It will have one deterministic tool that returns the deployment status of a project. The example is intentionally small so you can see the architecture clearly.

Prerequisites

The current OpenAI Agents SDK quickstart installs the package as openai-agents.

1. Create a project

mkdir first-agent
cd first-agent
python -m venv .venv
source .venv/bin/activate

On Windows, activate the virtual environment using the appropriate .venv\Scripts\activate command.

2. Install the SDK

pip install openai-agents

Set your API key in the environment rather than hard-coding it into source control.

export OPENAI_API_KEY="your-key-here"

3. Create a basic agent

from agents import Agent, Runner

agent = Agent(
    name="Developer Support Agent",
    instructions=(
        "Help developers understand project status. "
        "Be concise and do not invent deployment information."
    ),
)

result = Runner.run_sync(
    agent,
    "Explain what a health check endpoint is."
)

print(result.final_output)

This already uses an agent runtime, but it behaves much like a normal model-backed assistant because we have not added any external capability.

4. Add a function tool

The Agents SDK can turn a Python function into a tool and derive a schema from its type annotations and documentation.

from agents import Agent, Runner, function_tool

@function_tool
def get_deployment_status(project: str) -> str:
    """Return the current deployment status for a known project."""
    statuses = {
        "api": "healthy",
        "worker": "degraded",
        "dashboard": "healthy",
    }
    return statuses.get(project.lower(), "unknown project")

agent = Agent(
    name="Developer Support Agent",
    instructions=(
        "Help developers investigate deployment status. "
        "Use the deployment-status tool when status data is needed. "
        "Never invent a status."
    ),
    tools=[get_deployment_status],
)

result = Runner.run_sync(
    agent,
    "Is the worker healthy right now?"
)

print(result.final_output)

What happens during this run?

  1. The runner sends the user request, instructions and tool definition to the model.
  2. The model sees that current deployment status requires the tool.
  3. It requests get_deployment_status with an argument such as {"project":"worker"}.
  4. The SDK executes your Python function.
  5. The result degraded is returned to the model.
  6. The model produces the final user-facing answer.

That is the agent loop in practical form.

5. Replace the toy dictionary with a real service

In production, the tool can call an internal API or service layer. Keep credentials and authorization in your application, not in model context.

@function_tool
def get_deployment_status(project: str) -> dict:
    """Fetch deployment health for one approved project."""
    if project not in ALLOWED_PROJECTS:
        return {"ok": False, "error": "unknown_project"}

    response = deployment_client.get_status(project)
    return {
        "ok": True,
        "project": project,
        "status": response.status,
        "checked_at": response.checked_at,
    }

Why normalize the result?

Your downstream API might return logs, internal hostnames and dozens of fields. The model only needs the information required for the next decision. A small structured response is cheaper, safer and easier to reason about.

6. Add tool errors intentionally

Do not return stack traces to the model. Convert expected failures into a small domain-level result.

{
  "ok": false,
  "error": "service_unavailable",
  "message": "Deployment status is temporarily unavailable."
}

Your application can separately log the real exception with correlation IDs for debugging.

7. Add limits before adding more autonomy

A production agent should have explicit boundaries: tool timeouts, request timeouts, step limits, read/write separation and human approval for sensitive actions. The exact controls depend on your runtime and product.

8. Inspect traces

OpenAI's current Agents SDK includes tracing for agent runs, model generations, function tool calls, handoffs and guardrails. Tracing helps answer questions such as: Why did the model choose this tool? How many turns did the task require? Which tool failed?

Common beginner mistakes

Putting business logic in the prompt

“Only deploy if tests pass” is useful guidance, but the deploy function itself should verify test status before making a deployment.

Giving the agent one giant generic tool

A tool like run_admin_action(action, payload) is difficult to secure and difficult for the model to use reliably. Prefer narrow business capabilities.

Hard-coding secrets

Keep API keys in environment variables or a secret manager. Never return credentials in tool output.

Skipping evaluation

Create a small test set before adding more tools. Include cases where the correct behavior is to ask for clarification or refuse to call a tool.

Where MCP fits next

Our tool currently lives directly inside the agent application. That is fine for local capabilities. MCP becomes valuable when you want a standardized server that can expose tools, resources or prompts to multiple AI clients. The later modules of this course will build those servers from scratch.

You now have the complete Module 1 mental model: the model decides; tools expose capabilities; the runtime executes the loop; deterministic code controls permissions and side effects.

Official references