Most AI support bots disappoint for the same reason: they can talk, but they can’t do anything. Ask about an order and you get a polite paragraph about checking your email. The model was never the problem. It simply had no access to the data that would answer the question.
An agent that actually helps needs two things a plain chatbot lacks: your real data, and a safe way to act on it. This post shows how I approach that in a Laravel application, using tool use (also called function calling) so the model can look things up in your database without ever touching it directly.
The pattern: the model asks, your code decides
With tool use, you describe a small set of functions to the model: their names, what they do and what inputs they take. When answering a question needs data, the model replies with a request to call one of those tools. Your code runs it, with your validation and permission checks, and sends the result back. The model then answers using real data.
The model never gets database credentials, never writes SQL and never sees anything you didn’t return. You decide exactly what is reachable.
Start with narrow, read-only tools
The most common mistake is giving the agent one giant “run a query” tool. Instead, give it a few tools that each do one job, scoped to the signed-in customer:
$tools = [
[
'name' => 'get_order_status',
'description' => 'Look up the status, items and delivery estimate of one '
.'of the current customer\'s orders by its order number.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'order_number' => ['type' => 'string', 'description' => 'For example A10293'],
],
'required' => ['order_number'],
],
],
[
'name' => 'search_help_articles',
'description' => 'Search the help centre for articles relevant to a question.',
'inputSchema' => [
'type' => 'object',
'properties' => ['query' => ['type' => 'string']],
'required' => ['query'],
],
],
];
Notice what isn’t there: no customer ID input. The tool code takes the customer from the authenticated session, so even a cleverly worded message can’t make the agent fetch someone else’s order.
The loop
The conversation runs in a loop: send the messages and tools, run any tools the model asks for, send the results back, and repeat until the model gives a final answer. Here it is with the official Anthropic PHP SDK:
use Anthropic\Client;
use Anthropic\Messages\ToolUseBlock;
$client = new Client(apiKey: config('services.anthropic.key'));
$messages = [['role' => 'user', 'content' => $question]];
for ($turn = 0; $turn < 5; $turn++) {
$response = $client->messages->create(
model: 'claude-opus-5-5',
maxTokens: 16000,
system: 'You are the support assistant for Acme. Answer only from tool '
.'results and help articles. If you cannot find the answer, offer to '
.'hand over to a human.',
tools: $tools,
messages: $messages,
);
if ($response->stopReason !== 'tool_use') {
break; // final answer (or a refusal: check stopReason before showing it)
}
$results = [];
foreach ($response->content as $block) {
if ($block instanceof ToolUseBlock) {
$results[] = [
'type' => 'tool_result',
'toolUseID' => $block->id,
'content' => json_encode($this->runTool($block->name, $block->input, $user)),
];
}
}
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$messages[] = ['role' => 'user', 'content' => $results];
}
A few choices worth copying. The loop has a hard limit on turns, so a confused conversation can’t run forever. Every tool result goes back in a single user message. And runTool() is ordinary Laravel code: it resolves the tool name to a method, validates the input, applies the same policies as the rest of your app, and returns only the fields the agent needs.
Ground answers in your content
For questions that aren’t about a specific record, such as “how do I change my plan?”, the agent should answer from your own help content rather than from general knowledge. That is what the search_help_articles tool is for: it searches your articles (keyword search is a fine start; vector search helps once you have a lot of content) and returns the relevant passages. The system prompt tells the model to answer only from what the tools return, and to say so when it can’t find an answer.
This approach, retrieval-augmented generation, keeps answers current: update an article and the agent’s answers change with it, with no retraining involved.
Actions need a human in the loop
Reading data is low risk. Changing it, such as issuing a refund, cancelling an order or updating an address, is not. When you add action tools:
- Require confirmation. The agent proposes the action and the customer, or a staff member, approves it before your code runs it.
- Reuse your permissions. An action tool goes through the same authorization as the equivalent button in your app.
- Log everything. Store every tool call, its input, its result and who approved it.
Measure it before and after launch
AI features drift: a prompt tweak or a model change can quietly make answers worse. Before launch, collect a set of real customer questions with the answers you expect, and run them every time the prompt, tools or model change. After launch, log conversations (with sensible privacy rules), track how often the agent hands over to a human, and review the failures weekly.
The short version
- Give the model narrow tools, not database access.
- Take identity from the session, never from the model’s input.
- Run the loop with a turn limit and your normal validation and policies.
- Ground general answers in your own content.
- Put a human in front of any action that changes data.
- Keep an evaluation set and review real conversations.
Built this way, an AI agent becomes a fast, honest first line of support that knows your product, instead of a chatbot that apologises a lot. If you have a use case in mind, here is how I build AI agents and chatbots into existing products.