The API passes your function tools to the model and returns its tool_calls in the OpenAI shape. Artemii does not run your tools. You run them and send tool messages back, then call the API again. finish_reason is tool_calls when the model wants one run.
The loop
- Send
messagesandtools. - If the reply has
tool_calls, run each one. Parsefunction.arguments(a JSON string) and validate it. The model has read untrusted text, so treat arguments like user input. - Append the assistant message, then one
{role:"tool", tool_call_id, content}message per call. - Call the API again. Stop when the reply has no
tool_calls.
curl: define a tool
Shell
curl https://mayfair-ai.crelloo.workers.dev/v1/chat/completions \
-H "Authorization: Bearer $ARTEMII_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "arlo",
"messages": [{"role": "user", "content": "Where is order 1042?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up an order by its number.",
"parameters": {
"type": "object",
"properties": { "orderNumber": { "type": "string" } },
"required": ["orderNumber"]
}
}
}]
}'JavaScript: the whole loop
JavaScript
import { createClient } from './artemii.js';
const ai = createClient({ baseUrl: 'https://mayfair-ai.crelloo.workers.dev', apiKey: process.env.ARTEMII_KEY });
const tools = [{
type: 'function',
function: {
name: 'get_order_status',
description: 'Look up an order by its number.',
parameters: { type: 'object', properties: { orderNumber: { type: 'string' } }, required: ['orderNumber'] },
},
}];
// Artemii does not run your tools. You run them and send the result back as a "tool" message.
async function runMyTool(name, args) {
if (name === 'get_order_status') return { orderNumber: args.orderNumber, status: 'shipped' };
return { error: 'unknown tool' };
}
const messages = [{ role: 'user', content: 'Where is order 1042?' }];
for (let round = 0; round < 5; round++) {
const reply = await ai.chat({ model: 'arlo', messages, tools });
const message = reply.choices[0].message;
messages.push(message);
if (!message.tool_calls?.length) {
console.log(message.content);
break;
}
for (const call of message.tool_calls) {
const result = await runMyTool(call.function.name, JSON.parse(call.function.arguments));
messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) });
}
}Good practice
- Limit the number of rounds, as above.
- Ask the person before any action that sends, deletes, pays or changes data. The embeddable assistant does this for you with approval cards.
- Bound ids and lengths, and never build HTML or queries from model arguments.
- An invalid
toolsarray answers400 invalid_tools.
Tools in the app are different
Inside the Artemii app, the assistant can also use built-in tools and the services a person has connected. Those run in the app, not through this API. To let Artemii act inside your own Mayfair app, publish an app tools manifest.