In my final publish, . Instrument Calling is the mechanism that permits an AI mannequin to determine which perform must be used and with what arguments, as an alternative of simply producing textual content as output. By the top of that publish, we had a setup that might determine to get_current_weather or convert_currency, or do each directly by calling them in parallel, or neither of them, and simply generate textual content. In different phrases, the mannequin decides what it must do subsequent, we (the remainder of the code) execute that call, move again the consequence to the mannequin, and the mannequin finally supplies an knowledgeable reply to the person in textual content format.
A extra superior model of this loop doesn’t cease after only one spherical of mannequin deciding – code executing – passing again the consequence – mannequin answering. As an alternative of producing a response on the finish, the mannequin can use the results of one device name to determine whether or not, and which, device to name subsequent. As already talked about on the finish of the Instrument Calling publish, this can be a ReAct loop (Motive + Act), and is strictly what lets brokers deal with duties that may’t be solved in a single name.
However what would such a job be? Within the earlier publish’s parallel calling instance, we requested What is the climate in Athens and the way a lot is 100 USD in EUR?, that are two separate issues requiring the usage of two separate instruments to acquire a response, however are additionally unbiased from each other. In different phrases, we will reply these two questions independently, concurrently, with no need any data from the primary query with a purpose to reply to the second.
However what if we ask one thing like I wager my pal 100 EUR that it might rain in Athens right now. If I received, what number of USD is that? Right here, the mannequin received’t be capable of determine if it must name convert_currency till it first calls get_current_weather and finds out whether or not it really rained. Merely put, the reply to the second query relies upon completely on the end result of the primary. That is exactly the sort of dependency that parallel device calling can’t resolve in a single spherical, and precisely what a ReAct loop is constructed for.
So, let’s have a look!
🍨 DataCream is a e-newsletter about AI, information, and tech. In case you are interested by these subjects, subscribe right here!
However what precisely is a ReAct loop?
A ReAct loop is simply three steps repeated in sequence:
- Motive
- Act
- Observe

