Stop Writing Chatbots: How LLM function calling structured outputs Connect AI to Real APIs

Most junior developers think “working with AI” means writing a clever prompt and reading the paragraph that comes back. That assumption will hold you back. Real production software cannot parse a sentence—it needs predictable, machine-readable data. This is exactly where LLM function calling structured outputs come in.

In this guide, you will learn what LLM function calling and structured outputs actually are, why they matter more than prompt engineering alone, and how to implement them step by step so your AI can talk to databases, APIs, and backend services like a professional system—not a chatbot.

1. The Paragraph Problem

Imagine a user types: “Book me a flight to New York tomorrow.”

A basic chatbot might respond with: “Sure! I’ll look up flights to New York for you tomorrow, May 19, 2026. Shall I proceed?”

That’s a lovely sentence. Your API can’t do anything with it.

A real booking system needs:

json

{
  "destination": "NYC",
  "date": "2026-05-19",
  "passengers": 1
}

This is the core problem that LLM function calling and structured outputs solve. They force the model to respond with machine-parsable data instead of conversational text—turning an AI from a fancy text generator into a programmable component in your software architecture.

What Is LLM Function Calling?

LLM function calling is a feature that lets you define a set of functions — with names, descriptions, and parameter schemas — and pass them to a language model. When the model decides that one of those functions should be invoked based on the user’s message, it returns a structured call object instead of a plain-text reply.

The key insight: the LLM doesn’t execute the function. It just tells your code which function to call and with what arguments. Your application then actually makes the API call, hits the database, or triggers the service.

This means LLM function calling turns a model into a decision-making router—a brain that interprets user intent and maps it to machine actions.

Why it matters

  • The model’s output becomes reliably parseable
  • You can chain multiple function calls for complex workflows
  • The model learns from function descriptions, so you get smarter routing without custom training
  • It is the standard pattern for agentic AI systems

What Are Structured Outputs?

Structured outputs are a related but slightly different concept. Instead of declaring functions, you provide a JSON schema to the model and instruct it to respond only with data that matches that schema—nothing else, no preamble, no explanation.

OpenAI introduced a dedicated “Structured Outputs” mode in 2024 that uses a response_format parameter with a strict JSON schema. This guarantees the response will always conform to the defined shape.

LLM function calling and structured outputs are often used together: function calling decides what to do, and structured outputs define what shape the data should take.

Function Calling vs. Structured Outputs – What’s the Difference?

FeatureFunction CallingStructured Outputs
PurposeRoute user intent to a functionForce a fixed JSON shape on any response
Declared asList of function schemasJSON Schema in response_format
LLM decides?Yes — the model picks which function to callNo — the schema is always applied
Best forMulti-step agentic tasksSingle-turn data extraction
Supported byOpenAI, Gemini, Claude, MistralOpenAI (strict mode), Gemini

Both are examples of LLM function calling and structured outputs working at different layers of the same pipeline.

Real-World Use Case: The Flight Booking Problem

Here is how LLM function calling handles the flight booking scenario end-to-end.

Step 1 — User input:

“Book me a flight to New York tomorrow.”

Step 2 — You define a function schema:

json

{
  "name": "book_flight",
  "description": "Books a flight for a user",
  "parameters": {
    "type": "object",
    "properties": {
      "destination": {
        "type": "string",
        "description": "IATA code or city name"
      },
      "date": {
        "type": "string",
        "description": "Travel date in YYYY-MM-DD format"
      },
      "passengers": {
        "type": "integer",
        "description": "Number of passengers"
      }
    },
    "required": ["destination", "date"]
  }
}

Step 3 — The model returns a function call object:

json

{
  "name": "book_flight",
  "arguments": {
    "destination": "NYC",
    "date": "2026-05-19",
    "passengers": 1
  }
}

Step 4 — Your code executes the actual API call:

python

import json

def handle_llm_response(response):
    tool_call = response.choices[0].message.tool_calls[0]
    args = json.loads(tool_call.function.arguments)
    result = flight_api.book(
        destination=args["destination"],
        date=args["date"],
        passengers=args.get("passengers", 1)
    )
    return result

The LLM never touches the database. It simply translates human language into a structured instruction that your code can act on. That’s the entire power of LLM function calling and structured outputs in one flow.

Implementing LLM Function Calling with OpenAI

Here is a minimal working implementation using the OpenAI Python SDK.

python

from openai import OpenAI
import json

client = OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "The city name"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"]
                    }
                },
                "required": ["city"]
            }
        }
    }
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "What's the weather in Mumbai?"}],
    tools=tools,
    tool_choice="auto"
)

# Parse the function call
tool_call = response.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
print(args)
# Output: {"city": "Mumbai", "unit": "celsius"}

Notice how tool_choice="auto" lets the model decide whether to call a function or respond conversationally. If you set tool_choice={"type": "function", "function": {"name": "get_weather"}}, you force it to always call that specific function—a stricter form of LLM function calling and structured outputs that removes ambiguity entirely.

Implementing Structured Outputs (OpenAI Strict Mode)

