30 Days of AIDay 23 of 30 · Week 4: Enterprise Workflows
Course outline▾

Week 4 · Enterprise Workflows

  1. Day 22Autonomous Agents — From Passive LLMs to Goal-Driven Execution
  2. Day 23Tool Use and Function Calling — Connecting LLMs to APIs, Databases and Shells
  3. Day 24Multi-Agent Orchestration: Supervisor, Worker, and Evaluator PatternsComing soon · 2026-10-24 · 7 PM IST
  4. Day 25Automated CI/CD: Automating the Software Lifecycle for AI ModelsComing soon · 2026-10-25 · 7 PM IST
  5. Day 26Zero-Trust Security for AI: Sandboxing Ephemeral Execution & Model EgressComing soon · 2026-10-26 · 7 PM IST
  6. Day 27Observability & Tracing for Agentic Systems (Telemetry, Logs, and Metrics)Coming soon · 2026-10-27 · 7 PM IST
  7. Day 28Human-in-the-Loop Architecture: Machine Second, Human First in PracticeComing soon · 2026-10-28 · 7 PM IST
  8. Day 29Managing Technical Debt, Drift, and Model Governance in Enterprise ITComing soon · 2026-10-29 · 7 PM IST
  9. Day 30The 10-Year Horizon: Architecting IT Strategy for the AI-Native EnterpriseComing soon · 2026-10-30 · 7 PM IST
Open the course page →

Day 23: Tool Use and Function Calling — Connecting LLMs to APIs, Databases and Shells

2026-10-23 · 8 min read

Watch the video lesson, or subscribe on YouTube for a new lesson every day.

By default a language model is an isolated reasoning engine. It cannot read the current time, query your private database or run a command. Function calling, also called tool use, is the bridge that connects that engine to real systems, so the model becomes a router that decides what to call while your software does the calling.

In plain terms: The model is a manager who can write instructions but cannot touch anything. It hands a clear, structured request to an assistant (your application), who does the task and reports back.

A model on its own is isolated LLMweights onlyCurrent time✗Private database✗Live API✗Command line✗Function calling is the bridge to all of these.The model asks. Your software acts.
On its own an LLM only has its trained weights. It cannot read the current time, query a private database, call a live API or run commands. Function calling is the bridge: the model asks for an action and your software performs it.

The four-step protocol

A common misconception is that the LLM runs the code. It does not. Function calling is a deterministic, four-step exchange.

  1. Declare the tools. Your application sends the model a list of tools described with JSON schemas: the name, what it is for and its typed parameters.
  2. Model invocation. If the question needs outside data or an action, the model replies with a structured tool call (the function name and exact arguments) instead of ordinary text.
  3. Client-side execution. Your application intercepts that reply, validates it and runs the real code: a SQL query, a REST call, a script.
  4. Observation injection. Your application sends the result back as a tool message, and the model uses it to finish its answer.
Function calling in four steps Your appLLMReal system1. Declare tools (JSON schemas)2. Model returns a tool call as JSON3. App validates and runs the code4. Result goes back as an observationThen the model writes the final answer.The model never runs code. Your application does.
In function calling the application declares tools as JSON schemas, the model replies with a structured tool call instead of text, the application validates and executes the real code, and the result is returned to the model as an observation so it can write the final answer. The model never runs code itself.
What a tool call looks like 1. Tool schema you providename: "get_gpu_status"description: Returns GPU health for a nodeparameters: { node: string, required }2. Model's reply (structured, not prose){ "name": "get_gpu_status", "arguments": { "node": "gpu-07" } }3. Observation your app sends back{ "node": "gpu-07", "status": "degraded" }Illustrative names and values.
The application describes a tool with a name, a purpose and typed parameters. The model answers with a structured JSON tool call naming the function and its arguments, and the application sends the real result back as an observation. The names and values are illustrative.

Type safety and validation

Free text breaks software pipelines. Use structured arguments and validate them with JSON Schema or a library such as Pydantic, so a malformed value is rejected before it reaches your backend. Then add checks the model cannot override: an allow-list of tools and a permission check for the current user.

Validate before you execute Schema checktypes and required fieldsAllow-listis this tool permitted?Permissionsmay this user do this?Execute and logthe real code runs Rejectedbad or unsafe callThe model suggests. Your code decides.Pydantic or JSON Schema keeps arguments type-safe.
Before running a tool call, the application checks the arguments against a schema, checks the tool is on an allow-list, checks the user has permission, and only then executes and logs it. Bad or unsafe calls are rejected. Pydantic or JSON Schema keeps arguments type-safe.

Here is the loop with an OpenAI-compatible API. Ollama and vLLM from Day 16 expose the same interface, so the pattern works with a local model too. It assumes a model that supports tool calling, and get_gpu_status is a stand-in for your own function.

import json
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()                     # or OpenAI(base_url="http://localhost:11434/v1", api_key="x")

class GpuArgs(BaseModel):             # type-safe arguments
    node: str

def get_gpu_status(node: str) -> dict:
    return {"node": node, "status": "degraded"}   # replace with a real lookup

TOOLS = [{"type": "function", "function": {
    "name": "get_gpu_status",
    "description": "Returns GPU health for a node",
    "parameters": GpuArgs.model_json_schema(),
}}]
ALLOWED = {"get_gpu_status": (GpuArgs, get_gpu_status)}

messages = [{"role": "user", "content": "Is gpu-07 healthy?"}]
reply = client.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=TOOLS)
msg = reply.choices[0].message

if msg.tool_calls:
    messages.append(msg)
    for call in msg.tool_calls:
        schema, fn = ALLOWED[call.function.name]                       # allow-list
        args = schema.model_validate_json(call.function.arguments)     # validate
        result = fn(**args.model_dump())                               # execute
        messages.append({"role": "tool", "tool_call_id": call.id,
                         "content": json.dumps(result)})               # observation
    final = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
    print(final.choices[0].message.content)

Restricting the model to proposing structured arguments keeps execution under your control: authenticated, validated and logged inside your own application.

Coming Up Next

Day 24: Multi-agent orchestration: supervisor, worker and evaluator patterns.

#FunctionCalling #ToolUse #APIs #Pydantic #SoftwareArchitecture