Tools are the mechanism through which OrbitAI agents interact with the external world and perform specialized operations. They enable agents to go beyond language processing to take concrete actions, access external resources, and manipulate data.
Extensible
Built-in tools and custom tool creation support
Type-Safe
JSON Schema validation ensures correct usage
Async-Ready
Fully asynchronous execution with Swift Concurrency
Agents automatically determine which tools to use based on task requirements, available tools, and context. The LLM reasons about tool usage and orchestrates multi-tool workflows.
Schema Validation
Every tool defines its input parameters using JSON Schema, ensuring type safety and preventing invalid tool usage before execution.
Execution Tracking
Tool execution is monitored with detailed metrics including execution time, success status, input/output sizes, and error information.
Error Handling
Built-in error handling mechanisms ensure graceful failures with detailed error messages and recovery strategies.
Description: Read and process file contents
Parameters: path (string), encoding (optional)
Returns: File contents as string or data
file_writer
Description: Write or update file contents
Parameters: path (string), content (string), mode (append/overwrite)
Returns: Success status and file path
directory_list
Description: List directory contents with filtering
Parameters: path (string), recursive (boolean), filter (regex)
Returns: Array of file/directory information
csv_processor
Description: Parse and manipulate CSV data
Parameters: file_path (string), operation (read/write/transform)
Returns: Structured data or success status
data_analyzer
Description: Analyze datasets with statistical operations
Parameters: data (array), operations (mean/median/std/etc)
Returns: Statistical analysis results
Description: Search the internet for current information
Parameters: query (string), num_results (integer, 1-10)
Returns: Array of search results with URLs and snippets
web_scraping
Description: Extract content from web pages
Parameters: url (string), selectors (CSS/XPath)
Returns: Extracted content and metadata
api_caller
Description: Make HTTP requests to external APIs
Parameters: url, method, headers, body, authReturns: Response data and status
geocoding
Description: Convert addresses to coordinates and vice versa
Parameters: query (string), type (forward/reverse)
Returns: Location data with coordinates
Description: Interact with Apple Calendar (EventKit)
Parameters: operation (read/create/update/delete), event_dataReturns: Calendar events or success status
apple_reminders
Description: Manage Apple Reminders
Parameters: operation, reminder_data, list_nameReturns: Reminders or success status
local_notifications
Description: Schedule and manage local notifications
Parameters: title, body, trigger, identifierReturns: Notification identifier and delivery status
Assign tools to an agent to define its general capabilities across all tasks:
Copy
let researchAgent = Agent( role: "Research Analyst", purpose: "Conduct comprehensive market research and analysis", context: """ Expert researcher with access to web resources, data analysis tools, and visualization capabilities. """, tools: [ "web_search", "web_scraping", "data_analyzer", "chart_generator", "file_writer" ])
When to use: When tools are fundamental to the agent’s role and will be used across multiple tasks.
Task tools completely override agent tools—they do not merge. If you need agent tools plus additional task tools, explicitly list all required tools in the task definition.
import OrbitAIfinal class DatabaseQueryTool: BaseTool { override var name: String { "database_query" } override var description: String { """ Query the company database for information. Use this when you need to retrieve records, check inventory, or analyze stored data. """ }}
override func execute( with parameters: Metadata) async throws -> ToolResult { // Validate parameters guard let query = parameters["query"]?.stringValue else { throw OrbitAIError.invalidToolParameters( "Missing required parameter: query" ) } // Apply defaults let limit = parameters["limit"]?.intValue ?? 100 let timeout = parameters["timeout"]?.doubleValue ?? 30.0 // Validate constraints guard (1...1000).contains(limit) else { throw OrbitAIError.invalidToolParameters( "Limit must be between 1 and 1000" ) } // Execute operation let results = try await database.execute( query, limit: limit, timeout: timeout ) // Format results var resultData = Metadata() resultData["rows"] = .array(results.map { .dictionary($0) }) resultData["count"] = .int(results.count) resultData["query"] = .string(query) return ToolResult(success: true, data: resultData)}
4
Register Tool
Copy
// Register globallylet toolsHandler = ToolsHandler.sharedawait toolsHandler.registerTool(DatabaseQueryTool())// Now available to all agentslet agent = Agent( role: "Data Analyst", purpose: "Query and analyze company data", context: "Expert in SQL and data analysis", tools: ["database_query", "data_analyzer"])
Here’s a more sophisticated tool with error handling, validation, and metrics:
Copy
import OrbitAIimport Foundationfinal class EmailSenderTool: BaseTool { private let emailService: EmailService init(emailService: EmailService) { self.emailService = emailService } override var name: String { "send_email" } override var description: String { """ Send email messages to recipients with optional attachments. Supports both plain text and HTML formatting. """ } override var parametersSchema: JSONSchema { JSONSchema( type: .object, properties: [ "to": JSONSchema( type: .array, items: JSONSchema(type: .string), description: "Recipient email addresses" ), "subject": JSONSchema( type: .string, description: "Email subject line" ), "body": JSONSchema( type: .string, description: "Email body content" ), "format": JSONSchema( type: .string, enum: ["text", "html"], description: "Body format (default: text)" ), "attachments": JSONSchema( type: .array, items: JSONSchema(type: .string), description: "File paths to attach" ), "cc": JSONSchema( type: .array, items: JSONSchema(type: .string), description: "CC recipients (optional)" ), "bcc": JSONSchema( type: .array, items: JSONSchema(type: .string), description: "BCC recipients (optional)" ) ], required: ["to", "subject", "body"] ) } override func execute( with parameters: Metadata ) async throws -> ToolResult { // Validate required parameters guard let recipients = parameters["to"]?.arrayValue?.compactMap({ $0.stringValue }), !recipients.isEmpty else { return ToolResult( success: false, error: "At least one recipient email is required" ) } guard let subject = parameters["subject"]?.stringValue, !subject.isEmpty else { return ToolResult( success: false, error: "Email subject cannot be empty" ) } guard let body = parameters["body"]?.stringValue else { return ToolResult( success: false, error: "Email body is required" ) } // Validate email addresses for email in recipients { guard isValidEmail(email) else { return ToolResult( success: false, error: "Invalid email address: \(email)" ) } } // Parse optional parameters let format = parameters["format"]?.stringValue ?? "text" let cc = parameters["cc"]?.arrayValue?.compactMap { $0.stringValue } ?? [] let bcc = parameters["bcc"]?.arrayValue?.compactMap { $0.stringValue } ?? [] let attachmentPaths = parameters["attachments"]?.arrayValue?.compactMap { $0.stringValue } ?? [] // Validate attachments exist for path in attachmentPaths { guard FileManager.default.fileExists(atPath: path) else { return ToolResult( success: false, error: "Attachment not found: \(path)" ) } } // Send email do { let messageID = try await emailService.send( to: recipients, cc: cc, bcc: bcc, subject: subject, body: body, format: format == "html" ? .html : .text, attachments: attachmentPaths ) // Build result var resultData = Metadata() resultData["message_id"] = .string(messageID) resultData["recipients_count"] = .int(recipients.count) resultData["sent_at"] = .string(ISO8601DateFormatter().string(from: Date())) return ToolResult(success: true, data: resultData) } catch { return ToolResult( success: false, error: "Failed to send email: \(error.localizedDescription)" ) } } private func isValidEmail(_ email: String) -> Bool { let emailRegex = #"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$"# return email.range( of: emailRegex, options: [.caseInsensitive, .regularExpression] ) != nil }}
Always return detailed error messages in ToolResult when execution fails. This helps agents understand what went wrong and potentially retry with corrected parameters.
OrbitAI integrates tools with LLM function calling for intelligent tool usage:
Copy
// Tools are automatically converted to LLM function schemaslet webSearchTool = ToolSchema( function: FunctionSchema( name: "web_search", description: "Search the web for current information", parameters: JSONSchema( type: .object, properties: [ "query": JSONSchema( type: .string, description: "Search query" ), "num_results": JSONSchema( type: .integer, description: "Number of results (1-10)" ) ], required: ["query"] ) ))// LLM request with toollet request = LLMRequest( messages: [ .system("You can search the web for current information."), .user("What are the latest AI developments this week?") ], tools: [webSearchTool])// LLM decides to use the toollet response = try await llm.generateResponse(for: request)if case .toolCall(let toolCall) = response.finishReason { // Execute the tool let tool = await toolsHandler.tool(named: toolCall.name) let result = try await tool?.execute(with: toolCall.parameters)}
let task = ORTask( description: """ Research the top 5 AI companies by market cap, analyze their stock performance over the last year, create a comparative chart, and save a report. """, expectedOutput: "Stock performance analysis report with charts", tools: [ "web_search", // 1. Find top AI companies "api_caller", // 2. Fetch stock data "data_analyzer", // 3. Analyze performance "chart_generator", // 4. Create charts "file_writer" // 5. Save report ])// Agent automatically:// 1. Uses web_search to find companies// 2. Uses api_caller to get stock data for each// 3. Uses data_analyzer to compute metrics// 4. Uses chart_generator to visualize// 5. Uses file_writer to save final report
final class WebSearchToolV1: BaseTool { override var name: String { "web_search" } // V1 implementation}final class WebSearchToolV2: BaseTool { override var name: String { "web_search_v2" } // V2 implementation with enhanced features}// Register both versionsawait toolsHandler.registerTool(WebSearchToolV1())await toolsHandler.registerTool(WebSearchToolV2())// Use specific version in agentlet modernAgent = Agent( role: "Researcher", tools: ["web_search_v2"] // Use latest version)let legacyAgent = Agent( role: "Legacy Researcher", tools: ["web_search"] // Use V1 for compatibility)
OrbitAI automatically tracks tool usage with detailed metrics:
Copy
public struct ToolUsage: Codable, Sendable { public let toolName: String // Name of the tool used public let executionTime: TimeInterval // Execution duration public let success: Bool // Execution success status public let inputSize: Int // Size of input parameters public let outputSize: Int // Size of output data}
Cause: Parameters don’t match the schema or fail validation.Solution:
Copy
// Validate early and provide clear error messagesguard let query = parameters["query"]?.stringValue, !query.isEmpty else { return ToolResult( success: false, error: "Parameter 'query' is required and cannot be empty" )}guard query.count >= 2 && query.count <= 200 else { return ToolResult( success: false, error: "Query must be between 2 and 200 characters" )}
Tool Not Registered
Cause: Agent references a tool that hasn’t been registered with ToolsHandler.Solution:
Copy
// Verify registration before orbit creationlet requiredTools = ["custom_tool", "web_search"]let toolsHandler = ToolsHandler.sharedfor toolName in requiredTools { let isAvailable = await toolsHandler.isToolAvailable(named: toolName) if !isAvailable { print("Warning: Tool '\(toolName)' not registered") // Register it if toolName == "custom_tool" { await toolsHandler.registerTool(CustomTool()) } }}
Execution Timeout
Cause: Tool operation takes too long to complete.Solution:
Copy
// Implement timeout handlinglet timeout: TimeInterval = 30.0do { let result = try await withThrowingTaskGroup(of: ToolResult.self) { group in group.addTask { try await self.performOperation(parameters) } group.addTask { try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000)) throw ToolError.timeout } let result = try await group.next()! group.cancelAll() return result } return result} catch { return ToolResult( success: false, error: "Operation timed out after \(timeout) seconds" )}
External API Failures
Cause: External service is unavailable or returns an error.Solution:
Copy
// Implement retry logic with exponential backofffunc executeWithRetry( parameters: Metadata, maxAttempts: Int = 3) async throws -> ToolResult { var lastError: Error? for attempt in 1...maxAttempts { do { return try await callExternalAPI(parameters) } catch { lastError = error if attempt < maxAttempts { let delay = pow(2.0, Double(attempt - 1)) try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) } } } return ToolResult( success: false, error: "Failed after \(maxAttempts) attempts: \(lastError?.localizedDescription ?? "Unknown error")" )}
Use descriptive, action-oriented namesGood: send_email, analyze_sentiment, fetch_stock_priceBad: tool1, helper, process
Detailed Descriptions
Write comprehensive descriptions that guide LLM usage
Copy
description: """Search the web for current information.Use when you need real-time data, news,or information not in your training data.Returns up to 10 results with URLs and snippets."""
Pro Tip: Start with built-in tools and only create custom tools when you need domain-specific functionality or external integrations not covered by the built-in suite.