Originally of the loop, the mannequin causes about what data it already is aware of and what extra data is lacking with a purpose to present an accurate response to the person’s question. It then acts by calling an applicable device with the aim of acquiring this lacking data. Lastly, as soon as the respective device name is executed and its result’s handed again to the mannequin, the mannequin observes the consequence (provides the device’s consequence into its context). Then, it loops again to reasoning once more, besides this time with this new remark sitting in its context. This loop is repeated till the mannequin evaluates that the accessible data is sufficient for answering the person’s question, and at this level, it stops calling instruments and simply responds with textual content.
However isn’t this like the identical because the device calls we already know? Sort of, however not precisely. The half that makes this completely different from what we coated within the Instrument Calling publish is the loop itself. In a single device name, the mannequin asks for one thing, will get it, and that’s the top of the transaction so far as that decision is worried. Within the ReAct loop, the dialog stays open, as every new remark turns into new context for the subsequent reasoning step, and the mannequin can change its plan based mostly on what it simply realized.
Identical Instruments, New Trick
To make this concrete, let’s return to the wager instance from the intro and suppose by what the mannequin really must do with a purpose to present us a dependable reply. The query is: I wager my pal 100 EUR that it might rain in Athens right now. If I received, what number of USD is that? Discover the conditional assertion in the midst of it: if I received. Whether or not the mannequin must convert any foreign money in any respect relies on what the climate name returns. If it rained, the mannequin must name convert_currency with 100 EUR as an enter parameter and provides again the transformed winnings. If it didn’t rain, the wager is misplaced, convert_currency is irrelevant, and the mannequin ought to simply straight return the respective textual content, with out making a second name.
To place it in another way, the mannequin genuinely can’t plan its full sequence of device calls upfront. It has to verify the climate first, observe the consequence, purpose about what that consequence implies for the wager situation, and solely then determine whether or not a second device name is required. In contrast to the parallel device calling that labored properly for answering What is the climate in Athens and the way a lot is 100 USD in EUR?, this query requires a loop.
The great factor a couple of ReAct loop is that it doesn’t want new instruments. We will nonetheless use the identical capabilities, simply in a special method. So we’re going to be utilizing get_current_weather and convert_currency precisely as we constructed them final time utilizing Open-Meteo for climate and Frankfurter for foreign money conversion (each nonetheless requiring no API key):
import requests
import json
from openai import OpenAI
consumer = OpenAI(api_key="your_api_key")
def get_current_weather(metropolis: str, unit: str = "celsius") -> dict:
# Step 1: geocode town identify to coordinates
geo = requests.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"identify": metropolis, "rely": 1}
).json()
lat = geo["results"][0]["latitude"]
lon = geo["results"][0]["longitude"]
# Step 2: fetch present climate
climate = requests.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": lat,
"longitude": lon,
"present": "temperature_2m,precipitation",
"temperature_unit": unit
}
).json()
return {
"metropolis": metropolis,
"temperature": climate["current"]["temperature_2m"],
"precipitation_mm": climate["current"]["precipitation"],
"unit": unit
}
def convert_currency(quantity: float, from_currency: str, to_currency: str) -> dict:
response = requests.get(
f"https://api.frankfurter.dev/v2/charge/{from_currency}/{to_currency}"
).json()
charge = response["rate"]
transformed = spherical(quantity * charge, 2)
return {
"quantity": quantity,
"from_currency": from_currency,
"to_currency": to_currency,
"converted_amount": transformed,
"charge": charge
}
Discover one small addition in comparison with final time: get_current_weather now additionally returns precipitation_mm, since that’s the sphere the mannequin wants with a purpose to consider the wager situation. Every little thing else is identical. The instruments schema can be unchanged from our earlier publish:
instruments = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather for a given city, including temperature and precipitation",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The name of the city"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
},
{
"kind": "perform",
"perform": {
"identify": "convert_currency",
"description": "Convert an quantity from one foreign money to a different",
"parameters": {
"kind": "object",
"properties": {
"quantity": {"kind": "quantity", "description": "The quantity to transform"},
"from_currency": {"kind": "string", "description": "The supply foreign money code, e.g. EUR"},
"to_currency": {"kind": "string", "description": "The goal foreign money code, e.g. USD"}
},
"required": ["amount", "from_currency", "to_currency"]
}
}
}
]
We additionally must outline a lookup dictionary that our code will use to dispatch the mannequin’s device option to the precise Python perform:
available_functions = {
"get_current_weather": get_current_weather,
"convert_currency": convert_currency
}
This lets us go from a device identify the mannequin offers us again, as a string, to the precise Python perform we run. We’ll want that mapping in a second, since this time we don’t know prematurely what number of device calls we’re going to need to resolve, and even whether or not there will likely be multiple.
Watching the loop suppose
Right here’s the half that’s really new. As an alternative of creating one request and studying off the device name, we wrap the entire trade in a loop. On every move, we ship the mannequin the total dialog up to now, verify whether or not it requested for a device, run that device if that’s the case, append the consequence, and go round once more. We solely cease when the mannequin responds with plain textual content and no device calls left to make.
messages = [
{
"role": "user",
"content": "I bet my friend 100 EUR that it would rain in Athens today. If I won, how many USD is that?"
}
]
max_iterations = 5
for i in vary(max_iterations):
print(f"--- Step {i + 1}: Motive ---")
response = consumer.chat.completions.create(
mannequin="gpt-4o-mini",
messages=messages,
instruments=instruments
)
message = response.decisions[0].message
messages.append(message)
# If there isn't any device name, the mannequin is able to reply
if not message.tool_calls:
print("Remaining reply:")
print(message.content material)
break
# In any other case, act on each device name the mannequin requested
for tool_call in message.tool_calls:
function_name = tool_call.perform.identify
function_args = json.masses(tool_call.perform.arguments)
print(f"--- Step {i + 1}: Act ({function_name}) ---")
print(f"Calling {function_name} with {function_args}")
function_response = available_functions[function_name](**function_args)
print(f"--- Step {i + 1}: Observe ---")
print(function_response)
# Feed the remark again in so the subsequent Motive step can use it
messages.append({
"function": "device",
"tool_call_id": tool_call.id,
"content material": json.dumps(function_response)
})
Additionally, discover the max_iterations cap stopping a mannequin that decides it wants “only one extra piece of data” from looping indefinitely. That is of specific significance as a result of we’re paying for each name to the mannequin inside every of these loops.
In the end, the ensuing remark of the loop is appended as a function: "device" message tied to the particular tool_call_id. This enables the mannequin to match every consequence again to the decision that produced it.
And now that we have now arrange all the pieces, we will lastly see the ReAct loop in motion.
So, our wager query can play out two methods relying on what the climate really is. Let’s have a look at each.
1. If it rained in Athens, our code would print within the terminal one thing like the next:
--- Step 1: Motive ---
--- Step 1: Act (get_current_weather) ---
Calling get_current_weather with {'metropolis': 'Athens'}
--- Step 1: Observe ---
{'metropolis': 'Athens', 'temperature': 17.4, 'precipitation_mm': 3.2, 'unit': 'celsius'}
--- Step 2: Motive ---
--- Step 2: Act (convert_currency) ---
Calling convert_currency with {'quantity': 100, 'from_currency': 'EUR', 'to_currency': 'USD'}
--- Step 2: Observe ---
{'quantity': 100, 'from_currency': 'EUR', 'to_currency': 'USD', 'converted_amount': 108.5, 'charge': 1.085}
--- Step 3: Motive ---
Remaining reply:
It did rain in Athens right now (3.2mm of precipitation), so that you received the wager!
Your 100 EUR comes out to 108.50 USD at right now's trade charge.
2. And if it didn’t rain in Athens, we’d get the next printout:
--- Step 1: Motive ---
--- Step 1: Act (get_current_weather) ---
Calling get_current_weather with {'metropolis': 'Athens'}
--- Step 1: Observe ---
{'metropolis': 'Athens', 'temperature': 34.1, 'precipitation_mm': 0.0, 'unit': 'celsius'}
--- Step 2: Motive ---
Remaining reply:
Sadly, it didn't rain in Athens right now, so it seems such as you misplaced the wager.
No foreign money conversion wanted!
Have a look at what occurred within the second state of affairs: the loop ran precisely as soon as. The mannequin noticed that precipitation_mm was 0.0, reasoned that the wager situation wasn’t met, and stopped with out ever calling convert_currency. No person advised it to skip the second device name, but it surely fairly determined that by itself, based mostly purely on what it noticed within the first run of the loop.
That is the foremost differentiation (a minimum of for this straightforward state of affairs) between parallel device calling and the ReAct loop. In parallel device calling, we wouldn’t be capable of exit early from all the course of, and never carry out the decision convert_currency. As an alternative, in a parallel setup, each instruments would have been referred to as upfront, and the mannequin would compose the ultimate response afterward. That is of specific significance as a result of keep in mind! we do pay for each name to the mannequin. Thus, having the ability to architecturally slender down the AI mannequin calls to what we’d like, with out performing pointless further calls, could be very substantial.
On my thoughts
So, when does a ReAct loop really beat parallel device calling?
The reply is: every time the variety of device calls, or the arguments to these calls, can solely be decided after seeing an earlier consequence.
In our wager instance, the mannequin can’t determine whether or not to name convert_currency in any respect till get_current_weather tells whether or not it rained. No quantity of upfront reasoning resolves that, as a result of the knowledge merely doesn’t exist but inside the mannequin’s world. We’ve got to step outdoors of the mannequin’s world, decide up exterior data from the climate API, and add it to the mannequin’s context. Quite the opposite, parallel device calling assumes the mannequin already is aware of what it wants earlier than it initiates any device calls. A ReAct loop doesn’t require that assumption: it lets the mannequin uncover what it wants because it goes.
Particularly, a ReAct loop wins over parallel device calling the next circumstances:
- When one result’s a situation for whether or not one other name is required in any respect, as within the wager instance.
- When the arguments to a later name depend upon the worth returned by an earlier one. For instance, if the mannequin first needed to lookup which foreign money a metropolis makes use of earlier than it may name
convert_currencywith the appropriate code. - When an earlier consequence comes again unexpectedly, for instance, the person could present a metropolis identify that doesn’t geocode, or an API returns an error, and the mannequin must adapt its plan fairly than simply report again no matter it acquired.
Nonetheless, in a simple case the place all of the wanted instruments and their arguments are apparent from the person’s message alone, parallel device calling is definitely the higher selection, since on this approach we get fewer round-trips, much less latency, and the identical consequence.
To me, probably the most fascinating a part of shifting from parallel device calling to the ReAct loop is how little code it really took 😅: a for loop, an if assertion, and a dictionary lookup. Nonetheless, that small quantity of code is doing wonders. This ReAct loop, in a single kind or one other, is the precise mechanism behind most of what folks imply by an “agent”.
✨ Thanks for studying! ✨
When you made it this far, you may discover pialgorithms helpful — a platform we’ve been constructing that helps groups securely handle organizational information in a single place.
Cherished this publish? Be part of me on 💌Substack and 💼LinkedIn
All pictures by the writer, besides talked about in any other case















