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.
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.
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.
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.
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.
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).
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 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