Developers Tools and function calling

Tools and function calling.

You define the functions. You run them.

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

  1. Send messages and tools.
  2. If the reply has tool_calls, run each one. Parse function.arguments (a JSON string) and validate it. The model has read untrusted text, so treat arguments like user input.
  3. Append the assistant message, then one {role:"tool", tool_call_id, content} message per call.
  4. 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 tools array answers 400 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.