Updated October 31, 2026. An AI agent is a software system that uses a model to choose steps, call permitted tools and work toward a goal. You do not need an army of cooperating bots to build one. This practical Python tutorial starts with a small, read-only product-catalog assistant using the OpenAI Agents SDK, then explains state, safety, testing, costs and deployment.
What you will build: a terminal program that answers questions about a tiny predefined product catalog by calling a Python function. It does not browse the web, write files, charge customers or modify any external system. The example requires Python, an OpenAI API key and paid API usage. The code follows the official Agents SDK quickstart; check current documentation if the SDK changes.
1. What is an AI agent?
An AI agent combines a model with instructions and a runtime that can execute approved tools. Given a goal, the model may decide which tool to call, inspect the returned information and produce an answer. An agent is not necessarily autonomous, self-learning or continuously running. An ordinary chatbot may simply answer a prompt; an agent can take multiple constrained steps using tools. A conventional script is often preferable when the steps are already known.
| Approach | Useful when | Main trade-off |
|---|---|---|
| Plain Python script | Rules and steps are fixed | Predictable but less flexible with ambiguous language |
| Chatbot without tools | You need explanations or drafts | Cannot independently verify external facts |
| Tool-using agent | Requests vary and the system must select permitted operations | Requires authorization, monitoring, testing and cost controls |
2. Understand the agent loop
- The user requests something, for example, “Which catalog item costs under $50?”
- The model receives instructions and descriptions of tools.
- The model may request a tool call using structured arguments.
- The application executes only tools it has registered and returns their results.
- The model uses those results to answer or request another authorized tool call.
- The runtime stops on a final response, error or configured limit.
The model does not gain unrestricted access to your computer by being called an agent. Permissions come from the application and its tools. The SDK runner documentation describes this execution loop.
3. Prerequisites: Python and API access
Use a supported Python installation and a fresh virtual environment. Open a terminal in an empty project directory:
mkdir my-first-agent
cd my-first-agent
python -m venv .venv
Activate it on Windows PowerShell:
.venv\Scripts\Activate.ps1
Or on macOS/Linux:
source .venv/bin/activate
Then install the SDK:
python -m pip install --upgrade openai-agents
Create an OpenAI API key using the official developer dashboard. Set it in your current shell without putting it into code or Git:
Windows PowerShell:
$env:OPENAI_API_KEY = "YOUR_API_KEY"
macOS/Linux:
export OPENAI_API_KEY="YOUR_API_KEY"
Security: Never commit real API keys, paste them into a blog comment or embed them in browser-side JavaScript. These commands set an environment variable for the current shell; follow your organization’s secret-management process in deployment. API billing is separate from a ChatGPT subscription.
4. Your first working AI agent
Create a file named agent_demo.py and paste this code:
import asyncio
from agents import Agent, Runner, function_tool
PRODUCTS = {
"notebook": {"price_usd": 12, "stock": 8},
"headphones": {"price_usd": 45, "stock": 3},
"monitor": {"price_usd": 180, "stock": 2},
}
def product_message(name: str) -> str:
"""Look up the price and stock of a catalog product by name."""
key = name.strip().lower()
if key not in PRODUCTS:
return "Product not found. Available: " + ", ".join(PRODUCTS)
item = PRODUCTS[key]
return (
f"{key}: USD {item['price_usd']}; "
f"{item['stock']} units in stock."
)
@function_tool
def lookup_product(name: str) -> str:
"""Look up the price and stock of a catalog product by name."""
return product_message(name)
agent = Agent(
name="Catalog Helper",
instructions=(
"Answer questions only about the provided product catalog. "
"Use lookup_product for product facts. "
"Do not invent prices, inventory, discounts or purchases. "
"If information is unavailable, say so."
),
tools=[lookup_product],
)
async def main():
result = await Runner.run(
agent, "How much are the headphones and are they in stock?"
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
Run python agent_demo.py. The agent should use the read-only tool and respond that the headphones cost USD 45 and have three units in stock. The exact wording and tool choice can vary, so inspect execution traces rather than assuming the model always calls the function. The actual model response requires your own API credentials and was not executed as part of this article.
5. What function calling actually does
Decorating lookup_product makes its description and parameter schema available to the agent runtime. The model can request a call such as lookup_product(name="headphones"). The Python program executes the function; the model cannot run arbitrary Python merely because it requested a tool. A production tool should validate identifiers, authorization and input lengths inside the application. Instructions alone are not a security boundary.
This tutorial intentionally has no purchase or update tool. To support real inventory, connect a trusted, permission-checked data service rather than replacing the hardcoded catalog with unrestricted SQL.
6. State, conversation history and retrieval
An agent can be stateless, use a session to preserve conversational turns, or access a database. These are different choices. You do not need a vector database just to remember an order number or keep the previous question. The SDK supports sessions and explicit history management; consult the sessions documentation. Vector search becomes useful when retrieving relevant passages from a larger collection of documents, not as a mandatory feature of every agent.
Storing history does not mean that the underlying language model has retrained itself. Set retention limits, restrict access and avoid storing secrets or unnecessary personal information.
7. Guardrails, human approvals and prompt injection
A tool-using agent may encounter misleading text from websites, files or tool results. Treat such content as untrusted data. Never allow a retrieved document to redefine the agent’s permissions.
- Allowlist tools: Register only the functions needed for the task.
- Validate arguments: Check IDs, formats, access rights and quotas in application code.
- Require approval: Pause before payments, sending emails, deleting records, deploying code or publishing content.
- Protect credentials: Keep keys server-side and use least-privilege service accounts.
- Bound execution: Configure tool-call limits, retries, timeouts and spending alerts.
- Record decisions safely: Log permitted actions without exposing sensitive data.
The SDK provides guardrail mechanisms and human-in-the-loop approval flows. Neither replaces application-side authorization.
8. Test the agent before deployment
Start by testing the deterministic tool separately from the model. A tiny unit test can check its behavior without any network access:
import copy
import unittest
from agent_demo import PRODUCTS, product_message
class CatalogTests(unittest.TestCase):
def test_known_products(self):
expected = {
"notebook": "notebook: USD 12; 8 units in stock.",
"headphones": "headphones: USD 45; 3 units in stock.",
"monitor": "monitor: USD 180; 2 units in stock.",
}
for name, message in expected.items():
with self.subTest(name=name):
self.assertEqual(product_message(name), message)
def test_case_and_whitespace(self):
self.assertEqual(
product_message(" HEADPHONES "),
"headphones: USD 45; 3 units in stock.",
)
def test_unknown_and_empty_names(self):
expected = "Product not found. Available: notebook, headphones, monitor"
for name in ("unknown", "", " ", "invent a discount"):
with self.subTest(name=name):
self.assertEqual(product_message(name), expected)
def test_lookup_does_not_change_inventory(self):
before = copy.deepcopy(PRODUCTS)
product_message("headphones")
self.assertEqual(PRODUCTS, before)
if __name__ == "__main__":
unittest.main()
Save the tests as test_agent_demo.py beside agent_demo.py, activate the same virtual environment and run python -m unittest -v test_agent_demo. These tests call product_message, the same catalog function used by the agent’s lookup_product tool. They check known items, case and whitespace, unknown and empty names, and unchanged inventory without calling a model. Next, use the reader lab below to check live tool selection, final answers and usage. Local tests do not establish live model reliability or resistance to prompt injection.
Reader lab: test the live model, tool selection and API cost
Validation status, 1 November 2026: local checks passed using Python 3.12.14 and openai-agents 0.24.0: all four unit tests passed, including their parameterised cases, and four direct SDK tool invocations matched the shared catalog function. Live API behaviour and cost have not been verified by TechDrivenz. Follow the reader lab below to test them with your own API account.
To complete the live checks yourself, use your own authorised OpenAI API account with billing enabled. ChatGPT subscription access is separate. Keep your API key in your terminal environment, never in this article’s comments, source code or screenshots. Each attempt can incur charges, including retries and some failed runs.
- Save the earlier example as
agent_demo.pyand activate its virtual environment. For the version checked here, installpython -m pip install openai-agents==0.24.0. - Set
OPENAI_API_KEYas explained above. Choose an API model available to your project that supports function calling. SetOPENAI_MODELto its exact ID, replacing the placeholder below. - Review current model pricing and your API project’s usage settings before running. Start with one prompt.
Windows PowerShell:
$env:OPENAI_MODEL = "YOUR_AVAILABLE_API_MODEL_ID"
macOS/Linux:
export OPENAI_MODEL="YOUR_AVAILABLE_API_MODEL_ID"
Open the live-test script
Save this as live_check.py beside agent_demo.py, then run python live_check.py. It imports the original agent, prints tool calls and usage, and disables SDK tracing for this small test. The original file’s main guard prevents its demo from running on import.
import asyncio
import os
from agents import Runner, RunConfig
from agent_demo import agent
async def main():
if not os.environ.get("OPENAI_API_KEY"):
raise SystemExit("Set OPENAI_API_KEY in your terminal first.")
model = os.environ.get("OPENAI_MODEL")
if not model:
raise SystemExit("Set OPENAI_MODEL to an API model available to you.")
test_agent = agent.clone(model=model)
prompt = "How much are the headphones and are they in stock?"
result = await asyncio.wait_for(
Runner.run(
test_agent, prompt, max_turns=4,
run_config=RunConfig(tracing_disabled=True),
),
timeout=60,
)
print("MODEL:", model)
print("PROMPT:", prompt)
for item in result.new_items:
if item.type == "tool_call_item":
print("TOOL:", getattr(item.raw_item, "name", None))
print("ARGUMENTS:", getattr(item.raw_item, "arguments", None))
elif item.type == "tool_call_output_item":
print("TOOL RESULT:", item.output)
print("FINAL ANSWER:", result.final_output)
usage = result.context_wrapper.usage
print("MODEL REQUESTS:", usage.requests)
print("INPUT TOKENS:", usage.input_tokens)
print("CACHED INPUT TOKENS:",
getattr(usage.input_tokens_details, "cached_tokens", 0))
print("OUTPUT TOKENS:", usage.output_tokens)
print("TOTAL TOKENS:", usage.total_tokens)
if __name__ == "__main__":
asyncio.run(main())
The four-turn limit bounds model turns, not token spending or the number of parallel tool calls. The 60-second timeout is a local waiting limit, not a guarantee that provider processing or billing stops. Review usage if a run fails. Do not retry repeatedly without investigating the error.
What counts as a successful live test?
| Check | What to inspect |
|---|---|
| Tool selection | A printed lookup_product call with the name headphones. A correct answer alone does not prove the tool was used. |
| Tool result | The catalog result reports USD 45 and three units in stock. |
| Final response | Matches those facts without inventing discounts, orders or purchases. Wording can vary. |
| Usage | Record the model ID, model requests, token counts, date, SDK version and pass/fail outcome. |
Then change the script’s prompt and run individual checks for an unknown product, an ambiguous request and a request to invent a discount. Expect unknown items to remain unknown, ambiguity to be handled without fabricated product facts, and discounts not to be invented. Repeat cases to observe variability; a few passes do not establish reliability or resistance to prompt injection.
How to estimate the cost
Use the exact model’s current API pricing. For standard text-token pricing quoted per million tokens, estimate:
Cost = [(input tokens − cached input tokens) × input rate + cached input tokens × cached-input rate + output tokens × output rate] ÷ 1,000,000.
Sum every model request in the run. If the model has different billing rules, service tiers or extra charges, apply those instead. Do not add reasoning tokens again if they are already included in billable output usage. The local Python catalog lookup has no separate external-service charge, but the model’s processing of tool definitions and results consumes tokens.
Compare the estimate with your API usage dashboard, allowing for reporting delay and other activity in the project. Usage counters are not an invoice or a spending cap. A result of zero or missing usage is not proof that an attempted request was free.
If it fails: check the active environment and installed package for import errors; check credentials for authentication errors; check model access for unavailable-model errors; and check billing, quota and rate limits before retrying. A turn-limit error means the agent did not finish within the configured turns. Share only sanitised errors, never keys.
Implementation references: SDK run results and usage accounting. The added script was checked for syntax and configuration compatibility locally; its live API path remains for readers to execute.
9. Monitoring, tracing, cost and errors
The OpenAI Agents SDK includes tracing for model requests, tool calls and agent runs. The tracing documentation explains how to view and configure traces. Review retention and sensitivity before recording customer data or tool results. In production, add error handling around network failures, tool exceptions, invalid input and rate limits. Track model usage, cost per successful task, tool failure rates and escalation frequency. Avoid infinite retry loops.
10. Single-agent or multi-agent?
Start with one agent. Introduce multiple specialized agents only when their separation improves measurable reliability or maintainability. The SDK supports handoffs and using agents as tools, but either approach increases orchestration complexity. Prefer deterministic code for simple branching and business rules.
11. Which framework should you choose?
| Option | Good starting use |
|---|---|
| Plain Python | Fixed workflow with no need for model-directed tools |
| OpenAI Agents SDK | Python agents with defined tools, orchestration and traces |
| OpenAI Responses API | Custom control over model/tool interactions |
| LangGraph | Applications needing explicit stateful graph-based orchestration |
| Haystack | Retrieval-oriented pipelines and search-integrated AI applications |
Check current project documentation, licensing and versions before selecting a library. The best tool depends on your architecture, not a popularity ranking.
12. Production deployment checklist
- Keep API credentials in a server-side secrets manager.
- Separate development, testing and production environments.
- Implement authentication, permissions and per-user limits.
- Review all tools for destructive side effects and require approval where necessary.
- Configure monitoring, trace privacy, timeouts and error budgets.
- Test known tasks and adversarial prompts before release.
- Set API usage budgets and review provider availability and terms.
- Document incident response, rollback and human escalation paths.
Frequently asked questions
Do AI agents always need a vector database?
No. Many useful agents operate with only registered tools, conversation context or a conventional database.
Can an agent automatically learn from every interaction?
Not necessarily. Stored history, retrieval and model training are distinct mechanisms.
Can I build an AI agent without a framework?
Yes. You can manage requests, tool calls, validation and state with your own application code, although a maintained SDK can reduce orchestration work.
Does a ChatGPT subscription cover API usage?
No. OpenAI API usage is billed separately under the API’s pricing and account terms.
Can I give an agent write access to production systems?
Only with narrowly scoped permissions, independently enforced authorization, audit logs, testing and approval controls appropriate to the consequences. Start read-only.
Related TechDrivenz guides
For fundamentals, read What Is ChatGPT and How Does It Work? and our AI and automation learning guide. See AI content detection and its limitations for another example of why generated results need evaluation.
Conclusion
A useful AI agent is not simply a chatbot with an impressive name. It is a controlled application that combines a model with narrowly defined tools, clear instructions, validation, test cases and operational safeguards. Build one simple read-only agent first. Add memory, external actions and multiple agents only when a real requirement justifies the complexity.






