Function Calling: The LLM Picks a Predefined Function

~12 min read

Function calling lets the LLM decide which developer-defined function to invoke based on the user's prompt. It predates MCP and still has real limitations: the M×N integration problem, tight app-specific coupling, and manual update propagation.

Before MCP became mainstream, most AI workflows relied on traditional function calling for tools. Function calling enables LLMs to execute predefined functions based on user inputs: developers create functions with clear input and output parameters, the LLM interprets the user's input to identify which function is appropriate to call, and the application executes that function, processes the result, and returns the response to the user.

This is a genuinely useful capability — it's what first let LLMs move beyond pure text generation into taking real actions — but this course is direct about three specific limitations that show up as systems scale. First, as the number of functions grows, managing and integrating them becomes complex — this is exactly the M×N integration problem: M different AI applications each needing to integrate with N different functions/tools requires, in the worst case, M×N distinct integrations, since each AI app's function-calling setup is typically built specifically for that app.

Second, functions are closely tied to specific applications, making reuse across different systems genuinely challenging — a function carefully defined and wired up for one AI application doesn't automatically become available to a different AI application without redoing that integration work from scratch, even if the underlying capability (say, 'search flights') is conceptually identical.

Third, any changes to a function require manual updates across every instance where that function is used — there's no mechanism for a function's interface change to propagate automatically to every application that's integrated with it, the same fundamental issue traditional APIs have. Function calling is a real and useful capability, but these three limitations — the M×N problem, tight app-specific coupling, and manual update propagation — are exactly the gaps MCP was built to close, which the next subtopic covers.

💻 Code example

from openai import OpenAI
import json

client = OpenAI()

# Function calling: a tool defined and wired up SPECIFICALLY for this
# one application — reusing it in a different AI app means redoing
# this entire integration from scratch
get_weather_tool = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

def app_specific_function_calling(user_message: str) -> str:
    resp = client.chat.completions.create(
        model="gpt-4.1", messages=[{"role": "user", "content": user_message}],
        tools=get_weather_tool,
    )
    choice = resp.choices[0].message
    if choice.tool_calls:
        args = json.loads(choice.tool_calls[0].function.arguments)
        return f"Would call get_weather({args})"
    return choice.content

# If a SECOND AI app also wants get_weather, this entire tool
# definition + wiring has to be duplicated there too — no reuse

💬 Deep Dive with AI

Key points

  • Function calling lets the LLM decide which developer-defined function to call, based on interpreting the user's input
  • It predates MCP and is genuinely useful — it's what first let LLMs take real actions beyond text generation
  • Limitation 1: the M×N integration problem — M AI apps × N functions can require M×N distinct integrations
  • Limitation 2: functions are tightly coupled to specific applications, making reuse across systems hard
  • Limitation 3: any function change requires manual updates across every instance using it, same as traditional APIs