Foundation Models:ツール呼び出しを並列/直列に反復させる

前回、以下の記事を参考にすると、1 回のレスポンス(session.respond(…) の呼び出し)に対して、モデルが“同じツールを複数回呼び出す”こと が可能そうだと分かった。

SwiftUI: Demystify Foundation Model in SUPER Detail! With a Chat App! | by Itsuki | Jun, 2025 | Level Up Coding

この記事の実装通りに試してみたところ確かに、ToolCalls が複数同時に呼び出されることが確認できた。(読みやすく整形)

Transcript(entries: 
   [(Instructions) , 
    (Prompt) Send my love to Pikachu and Bulbasaur.
    Response Format: <nil>, 
   (ToolCalls) SendLoveToPokemon: {"name": "Pikachu"}
                SendLoveToPokemon: {"name": "Bulbasaur"}, 
    (ToolOutput SendLoveToPokemon) ["Love sent to Pikachu!"], 
    (ToolOutput SendLoveToPokemon) ["Love sent to Bulbasaur!"], 
    (Response) Love has been sent to Pikachu and Bulbasaur.
   ]
)

ポケモン名の取得をツールに分割するととどうなるか試してみた。

import Foundation
import FoundationModels
import Playgrounds

struct GetPokemonListTool: Tool {
    let name = "GetPokemonList"
    let description = "Get a pokemon name list."
    
    @Generable
    struct Arguments {
    }
    
    func call(arguments: Arguments) async throws -> [String] {
        return ["Pikachu", "Kairyu", "Yadoran", "Pijon"]
   }
}

struct SendLoveToPokemonTool: Tool {
    let name = "SendLoveToPokemon"
    let description = "Send love to a pokemon."
    
    @Generable
    struct Arguments {
        @Guide(description: "The name of the pokemon to send love to.")
        let name: String
    }
    
    func call(arguments: Arguments) async throws -> [String] {
        return ["Love sent to \(arguments.name)!"]
   }
}

#Playground {
    let session = LanguageModelSession(tools: [GetPokemonListTool(), SendLoveToPokemonTool()])
    let response = try await session.respond(to: "Get a pokemon list and send love to each pokemon.")
    print(session.transcript)
}

結果は以下の通り。ToolCalls → ToolOutput → ToolCalls → ToolOutput とツールが交互に呼び出されていることを確認できた。

Transcript(entries: 
   [(Instructions) , 
    (Prompt) Get a pokemon list and send love to each pokemon.
    Response Format: <nil>, 
    (Response) null, 
    (ToolCalls) GetPokemonList: {}, 
    (ToolOutput GetPokemonList) ["Pikachu", "Kairyu", "Yadoran", "Pijon"], 
    (Response) null, 
    (ToolCalls) SendLoveToPokemon: {"name": "Yadoran"}
                SendLoveToPokemon: {"name": "Kairyu"}
                SendLoveToPokemon: {"name": "Pijon"}
                SendLoveToPokemon: {"name": "Pikachu"}, 
    (ToolOutput SendLoveToPokemon) ["Love sent to Yadoran!"], 
    (ToolOutput SendLoveToPokemon) ["Love sent to Kairyu!"], 
    (ToolOutput SendLoveToPokemon) ["Love sent to Pijon!"], 
    (ToolOutput SendLoveToPokemon) ["Love sent to Pikachu!"], 
    (Response) Love has been successfully sent to Yadoran, Kairyu, Pijon, and Pikachu!
   ]
)

ちなみに、プロンプトの書き方によっては失敗することがあった。(「すべてのポケモンに送る」という指示から、一度に複数へ send love するツールがあることを期待したっぽい?)モデルの反応から失敗原因を探り、期待通りに処理してくれるようプロンプトを調整することが重要。こうした試行錯誤に際しては、やはり#Playground が有能。

Prompt:
Get a pokemon list and send love to the all pokemons.

Response: 
The "GetPokemonList" function can provide a list of pokemons, but it doesn't allow sending love to all pokemons at once. The "SendLoveToPokemon" function can only be used for one specific pokemon at a time. 

To proceed, I need the names of the pokemons you wish to send love to. Please provide the names.