Function Calling in the OpenAI API: Real Examples

By Carlos Montiel | Enterprise AI Specialist
Leer en español →
Published: 2026-07-28 | By: Carlos Montiel | Reading time: ~4 minutes

Function calling is the mechanism that lets an OpenAI model stop just "talking" and start triggering real code: querying a database, executing a business action, or returning data in an exact format your system can process without parsing free text.

The problem it solves: free text isn't an interface

Before function calling, integrating an LLM with a real system meant asking it to "respond in JSON" and hoping the format stayed consistent, plus parsing with fragile regex. Function calling (renamed `tools` in the current API) fixes this at the root: you define available functions with a strict JSON Schema, and the model returns a structured, validatable call to that function when it decides it's needed, instead of free text.

It's important to understand that the model never executes the function directly — it only generates the structured intent (function name + arguments). Your code is responsible for executing it and returning the result to the model in a following turn.

Defining a tool

tools = [{ "type": "function", "function": { "name": "get_weather", "description": "Gets the current weather for a given city.", "parameters": { "type": "object", "properties": { "city": {"type": "string"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} }, "required": ["city"] } } }] response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "What's the weather like in Guatemala today?"}], tools=tools, ) tool_call = response.choices[0].message.tool_calls[0] print(tool_call.function.name) # "get_weather" print(tool_call.function.arguments) # '{"city": "Guatemala"}'

Completing the cycle: executing and returning the result

The full cycle requires a second turn where you give the model back the real result of executing the function, so it can formulate the final natural-language answer.

import json args = json.loads(tool_call.function.arguments) result = get_real_weather(args["city"]) # your real function messages.append(response.choices[0].message) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result) }) final_response = client.chat.completions.create( model="gpt-4o", messages=messages, tools=tools ) print(final_response.choices[0].message.content)

Parallel function calling

Current models can request multiple function calls in a single turn when the task requires it (for example, "give me the weather in Guatemala and in Mexico City"). Your code must iterate over `tool_calls` (which is a list) and respond to each one with its corresponding `tool_call_id` before the next turn — skipping one causes an API error because it expects a response for every pending call.

Structured Outputs: guaranteed exact schema

With `strict: true` in the function definition (or using `response_format` with a JSON Schema for direct outputs without function calling), the API guarantees the output exactly matches the defined schema — no missing fields, no wrong types, no need for extra validation or retries for malformed JSON. This was a real limitation in earlier API versions, where the model would occasionally produce JSON that was almost valid but not quite.

tools[0]["function"]["strict"] = True tools[0]["function"]["parameters"]["additionalProperties"] = False

Real production use cases

The most common patterns in enterprise implementations: intent routers (an `escalate_to_human` function the model invokes when it detects frustration or a topic outside its scope), query agents (read-only functions into databases, with the LLM deciding what to query based on the question), and structured document extraction (turning unstructured invoices or emails into JSON with fixed fields, using the function as a "mold" for the expected output, not as a real external action).

Common mistakes to avoid

Defining too many similar functions (the model gets confused about which to use when there's semantic overlap), ambiguous parameter descriptions (if `city` doesn't clarify whether it accepts "NYC" or requires "New York City," the model guesses), and not validating received arguments before executing them — the model can generate out-of-range or malformed values, and your execution code must treat those arguments as untrusted input, just like any user input.

Carlos Montiel
Enterprise AI Solutions Architect
Specialist in LLMs, Agents, and Orchestration
guatemalia.com/en/#contact · info@guatemalia.com

Need to implement AI at your company?

Carlos Montiel is an enterprise AI solutions architect. He implements LLMs, Agents, RAG, and orchestrators for companies across Guatemala and Latin America. Reach out for a consultation.

Contact Carlos Montiel

info@guatemalia.com