独学で勉強を始め、現在はSwiftUIを使ったAppを個人開発しています。
その中で最近興味が湧いてきたのがAIコンシェルジュなどのAIを使ったコミュニケーションApp。
とはいえあくまで個人開発なのでできるだけシンプルに低コストでAIコンシェルジュAppを実現させたい(まだまだ難しく複雑なコードはわからないw)
また個人情報保護の観点からも個人開発で管理可能な範囲のものにしたい。
どうしたものかと考えていたところ、WWDC25で発表されていたiOS26対応のFoundation Models Framework が使えるのでは?と思いついたので、とりあえず機械学習とAIを参考にFoundation Modelフレームワークについて勉強しながら開発を進めていこうと思います。
※最初に準備が必要なこと
これを書いている段階では、Foundation Models Frameworkが X-code26β版 以上にしか対応していないため、はじめに環境セットアップが必要でした。
ダウンロードURL:
それに、X-codeあるあるなのかは解らないのですが、新しいバージョンをすぐにダウンロードすると不具合で今まで作成したAppのファイルに悪影響が出ることがよくあるので、自分は常にバージョン別でダウンロードして過去のX-codeもつかえる状態にしています。(本当はファイル増えるし嫌なのですが...トラブルになるよりは良いかと)
テストなどで実機を使う場合用に、iPhoneは設定 > 一般 >ソフトウェアアップデート > ベータアップデート をオンにしておくことも忘れずに。
9/26 現在は X-code26 RCをダウンロードすることで対応できます。
機械学習・AI機能
-使用するフレームワーク-
Foundation Models Framework
このフレームワークは Apple intelligence を使うためのフレームワーク。
iPhoneの端末内で完結する LLM でインターネットに接続不要 なため、どんな環境でも使用可能わずか3行のSwiftコードで、文章の要約・データ抽出・分類などのAI機能をアプリに組み込めるほか、インターネット接続なしで動作し、ユーザーのデータはデバイス内に留まるためプライバシーが保護される。
AIの制度はさせおき個人開発の身としてはありがたいものだと思う。
-公式ドキュメント参照-
簡単なコードの例:
import FoundationModels
let session = LanguageModelSession()
let response = try await session.respond(to: "この文章を要約してください")
セッションを作成のためのサンプルコード:
import FoundationModels
func respond(userInput: String) async throws -> String {
let session = LanguageModelSession(instructions: """
You are a friendly barista in a world full of pixels.
Respond to the player’s question.
"""
)
let response = try await session.respond(to: userInput)
return response.content
}
以下の箇所が作成するセクションの最初に与える最初のエントリ。ここでAIの性格や人格を設定することでAIの返答をコントロールできる ↓
let session = LanguageModelSession(instructions: """
You are a friendly barista in a world full of pixels.
Respond to the player’s question.
"""
)
なるほど、確かにコード自体はシンプルに書けそう。
ただこのまま使うだけだとAIとのセッションのトランスクリプト(会話)が長くなりすぎてメモリ限界に到達してしまいトエラーが発生してしまうらしい。
パンクしてしまうということかな?確かに他のAIなどを使っていても文字数制限などがあるしやり取りをを常に簡潔化しておく必要があるみたい。
これを解決する方法の一つとして、
var session = LanguageModelSession()
do {
let answer = try await session.respond(to: prompt)
print(answer.content)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// 前回のセッションの履歴がない新しいセッション
session = LanguageModelSession()
}
というエラーハンドリングを用いれば良いみたい。
でもこれだけでは、ここまでの会話の履歴などもリセットされてしまうし何より最初に設定したAIの人格などが引き継がれない。
そこで、
var session = LanguageModelSession()
do {
let answer = try await session.respond(to: prompt)
print(answer.content)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// 前回のセッションの履歴を含む新しいセッション
session = newSession(previousSession: session)
}
private func newSession(previousSession: LanguageModelSession) -> LanguageModelSession {
let allEntries = previousSession.transcript.entries
var condensedEntries = [Transcript.Entry]()
if let firstEntry = allEntries.first {
condensedEntries.append(firstEntry)
if allEntries.count > 1, let lastEntry = allEntries.last {
condensedEntries.append(lastEntry)
}
}
let condensedTranscript = Transcript(entries: condensedEntries)
// 注: トランスクリプトには手順が含まれています
return LanguageModelSession(transcript: condensedTranscript)
}
このコードの仕組みを簡単にまとめると...
トランスクリプト構成例:
┌─────────────────────────────────────┐
│ エントリー1(最初) │
│ ├ システム指示: "あなたは親切なバリスタです" │
│ └ ユーザー入力: なし │
├─────────────────────────────────────┤
│ エントリー2 │
│ ├ ユーザー入力: "こんにちは" │
│ └ AI応答: "いらっしゃいませ!" │
├─────────────────────────────────────┤
│ エントリー3 │
│ ├ ユーザー入力: "コーヒーください" │
│ └ AI応答: "はい、どちらがお好みですか?" │
├─────────────────────────────────────┤
│ エントリー4(最後) │
│ ├ ユーザー入力: "ブラックで" │
│ └ AI応答: "かしこまりました!" │
└─────────────────────────────────────┘
エントリー1の最初の会話(指示)を常に与えることで、キャラクターの一貫性が崩れず会話の「文脈・世界観」を維持できると言うこと。
AIは人と違って会話の文章全てを記録しようとしてしまうが故にパンクしてエラーをおこしてしまう、だけどリセットするとそもそも何の会話だったかも含めリセットし会話が成立しなくなってしまう。
人と違って真っ直ぐすぎて不器用なのねAIって...
とりあえず、ここまでの学習をもとにとりあえずコードを書いてみた↓
import FoundationModels
import Foundation
import SwiftUI
@available(iOS 26.0, *)
struct AITestManager {
// 共通の指示を定数として定義(AIの人格・性格)
private static let baristaInstructions = """
You are a friendly barista in a world full of pixels.
Respond to the player's question.
"""
// セッションを作成
static func respond(userInput: String) async throws -> String {
// AIの人格を設定
let session = LanguageModelSession(instructions: baristaInstructions)
// ユーザー入力に対する応答を生成
let response = try await session.respond(to: userInput)
return response.content
}
// コンテキストサイズ超過時の処理
static func sizeErrorResponse(prompt: String) async throws -> String {
do {
// 通常の応答を試行
return try await respond(userInput: prompt)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// メモリ制限に達した場合、履歴を圧縮して新セッションを作成
// 最初の指示を引き継ぐ(AIの人格)
let session = LanguageModelSession(instructions: baristaInstructions)
let newSession = resetSession(previousSession: session)
// リトライ実行
let retryAnswer = try await newSession.respond(to: prompt)
return retryAnswer.content
}
}
// 新しいセッションの作成
private static func resetSession(previousSession: LanguageModelSession) -> LanguageModelSession {
// コンテキストサイズ超過時に会話履歴を圧縮する
let allEntries = previousSession.transcript
var condensedEntries = [Transcript.Entry]()
// 最初のエントリー(AI の指示・人格設定)は必ず保持
if let firstEntry = allEntries.first {
condensedEntries.append(firstEntry)
// 直近の会話も保持して文脈を維持
if allEntries.count > 1, let lastEntry = allEntries.last {
condensedEntries.append(lastEntry)
}
}
// 圧縮されたトランスクリプトで新セッションを作成
let condensedTranscript = Transcript(entries: condensedEntries)
return LanguageModelSession(transcript: condensedTranscript)
}
}
これでView側で呼び出せばいいのかと思うが...
今回はここまで。
書き方・解釈に間違いがあれば勉強になりますのでぜひコメントお願いします!
引き続きAIApp開発に向けて勉強した内容をまとめつつ記録していきます。