On this page
Function calling lets a model request an application operation instead of only writing a reply. A support assistant might ask to look up an order, inspect delivery options or prepare a return. Your application receives that request, decides whether it is valid and executes the permitted operation.
That separation is the important part. The model proposes an action; it does not acquire the user's permissions or become a trusted backend. A robust integration treats every generated tool request as untrusted input, even when the provider guarantees that it matches a JSON schema.
Design tools around bounded operations
Start with narrow tool contracts that describe useful product actions. Prefer looking up the delivery status of an authorised order over a generic tool that accepts any SQL statement. Small contracts are easier to explain, validate, audit and test against real user intentions.
Write descriptions that distinguish similar tools. Specify when to use each operation, what inputs mean and which side effects it has. Ambiguous names encourage unnecessary calls or mistaken choices. Avoid exposing internal endpoints simply because they already exist; design an adapter that presents the model with a safer interface.
Separate read and write operations. Looking up available appointment slots is not equivalent to booking one. A write tool should make its effect obvious and require the identifiers and preconditions needed to execute safely. Do not bundle several unrelated mutations into one convenient “manage account” function.
The OpenAI API documentation explains provider-specific tool declaration and response formats. Follow the SDK's actual protocol rather than treating illustrative snippets as portable across services. Tool names, call identifiers and message sequencing are transport details that must remain consistent.
Validate arguments beyond their shape
Define an argument schema with required properties, enums and bounded strings where supported. Reject unexpected fields when the contract does not permit them. A schema can ensure that an order identifier is a string, but cannot prove that the identifier belongs to the current user.
Use the JSON to TypeScript tool to turn a representative, non-sensitive tool payload into draft interface types. Review optional fields and unions against the actual contract. Generated TypeScript helps development, but types disappear at runtime; the server still needs a real validator for every incoming tool call.
A small read-only contract could look like this:
{
"name": "get_order_status",
"description": "Read delivery status for one authorised order",
"parameters": {
"type": "object",
"properties": {
"orderId": { "type": "string", "maxLength": 80 }
},
"required": ["orderId"],
"additionalProperties": false
}
}Perform semantic validation after parsing: check identifier format, resource existence, tenant membership and current state. Resolve the authenticated user from the server session, not a model-provided user field. A perfectly formatted request can still be unauthorised or nonsensical.
Treat model-generated URLs, paths and query fragments with particular care. Prefer fixed endpoint adapters and parameterised data access. Do not interpolate generated arguments into a shell command or allow unrestricted network fetching merely because the JSON validates.
Build a bounded execution loop
The application sends the user request and available tool definitions, receives a proposed call, validates and executes it, then returns the result through the provider's expected message format. The model can then answer or request another permitted operation. Keep execution ownership in your server throughout this loop.
Set maximum tool rounds, total elapsed time, token usage and cumulative execution cost. Several individually valid calls can still form an expensive or unhelpful sequence. Stop with a clear explanation or handoff when a limit is reached rather than allowing the model to keep searching indefinitely.
Represent tool failures explicitly. Distinguish not found, permission denied, temporary unavailability and invalid arguments, without revealing sensitive internals. Do not return an empty result for every error: the model may interpret that as evidence that no order or appointment exists.
Preserve call identifiers and associate each result with the correct request. If you allow parallel reads, ensure they are independent and permitted for the same context. Avoid parallel mutations unless your application explicitly handles ordering, conflicts and recovery.
The LLM cost control guide explains why iteration budgets matter alongside token ceilings. A tool loop can spend money in search services or other APIs as well as in the model itself.
Put approval in front of consequential writes
Use explicit confirmation for actions such as sending messages, changing account access or placing an order. Show the user the actual effect, recipient, amount and relevant identifiers. A vague “continue?” prompt does not establish informed approval for an action assembled from generated arguments.
Bind approval to the exact validated operation, including its parameters and relevant state. If those parameters change, ask again. Recheck permissions and preconditions at execution time because account access, prices or availability may have changed while the user reviewed the proposal.
Use idempotency protection for repeatable requests where the underlying service supports it. Store execution state so a retry after a timeout does not blindly repeat a payment or booking. An uncertain network outcome requires reconciliation, not the assumption that the first attempt failed.
The OWASP API Security project is a practical reference for authorization and resource-consumption risks. The companion API proxy security article explains why credentials and policy enforcement must stay behind a trusted server boundary.
Treat tool results as untrusted evidence
Tool responses may include user-written notes, external pages or uploaded documents. Those fields can contain malicious instructions. Enclosing them in a tool-result message helps organise context, but does not make the contents trustworthy or override the need for downstream checks.
Return minimal result data. If the assistant only needs delivery status, do not include payment details or the entire customer record. Reducing unnecessary context limits disclosure risk, improves relevance and reduces token consumption. Apply data access controls before content reaches the model.
The OWASP prompt injection guidance describes attacks that can arrive through retrieved content. Test whether a malicious note can cause another lookup, leak information or persuade the assistant to propose a write outside the user's request.
Evaluate full sequences, not just individual calls. Include expired sessions, cross-tenant identifiers, duplicate submissions, stale approvals and provider outages. Record safe audit metadata linking user intent, validated proposal, approval and execution outcome. The AI hub can help frame the wider workflow, but release readiness depends on your own evidence that these boundaries hold.
Key takeaways
- Expose narrow tools instead of general database or network access.
- Validate permissions and business meaning after checking argument shape.
- Bound execution loops and return clear, structured failure states.
- Bind user approval to exact write parameters and handle duplicate execution.
- Minimise tool results and test hostile content across complete action sequences.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor