Building an AI Shopping Assistant with Claude Tool Use
2026-09-15
One of the most interesting features we shipped in Storich is an AI-powered shopping assistant that can look up products, add items to the user's cart, negotiate prices, and make personalised recommendations, all through a natural language chat interface. Here is how we built it using Anthropic's Claude API with tool use.
Tool use (also called function calling) lets you give Claude a set of functions it can invoke during a conversation. Instead of just generating text, Claude can decide mid-response to call one of your functions, receive the result, and incorporate it into its reply. This turns Claude from a text generator into an agent that can interact with your systems.
We define tools as JSON schemas that describe what each function does and what parameters it accepts:
const shoppingTools = [
{
name: 'search_products',
description:
'Search for products in the Storich catalogue by name or category. ' +
'Use this when the user asks about available products or what we stock.',
input_schema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search term, e.g. "tomatoes" or "cooking oil"',
},
category: {
type: 'string',
description: 'Optional category filter',
},
},
required: ['query'],
},
},
{
name: 'add_to_cart',
description:
"Add a product to the user's cart. Only call this after confirming " +
'the product ID from a search result.',
input_schema: {
type: 'object',
properties: {
productId: { type: 'string' },
quantity: { type: 'number' },
},
required: ['productId', 'quantity'],
},
},
{
name: 'make_offer',
description:
'Apply a negotiated discount to a product when the user haggles. ' +
'Maximum discount is 10%.',
input_schema: {
type: 'object',
properties: {
productId: { type: 'string' },
discountPercent: { type: 'number' },
},
required: ['productId', 'discountPercent'],
},
},
];
Claude may call multiple tools across multiple turns before giving a final response. You handle this with a loop:
async function runAssistant(
userId: string,
userMessage: string,
conversationHistory: MessageParam[],
): Promise<string> {
const client = new Anthropic();
const messages: MessageParam[] = [
...conversationHistory,
{ role: 'user', content: userMessage },
];
while (true) {
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
system: `You are Storich's shopping assistant. Help customers find
groceries, add items to their cart, and get the best prices.
Always search before adding to cart. You can negotiate up to
10% off when customers ask for a better price.`,
tools: shoppingTools,
messages,
});
// Claude is done, return the text response
if (response.stop_reason === 'end_turn') {
const textBlock = response.content.find((b) => b.type === 'text');
return textBlock?.text ?? '';
}
// Claude wants to call tools
if (response.stop_reason === 'tool_use') {
const toolResults: ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type !== 'tool_use') continue;
const result = await executeTool(userId, block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result),
});
}
// Feed tool results back to Claude
messages.push({ role: 'assistant', content: response.content });
messages.push({ role: 'user', content: toolResults });
}
}
}
async function executeTool(
userId: string,
toolName: string,
input: Record<string, unknown>,
): Promise<unknown> {
switch (toolName) {
case 'search_products': {
return productsService.search({
query: input.query as string,
category: input.category as string | undefined,
});
}
case 'add_to_cart': {
return cartService.addItem(userId, {
productId: input.productId as string,
quantity: input.quantity as number,
});
}
case 'make_offer': {
// Enforce max discount in the executor, not in Claude's input
const discount = Math.min(input.discountPercent as number, 10);
return offersService.applyDiscount(userId, {
productId: input.productId as string,
discountPercent: discount,
});
}
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
User: "Do you have palm oil? How much for 5 litres?"
Claude calls search_products({ query: "palm oil" })
Tool returns: [{ id: "p123", name: "Kings Palm Oil 5L", price: "4500" }]
Claude: "Yes! Kings Palm Oil 5L is ₦4,500. Want me to add it to your cart?"
User: "Can you do ₦4,000?"
Claude calls make_offer({ productId: "p123", discountPercent: 11 })
Executor enforces max 10%, returns: { finalPrice: "4050", discountApplied: "10%" }
Claude: "Best I can do is 10% off, ₦4,050. Want to go ahead?"
User: "Yes add it"
Claude calls add_to_cart({ productId: "p123", quantity: 1 })
Tool returns: { success: true }
Claude: "Done! Kings Palm Oil 5L (₦4,050) is in your cart."
end_turn.