Section 5/141 menit
5. Advanced Tool Calling: Paralel, Chaining, dan Error Recovery
5. Advanced Tool Calling: Paralel, Chaining, dan Error Recovery
Tool Execution dalam Agentic Loop
Saat session dikonfigurasi dengan tools, model dapat memanggil tools secara berurutan atau berulang sebelum menghasilkan jawaban akhir. Framework menjalankan loop ini secara otomatis, tapi Anda perlu merancang tools dengan mempertimbangkan pola ini.
swift
Prompt → [Model memilih tool] → [Framework run tool] → [Hasil ke model]
↑___________________________| (loop jika model minta tool lagi)
↓ (jika model sudah cukup data)
Response akhir
Tool dengan Retry Logic
Tools bisa gagal — network error, timeout, atau data tidak tersedia. Bungkus dengan retry bawaan:
swift
struct RobustWeatherTool: Tool {
let name = "get_weather"
let description = "Mengambil cuaca terkini. Coba otomatis hingga 3x jika gagal."
@Generable
struct Arguments {
@Guide(description: "Nama kota")
var city: String
}
func call(arguments: Arguments) async throws -> ToolOutput {
var lastError: Error?
for attempt in 1...3 {
do {
let weather = try await WeatherAPI.fetch(city: arguments.city)
return ToolOutput("Cuaca \(arguments.city): \(weather.description), \(weather.tempCelsius)°C")
} catch {
lastError = error
if attempt < 3 {
try await Task.sleep(nanoseconds: UInt64(attempt) * 500_000_000)
}
}
}
// Setelah 3 kali gagal, kembalikan informasi kegagalan ke model
// Model akan merespons dengan acknowledgment bahwa data tidak tersedia
return ToolOutput("Error: Tidak bisa mengambil data cuaca untuk \(arguments.city) saat ini. Informasikan ke pengguna bahwa data tidak tersedia.")
}
}
Tool yang Mengekspos Partial Results
Untuk tool dengan data besar, kembalikan subset yang relevan:
swift
struct DatabaseSearchTool: Tool {
let name = "search_products"
let description = "Mencari produk di database. Mengembalikan maksimal 5 hasil paling relevan."
@Generable
struct Arguments {
@Guide(description: "Query pencarian")
var query: String
@Guide(description: "Kategori filter, kosong string jika tidak difilter")
var category: String
@Guide(description: "Harga maksimum dalam rupiah, 0 jika tidak ada batas")
var maxPriceIDR: Int
}
func call(arguments: Arguments) async throws -> ToolOutput {
let results = try await ProductDatabase.search(
query: arguments.query,
category: arguments.category.isEmpty ? nil : arguments.category,
maxPrice: arguments.maxPriceIDR == 0 ? nil : Double(arguments.maxPriceIDR)
)
// Batasi ke 5 hasil — model tidak perlu semua data
let topResults = results.prefix(5)
if topResults.isEmpty {
return ToolOutput("Tidak ada produk ditemukan untuk query: \(arguments.query)")
}
let formatted = topResults.map { product in
"- \(product.name): Rp\(product.price.formatted()) (\(product.category))"
}.joined(separator: "\n")
return ToolOutput("Ditemukan \(results.count) produk, menampilkan 5 teratas:\n\(formatted)")
}
}
Chaining Tools: Hasil Tool Mempengaruhi Tool Berikutnya
Model secara alami bisa menggunakan hasil satu tool sebagai input untuk tool berikutnya. Rancang tools dengan output yang informatif:
swift
// Tool 1: Temukan user berdasarkan nama
struct FindUserTool: Tool {
let name = "find_user"
let description = "Cari user berdasarkan nama atau email. Kembalikan user ID jika ditemukan."
@Generable
struct Arguments {
@Guide(description: "Nama atau email yang dicari")
var query: String
}
func call(arguments: Arguments) async throws -> ToolOutput {
guard let user = try await UserDB.search(query: arguments.query) else {
return ToolOutput("User '\(arguments.query)' tidak ditemukan di database.")
}
// Output informatif yang bisa digunakan tool berikutnya
return ToolOutput("User ditemukan: ID=\(user.id), Nama=\(user.fullName), Email=\(user.email), Level=\(user.subscriptionLevel)")
}
}
// Tool 2: Ambil order berdasarkan user ID
struct GetUserOrdersTool: Tool {
let name = "get_user_orders"
let description = "Ambil daftar order user berdasarkan user ID. Gunakan ID dari hasil find_user."
@Generable
struct Arguments {
@Guide(description: "User ID yang didapat dari tool find_user")
var userId: String
@Guide(description: "Jumlah order terbaru yang diambil, antara 1 sampai 10")
var limit: Int
}
func call(arguments: Arguments) async throws -> ToolOutput {
let orders = try await OrderDB.recentOrders(userId: arguments.userId, limit: arguments.limit)
if orders.isEmpty {
return ToolOutput("User \(arguments.userId) belum memiliki order.")
}
let summary = orders.map { order in
"Order #\(order.id): \(order.itemCount) item, Rp\(order.total.formatted()), Status: \(order.status)"
}.joined(separator: "\n")
return ToolOutput("Order terbaru:\n\(summary)")
}
}
// Session dengan kedua tools — model akan chain otomatis saat diperlukan
let customerSupportSession = LanguageModelSession(
instructions: Instructions("Kamu adalah agen customer support. Gunakan tools untuk mengambil data yang diperlukan sebelum menjawab."),
tools: [FindUserTool(), GetUserOrdersTool()]
)
// Query ini akan trigger: find_user("budi@example.com") → get_user_orders(userId)
let response = try await customerSupportSession.respond(
to: "Tampilkan 3 order terbaru untuk pelanggan budi@example.com"
)