Agents vs. a Single API Call — When You Need One
What Every Previous Unit Had in Common
Every example from Unit 1 through Unit 10 shared one structural property: your own application code was in control of the loop. You decided when to call client.responses.create(), you decided what to do with the result, and if the result included a function call (Unit 8) or a built-in tool call (Unit 9), your code — or the platform, for a built-in tool — handled it and fed the outcome back in. The model never decided on its own to keep working after handing control back to you; every round trip through the loop was something your code explicitly orchestrated.
This works well for a bounded number of well-understood steps. It becomes noticeably more work as a task grows in one specific way: when the number of steps needed to complete it isn't known in advance, when several distinct roles or areas of specialization need to hand a task to each other, or when the task benefits from a model reasoning about its own next step — "have I gathered enough information yet?", "should I hand this off to a specialist?" — rather than your application code making that decision on the model's behalf every single time.
What an Agent Actually Is
An agent, in the sense this unit uses the term, is a configuration bundling together a model, a set of instructions, and a set of tools it's allowed to use — paired with a runtime that manages the loop of calling the model, executing any tool calls it requests, and feeding the results back in, repeating until the agent produces a final answer, without your application code manually orchestrating each round trip.
from agents import Agent, Runner
triage_agent = Agent(
name="Triage Agent",
instructions="Help the user with general questions about their account.",
model="gpt-5.6-terra",
)
result = Runner.run_sync(triage_agent, "What's the status of my last order?")
print(result.final_output)
Note: The exact package name, import paths, and class and method names for the Agents SDK can change across versions. Confirm the current package name, installation instructions, and API surface against the current official documentation before relying on these specifics in production code.
Two things are worth noticing here, in contrast to every prior unit's client.responses.create() pattern. First, there is no explicit loop in this code at all — Runner.run_sync() is what manages calling the model, checking whether it produced a final answer or requested a tool, and continuing until a final answer is reached; Unit 8's run_conversation_with_tools() function, which you wrote and controlled yourself, is functionally replaced here by code the SDK provides. Second, an Agent is a reusable, named configuration — you define it once, with its instructions and model, and can run it against many different inputs, rather than building the whole request from scratch every time.
Why Not Just Use client.responses.create() With a Bigger Loop?
Nothing about function calling or built-in tools (Units 8 and 9) technically requires the Agents SDK — every one of this course's earlier projects built exactly this kind of multi-step, multi-tool interaction using client.responses.create() directly, with your own code managing the loop. The Agents SDK doesn't add a capability that wasn't otherwise possible; it removes boilerplate and adds structure for a specific category of application: one involving multiple distinct roles that need to hand work to each other, safety checks that need to run consistently across every step, or a genuinely unpredictable number of steps where writing your own loop-with-a-safety-cap (as Unit 8, Lesson 3's max_rounds did) starts to feel like reimplementing something that already exists.
| Aspect | client.responses.create() with your own loop (Units 8-9) | The Agents SDK (this unit) |
|---|---|---|
| Who manages the tool-call loop | Your application code, explicitly | The SDK's Runner, automatically |
| Multiple specialized roles | Built manually — separate functions, separate prompts, your own routing logic | Built in — separate Agent instances with a handoff mechanism between them |
| Consistent safety checks across steps | Applied manually wherever you remember to add them | Guardrails apply consistently through the SDK's runtime |
| Visibility into what happened during a run | Whatever you build yourself (Unit 8's log_tool_selection_for_test_questions(), for instance) | Built-in tracing (Lesson 6) |
| Best suited for | A well-defined, bounded interaction with tools you fully control | A task better modeled as multiple cooperating roles, or with a genuinely open-ended number of steps |
Neither column is a strictly better way to build with the platform; they're suited to different shapes of problem. A single well-defined task — Unit 8's weather assistant, Unit 9's research assistant — is entirely reasonable to build directly against client.responses.create(), and doing so gives you full, direct control over every step. A system that needs several distinct roles cooperating, consistent safety enforcement across an unpredictable number of steps, or built-in observability into a complex run is where the Agents SDK's structure starts to pay for itself.
When You Actually Need an Agent, Rather Than a Direct API Call
Reach for the Agents SDK specifically when a task exhibits one or more of these properties: multiple distinct roles that benefit from being modeled as separate agents handing work to each other (a triage agent routing to a billing specialist or a technical specialist, covered in Lesson 4), a genuinely unpredictable number of steps where a fixed max_rounds safety cap (Unit 8, Lesson 3) feels like an awkward substitute for letting the runtime manage iteration itself, a need for safety checks — input validation, output review — that must apply consistently regardless of which specific tool or model call is happening at any given moment (Lesson 5), or a need to inspect and debug exactly what happened across a complex, multi-step interaction after the fact (Lesson 6).
Conversely, a single, well-scoped task with a known, bounded set of tools — extracting structured data from a document (Unit 6), answering a question using a fixed set of built-in tools (Unit 9) — has no particular need for the Agents SDK's additional structure. Building it directly against client.responses.create(), as this course did throughout Units 1 through 10, remains the simpler and entirely sufficient choice. This unit's remaining lessons build up the Agents SDK's core pieces — defining an agent, giving it tools, handoffs between agents, guardrails, and tracing — culminating in a project (Lesson 7) that genuinely needs several of these properties at once, which is where the SDK's value becomes concrete rather than abstract.
Seeing the Equivalence Directly
It's worth making the equivalence between the two approaches concrete rather than taking it on faith. A simplified version of what Runner.run_sync() does internally looks structurally identical to the tool-calling loop Unit 8, Lesson 3 built by hand.
import json
def run_agent_loop_by_hand(client, instructions: str, tools: list[dict], available_functions: dict, user_input: str, max_rounds: int = 5):
input_messages = [{"role": "user", "content": user_input}]
for _ in range(max_rounds):
response = client.responses.create(
model="gpt-5.6-terra",
instructions=instructions,
input=input_messages,
tools=tools,
)
function_calls = [item for item in response.output if item.type == "function_call"]
if not function_calls:
return response.output_text
for call in function_calls:
arguments = json.loads(call.arguments)
result = available_functions[call.name](**arguments)
input_messages.append(call)
input_messages.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
})
return "Max rounds reached without a final answer."
This is, structurally, the same max_rounds-bounded loop Unit 8, Lesson 3 introduced: call the model, check for function calls, execute them, feed the results back in, repeat until a final answer or a safety cap is reached. Runner.run_sync() performs this same sequence of steps internally; the Agents SDK's value isn't a different underlying mechanism, it's not having to write and maintain this loop yourself, plus the additional structure (handoffs, guardrails, tracing) layered on top of it in the lessons that follow.
Common Mistakes
Reaching for the Agents SDK for a task that's really just a single well-defined tool-calling interaction, taking on additional structure and a new dependency for a problem Unit 8's direct client.responses.create() loop already solves cleanly.
Assuming the Agents SDK adds a fundamentally new capability, rather than understanding it as a structured way to build the same kind of multi-step, multi-tool interaction this course has already been building directly since Unit 8.
Building a single monolithic agent to handle a task that's naturally several distinct roles, missing the opportunity to model it as multiple cooperating agents with clear handoffs, which Lesson 4 covers directly.
Best Practices
Default to a direct client.responses.create() call for a single, well-scoped task, reserving the Agents SDK for tasks that genuinely need multiple cooperating roles, consistent cross-step safety enforcement, or built-in observability.
Recognize the specific properties that make the Agents SDK worth its added structure — multiple roles, an unpredictable number of steps, cross-cutting safety requirements, or a need for detailed run inspection — rather than adopting it as a default framework for every tool-using task.
Treat what you already know about tool calling (Unit 8) and built-in tools (Unit 9) as directly transferable, since the Agents SDK builds on the same underlying model behavior this course has already covered in depth, just with a different runtime managing the loop.