Tool Calling (including Function Calling)

Course overview

Tool Calling lets a model express, in a structured form, which capability it wants to use and with which arguments. Function Calling usually means a function tool that the application declares with JSON Schema. It connects natural-language understanding to databases, APIs, files, search, and business actions: the interface that moves an agent from “can talk” to “can look things up and do work.”

A call proposal is not execution

For client-side function tools, the model produces only a candidate call. Your trusted application must parse it, enforce an allowlist and schema, validate business rules, authenticate and authorize, obtain approval when required, apply idempotency and timeouts, execute, audit, and return a result. Schema conformance grants no permission, and a prompt cannot replace any of those controls.

Stable concepts and provider adaptation

As checked on 2026-07-21, OpenAI uses “tool calling” for function calling and distinguishes JSON-Schema functions, free-form custom tools, and platform-built-in tools. Anthropic distinguishes client-side from server-side tools, and Gemini likewise states that client functions are executed by the application. Message blocks, finish reasons, strict-mode behavior, parallelism, and built-in tools vary, but the stable flow does not:

application declares capabilities → model proposes a call → application validates, authorizes, and executes
                                → application correlates and returns the result → model continues or stops

Learn the provider-neutral execution contract first, then map OpenAI function_call/function_call_output, Anthropic tool_use/tool_result, and equivalent provider objects through an adapter. Provider field shapes must not leak into business handlers.

One current OpenAI nuance deserves explicit treatment: in Chat Completions, function tools are non-strict by default. In Responses, omitting strict causes an attempt to normalize the schema as strict; if that is incompatible, the behavior falls back to best effort and the parsed tool reports strict: false. Production integrations must configure and verify the target behavior explicitly; “Responses tries strict by default” is not an unconditional guarantee.

Trusted execution boundary

flowchart LR
    U["User and external content"] --> M["Model"]
    subgraph Untrusted["Untrusted proposal plane"]
        M --> P["tool proposal<br/>name + arguments"]
    end
    subgraph Trusted["Trusted execution plane (application)"]
        B["adapter binds execution context<br/>call / operation / idempotency"] --> V["registry + schema"]
        V --> A["business validation + AuthN/AuthZ"]
        A --> I["idempotency state / receipt lookup"]
        I --> H{"Does first execution require approval?"}
        H -->|yes| W["durably wait<br/>bind arguments and expiry"]
        H -->|no| X["recheck before execution"]
        W --> X
        X --> D["deadline + scheduling"]
        D --> T["tool handler"]
    end
    P --> B
    T --> S["external system"]
    S --> R["per-tool output validation<br/>content remains untrusted"]
    R --> MR["model_result<br/>returned to model"]
    R --> PA["protected_audit<br/>protected storage"]
    MR --> M

The model supplies only the tool name and arguments. The adapter extracts a provider call ID from provider events and records it in principal scope; the application or orchestrator generates the operation ID and idempotency key. The model cannot inject that trusted metadata into arguments and decide it for itself. Permissions, approval, idempotency, and real execution stay outside the model. Even structured external-system output can be stale, wrong, or contain prompt injection.

Source and maintenance status

The teaching structure, threat model, offline dispatcher, fixtures, and tests in this course are original to this project. Official documentation, specifications, and OWASP material are used to verify product behavior and engineering boundaries; this course does not reproduce third-party prose or images. Provider APIs and model capabilities evolve, so the course is marked dynamic. See Content quality and source-labeling standard for the meaning of source and status labels.

Place in the overall learning path

This course belongs to the Agent Runtime domain and is a core entry point for the Agent Application, Agent Platform, and Multimodal Real-Time tracks.

  • JSON and API provide structure, error, and network foundations.
  • The API material handles provider requests, streaming events, and error adaptation.
  • RAG supplies external knowledge evidence; controlled tools are better for live state and side effects.
  • This course establishes the trusted execution boundary for one or more calls.
  • Agent Core places calls inside a stateful loop with budgets and termination conditions.
  • MCP standardizes capability discovery and invocation protocols; it does not replace business authorization or idempotency.

