Tool calling lets the model invoke Swift callbacks for actions such as lookup, computation, or retrieval.

Not every model supports tool calling well. For reliable results, start with recent tool-capable instruction models such as the Qwen family.

Declaring a tool

Each tool needs:

  • a name
  • a description
  • ordered parameter definitions
  • a callback that returns String
import Foundation
import Quaynor

let circleArea = Tool(
    name: "circle_area",
    description: "Calculates the area of a circle from its radius.",
    parameters: [
        ToolParameterDefinition(
            name: "radius",
            schema: .number(description: "The circle radius.")
        )
    ]
) { args in
    let radius = (args[0] as? Double) ?? 0
    let area = Double.pi * radius * radius
    return String(format: "%.2f", area)
}

Attach it when creating a chat:

let chat = try await Chat.fromPath(
    modelPath: "/path/to/model.gguf",
    tools: [circleArea]
)

Async tools

Async callbacks work too:

let readStatus = Tool(
    name: "read_status",
    description: "Reads the current deployment status.",
    parameters: []
) { _ in
    try await Task.sleep(nanoseconds: 200_000_000)
    return "Deployment healthy"
}

Object parameters

Use ToolSchema.object and related schema types when a tool needs structured arguments:

let scheduleMeeting = Tool(
    name: "schedule_meeting",
    description: "Schedules a meeting.",
    parameters: [
        ToolParameterDefinition(
            name: "request",
            schema: .object(
                properties: [
                    ToolProperty(
                        name: "title",
                        schema: .string(description: "Meeting title.")
                    ),
                    ToolProperty(
                        name: "duration_minutes",
                        schema: .integer(description: "Duration in minutes.")
                    )
                ],
                description: "The meeting request payload."
            )
        )
    ]
) { args in
    let request = args[0] as? [String: Any?] ?? [:]
    let title = request["title"] as? String ?? "Untitled"
    return "Scheduled \(title)"
}

Updating tools on an existing chat

try await chat.setTools([circleArea, readStatus])

Tool calls consume context, so plan for a larger contextSize when your agent relies heavily on tools.