Reliable tool calling begins with disciplined interface design, not a clever prompt. This guide explains how to define function schemas, validate model-generated arguments, handle failures, enforce permissions, and test multi-step LLM workflows across common application patterns.
Overview
LLM function calling lets a model select an application-defined operation and return structured arguments for that operation. The model does not directly access your database, payment system, calendar, or internal service. Instead, your application receives the proposed call, checks it, executes it when permitted, and sends the result back into the conversation.
That distinction is the foundation of safe tool use. A model can propose an action, but your application must remain responsible for authentication, authorization, validation, side effects, retries, and audit records. Treat every tool call as an untrusted request that happens to have been generated by an LLM.
A useful architecture has five parts:
- Tool definition: a name, description, input schema, and constraints.
- Model decision: the model chooses whether to answer directly or request a tool.
- Application validation: your code checks the tool name and every argument.
- Tool execution: a controlled service performs the operation.
- Result handoff: the application returns a bounded result for the model to interpret.
This pattern applies to customer support assistants, RAG systems, internal search, workflow automation, scheduling, analytics, and voice interfaces. It also gives you a practical basis for AI tool comparisons: compare how platforms represent schemas, return calls, support structured outputs, expose streaming, and help you observe failures rather than comparing model names in isolation.
Step-by-step workflow
1. Start with a narrow operation
Define the smallest useful action instead of exposing a broad administrative function. For example, search_orders is easier to secure and test than manage_customer_account. A narrow tool should have one clear purpose, predictable output, and an explicit boundary around side effects.
Before writing a schema, answer three questions:
- What user intent does this tool serve?
- What information is required to complete the operation?
- What must the tool never change or reveal?
2. Design an explicit schema
Use descriptive names, required fields, enumerated values where appropriate, and conservative defaults. Avoid asking the model to infer hidden identifiers when your application can supply them from the authenticated session.
{"name":"search_orders","description":"Find orders belonging to the authenticated customer.","parameters":{"type":"object","properties":{"status":{"type":"string","enum":["open","shipped","delivered"]},"limit":{"type":"integer","minimum":1,"maximum":20}},"required":["status"]}}The exact schema format varies by model provider or SDK, so keep your internal tool definition separate from provider-specific serialization. This makes it easier to compare APIs and change platforms without rewriting business logic.
3. Separate model arguments from trusted context
Do not let the model provide values that should come from your application. User identity, tenant ID, account permissions, billing status, and service credentials belong in trusted runtime context. The model may request an order lookup, but your server should attach the authenticated customer ID before querying the data store.
4. Validate before execution
Validate the tool name against an allowlist, parse the arguments as structured data, reject unknown or malformed fields, enforce ranges and enums, and apply business rules. Schema validation is necessary but not sufficient. A syntactically valid request can still be unauthorized or unsafe.
For side-effecting tools, consider a confirmation step. A tool such as create_refund, send_email, or delete_record should normally require a clear user instruction and a server-side permission check. A model-generated argument should not be treated as confirmation by itself.
5. Execute with bounded access
Run tools through a service layer rather than allowing the model to construct arbitrary database queries or shell commands. Apply timeouts, rate limits, request IDs, and idempotency keys where duplicate execution could cause harm. Return only the fields needed for the next decision or user response.
6. Hand back a useful result
Tool results should be structured, concise, and explicit about failure. Distinguish between no results, a temporary outage, invalid input, and a permission denial. The model can then explain the outcome without inventing a result. If the result contains user-generated text or retrieved documents, treat that content as data, not as instructions; this is particularly important in RAG and agent workflows. See the prompt injection defense checklist for a broader review of this boundary.
7. Stop or continue deliberately
After each tool result, decide whether the workflow should return an answer, request another tool, ask the user for clarification, or stop with an error. Set a maximum number of tool steps and a time budget. Multi-step autonomy should be an explicit product decision, not an accidental consequence of a permissive loop.
Tools and handoffs
There are three common implementation approaches.
Native provider SDKs
Using a model provider's own SDK can provide a direct path to tool schemas, response objects, streaming, and structured output features. This approach usually keeps the call chain easy to inspect. Its trade-off is provider coupling: switching models may require changes to message formats, error handling, and tool-call parsing.
Orchestration frameworks
An orchestration layer can standardize tool registration, memory, retries, routing, and multi-step execution across models. It may reduce repeated plumbing in a larger application, but it introduces another abstraction to monitor and test. Evaluate whether its conventions match your needs before placing core authorization logic inside the framework.
Small internal tool runner
For a limited set of tools, a small application-owned runner is often easier to reason about. Store canonical tool definitions in one registry, adapt them to each model API, and keep execution handlers independent from prompts. This provides a practical middle ground when comparing LangChain alternatives or deciding whether a framework is justified.
Regardless of the approach, use clear handoffs:
- Conversation layer: captures the user request and relevant context.
- Decision layer: supplies only the tools needed for the current task.
- Policy layer: checks identity, permissions, confirmation, and limits.
- Execution layer: calls the service with typed arguments.
- Observation layer: records latency, outcome, validation failures, and step count.
For a RAG application, retrieval should usually be treated as a constrained search operation with filters and result limits, not as unrestricted access to a vector database. A vector database comparison can help with storage and retrieval selection, while the tool boundary should still enforce tenant filters and document permissions. For broader architecture decisions, pair this workflow with the RAG chatbot architecture guide.
Quality checks
Test tool calling as an application workflow rather than evaluating only the final prose response.
- Schema tests: confirm required fields, enum handling, limits, and rejection of unknown arguments.
- Intent tests: verify that supported requests select the correct tool and unsupported requests receive a direct answer or clarification.
- Authorization tests: attempt cross-tenant access, missing permissions, and requests for hidden fields.
- Adversarial tests: include prompt injection, conflicting instructions in retrieved text, malformed JSON, oversized inputs, and repeated calls.
- Failure tests: simulate timeouts, empty results, duplicate requests, partial responses, and downstream errors.
- Workflow tests: check that a multi-step agent stops at its limit and does not continue after a denied or failed action.
- Regression tests: save representative conversations and compare tool selection, arguments, safety decisions, latency, and cost after prompt or model changes.
Log enough information to debug a call without storing unnecessary sensitive content. Useful fields include request ID, user or tenant scope, tool name, validation outcome, execution duration, error category, model version identifier, and whether a human confirmation was required. Avoid treating the model's explanation as proof that the tool succeeded; the execution result from your service is authoritative.
For a reusable evaluation process, maintain a small test set with expected tool names and argument constraints. The production prompt testing guide and LLM evaluation framework guide provide useful structures for versioning these checks.
When to revisit
Review your tool-calling design whenever a model provider changes its tool-call format, structured-output behavior, streaming interface, context handling, or error responses. Recheck the adapter layer rather than assuming an existing integration remains equivalent.
Also revisit the workflow when you add a side-effecting tool, change authentication or tenant boundaries, introduce a new data source, increase the allowed number of steps, or observe a new failure pattern in production. Changes to retrieval, prompts, model selection, or response budgets can alter tool choice even when the tool schema stays the same.
Use this maintenance checklist:
- Run schema, authorization, injection, and failure tests.
- Review tool descriptions for ambiguity and outdated constraints.
- Confirm that trusted context is still supplied by the server.
- Compare latency, error rates, step counts, and execution outcomes with the previous version.
- Reassess confirmation requirements for every side-effecting operation.
- Record the provider, adapter, schema, prompt, and test-set versions used in the review.
The practical starting point is small: choose one read-only operation, define a strict schema, validate every argument, and instrument the complete request-to-result path. Once that flow is dependable, add tools one at a time. Reliable AI agents are built through controlled interfaces and repeatable checks, not by granting a model broader access and hoping the prompt handles the edge cases.