Skip to content

Write the loop

Your code, the real model's replies.

Your turn. The client replays Gemini's real replies from the last two runs, and checks that you send every tool result back correctly.

Sign in to run your code. It's free.

Write it yourself

Input: a run name. Client(run) replays the real Gemini replies from that run, in order, and checks you sent every tool result back.

Loop until stop_reason is end_turn. For each tool_use block, run call_tool(block.name, block.input), print tool: name(key=value, …) -> output, and send all the results back in one user message. Then print the answer.

▸ Runs first: set-up code (65 lines). Open to read it.
1import json
2
3RUNS = {"late-order": [{"content": [{"type": "tool_use", "id": "call_1", "name": "get_order", "input": {"order_id": "A123"}}], "stop_reason": "tool_use"}, {"content": [{"type": "tool_use", "id": "call_2", "name": "track_shipment", "input": {"tracking_id": "TRK-889"}}], "stop_reason": "tool_use"}, {"content": [{"type": "text", "text": "Your order A123 is currently at the Pune hub. It has been delayed due to heavy rain and is now estimated to arrive by October 15, 2026. Thank you for your patience!"}], "stop_reason": "end_turn"}], "weekend-trip": [{"content": [{"type": "tool_use", "id": "call_1", "name": "get_weather", "input": {"city": "Goa"}}, {"type": "tool_use", "id": "call_2", "name": "get_weather", "input": {"city": "Manali"}}, {"type": "tool_use", "id": "call_3", "name": "get_weather", "input": {"city": "Jaipur"}}, {"type": "tool_use", "id": "call_4", "name": "hotel_price", "input": {"city": "Goa"}}, {"type": "tool_use", "id": "call_5", "name": "hotel_price", "input": {"city": "Manali"}}, {"type": "tool_use", "id": "call_6", "name": "hotel_price", "input": {"city": "Jaipur"}}], "stop_reason": "tool_use"}, {"content": [{"type": "text", "text": "Choose Jaipur! It has a 0% rain chance and costs Rs 5,200 for two nights, fitting your Rs 8,000 budget perfectly. Manali is also an option at Rs 6,200 with a 10% rain chance. Avoid Goa; it has a 70% rain chance and exceeds your budget at Rs 8,400 for two nights."}], "stop_reason": "end_turn"}], "endless-search": [{"content": [{"type": "tool_use", "id": "call_1", "name": "search", "input": {"query": "first ever IPL match attendance"}}], "stop_reason": "tool_use"}, {"content": [{"type": "tool_use", "id": "call_2", "name": "search", "input": {"query": "attendance RCB vs KKR April 18 2008 IPL first match"}}], "stop_reason": "tool_use"}, {"content": [{"type": "tool_use", "id": "call_3", "name": "search", "input": {"query": "IPL 2008 first match RCB vs KKR attendance ESPNcricinfo"}}], "stop_reason": "tool_use"}, {"content": [{"type": "tool_use", "id": "call_4", "name": "search", "input": {"query": "how many people attended the first IPL match in 2008"}}], "stop_reason": "tool_use"}, {"content": [{"type": "tool_use", "id": "call_5", "name": "search", "input": {"query": "official attendance RCB vs KKR 18 April 2008"}}], "stop_reason": "tool_use"}, {"content": [{"type": "tool_use", "id": "call_6", "name": "search", "input": {"query": "\"attendance\" \"first IPL match\" 2008"}}], "stop_reason": "tool_use"}, {"content": [{"type": "tool_use", "id": "call_7", "name": "search", "input": {"query": "M. Chinnaswamy Stadium attendance record IPL 2008 first match"}}], "stop_reason": "tool_use"}, {"content": [{"type": "text", "text": "The first-ever IPL match took place on **April 18, 2008**, at the **M. Chinnaswamy Stadium** in Bangalore, between the Royal Challengers Bangalore (RCB) and the Kolkata Knight Riders (KKR).\n\nThe exact attendance for this historic match is widely reported as **55,000**. \n\nAt the time, the stadium was packed to its full capacity to witness the opening ceremony and Brendon McCullum’s legendary innings of 158* off 73 balls. While the modern-day capacity of the M. Chinnaswamy Stadium is approximately 32,000 to 40,000 due to renovations and seat installations, in 2008, the stadium could accommodate a larger crowd, and the official turnout was recorded at the 55,000 mark."}], "stop_reason": "end_turn"}]}
4TASKS = {"late-order": "My order A123 hasn't arrived yet. Where is it?", "weekend-trip": "Goa, Manali or Jaipur this weekend? I hate rain and have Rs 8000 for 2 nights.", "endless-search": "What was the exact attendance at the first ever IPL match?"}
5_OUTPUTS = {"get_order(order_id=A123)": "{\"order_id\":\"A123\",\"status\":\"shipped\",\"tracking_id\":\"TRK-889\",\"promised\":\"2026-10-12\"}", "track_shipment(tracking_id=TRK-889)": "{\"status\":\"delayed\",\"location\":\"Pune hub\",\"new_estimate\":\"2026-10-15\",\"reason\":\"heavy rain\"}", "get_weather(city=Goa)": "{\"temp_c\":31,\"rain_chance\":0.7}", "get_weather(city=Manali)": "{\"temp_c\":6,\"rain_chance\":0.1}", "get_weather(city=Jaipur)": "{\"temp_c\":29,\"rain_chance\":0}", "hotel_price(city=Goa)": "4200", "hotel_price(city=Manali)": "3100", "hotel_price(city=Jaipur)": "2600", "search(query=first ever IPL match attendance)": "No results found.", "search(query=attendance RCB vs KKR April 18 2008 IPL first match)": "No results found.", "search(query=IPL 2008 first match RCB vs KKR attendance ESPNcricinfo)": "No results found.", "search(query=how many people attended the first IPL match in 2008)": "No results found.", "search(query=official attendance RCB vs KKR 18 April 2008)": "No results found.", "search(query=\"attendance\" \"first IPL match\" 2008)": "No results found.", "search(query=M. Chinnaswamy Stadium attendance record IPL 2008 first match)": "No results found."}
6
7
8class Block:
9 def __init__(self, d):
10 self.type = d["type"]
11 self.text = d.get("text")
12 self.id = d.get("id")
13 self.name = d.get("name")
14 self.input = d.get("input")
15
16
17class Reply:
18 def __init__(self, d):
19 self.content = [Block(b) for b in d["content"]]
20 self.stop_reason = d["stop_reason"]
21
22
23class _Messages:
24 def __init__(self, client):
25 self._client = client
26
27 def create(self, model=None, max_tokens=None, messages=None, tools=None, system=None):
28 return self._client._next(messages or [])
29
30
31class Client:
32 """Replays the real model's replies from a recorded run, in order, like client.messages.create."""
33
34 def __init__(self, run):
35 self._replies = RUNS[run]
36 self._waiting = []
37 self.calls = 0
38 self.messages = _Messages(self)
39
40 def _next(self, messages):
41 if self._waiting:
42 if len(messages) < 2 or messages[-2].get("role") != "assistant":
43 raise RuntimeError("Add the model's reply to messages (role 'assistant') before sending tool results.")
44 last = messages[-1]
45 blocks = last.get("content") if last.get("role") == "user" and isinstance(last.get("content"), list) else []
46 sent = [b.get("tool_use_id") for b in blocks if isinstance(b, dict) and b.get("type") == "tool_result"]
47 missing = [i for i in self._waiting if i not in sent]
48 if missing:
49 raise RuntimeError("The model is still waiting for: " + ", ".join(missing) + ". Send every result back in one user message of tool_result blocks.")
50 if self.calls == len(self._replies):
51 raise RuntimeError("The real model had already finished: stop when stop_reason is 'end_turn'.")
52 reply = Reply(self._replies[self.calls])
53 self.calls += 1
54 self._waiting = [b.id for b in reply.content if b.type == "tool_use"]
55 return reply
56
57
58def call_tool(name, input):
59 """Runs a tool and returns its output: the same output the real run got."""
60 key = name + "(" + ", ".join(k + "=" + str(v) for k, v in input.items()) + ")"
61 return _OUTPUTS.get(key, "ERROR no data for " + key)
62
63
64def text_of(reply):
65 return " ".join(b.text for b in reply.content if b.type == "text")