Agent Skills by ALSEL
Anthropic ClaudeLLM・AI開発⭐ リポ 337品質スコア 85/100

foundation-models

AppleのFoundation Modelsフレームワークを使用したデバイス上でのLLM統合です。AI によるテキスト生成、構造化された出力、またはツール呼び出しを実装する際に活用できます。

description の原文を見る

On-device LLM integration using Apple's Foundation Models framework. Use when implementing AI text generation, structured output, or tool calling.

SKILL.md 本文

Foundation Models

Appleのオンデバイス LLMをアプリに統合して、プライバシーを保護するAI機能を実現します。

このスキルが有効になるケース

  • ユーザーがAIテキスト生成機能を希望している
  • ユーザーが自然言語から構造化データを必要としている
  • ユーザーがプロンプトまたはLLM統合について質問している
  • ユーザーがAIアシスタントの実装を希望している
  • ユーザーがコンテンツの要約または抽出を必要としている

クイックスタート

1. 利用可能性を確認する

import FoundationModels

struct IntelligentView: View {
    private var model = SystemLanguageModel.default

    var body: some View {
        switch model.availability {
        case .available:
            ContentView()
        case .unavailable(.deviceNotEligible):
            UnsupportedDeviceView()
        case .unavailable(.appleIntelligenceNotEnabled):
            EnableIntelligenceView()
        case .unavailable(.modelNotReady):
            ModelDownloadingView()
        case .unavailable(let reason):
            ErrorView(reason: reason)
        }
    }
}

2. セッションを作成する

// シンプルなセッション
let session = LanguageModelSession()

// インストラクション付きセッション
let session = LanguageModelSession(instructions: """
    You are a helpful cooking assistant.
    Provide concise, practical advice for home cooks.
    """)

3. レスポンスを生成する

let response = try await session.respond(to: "What's a quick dinner idea?")
print(response.content)

プロンプトエンジニアリングのベストプラクティス

インストラクション公式

インストラクションはモデルのペルソナと制約を設定します。プロンプトよりも優先されます。

[Role] + [Task] + [Style] + [Safety]

例:

let instructions = """
    You are a fitness coach specializing in home workouts.
    Help users create exercise routines based on their equipment and goals.
    Keep responses under 100 words and use bullet points for exercises.
    Decline requests for medical advice and suggest consulting a doctor.
    """

インストラクション構成要素

構成要素目的
Roleペルソナを定義「You are a travel expert」
Task実行内容「Help plan itineraries」
Style出力形式「Use bullet points, be concise」
Safety境界線「Don't provide medical advice」

効果的なプロンプト

プロンプトはユーザー入力です。以下の特性を持つようにします:

原則悪い例良い例
具体的「Help with cooking」「Suggest a 30-minute vegetarian dinner」
制約付き「Tell me about dogs」「Describe Golden Retrievers in 3 sentences」
焦点を絞った「I need help with many things」「What ingredients substitute for eggs in baking?」

プロンプトパターン

質問パターン:

let prompt = "What are three ways to reduce food waste at home?"

コマンドパターン:

let prompt = "Create a weekly meal plan for a family of four, budget-friendly."

抽出パターン:

let prompt = """
    Extract the following from this email:
    - Sender name
    - Meeting date
    - Action items

    Email: \(emailContent)
    """

変換パターン:

let prompt = "Rewrite this text to be more formal: \(casualText)"

@Generableで構造化出力を取得

生の文字列の代わりに、型付きSwiftデータを取得します。

生成可能な型を定義

@Generable(description: "A recipe suggestion")
struct Recipe {
    var name: String

    @Guide(description: "Cooking time in minutes", .range(5...180))
    var cookingTime: Int

    @Guide(description: "Difficulty level", .options(["Easy", "Medium", "Hard"]))
    var difficulty: String

    @Guide(description: "List of ingredients", .count(3...15))
    var ingredients: [String]

    @Guide(description: "Step-by-step instructions")
    var instructions: [String]
}

@Guide制約

制約ユースケース
.range(min...max)数値の範囲.range(1...100)
.options([...])列挙型のような選択肢.options(["Low", "Medium", "High"])
.count(n)配列の正確な長さ.count(5)
.count(min...max)配列の長さの範囲.count(3...10)

構造化データを生成

let session = LanguageModelSession(instructions: """
    You are a recipe assistant. Generate practical, home-cook friendly recipes.
    """)

let recipe = try await session.respond(
    to: "Suggest a quick pasta dish",
    generating: Recipe.self
)

print("Recipe: \(recipe.content.name)")
print("Time: \(recipe.content.cookingTime) minutes")
print("Ingredients: \(recipe.content.ingredients.joined(separator: ", "))")

複雑なネストされた構造

@Generable(description: "A travel itinerary")
struct Itinerary {
    var destination: String

    @Guide(description: "Daily activities for the trip")
    var days: [DayPlan]
}

