Introduction to LLM Agents · Introduction to LLM Agents · 5 min read
Closing the loop
We have a reasoning step that decides which tool to call, and a dispatcher that executes it. The missing piece is the return trip: sending the tool result back to the LLM so it can formulate a final answer. That third step closes the loop.
The loop is: reason (LLM decides) → act (tool runs) → respond (LLM sees the result and answers).
Sending the tool result back
The OpenAI API requires you to send the tool result as a message with role "tool", tagged with the call_id from the original tool call. The LLM then reads the result and produces its final response:
def respond_step(task: str, decision: dict, tool_result: str) -> str:
"""Give the LLM the tool result and get the final answer.
task: the original user question
decision: the tool call dict from reasoning_step
tool_result: the string returned by call_tool
"""
response = client.chat.completions.create(
model = "gpt-4o-mini",
messages = [
# the original user task
{"role": "user", "content": task},
# the LLM's decision to call a tool (reconstructed)
{
"role": "assistant",
"tool_calls": [
{
"id": decision["call_id"],
"type": "function",
"function": {
"name": decision["tool_name"],
"arguments": decision["arguments"],
},
}
],
},
# the tool's result
{
"role": "tool",
"tool_call_id": decision["call_id"],
"content": tool_result,
},
],
)
return response.choices[0].message.content
The complete agent
The tools, the registry, and the tool descriptions are exactly the ones we built in lessons 1 to 3, so we do not repeat them here. The new piece is the loop itself, plus one small helper that runs a single tool call safely:
import json
# From lessons 1 to 3:
# get_weather, get_news the two tool functions
# TOOL_REGISTRY name -> function lookup
# TOOLS the JSON descriptions the LLM reads
# client the OpenAI client
def run_tool_call(tool_call) -> str:
"""Execute one tool call and return its result as a string.
The LLM generates the arguments as a JSON string, and it can get that
JSON wrong. Parse inside try/except so malformed arguments produce an
error string the LLM can read, instead of crashing the whole agent.
"""
name = tool_call.function.name
try:
arguments = json.loads(tool_call.function.arguments)
except json.JSONDecodeError:
return f"Error: the arguments for '{name}' were not valid JSON"
return TOOL_REGISTRY[name](**arguments)
def run_agent(task: str) -> str:
"""Run one full agent loop: reason, act, respond."""
# Step 1: LLM decides what to do
response = client.chat.completions.create(
model = "gpt-4o-mini",
messages = [{"role": "user", "content": task}],
tools = TOOLS,
)
message = response.choices[0].message
# If no tool was needed, return the direct answer
if not message.tool_calls:
return message.content
# Step 2: the LLM can ask for several tools in one turn, so run every
# tool call, not just the first. Each result is a "tool" message tagged
# with its call_id so the LLM can match each result to its request.
tool_messages = [
{"role": "tool", "tool_call_id": tc.id, "content": run_tool_call(tc)}
for tc in message.tool_calls
]
# Step 3: send back the task, the assistant turn (which carries all of
# its tool_calls), and every tool result, then return the final answer.
final = client.chat.completions.create(
model = "gpt-4o-mini",
messages = [
{"role": "user", "content": task},
message, # assistant turn, including all its tool_calls
*tool_messages, # one result per tool call
],
)
return final.choices[0].message.content
# --- Example calls and their output ---
print(run_agent("What is the weather like in Tokyo right now?"))
# It is currently partly cloudy in Tokyo with a temperature of 22°C.
print(run_agent("What are the latest headlines about renewable energy?"))
# Here are the three most recent headlines about renewable energy: ...
This is a complete agent. It perceives (reads the task), reasons (LLM decides what to do), acts (calls the tool), and responds (LLM formulates the answer from the result). Two LLM calls when a tool is used (reason, then respond), one when the LLM answers directly.
In the next lesson, we deliberately break this agent in the three ways that beginners encounter most often, so you know what the failures look like and how to fix them.