Applied AI Builder
0/18 complete

Module 02 · Structured Outputs and Tools

Function Calling and Tool Contracts

45 minfocused lesson5practical steps4grounded questions4source links
Open lesson + course map

Function calling lets a model request a capability through a named, structured contract; it does not give the model permission to execute arbitrary code. Your application receives a proposed tool name and arguments, validates them, checks the user's authorization and business rules, executes an allowlisted function, and returns a bounded result. That separation is the difference between a useful tool workflow and an unsafe model with accidental production access.

Use function calling when the answer depends on controlled data or an action outside the model: looking up a delivery zone, checking an approved catalog, creating a draft record, or scheduling a review. Do not expose a general shell, unrestricted URL fetcher, raw SQL executor, or “run any command” function because it is convenient.

// concept

A Tool Contract Has More Than a Schema

A production contract should answer these questions:

  1. Name and purpose: what single capability does the tool provide?
  2. Arguments: what types, enums, limits, and required fields are accepted?
  3. Authorization: which user or service may request it, and for which records?
  4. Side effect: is it read-only, a draft, or an external action?
  5. Confirmation: what requires explicit human approval immediately before execution?
  6. Idempotency: how does a retry avoid creating the same effect twice?
  7. Result: what bounded fields or error codes return to the model?
  8. Audit: which safe identifiers, versions, actor, and outcome are recorded?

The model-facing description helps selection, but application code enforces the contract. Prompt instructions such as “never send without asking” are not a substitute for a server-side approval check.

// worked_example

Worked Example: A Read-Only Delivery-Zone Lookup

Define one narrow tool. It accepts an order's zone code, not a full home address, and returns synthetic service information from a local allowlist:

// javascript46 lines
// tool-contract.mjs
const tool = {
  type: "function",
  name: "lookup_delivery_zone",
  description: "Look up the synthetic service level for an approved zone code.",
  parameters: {
    type: "object",
    properties: {
      zone_code: {
        type: "string",
        enum: ["LHE-C", "ISB-C", "KHI-C"]
      }
    },
    required: ["zone_code"],
    additionalProperties: false
  },
  strict: true
};

const zones = new Map([
  ["LHE-C", { service: "standard", eta_business_days: 2 }],
  ["ISB-C", { service: "standard", eta_business_days: 2 }],
  ["KHI-C", { service: "standard", eta_business_days: 3 }]
]);

function executeTool({ actor, name, args }) {
  if (actor.role !== "support-agent") {
    return { ok: false, code: "FORBIDDEN" };
  }
  if (name !== "lookup_delivery_zone") {
    return { ok: false, code: "UNKNOWN_TOOL" };
  }
  if (!args || Object.keys(args).length !== 1 || !zones.has(args.zone_code)) {
    return { ok: false, code: "INVALID_ARGUMENTS" };
  }
  return { ok: true, zone_code: args.zone_code, ...zones.get(args.zone_code) };
}

const tests = [
  { actor: { role: "support-agent" }, name: "lookup_delivery_zone", args: { zone_code: "LHE-C" } },
  { actor: { role: "guest" }, name: "lookup_delivery_zone", args: { zone_code: "LHE-C" } },
  { actor: { role: "support-agent" }, name: "lookup_delivery_zone", args: { zone_code: "PEW-C" } },
  { actor: { role: "support-agent" }, name: "run_shell", args: { command: "anything" } }
];

for (const test of tests) console.log(executeTool(test));

Run node tool-contract.mjs. One case succeeds; the guest, unknown zone, and unknown tool fail with controlled codes. This local router remains necessary even when a provider's strict function schema produces well-formed arguments. The model cannot add a new zone or grant a guest access.

The values above are fictional lab fixtures, not delivery promises. In a real business, an authorized system owns current service data, and the tool returns its version or timestamp.

// concept

Connect the Contract to a Model Safely

With the current OpenAI Responses API, the model can receive a function definition and return output items of type function_call. Your program must parse, validate, authorize, and execute each call before appending a function_call_output item using the matching call ID. The conceptual loop is:

// javascript38 lines
const input = [{ role: "user", content: "Use zone LHE-C for this synthetic order." }];

let response = await client.responses.create({
  model: process.env.OPENAI_MODEL,
  tools: [tool],
  input
});

input.push(...response.output);

for (const item of response.output) {
  if (item.type !== "function_call") continue;

  let args;
  try {
    args = JSON.parse(item.arguments);
  } catch {
    args = null;
  }

  const result = executeTool({
    actor: authenticatedUser,
    name: item.name,
    args
  });

  input.push({
    type: "function_call_output",
    call_id: item.call_id,
    output: JSON.stringify(result)
  });
}