@Generable(description: "Activities for one day")
struct DayPlan {
    var dayNumber: Int

    @Guide(description: "Morning activity")
    var morning: String

    @Guide(description: "Afternoon activity")
    var afternoon: String

    @Guide(description: "Evening activity")
    var evening: String
}

ツール呼び出し

モデルにコードを呼び出させて、データにアクセスしたりアクションを実行します。

ツールを定義

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

    struct Arguments: Codable {
        var location: String
    }

    func call(arguments: Arguments) async throws -> ToolOutput {
        let weather = await WeatherService.shared.fetch(for: arguments.location)
        return .string("Temperature: \(weather.temp)°F, Conditions: \(weather.conditions)")
    }
}

セッションでツールを使用

let weatherTool = WeatherTool()
let session = LanguageModelSession(
    instructions: "You help users plan outdoor activities based on weather.",
    tools: [weatherTool]
)

// モデルは必要に応じて自動的にツールを呼び出します
let response = try await session.respond(
    to: "Should I go hiking in San Francisco today?"
)

ツールエラーハンドリング

do {
    let response = try await session.respond(to: prompt)
} catch let error as LanguageModelSession.ToolCallError {
    print("Tool '\(error.tool.name)' failed: \(error.underlyingError)")
} catch {
    print("Generation error: \(error)")
}

スナップショットストリーミング

生成中のレスポンスを表示して、より良いUXを実現します。

SwiftUIにストリーミング

@Generable
struct StoryIdea {
    var title: String

    @Guide(description: "A brief plot summary")
    var plot: String

    @Guide(description: "Main characters", .count(2...4))
    var characters: [String]
}

struct StreamingView: View {
    @State private var partial: StoryIdea.PartiallyGenerated?
    @State private var isGenerating = false

    var body: some View {
        VStack(alignment: .leading) {
            if let partial {
                if let title = partial.title {
                    Text(title).font(.headline)
                }
                if let plot = partial.plot {
                    Text(plot)
                }
                if let characters = partial.characters {
                    ForEach(characters, id: \.self) { char in
                        Text("• \(char)")
                    }
                }
            }

            Button("Generate Story Idea") {
                Task { await generateStory() }
            }
            .disabled(isGenerating)
        }
    }

    func generateStory() async {
        isGenerating = true
        defer { isGenerating = false }

        let session = LanguageModelSession()
        let stream = session.streamResponse(
            to: "Create a sci-fi story idea",
            generating: StoryIdea.self
        )

        for try await snapshot in stream {
            partial = snapshot
        }
    }
}

複数ターンの会話

セッションを再利用してコンテキストを保持します。

@Observable
final class ChatViewModel {
    private var session: LanguageModelSession?
    var messages: [ChatMessage] = []

    func startConversation() {
        session = LanguageModelSession(instructions: """
            You are a helpful assistant. Remember context from earlier in our conversation.
            """)
    }

    func send(_ message: String) async throws {
        guard let session else { return }

        messages.append(ChatMessage(role: .user, content: message))

        let response = try await session.respond(to: message)

        messages.append(ChatMessage(role: .assistant, content: response.content))
    }
}

エラーハンドリング

do {
    let response = try await session.respond(to: prompt)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
    // コンテキストが大きすぎます (>4,096トークン)
    // 解決策: 新しいセッションを開始するか、小さいリクエストに分割します
} catch LanguageModelSession.GenerationError.cancelled {
    // リクエストがキャンセルされました
} catch {
    print("Unexpected error: \(error)")
}

コンテキストウィンドウ管理

コンテキストサイズをプログラム的に取得

4096 をハードコードしないでください — 実行時にモデルにクエリします:

let model = SystemLanguageModel.default
let contextSize = try await model.contextSize
print("Context size: \(contextSize)") // 4096 (current limit)

contextSize には @backDeployed(before: iOS 26.4, macOS 26.4, visionOS 26.4) がマークされています。

実行時のトークン使用量を測定

推測の代わりに tokenUsage(for:) を使用して、各構成要素の正確なトークンコストを測定します:

let model = SystemLanguageModel.default

// インストラクションコストを測定
let instructions = Instructions("You're a helpful assistant that generates haiku.")
let instructionTokens = try await model.tokenUsage(for: instructions)
print(instructionTokens.tokenCount) // 16

// インストラクション+ツールを組み合わせて測定
let tools = [MoodTool()]
let combinedTokens = try await model.tokenUsage(for: instructions, tools: tools)
print(combinedTokens.tokenCount) // 79 — tools add significant overhead!

// プロンプトを測定
let prompt = Prompt("Generate a haiku about Swift")
let promptTokens = try await model.tokenUsage(for: prompt)
print(promptTokens.tokenCount) // 14

// マルチターンセッションで累積使用量を追跡
let session = LanguageModelSession(model: model, tools: tools, instructions: instructions)
let response = try await session.respond(to: prompt)
let transcriptTokens = try await model.tokenUsage(for: session.transcript)
print(transcriptTokens.tokenCount)

