The agentic loop
The agentic loop is the pattern behind every AI agent. It's simple: call the AI → check if it wants to use a tool → execute the tool → send the result back → repeat until the AI gives a final answer. This loop can handle multi-step problems automatically.
The agentic loop
The AI keeps calling tools until it has enough information to answer.
Exactly right. The AI only decides which function to call and what arguments to pass. Your code is responsible for actually executing the function and sending the result back. This is a key safety feature because you control what gets executed.
# Function registry maps tool names to actual Python functions
function_registry = {
"add": add, "multiply": multiply, "divide": divide
}The registry is a dictionary that maps tool names (strings) to functions. When the AI says "call multiply", we look up the actual Python function here.
Now we build the agentic loop function. It sends the user message to the AI along with the tool schemas, then checks whether the AI wants to call a tool or give a final answer.
def call_with_tools(user_message: str, tools: list, max_iterations: int = 5) -> str:
"""The agentic loop: call AI → execute tools → repeat."""
messages = [{"role": "user", "content": user_message}]
for iteration in range(max_iterations):
response = completion(
model=DEFAULT_MODEL,
messages=messages,
tools=tools,
tool_choice="auto"
)
assistant_message = response.choices[0].message
# No tool calls? We have the final answer!
if not assistant_message.tool_calls:
return assistant_message.contentThe loop sends messages to the AI with the available tools. If the AI responds without requesting any tool calls, we have our final answer and return it.
When the AI does request tool calls, we need to execute each one and add the results back to the conversation. The AI can then use those results to make more tool calls or produce a final answer.
# (inside the for loop, after the early return)
messages.append(assistant_message)
# Execute each tool call
for tool_call in assistant_message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
result = function_registry[func_name](**func_args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
return "Max iterations reached"For each tool call, we look up the function in our registry, execute it with the AI-provided arguments, and append the result as a "tool" message. The loop then sends everything back to the AI.
Let us test the complete agentic loop. We will start with a simple single-tool call, then try a complex question that requires chaining multiple tools automatically.
# Simple: one tool call
answer = call_with_tools("What is 15 multiplied by 7?", calculator_tools)
# AI calls: multiply(15, 7) → 105
# Answer: "15 multiplied by 7 equals 105."
# Complex: AI chains multiple tools automatically!
answer = call_with_tools("(25 + 15) x 3 - 10", calculator_tools)
# AI calls: add(25, 15) → 40
# AI calls: multiply(40, 3) → 120
# AI calls: add(120, -10) → 110
# Answer: "The result of (25 + 15) x 3 - 10 is 110."The AI automatically chains tool calls for multi-step problems. Each intermediate result feeds into the next call.
That is exactly why we have max_iterations in the loop. Without it, a confused AI could keep calling tools endlessly. The default limit of 5 iterations is enough for most tasks. In production, you would also add timeout limits and cost tracking to stay safe.
AI prompt: Try it with AI
Loading practice…
Ordering exercise: Agentic loop steps
Loading practice…
Fill in the blanks: Complete the tool calling loop
Loading practice…
Hints: Hints
Loading practice…
You have built the agentic loop, the core pattern behind every AI agent. Next, we will give our agent multiple tools at once and watch it choose the right one for each task.