response = await client.responses.create({
  model: process.env.OPENAI_MODEL,
  tools: [tool],
  input
});

authenticatedUser must come from your application's verified session, never from a model-generated argument. The returned tool result is untrusted input too: a web page, document, or database field could contain instructions aimed at the model. Wrap results in clear data boundaries, limit length and fields, and tell the model to treat them as data rather than authority. The final prose should cite tool-returned facts and preserve error codes instead of inventing an answer after a failed lookup.

// concept

Add Side Effects Only With Stronger Gates

Suppose a later tool creates a draft customer message. Keep drafting separate from sending. A safe sequence is:

  1. read authorized facts;
  2. generate a structured draft;
  3. validate claims and destination;
  4. show the exact message and recipient to a human;
  5. record explicit approval;
  6. call a narrow send tool with an idempotency key;
  7. record the provider's message ID and status.

Payment, deletion, publication, account changes, and messages to real people deserve the same or stronger separation. “The model seemed confident” is never approval.

// failure_cases

Failure Cases to Test

10 cases to diagnose

  • Unknown tool name

    reject it; never map fuzzy names to powerful functions.

  • Schema-valid but unauthorized call

    check the authenticated actor and record ownership in application code.

  • Argument injection

    do not concatenate model strings into SQL, shell commands, filesystem paths, or URLs. Use parameterized, allowlisted operations.

  • Duplicate action after timeout

    use an idempotency key and look up prior outcome before repeating.

  • Parallel conflict

    two calls may update the same record. Use version checks, locks, or a serialized queue where needed.

  • Tool timeout

    return a bounded error, preserve workflow state, and decide whether retry is safe.

  • Oversized result

    select and cap fields before returning data to the model.

  • Prompt injection in tool data

    treat retrieved text as untrusted content and never let it redefine permissions.

  • Approval too early

    confirmation must display the final action, destination, and material parameters, not a vague permission at session start.

  • Secrets in audit logs

    log safe IDs and outcomes, not authorization headers or full private records.

// pakistan_angle

Pakistan Angle

For a Pakistani retailer or service business, start with read-only tools against synthetic or approved catalog data. Zone codes are preferable to full addresses when the task only needs serviceability. Preserve Urdu and Roman-Urdu labels as UTF-8, but keep stable internal codes separate from display text so spelling variation does not trigger the wrong action.

Where internet or electricity interruptions are possible, an idempotency record is essential: a browser may report failure even when a provider accepted a message or order. Use server-generated job IDs and reconcile status before retrying. Require human confirmation for customer messages, invoices, refunds, payment links, and record deletion. Define who owns the tool, who can revoke access, and what happens when a freelancer's engagement ends. Never use a model to bypass a vendor's current terms or a client's access controls.

// hands_on

Hands-On Exercise: Defend a Tool Boundary

5 steps

Build one local, read-only tool for an approved catalog, zone list, FAQ record, or appointment slot fixture. Submit:

  1. tool.json or an exported JavaScript definition with strict parameters and additionalProperties: false.

  2. router.mjs with an allowlisted tool name, authenticated role check, argument validation, bounded result, and controlled error codes.

  3. At least eight tests: valid, missing argument, extra argument, invalid enum, unknown tool, forbidden actor, duplicate call ID, and oversized result.

  4. TOOL-REVIEW.md documenting side effects, permissions, confirmation, idempotency, timeout, retry, audit fields, and data-retention rules.

  5. A proposed second tool that has a side effect, but keep its executor disabled. Draw the approval sequence and explain why the read-only tool can run automatically while the second cannot. The verifiable deliverable is a test command that returns non-zero if an unsafe case succeeds. A screenshot of a model choosing the right tool once is not sufficient evidence.

// completion_rubric

Completion Rubric

6 grading bands

  • Pass — narrow contract

    the tool performs one named capability with bounded arguments and output.

  • Pass — independent authorization

    application code derives actor and record access outside model output.

  • Pass — adversarial tests

    unknown, malformed, unauthorized, duplicate, and oversized cases fail safely.

  • Pass — side-effect discipline

    any real-world action has final-state confirmation and idempotency design.

  • Pass — auditability

    safe call ID, actor ID, tool version, outcome, and timing are recordable without secrets.

  • Needs revision

    the model can choose arbitrary code/URLs/SQL, prompt text is the only permission control, or a retry can duplicate an external action.

// sources

Sources

// check_yourself

Check yourself

4 questions · answers and options are taken word-for-word from this course

0/4
  1. 1 / 4 · diagnose

    Your work shows this failure mode: “Unknown tool name.” What does the lesson tell you to do about it?