⚠️ ツールはトークン消費を増加させます

ツール定義はJSONスキーマとしてシリアル化され、トークン使用量が大幅に増加します。上の例では、単一のツールを追加すると、インストラクションが 16 → 79トークン に跳ね上がりました (約5倍)。ツールを追加する前に必ず測定し、特定のセッションで本当に必要かどうかを検討してください。

トークン予算モニタリング

コンテキスト使用量をパーセンテージで追跡して、オーバーフロー防止します:

extension SystemLanguageModel.TokenUsage {
    func percent(ofContextSize contextSize: Int) -> Float {
        guard contextSize > 0 else { return 0 }
        return Float(tokenCount) / Float(contextSize)
    }

    func formattedPercent(ofContextSize contextSize: Int) -> String {
        percent(ofContextSize: contextSize)
            .formatted(.percent.precision(.fractionLength(0)).rounded(rule: .down))
    }
}

// 使用方法
let contextSize = try await model.contextSize
print(instructionTokens.formattedPercent(ofContextSize: contextSize)) // "0%"
print(combinedTokens.formattedPercent(ofContextSize: contextSize))    // "1%"

プリフライトトークン予算チェック

exceededContextWindowSize エラー防止のため、送信前にプロンプトが収まるかを確認します:

func canFit(prompt: Prompt, in session: LanguageModelSession, model: SystemLanguageModel) async throws -> Bool {
    let contextSize = try await model.contextSize
    let transcriptTokens = try await model.tokenUsage(for: session.transcript)
    let promptTokens = try await model.tokenUsage(for: prompt)
    let totalNeeded = transcriptTokens.tokenCount + promptTokens.tokenCount
    return totalNeeded < contextSize
}

TranscriptDebugMenuでデバッグ

開発中は、SwiftUIビューの階層に TranscriptDebugMenu をドロップして、会話トランスクリプト全体とトークン消費をリアルタイムで視覚的に検査します。

大規模コンテンツの戦略

// コンテンツをチャンクに分割
func processLargeDocument(_ document: String) async throws -> [Summary] {
    let chunks = document.split(every: 10000) // ~2500トークン/チャンク
    var summaries: [Summary] = []

    for chunk in chunks {
        let session = LanguageModelSession() // チャンクごとに新規セッション
        let summary = try await session.respond(
            to: "Summarize this section: \(chunk)",
            generating: Summary.self
        )
        summaries.append(summary.content)
    }

    return summaries
}

生成オプション

let options = GenerationOptions(
    temperature: 0.7  // 0.0 = deterministic, 2.0 = creative
)

let response = try await session.respond(
    to: prompt,
    options: options
)
温度ユースケース
0.0-0.3事実抽出、データ処理
0.5-0.7創造性と精度のバランス
1.0-2.0クリエイティブライティング、ブレインストーミング

一般的なパターン

コンテンツ抽出

@Generable
struct EventDetails {
    var title: String
    var date: String?
    var time: String?
    var location: String?
    var attendees: [String]
}

let session = LanguageModelSession(instructions: """
    Extract event details from text. Use nil for missing information.
    """)

let event = try await session.respond(
    to: "Extract: Team lunch next Friday at noon in Conference Room B with John and Sarah",
    generating: EventDetails.self
)

要約

let session = LanguageModelSession(instructions: """
    Summarize text concisely. Focus on key points and actionable items.
    Maximum 3 bullet points.
    """)

let response = try await session.respond(
    to: "Summarize this email: \(longEmail)"
)

分類

@Generable
struct Classification {
    @Guide(description: "Category", .options(["Bug", "Feature", "Question", "Other"]))
    var category: String

    @Guide(description: "Priority", .options(["Low", "Medium", "High"]))
    var priority: String

    @Guide(description: "Brief summary")
    var summary: String
}

let result = try await session.respond(
    to: "Classify this support ticket: \(ticketText)",
    generating: Classification.self
)

チェックリスト

リリース前に確認:

  • AI機能を表示する前にモデルの利用可能性を確認する
  • サポートされていないデバイスのフォールバックUIを提供する
  • すべてのエラーケースをグレースフルに処理する
  • さまざまなプロンプト長でテストする
  • コンテキストウィンドウの制限を超えていないことを確認する
  • 構造化出力には @Generable を使用する
  • 生成に対して適切なローディング状態を追加する
  • ストリーミングUXがレスポンシブに感じることをテストする
  • インストラクションが明確で具体的であることを確認する
  • インストラクションに安全性の境界線を含める

参照

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
rshankras
リポジトリ
rshankras/claude-code-apple-skills
ライセンス
MIT
最終更新
2026/4/20

Source: https://github.com/rshankras/claude-code-apple-skills / ライセンス: MIT

本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: rshankras · rshankras/claude-code-apple-skills · ライセンス: MIT