Tool Use: How a Model Reaches the Outside World

How a model emits a structured tool call, why your application is the thing that actually executes it, and how the request-execute-respond loop works on Bedrock.

A foundation model generates tokens. That is the whole of what it does. It cannot query your order database, refund a payment, or tell you today's exchange rate, because none of that is in its weights and none of it is reachable from inside a text generator. Tool use — also called function calling — is the protocol that closes that gap. You describe the functions your application can run; the model, instead of answering, emits a structured request to run one of them; your code runs it and hands the result back; the model carries on with the answer.

The loop

  1. You send the conversation plus a set of tool definitions (name, description, and a JSON Schema for the arguments).
  2. The model replies either with ordinary text, or with a toolUse block: a tool name, a generated toolUseId, and an arguments object.
  3. Your application validates the arguments and executes the real function.
  4. You append the model's message and a matching toolResult to the conversation and call the model again.
  5. The model reads the result and either answers, or asks for another tool. Repeat until it stops.

The model never executes anything

This is the point the exam keeps circling. A tool call is a request, expressed as JSON that the model was trained to produce. Nothing runs unless your code decides to run it. That makes your application the trust boundary: the place where arguments are validated, IAM permissions apply, rate limits bite, and audit records get written. If a tool can delete a customer record, the model asking politely is not authorisation.

What tool use is not

  • Not retrieval. Grounding a model in your documents is a knowledge base and RAG problem. A tool can wrap a retrieval call, but "answer from these documents" is not the same design as "take this action".
  • Not fine-tuning. Fine-tuning changes how the model writes; tool use changes what it can reach. Teaching a model new facts by fine-tuning is almost always the wrong lever.
  • Not an agent by itself. A fixed sequence of steps you orchestrate in code or Step Functions is a workflow. It becomes an agent when the model chooses which tools to call, in what order, and when to stop.

Where this lives on AWS

You have two levels of ownership. With the Amazon Bedrock Converse API you pass toolConfig and run the loop yourself — maximum control, and you own every retry. With Amazon Bedrock Agents you define action groups (an OpenAPI schema or a function definition) and AWS runs the orchestration loop for you, invoking a Lambda function per action, or handing control back to your caller with the RETURN_CONTROL option so you can execute it yourself.

The costs you inherit

Every turn re-sends the whole transcript, so a five-tool conversation is five inference calls of increasing size. Tool definitions themselves sit in the prompt on every single request, so a bloated tool catalogue is a permanent tax. And a model that keeps calling tools without converging will loop until you stop it — a maximum-iteration counter is not optional.

Example

Example · python
import json
import boto3

bedrock = boto3.client("bedrock-runtime", region_name="eu-west-1")

TOOL_CONFIG = {
    "tools": [{
        "toolSpec": {
            "name": "get_order_status",
            "description": (
                "Look up the current fulfilment status of a single customer "
                "order by its order ID. Returns status, carrier and ETA."
            ),
            "inputSchema": {"json": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "Order ID, e.g. ORD-10432",
                    }
                },
                "required": ["order_id"],
            }},
        }
    }]
}


def get_order_status(order_id: str) -> dict:
    # Your real implementation: DynamoDB, RDS, an internal API...
    return {"order_id": order_id, "status": "in_transit", "eta": "2026-09-02"}


messages = [{"role": "user", "content": [{"text": "Where is order ORD-10432?"}]}]

for _ in range(5):  # hard iteration cap: never loop unbounded
    response = bedrock.converse(
        modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
        messages=messages,
        toolConfig=TOOL_CONFIG,
    )
    reply = response["output"]["message"]
    messages.append(reply)  # append the assistant turn verbatim

    if response["stopReason"] != "tool_use":
        print("".join(b["text"] for b in reply["content"] if "text" in b))
        break

    results = []
    for block in reply["content"]:
        if "toolUse" not in block:
            continue
        call = block["toolUse"]
        # YOUR code executes the tool - the model only asked.
        output = get_order_status(**call["input"])
        results.append({"toolResult": {
            "toolUseId": call["toolUseId"],
            "content": [{"json": output}],
            "status": "success",
        }})

    messages.append({"role": "user", "content": results})

When to use it

  • A support chatbot answers "where is my parcel?" by calling an internal order-tracking API rather than guessing from training data.
  • A finance assistant converts currency by calling a live FX rate tool, so the figure is correct today rather than correct at training time.
  • An internal ops assistant restarts a failed ECS task by emitting a tool call that a Lambda function executes after checking the caller's IAM entitlement.

Discussion

  • Be the first to comment on this lesson.