Back
advanced

AI Agents & Autonomous Systems

Tool calling: a proposal becomes an action only after validation

Design tool schemas, validate arguments and permissions, and handle duplicate or failed actions without inventing success.

Lesson 30 of 67About 24 min with practice

A model proposes a reservation, but the tool response never arrives. What actually happened? Tool calling becomes dependable only when the application distinguishes a proposed action, an attempted write, and a confirmed result.

Before you begin: You understand JSON, application functions, and a basic agent loop.

This boundary is where many reliability problems begin. Treating model output as trusted application input can turn a small language error into a real state change.

Describe a narrow tool

A tool should have a clear purpose and a constrained input shape. A read-only get_workshop tool is easier to reason about than a tool called do_anything that accepts an arbitrary command. Use precise field names and descriptions, and distinguish missing values from valid empty values.

Structured output can enforce syntax or schema constraints when supported. It does not verify that a workshop ID exists, that a time is current, or that the signed-in user may access the record. Those checks belong in application code.

Resolve identity outside the model

Do not ask the model to choose the acting user's account ID. Obtain identity from the authenticated session and pass only the necessary request data through the model. Enforce ownership when reading and writing records.

This local simulation demonstrates that the server's user identity and validation control the result. It deliberately has no network or real booking system.

python
# Runnable: Python 3, standard library.
workshops = {'pottery': {'seats': 2}}
bookings = {}

def reserve(authenticated_user, workshop, request_id):
    key = (authenticated_user, request_id)
    if key in bookings:
        if bookings[key]['workshop'] != workshop:
            raise ValueError('Request ID reused for another action')
        return bookings[key]
    if workshop not in workshops or workshops[workshop]['seats'] <= 0:
        return {'ok': False, 'reason': 'unavailable'}
    workshops[workshop]['seats'] -= 1
    result = {'ok': True, 'workshop': workshop}
    bookings[key] = result
    return result

assert reserve('user-a', 'pottery', 'request-1')['ok']
assert reserve('user-a', 'pottery', 'request-1')['ok']
assert workshops['pottery']['seats'] == 1

The repeated request does not reserve a second seat. In production, the check and write must be atomic in durable storage; this in-memory example is not safe under concurrent requests. Permission checks and any required confirmation also happen before the action.

Reuse an ID for a different action

The local example correctly deduplicates a repeated reservation request. Now call it with the same authenticated user and request ID but a different workshop. The handler rejects that mismatch rather than returning an unrelated old booking or treating the ID as a fresh action.

Why bind an operation ID to its payload?

Deduplication should identify the same intended operation, not suppress arbitrary later operations that happen to reuse a key. Store enough of the validated payload to detect a conflict. In production, enforce this with atomic durable writes and the relevant ownership checks, not only a process-local dictionary.

For practice, add cases for a second authenticated user using the same request ID, a sold-out workshop, and a crash after a possible commit. State which cases should return an existing result, reject a conflict, or require reconciliation. This turns “retry safely” into specific observable behavior.

Return results the model can use honestly

Provide a structured status, stable identifiers, and an actionable error category. A timeout means the caller may not know whether the operation completed. Before retrying a side effect, use an idempotency key or query the operation status.

Preserve tool-call identifiers when returning results so the model can associate each observation with the correct request. Tool names, message formats, and call-ID fields differ across APIs; follow the current provider or framework documentation rather than mixing formats.

Never let the final response claim success solely because a call was proposed. Tie “confirmed” to a confirmed application result. Keep retrieved text and tool descriptions from untrusted sources from redefining permissions.

Exercise: the booking call times out after the database may have committed. Is an immediate retry with a new request ID safe?

Compare your reasoning

It can create a duplicate. Reuse the same operation identity or check status through a defined recovery path. “Retry” is not a complete policy until duplicate effects are handled.

Next, decide when several agents improve a task and when they merely multiply these coordination problems.

Sources

The current LangChain tools guide describes tool schemas and runtime context. MCP architecture provides a separate protocol-level view; transport interoperability does not supply application authorization.

Continue: Multi-Agent Systems.

Practice for this lesson

A proposal becomes an action only after validation

Resolve identity outside the model and make repeated calls safe.

About 12 min70 points3 checks and one written task
Loading your lesson progress...