Learning objectives

After completing this course, you can:

  • distinguish tools, function tools, custom tools, built-in tools, and client/server execution;
  • design complete input contracts with clear names, descriptions, and supported JSON Schema;
  • explain why strict/schema constrains shape but cannot prove business correctness, ownership, or permission;
  • source tenant, subject, roles, and service credentials from a trusted session rather than model arguments;
  • implement the allowlist → schema → business → AuthN/AuthZ → idempotency-state → approval-for-first-execution → execute pipeline;
  • bind an approval to the precise principal, tool, argument digest, operation/call, schema/policy revision, and expiry;
  • use a call ID to correlate a result, an operation ID to trace a business task, and an idempotency key to suppress duplicate side effects;
  • handle zero, one, many, parallel, dependent, and partially failed calls;
  • treat tool results as untrusted data and bound their size, type, provenance, and authority for the next step;
  • separate model-visible results from protected audit records and recompute request/result/call triple bindings;
  • handle the uncertainty of a timeout after an action may already have run;
  • persist idempotency and recovery state with SQLite uniqueness, an operation ledger, a transactional outbox, leases, and receipt reconciliation;
  • explain why those measures still do not prove distributed exactly-once delivery; and
  • evaluate routing, arguments, authorization, execution, recovery, security, and end-to-end outcomes.

Prerequisites

  • JSON: types, strict parsing, and schemas.
  • API: authentication, HTTP, rate limits, timeouts, and retries.
  • Prompt Engineering: instruction boundaries.
  • Context Engineering: external data and state management.
  • Basic Python dataclasses, exceptions, and unit tests.

The project uses only the Python 3 standard library. It does not contact a real model, network, or business service.

Core terms

TermPlain-language explanationWhat it does not solve
tool definitionName, description, and input shape shown to the modelIt does not implement a function.
tool callTool name and arguments proposed by the modelIt does not mean the tool executed.
tool resultStructured output from the application or serviceIt is not automatically trustworthy.
handlerCode that performs the real action in the trusted applicationThe model must not choose it dynamically.
registry / allowlistExplicit mapping from tool name to schema, risk, and handlerIt is not resource authorization.
call IDCorrelates a call in one turn with its resultIt does not prevent duplicate business actions.
operation IDTraces a business task across turns or servicesIt does not provide idempotency.
idempotency keyRecognizes the same business intent on retryThe same key with different arguments must conflict.
approvalA human authorization record for one precise high-risk actionIt is not a permanent pass.
adapterConverts provider messages to internal finite statesIt does not contain business rules.
OrderLessonLearning output
1Tool contracts and schema designTool categories; input/output contracts; versioning policy
2Call proposals, validation, and authorizationTrust boundary, identity injection, resource authorization, and approval binding
3Execution loop and call correlationProvider-neutral state machine, ID correlation, and loop limits
4Multiple calls, parallelism, and dependenciesDAGs, join strategies, partial success, and compensation
5Results, errors, and untrusted dataDual projections, per-tool output schemas, triple digest binding, error catalog, and provider adapters
6Idempotency, timeouts, and observabilityDuplicate suppression, timeout ambiguity, audit, and SLOs
7Tool-calling evaluation and offline projectAn 18-scenario, 23-step Tool Result v2 fixture and 120 regression tests for resource limits, approvals, and adapters
8SQLite persistence, idempotency, and outbox recoveryA ledger/outbox/lease/receipt adapter above v2, with 94 regressions for current claims, approval context, multi-connection, and crashes

Hands-on entry point

FilePurpose
tool-cases.jsontool-cases-v2: 18 scenarios, 23 dispatch/query-status steps, and expected dual projections
tool_dispatcher.pyProposal/context separation, per-tool input/output contracts, authorization/approval/idempotency, explicit status reconciliation, dual projections, triple bindings, and three provider adapters
test_tool_dispatcher.py120 tests for fixture limits, approver binding, current-business-state checks, failure recovery, output contamination, swap attacks, digest recomputation, provider-profile isolation, adapters, and CLI
persistence-case.jsonStrict JSON scenario for a persistent write action
persistent_tool_runtime.pySQLite operation ledger, transactional outbox, leases, receipt reconciliation, and PASS/BLOCK CLI
test_persistent_tool_runtime.py94 tests for JSON/database boundaries, current-principal resolution, approver and provider-context evidence, business state, call purpose, idempotency, crashes, authorization/contract drift, CLI/audit redaction, tampering, and multi-connection behavior

