SkillAgentSearch skills...

SwiftAgent

Native Swift SDK for building autonomous AI agents with Apple's FoundationModels design philosophy

Install / Use

/learn @SwiftedMind/SwiftAgent
About this skill

Quality Score

0/100

Category

Design

Supported Platforms

Universal

README

SwiftAgent

Native Swift SDK for building autonomous AI agents with Apple's FoundationModels design philosophy

SwiftAgent simplifies AI agent development by providing a clean, intuitive API that handles all the complexity of agent loops, tool execution, and adapter communication. Inspired by Apple's FoundationModels framework, it brings the same elegant, declarative approach to cross-platform AI agent development.

SwiftAgent in Action

import OpenAISession

@SessionSchema
struct CityExplorerSchema {
  @Tool var cityFacts = CityFactsTool()
  @Tool var reservation = ReservationTool()

  @Grounding(Date.self) var travelDate
  @Grounding([String].self) var mustVisitIdeas

  @StructuredOutput(ItinerarySummary.self) var itinerary
}

@MainActor
func planCopenhagenWeekend() async throws {
  let schema = CityExplorerSchema()
  let session = OpenAISession(
    schema: schema,
    instructions: "Design cinematic weekends. Call tools for local intel and reservations.",
    apiKey: "sk-..."
  )

  let response = try await session.respond(
    to: "Coffee, design, and dinner plans for two days in Copenhagen.",
    groundingWith: [
      .travelDate(Date(timeIntervalSinceNow: 86_400)),
      .mustVisitIdeas([
        "Coffee Collective, Nørrebro",
        "Designmuseum Denmark",
        "Kødbyens Fiskebar"
      ])
    ],
    generating: \.itinerary
  )

  print(response.content.headline)
  print(response.content.mustTry.joined(separator: " → "))

  for entry in try schema.resolve(session.transcript) {
    if case let .toolRun(.cityFacts(run)) = entry, let output = run.output {
      print("Local picks:", output)
    }

    if case let .prompt(prompt) = entry {
      print("Groundings:", prompt.sources)
    }
  }
}

Table of Contents

Features

  • Zero-Setup Agent Loops — Handle autonomous agent execution with just a few lines of code
  • Native Tool Integration — Use @Generable structs from FoundationModels as agent tools seamlessly
  • Adapter Agnostic — Abstract interface supports multiple AI adapters (OpenAI + Anthropic included, more coming)
  • Apple-Native Design — API inspired by FoundationModels for familiar, intuitive development
  • Modern Swift — Built with Swift 6, async/await, and latest concurrency features
  • Rich Logging — Comprehensive, human-readable logging for debugging and monitoring
  • Flexible Configuration — Fine-tune generation options, tools, and adapter settings

Quick Start

Installation

Add SwiftAgent to your Swift project:

// Package.swift
dependencies: [
  .package(url: "https://github.com/SwiftedMind/SwiftAgent.git", branch: "main")
]

// OpenAI target
.product(name: "OpenAISession", package: "SwiftAgent")

// Anthropic target
.product(name: "AnthropicSession", package: "SwiftAgent")

Then import the target you need:

// For OpenAI
import OpenAISession

// For Anthropic
import AnthropicSession

Basic Usage

Create an OpenAISession with your default instructions and call respond whenever you need a single-turn answer. The session tracks conversation state for you, so you can start simple and layer on additional features later.

import OpenAISession

let session = OpenAISession(
  instructions: "You are a helpful assistant.",
  apiKey: "sk-...",
)

// Create a response
let response = try await session.respond(to: "What's the weather like in San Francisco?")

// Process response
print(response.content)

Or use Anthropic:

import AnthropicSession

let session = AnthropicSession(
  instructions: "You are a helpful assistant.",
  apiKey: "sk-ant-...",
)

let response = try await session.respond(to: "What's the weather like in San Francisco?")

print(response.content)

[!NOTE] Using an API key directly is great for prototyping, but do not ship it in production apps. For shipping apps, use a secure proxy with per‑turn tokens. See Proxy Servers for more information.

Building Tools