When you do not need function routing and just want guaranteed JSON shape, use OpenAI’s Structured Outputs feature with response_format:

python

from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

class FlightDetails(BaseModel):
    destination: str
    date: str
    passengers: int

response = client.beta.chat.completions.parse(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "user", "content": "Book me a flight to New York tomorrow for 2 people"}
    ],
    response_format=FlightDetails,
)

flight = response.choices[0].message.parsed
print(flight.destination)  # NYC
print(flight.date)         # 2026-05-19
print(flight.passengers)   # 2

Using Pydantic models with parse() is the cleanest way to work with LLM function calling and structured outputs in Python—the SDK validates the schema automatically and raises an error if the model produces invalid output.

Gemini’s Approach: Function Declarations

Google Gemini handles LLM function calling through its function_declarations parameter in the API configuration. The pattern is nearly identical to OpenAI’s but uses slightly different naming conventions.

python

import google.generativeai as genai

genai.configure(api_key="YOUR_KEY")

tools = genai.protos.Tool(
    function_declarations=[
        genai.protos.FunctionDeclaration(
            name="search_product",
            description="Search for a product in the catalog",
            parameters=genai.protos.Schema(
                type=genai.protos.Type.OBJECT,
                properties={
                    "query": genai.protos.Schema(type=genai.protos.Type.STRING),
                    "max_price": genai.protos.Schema(type=genai.protos.Type.NUMBER),
                },
                required=["query"]
            )
        )
    ]
)

model = genai.GenerativeModel("gemini-1.5-pro", tools=[tools])
response = model.generate_content("Find headphones under ₹5000")

# Extract the function call
fc = response.candidates[0].content.parts[0].function_call
print(dict(fc.args))
# Output: {"query": "headphones", "max_price": 5000}

Gemini also supports a response_mime_type: "application/json" parameter combined with a schema prompt for structured outputs without function declarations—useful for data extraction tasks.

Claude’s Approach: Tool Use

Anthropic’s Claude uses the term tool use instead of function calling, but the concept is identical. You pass a tools array with each tool’s input schema, and Claude returns a tool_use content block when it decides a tool should be invoked.

python

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[
        {
            "name": "create_calendar_event",
            "description": "Create an event in the user's calendar",
            "input_schema": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "start_time": {"type": "string", "description": "ISO 8601 format"},
                    "duration_minutes": {"type": "integer"}
                },
                "required": ["title", "start_time"]
            }
        }
    ],
    messages=[{"role": "user", "content": "Schedule a standup meeting for 9am tomorrow"}]
)

# Find the tool_use block
for block in response.content:
    if block.type == "tool_use":
        print(block.name)   # create_calendar_event
        print(block.input)  # {"title": "Standup meeting", "start_time": "2026-05-19T09:00:00"}

Claude’s LLM function calling and structured outputs implementation is particularly strong at reasoning about when not to call a tool—a subtle skill that matters in production pipelines.

Common Mistakes Junior Developers Make

Even when developers know about LLM function calling and structured outputs, they often fall into these traps:

  • Mistake 1: Trusting the model blindly Always validate the parsed JSON against your schema before passing it to downstream APIs, even when using strict mode. Models can hallucinate parameter values even when the shape is correct.
  • Mistake 2: Putting too much logic in the function description The description trains the model’s routing decisions. Keep it factual and specific: “Retrieves a user’s order history from the database by user ID” is better than “Gets order stuff.”
  • Mistake 3: Not handling the no-call case When tool_choice="auto", the model may decide not to call any function. Your code must handle a plain text response as a fallback.
  • Mistake 4: Ignoring required vs. optional parameters Mark parameters as required only when the API will fail without them. Optional parameters with good defaults let the model make smarter calls with incomplete user input.
  • Mistake 5: Forgetting to send the tool result back In multi-turn function calling, after you execute the function you must return the result to the model in a tool role message. Otherwise the model doesn’t know the action succeeded and will hallucinate or loop.

When to Use Which Approach

ScenarioBest Approach
User triggers a specific backend actionFunction calling
Extracting structured data from a documentStructured outputs
Multi-step agent with branching logicFunction calling with multiple tools
Building a form filler or data parserStructured outputs
Real-time API integration (booking, ordering)Function calling
Classification or entity extractionStructured outputs

The practical answer: most real applications use LLM function calling and structured outputs together. Function calling routes intent; structured outputs guarantee the data shape of each function’s arguments.

Conclusion

The developers who thrive in the AI era are not the ones who write the most creative prompts; they are the ones who treat language models as programmable components in a larger system. LLM function calling and structured outputs are the bridge between natural language and the deterministic world of software engineering.

Once you master LLM function calling and structured outputs, you stop seeing AI as a black box that produces paragraphs and start seeing it as an intelligent dispatcher that maps user intent to machine actions. That shift in thinking is what separates a chatbot builder from a real AI engineer.

Pick one provider- OpenAI, Gemini, or Claude implement the flight booking example from this guide, and trace every step from user message to database call. The click into place when you see that first clean JSON object arrive from the model will change how you build software permanently.
Visit Newtum to learn how to adapt AI.

About The Author

Leave a Reply