Tool-Calling Agent
Build a loop where the model can call your tools, see the results, and keep going until it has an answer.
The loop
Section titled “The loop”- Define tools as a JSON-schema list.
- Send the messages + tools.
- If the model returns
tool_calls, execute them locally and append the results. - Repeat until the model returns a normal message.
Full example
Section titled “Full example”import jsonfrom openai import OpenAI
client = OpenAI(base_url="https://api.aiand.com/v1", api_key="sk-...")
def get_weather(city: str) -> str: return json.dumps({"city": city, "temp_c": 22, "conditions": "sunny"})
def search_web(query: str) -> str: return json.dumps({"results": [{"title": "Tokyo", "url": "https://en.wikipedia.org/wiki/Tokyo"}]})
TOOLS = [ { "type": "function", "function": { "name": "get_weather", "description": "Get current weather for a city.", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"], }, }, }, { "type": "function", "function": { "name": "search_web", "description": "Search the web.", "parameters": { "type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"], }, }, },]
HANDLERS = {"get_weather": get_weather, "search_web": search_web}
def run(question: str) -> str: messages = [{"role": "user", "content": question}] for _ in range(10): response = client.chat.completions.create( model="openai/gpt-oss-120b", messages=messages, tools=TOOLS, ) msg = response.choices[0].message messages.append(msg.model_dump(exclude_none=True))
if not msg.tool_calls: return msg.content
for call in msg.tool_calls: args = json.loads(call.function.arguments) result = HANDLERS[call.function.name](**args) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result, }) raise RuntimeError("Agent exceeded max iterations")
print(run("What's the weather in Tokyo, and is it famous for anything?"))- Cap iterations (10 above) so a misbehaving model can’t loop forever.
tool_choice="auto"is the default; force a specific tool withtool_choice={"type": "function", "function": {"name": "..."}}.- For parallel tool calls in a single turn, leave
parallel_tool_calls: true(the default) — the loop above already handles multipletool_callsper iteration.