
【iOS27】FoundationModels入門②(Tool)
はじめに
こんにちは。リテールアプリ共創部のYahiroです。
9月も下旬に入り、今年は梅雨が9月になったかのように雨が降りますね。
夏が終わったようで少し寂しい気がしますが、そろそろ衣替えをしていこうかと思います。
さて、Foundation ModelsにはiOS26からToolという仕組みが用意されています。
iOS27ではシステムツールとしてAppleがOCRTool・バーコードリーダ・端末内検索を用意してきましたが、今回は自作のツールを作って呼び出すところをやってみたいと思います。
この記事でわかること
Toolプロトコルで必須なのはdescriptionだけdescriptionはコメントではなく仕様書@Generableで引数の形をモデルに伝える- 戻り値は「値」ではなく「意味」を返す
検証環境
この記事では、以下の環境で検証しています。
- Mac mini(2024)
- Apple M4
- メモリ 16GB
- macOS GoldenGate 27.0
- Xcode 27.0
先に概要
自作のツールに書くのはざっくり以下の三つになります。
| 書くもの | 役割 |
|---|---|
description |
モデルが読む説明 |
Arguments |
モデルが埋める引数 |
call(arguments:) |
実際に動く処理 |
Tool プロトコルにはツール名を表す name もありますが、デフォルト実装があるので省略できます。省略すると型名がそのまま使われます。description の方はデフォルトがないので必須です。
ただし、instructionsやプロンプトの中で「SearchInventory ツールを使って」のようにツールを名前で指名する場合は、name を明示しておくと良いです。型名をそのまま使っていると、リファクタで型名を変えた瞬間にプロンプト側の指名が宙に浮いてしまいます。型名が長い場合に短い名前を見せたいときも同様です。
struct SearchInventoryTool: Tool {
let name = "SearchInventory"
let description = "商品名から、商品の在庫数を調べる"
// ...
}
在庫をチェックするツールを作ってみる
ここでは、アパレルを例に商品の在庫をチェックするツールを作ってみます。
import Playgrounds
import FoundationModels
#Playground {
let session = LanguageModelSession(
tools: [SearchInventoryTool()],
instructions: "店舗スタッフの質問にこたえてください"
)
let answer = try await session.respond(to: "トートバッグの在庫ある?")
print(answer.content)
}
struct SearchInventoryTool: Tool {
let description = "商品名から、商品の在庫数を調べる"
@Generable
struct Arguments {
@Guide(description: "検索する商品名")
let productName: String
}
func call(arguments: Arguments) async throws -> some PromptRepresentable {
let stock = inventoryMock(name: arguments.productName)
return "\(arguments.productName) の在庫は \(stock) 個です。"
}
}
// なにを渡されても2しか返さないモック関数
func inventoryMock(name: String) -> Int {
return 2
}
動かしてみると以下のような出力になりました。狙い通りですね。

モデルはdescriptionを見て判断する
モデルはToolの中身を確認しません。そのため、descriptionに読んで使い方がわかるように説明を書く必要があります。
今回で言うと、「商品名から、商品の在庫数を調べる」まで書いているので、モデルはToolの意図を解釈し、使用していました。
しかし、例えば「在庫チェック」のようにシンプルに書いてしまうと、モデルはToolの意図を理解できず、渡されたプロンプトに対して該当のToolを使用しないことがあります。
なお、iOS27では GenerationOptions の toolCallingMode で呼び出しを制御できるようになりました。.required で必ずツールを呼ばせる、.disallowed で使わせない、という指定ができます。既定は .allowed で、この場合はモデルの判断に任されます。
// 必ずツールを呼ばせる
let answer = try await session.respond(
to: "トートバッグの在庫ある?",
options: GenerationOptions(toolCallingMode: .required)
)
@Generableで引数の形をモデルに教える
Argumentsに@Generableをつけると、モデルに引数の形を教えることができます。
今回で言うと以下の部分です。
@Generable
struct Arguments {
@Guide(description: "検索する商品名")
let productName: String
}
@Guideでは、そのプロパティが何なのかをモデルに説明することができます。
省略してもコンパイルは通りますが、モデルが何を入れればいいか分かるようになるので、設定しておくと引数の精度が上がります。
また、@Generableをつけると逆方向の変換も自動で行われます。モデルが埋めた値は Arguments 型になった状態で call(arguments:) に届くので、受け取ったものを自分でパースする必要はありません。
戻り値は「値」ではなく「意味」を返す
callの戻り値は、ユーザが受け取るものではなく、モデルが受け取るものです。
そのため、今回のコードで言うと単に2と返してしまうと、モデルは何の数字かを理解できません。
そこで、以下のように意味が通る文章で返すようにしましょう。
func call(arguments: Arguments) async throws -> some PromptRepresentable {
let stock = inventoryMock(name: arguments.productName)
// トートバッグの在庫は2個です。と返るようにする
return "\(arguments.productName) の在庫は \(stock) 個です。"
}
まとめ
- 自作のツールは
description/Arguments/callの三つを書く nameは省略できる。descriptionは必須descriptionはモデルがツールを使うかどうかを判断する唯一の材料@Generableで引数の形が伝わり、モデルの出力からArgumentsへの変換も自動で行われる- 戻り値はユーザ向けではなくモデル向け。意味が分かる形で返す
最後に
今回は、アパレルを例に書きましたが、様々な業界でのドメイン特有の動きをToolとして組み込むことができそうです。
次回はダイナミックプロファイルについて触れていきます。値を返さずにアプリの状態を書き換えるToolを使うと、モデル自身が次の状態を選べるようになるので、そのあたりも合わせて見ていく予定です。
ではまた。