Run from the repository root:

$env:PYTHONDONTWRITEBYTECODE = '1'
$env:PYTHONIOENCODING = 'utf-8'
$examples = '.\docs-EN\tool-calling-function-calling\examples'
 
python -B -W error "$examples\tool_dispatcher.py" --fixture "$examples\tool-cases.json"
python -B -W error -m unittest discover -s $examples -p 'test_tool_dispatcher.py' -v
 
$persistence = Join-Path $examples 'persistence'
$db = Join-Path ([IO.Path]::GetTempPath()) ("tool-persistence-learning-{0}.sqlite3" -f [guid]::NewGuid().ToString('N'))
python -B -W error "$persistence\persistent_tool_runtime.py" --db $db --fixture "$persistence\persistence-case.json" dispatch
python -B -W error -m unittest discover -s $persistence -p 'test_persistent_tool_runtime.py' -v

The temporary database is created in the system temporary directory rather than the repository. The random GUID avoids collisions between concurrent runs.

Mastery criteria

  • I can state the responsibilities of the model, adapter, dispatcher, handler, and business service.
  • Tools resolve only from an explicit registry; unknown names never trigger dynamic import or eval.
  • The schema rejects missing, extra, wrong-type, and invalid-enum arguments.
  • Tenant, subject, roles, and service credentials do not come from the model.
  • Unauthorized resources return consistent, non-enumerating errors.
  • A write does not run without approval, after expiry, or after an argument, provider/API/adapter context, schema, or policy revision changes.
  • The same idempotency key and intent can replay across SQLite connections/runtime restarts; the same key with a different intent conflicts, and ledger/outbox writes share one local transaction.
  • I can explain why an expired outbox-worker lease may redeliver and why downstream idempotency is still mandatory; I do not claim exactly once.
  • I distinguish a confirmed pre-execution timeout from an unknown post-commit outcome; the latter is reconciled before any retry.
  • Idempotency keys have tenant/subject/tool scope and cannot let another tenant occupy the namespace.
  • Multiple calls run concurrently only when they are independent, conflict-free, and have explicit failure semantics.
  • Tool results remain untrusted data and cannot expand authority for the next action.
  • Per-tool output schemas reject extra control fields, sensitive fields, forged error origins, and input/output mismatches.
  • model_result and protected_audit are separate; provider payloads never expose the audit projection.
  • I can recompute full request/result/call SHA-256 values; call binding covers downstream request/receipt/status reference and rejects cross-call, cross-response, or evidence swaps.
  • I distinguish fresh, local_replay, and receipt_reconciled; unknown recovery happens only through explicit status query.
  • The loop has limits for turns, calls, time, cost, and repeated progress.
  • I can run and explain the 120 v2 tests and 94 persistent-layer tests, including their boundaries for offline execution contracts, SQLite-local races, at-least-once-compatible primitives, and real provider/distributed delivery.
Knowledge baseRelationship
APIParses provider output, streaming events, and API failures
RAGSupplies sourced knowledge; tools handle live state and actions
MCPSupplies standardized capability catalogs and transport, not business control
Agent CorePlaces calls in an observe–decide–act loop
Workflow AutomationUses reliable orchestration, retries, and compensation for fixed-path tasks
Evaluation systemsEvaluate routing, arguments, execution, recovery, and task success
AI SafetyCovers prompt injection, privilege escalation, data disclosure, and tool misuse

Primary references

Sources were retrieved on 2026-07-21. Tool fields, strict mode, parallelism limits, server-side tools, and model compatibility can change; lock the API/SDK/model version and recheck official documentation during integration.