Build a Working AI Agent Loop in Just 50 Lines of Python
This tutorial walks through a minimal 50‑line Python implementation of an AI Agent Loop, covering the core four‑step cycle, dual termination strategies, deterministic vs. autonomous designs, tool registration, and a complete runnable example.
Minimal Agent Loop Skeleton
The loop consists of four simple steps: (1) append the user’s input to a messages list, (2) send the list to an LLM, (3) inspect the LLM’s response for tool_calls, and (4) either execute the requested tool(s) and feed the result back into messages or return the final reply when no tool is needed. The outer for i in range(max_iterations) provides a hard iteration bound.
import json
from openai import OpenAI
client = OpenAI()
tools = {}
def register_tool(name, func, description, schema):
"""Register a tool with its function and JSON schema."""
tools[name] = {"func": func, "schema": {"type": "function", "function": {"name": name, "description": description, "parameters": schema}}}
def run_agent(user_input, system_prompt="You are a helpful assistant.", max_iterations=10):
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input},
]
for i in range(max_iterations):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=[t["schema"] for t in tools.values()] if tools else None,
)
msg = response.choices[0].message
messages.append(msg)
if not msg.tool_calls:
return msg.content # No tool needed – loop ends
for tool_call in msg.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if name not in tools:
result = f"Error: unknown tool {name}"
else:
try:
result = tools[name]["func"](**args)
except Exception as e:
result = f"Tool execution failed: {e}"
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(result)})
return "Maximum iterations reached, loop terminated."Termination Strategies
The max_iterations parameter acts as a safety net to prevent infinite loops when an LLM repeatedly calls the same tool or never signals completion. The second strategy relies on the LLM itself: if the response contains no tool_calls, the agent assumes the task is finished and returns the content. Combining both ensures graceful exits in normal cases and hard stops in pathological ones.
Source: Loop workflow – Google ADK [1]
Deterministic vs. Autonomous Loops
Google ADK’s LoopAgent follows a deterministic design: sub‑agents are pre‑ordered (e.g., CriticAgent → RefinerAgent) and the loop runs them in that fixed sequence until an exit_loop signal is emitted. This is ideal for repeatable pipelines such as "review → modify → review → modify…".
In contrast, an autonomous loop lets the LLM decide the next action based on the current context—e.g., reading a file, fixing a bug, or running a test—making the workflow dynamic and adaptable.
Adding Tools to the Loop
To make the agent useful, two example tools are registered: search_files (searches filenames by pattern) and read_file (reads file content with a 2000‑character limit). Each registration includes a clear description and a JSON schema that specifies required parameters, enabling the LLM to choose the correct tool.
import os
def search_files(pattern, directory="."):
"""Search for filenames containing the given pattern."""
results = []
for root, dirs, files in os.walk(directory):
for f in files:
if pattern.lower() in f.lower():
results.append(os.path.join(root, f))
return "
".join(results) if results else "No matching files"
def read_file(path):
"""Read file content, truncating to 2000 characters."""
if not os.path.exists(path):
return f"File not found: {path}"
with open(path, "r") as f:
return f.read()[:2000]
register_tool(
"search_files",
search_files,
"Search for files whose names contain a keyword",
{"type": "object", "properties": {"pattern": {"type": "string", "description": "Filename keyword"}, "directory": {"type": "string", "description": "Search directory, default current"}}, "required": ["pattern"]}
)
register_tool(
"read_file",
read_file,
"Read the content of a specified file",
{"type": "object", "properties": {"path": {"type": "string", "description": "File path"}}, "required": ["path"]}
)Running the Loop
Example invocation:
result = run_agent(
"Help me find a test file in the project and show its content",
system_prompt="You are a code assistant with file‑search and read capabilities.",
max_iterations=10,
)
print(result)The agent will (1) call search_files with the keyword "test", (2) pick one of the returned files, (3) call read_file on it, and (4) summarize the result for the user.
Limitations and Next Steps
No parallel tool execution – tasks are processed sequentially.
No context window management – long conversations may exceed token limits.
Lacks observability – internal reasoning is only visible via prints.
No hook system – cannot inject custom logic before/after tool calls.
Result handling is simplistic; production SDKs expose richer ResultMessage statuses (success, error_max_turns, error_max_budget_usd, error_during_execution, error_max_structured_output_retries).
The next article will dive into the inner workings of a single message inside an Agent, revealing why agents sometimes call multiple tools or pause unexpectedly.
Source: Loop workflow – Google ADK [2]
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Qborfy AI
A knowledge base that logs daily experiences and learning journeys, sharing them with you to grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