Create tools using Apple's @Generable macro for type-safe, schema-free tool definitions. Tools expose argument and output types that SwiftAgent validates for you, so the model can call into Swift code and receive strongly typed results without manual JSON parsing.

import FoundationModels
import OpenAISession

struct WeatherTool: Tool {
  let name = "get_weather"
  let description = "Get current weather for a location"

  @Generable
  struct Arguments {
    @Guide(description: "City name")
    let city: String

    @Guide(description: "Temperature unit")
    let unit: String
  }

  @Generable
  struct Output {
    let temperature: Double
    let condition: String
    let humidity: Int
  }

  func call(arguments: Arguments) async throws -> Output {
    return Output(
      temperature: 22.5,
      condition: "sunny",
      humidity: 65
    )
  }
}

let session = OpenAISession(
  tools: WeatherTool(),
  instructions: "You are a helpful assistant.",
  apiKey: "sk-...",
)

let response = try await session.respond(to: "What's the weather like in San Francisco?")

print(response.content)

[!NOTE] Unlike Apple's LanguageModelSession object, OpenAISession takes the tools parameter as variadic arguments. So instead of passing an array like tools: [WeatherTool(), OtherTool()], you pass the tools as a list of arguments tools: WeatherTool(), OtherTool().

Recoverable Tool Rejections

If a tool call fails in a way the agent can correct (such as an unknown identifier or other validation issue), throw a ToolRunRejection. SwiftAgent forwards the structured content you provide to the model without aborting the loop so the agent can adjust its next action.

SwiftAgent always wraps your payload in a standardized envelope that includes error: true and the reason string so the agent can reliably detect recoverable rejections.

For quick cases, attach string-keyed details with the convenience initializer:

struct CustomerLookupTool: Tool {
  func call(arguments: Arguments) async throws -> Output {
    guard let customer = try await directory.loadCustomer(id: arguments.customerId) else {
      throw ToolRunRejection(
        reason: "Customer not found",
        details: [
          "issue": "customerNotFound",
          "customerId": arguments.customerId
        ]
      )
    }

    return Output(summary: customer.summary)
  }
}

For richer payloads, pass any @Generable type via the content: initializer to return structured data:

@Generable
struct CustomerLookupRejectionDetails {
  var issue: String
  var customerId: String
  var suggestions: [String]
}

throw ToolRunRejection(
  reason: "Customer not found",
  content: CustomerLookupRejectionDetails(
    issue: "customerNotFound",
    customerId: arguments.customerId,
    suggestions: ["Ask the user to confirm the identifier"]
  )
)

Structured Responses

You can force the response to be structured by defining a type conforming to StructuredOutput and passing it to the session.respond method:

import FoundationModels
import OpenAISession

struct WeatherReport: StructuredOutput {
  static let name: String = "weatherReport"

  @Generable
  struct Schema {
    let temperature: Double
    let condition: String
    let humidity: Int
  }
}

let session = OpenAISession(
  tools: WeatherTool(),
  instructions: "You are a helpful assistant.",
  apiKey: "sk-...",
)

let response = try await session.respond(
  to: "What's the weather like in San Francisco?",
  generating: WeatherReport.self,
)

// Fully typed response content
print(response.content.temperature)
print(response.content.condition)
print(response.content.humidity)

The response body is now a fully typed WeatherReport. SwiftAgent validates the payload against your schema, so you can use the data immediately in UI or unit tests without defensive decoding.

Access Transcripts

Every OpenAISession maintains a running transcript that records prompts, reasoning steps, tool calls, and responses. Iterate over it to drive custom analytics, persistence, or UI updates:

import OpenAISession

let session = OpenAISession(
  instructions: "You are a helpful assistant.",
  apiKey: "sk-...",
)

for entry in session.transcript {
  switch entry {
  case let .prompt(prompt):
    print("Prompt: ", prompt)
  case let .reasoning(reasoning):
    print("Reasoning: ", reasoning)
  case let .toolCalls(toolCalls):
    print("Tool Calls: ", toolCalls)
  case let .toolOutput(toolOutpu
View on GitHub
GitHub Stars194
CategoryDesign
Updated5d ago
Forks14

Languages

Swift

Security Score

100/100

Audited on Mar 23, 2026

No findings