Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Buat asisten AI yang melampaui percakapan — dapat memanggil fungsi untuk melakukan tindakan. Asisten memutuskan kapan fungsi diperlukan, Anda menjalankannya, dan memberi umpan hasilnya kembali. Semuanya berjalan secara lokal dengan Foundry Local SDK.
Dalam tutorial ini, Anda akan belajar cara:
- Menyiapkan proyek dan menginstal SDK Lokal Foundry
- Mendefinisikan alat yang dapat dipanggil oleh asisten
- Mengirim pesan yang memicu penggunaan alat
- Jalankan alat dan kembalikan hasil ke model
- Menangani loop pemanggilan alat sepenuhnya
- Membersihkan sumber daya
Prasyarat
- Komputer Windows, macOS, atau Linux dengan RAM minimal 8 GB.
Repositori Penampung Sampel
Anda dapat menemukan kode sampel lengkap untuk artikel ini di repositori GitHub sampel Foundry. Untuk mengkloning repositori dan menavigasi ke sampel, gunakan:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/csharp/foundry-local/tutorial-tool-calling
Memasang paket
Jika Anda mengembangkan atau mengirim di Windows, pilih tab Windows. Paket Windows terintegrasi dengan runtime Windows ML — ini menyediakan area permukaan API yang sama dengan luas akselerasi perangkat keras yang lebih luas.
dotnet add package Microsoft.AI.Foundry.Local.WinML
dotnet add package OpenAI
Sampel C# di repositori GitHub adalah proyek yang telah dikonfigurasi sebelumnya. Jika Anda membangun dari awal, Anda harus membaca referensi Foundry Local SDK untuk detail selengkapnya tentang cara menyiapkan proyek C# Anda dengan Foundry Local.
Menentukan alat
Panggilan alat memungkinkan model untuk meminta agar kode Anda menjalankan sebuah fungsi dan mengembalikan hasilnya. Anda menentukan alat yang tersedia sebagai daftar skema JSON yang menjelaskan nama, tujuan, dan parameter setiap fungsi.
Buka
Program.csdan tambahkan definisi alat berikut:// --- Tool definitions --- List<ToolDefinition> tools = [ new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "get_weather", Description = "Get the current weather for a location", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "location", new PropertyDefinition() { Type = "string", Description = "The city or location" } }, { "unit", new PropertyDefinition() { Type = "string", Description = "Temperature unit (celsius or fahrenheit)" } } }, Required = ["location"] } } }, new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "calculate", Description = "Perform a math calculation", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "expression", new PropertyDefinition() { Type = "string", Description = "The math expression to evaluate" } } }, Required = ["expression"] } } } ]; // --- Tool implementations --- string ExecuteTool(string functionName, JsonElement arguments) { switch (functionName) { case "get_weather": var location = arguments.GetProperty("location") .GetString() ?? "unknown"; var unit = arguments.TryGetProperty("unit", out var u) ? u.GetString() ?? "celsius" : "celsius"; var temp = unit == "celsius" ? 22 : 72; return JsonSerializer.Serialize(new { location, temperature = temp, unit, condition = "Sunny" }); case "calculate": var expression = arguments.GetProperty("expression") .GetString() ?? ""; try { var result = new System.Data.DataTable() .Compute(expression, null); return JsonSerializer.Serialize(new { expression, result = result?.ToString() }); } catch (Exception ex) { return JsonSerializer.Serialize(new { error = ex.Message }); } default: return JsonSerializer.Serialize(new { error = $"Unknown function: {functionName}" }); } }Setiap definisi alat mencakup
name,descriptionyang membantu model memutuskan kapan harus menggunakannya, danparametersskema yang menjelaskan input yang diharapkan.Tambahkan metode C# yang mengimplementasikan setiap alat:
// --- Tool definitions --- List<ToolDefinition> tools = [ new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "get_weather", Description = "Get the current weather for a location", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "location", new PropertyDefinition() { Type = "string", Description = "The city or location" } }, { "unit", new PropertyDefinition() { Type = "string", Description = "Temperature unit (celsius or fahrenheit)" } } }, Required = ["location"] } } }, new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "calculate", Description = "Perform a math calculation", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "expression", new PropertyDefinition() { Type = "string", Description = "The math expression to evaluate" } } }, Required = ["expression"] } } } ]; // --- Tool implementations --- string ExecuteTool(string functionName, JsonElement arguments) { switch (functionName) { case "get_weather": var location = arguments.GetProperty("location") .GetString() ?? "unknown"; var unit = arguments.TryGetProperty("unit", out var u) ? u.GetString() ?? "celsius" : "celsius"; var temp = unit == "celsius" ? 22 : 72; return JsonSerializer.Serialize(new { location, temperature = temp, unit, condition = "Sunny" }); case "calculate": var expression = arguments.GetProperty("expression") .GetString() ?? ""; try { var result = new System.Data.DataTable() .Compute(expression, null); return JsonSerializer.Serialize(new { expression, result = result?.ToString() }); } catch (Exception ex) { return JsonSerializer.Serialize(new { error = ex.Message }); } default: return JsonSerializer.Serialize(new { error = $"Unknown function: {functionName}" }); } }Model tidak menjalankan fungsi-fungsi ini secara langsung. Ini mengembalikan permintaan panggilan alat dengan nama fungsi dan argumen, dan kode Anda menjalankan fungsi.
Mengirim pesan yang memicu penggunaan alat
Inisialisasi SDK Lokal Foundry, muat model, dan kirim pesan yang dapat dijawab model dengan memanggil alat.
// --- Main application ---
var config = new Configuration
{
AppName = "foundry_local_samples",
LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information
};
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(
Microsoft.Extensions.Logging.LogLevel.Information
);
});
var logger = loggerFactory.CreateLogger<Program>();
await FoundryLocalManager.CreateAsync(config, logger);
var mgr = FoundryLocalManager.Instance;
// Download and register all execution providers.
var currentEp = "";
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
{
if (epName != currentEp)
{
if (currentEp != "") Console.WriteLine();
currentEp = epName;
}
Console.Write($"\r {epName.PadRight(30)} {percent,6:F1}%");
});
if (currentEp != "") Console.WriteLine();
var catalog = await mgr.GetCatalogAsync();
var model = await catalog.GetModelAsync("qwen2.5-0.5b")
?? throw new Exception("Model not found");
await model.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f) Console.WriteLine();
});
await model.LoadAsync();
Console.WriteLine("Model loaded and ready.");
var chatClient = await model.GetChatClientAsync();
chatClient.Settings.ToolChoice = ToolChoice.Auto;
var messages = new List<ChatMessage>
{
new ChatMessage
{
Role = "system",
Content = "You are a helpful assistant with access to tools. " +
"Use them when needed to answer questions accurately."
}
};
Saat model menentukan bahwa alat diperlukan, respons berisi ToolCalls alih-alih pesan teks biasa. Langkah selanjutnya menunjukkan cara mendeteksi dan menangani panggilan ini.
Jalankan alat dan kembalikan hasil
Setelah model merespons dengan panggilan alat, Anda mengekstrak nama fungsi dan argumen, menjalankan fungsi, dan mengirim hasilnya kembali.
Console.WriteLine("\nTool-calling assistant ready! Type 'quit' to exit.\n");
while (true)
{
Console.Write("You: ");
var userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput) ||
userInput.Equals("quit", StringComparison.OrdinalIgnoreCase) ||
userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
messages.Add(new ChatMessage
{
Role = "user",
Content = userInput
});
var response = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var choice = response.Choices[0].Message;
if (choice.ToolCalls is { Count: > 0 })
{
messages.Add(choice);
foreach (var toolCall in choice.ToolCalls)
{
var toolArgs = JsonDocument.Parse(
toolCall.FunctionCall.Arguments
).RootElement;
Console.WriteLine(
$" Tool call: {toolCall.FunctionCall.Name}({toolArgs})"
);
var result = ExecuteTool(
toolCall.FunctionCall.Name, toolArgs
);
messages.Add(new ChatMessage
{
Role = "tool",
ToolCallId = toolCall.Id,
Content = result
});
}
var finalResponse = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var answer = finalResponse.Choices[0].Message.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
else
{
var answer = choice.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
}
await model.UnloadAsync();
Console.WriteLine("Model unloaded. Goodbye!");
Langkah-langkah utama dalam perulangan panggilan alat adalah:
-
Mendeteksi panggilan alat — periksa
response.Choices[0].Message.ToolCalls. - Jalankan fungsi — uraikan argumen dan panggil fungsi lokal Anda.
-
Kembalikan hasil — tambahkan pesan dengan menggunakan peran
tooldan pasangan yang sesuaiToolCallId. - Dapatkan jawaban akhir — model menggunakan hasil alat untuk menghasilkan respons alami.
Menangani loop pemanggilan alat sepenuhnya
Berikut adalah aplikasi lengkap yang menggabungkan definisi alat, inisialisasi SDK, dan perulangan panggilan alat ke dalam satu file yang dapat dijalankan.
Ganti konten Program.cs dengan kode lengkap berikut:
using System.Text.Json;
using Microsoft.AI.Foundry.Local;
using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels;
using Betalgo.Ranul.OpenAI.ObjectModels.ResponseModels;
using Betalgo.Ranul.OpenAI.ObjectModels.SharedModels;
using Microsoft.Extensions.Logging;
CancellationToken ct = CancellationToken.None;
// --- Tool definitions ---
List<ToolDefinition> tools =
[
new ToolDefinition
{
Type = "function",
Function = new FunctionDefinition()
{
Name = "get_weather",
Description = "Get the current weather for a location",
Parameters = new PropertyDefinition()
{
Type = "object",
Properties = new Dictionary<string, PropertyDefinition>()
{
{ "location", new PropertyDefinition() { Type = "string", Description = "The city or location" } },
{ "unit", new PropertyDefinition() { Type = "string", Description = "Temperature unit (celsius or fahrenheit)" } }
},
Required = ["location"]
}
}
},
new ToolDefinition
{
Type = "function",
Function = new FunctionDefinition()
{
Name = "calculate",
Description = "Perform a math calculation",
Parameters = new PropertyDefinition()
{
Type = "object",
Properties = new Dictionary<string, PropertyDefinition>()
{
{ "expression", new PropertyDefinition() { Type = "string", Description = "The math expression to evaluate" } }
},
Required = ["expression"]
}
}
}
];
// --- Tool implementations ---
string ExecuteTool(string functionName, JsonElement arguments)
{
switch (functionName)
{
case "get_weather":
var location = arguments.GetProperty("location")
.GetString() ?? "unknown";
var unit = arguments.TryGetProperty("unit", out var u)
? u.GetString() ?? "celsius"
: "celsius";
var temp = unit == "celsius" ? 22 : 72;
return JsonSerializer.Serialize(new
{
location,
temperature = temp,
unit,
condition = "Sunny"
});
case "calculate":
var expression = arguments.GetProperty("expression")
.GetString() ?? "";
try
{
var result = new System.Data.DataTable()
.Compute(expression, null);
return JsonSerializer.Serialize(new
{
expression,
result = result?.ToString()
});
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new
{
error = ex.Message
});
}
default:
return JsonSerializer.Serialize(new
{
error = $"Unknown function: {functionName}"
});
}
}
// --- Main application ---
var config = new Configuration
{
AppName = "foundry_local_samples",
LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information
};
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(
Microsoft.Extensions.Logging.LogLevel.Information
);
});
var logger = loggerFactory.CreateLogger<Program>();
await FoundryLocalManager.CreateAsync(config, logger);
var mgr = FoundryLocalManager.Instance;
// Download and register all execution providers.
var currentEp = "";
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
{
if (epName != currentEp)
{
if (currentEp != "") Console.WriteLine();
currentEp = epName;
}
Console.Write($"\r {epName.PadRight(30)} {percent,6:F1}%");
});
if (currentEp != "") Console.WriteLine();
var catalog = await mgr.GetCatalogAsync();
var model = await catalog.GetModelAsync("qwen2.5-0.5b")
?? throw new Exception("Model not found");
await model.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f) Console.WriteLine();
});
await model.LoadAsync();
Console.WriteLine("Model loaded and ready.");
var chatClient = await model.GetChatClientAsync();
chatClient.Settings.ToolChoice = ToolChoice.Auto;
var messages = new List<ChatMessage>
{
new ChatMessage
{
Role = "system",
Content = "You are a helpful assistant with access to tools. " +
"Use them when needed to answer questions accurately."
}
};
Console.WriteLine("\nTool-calling assistant ready! Type 'quit' to exit.\n");
while (true)
{
Console.Write("You: ");
var userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput) ||
userInput.Equals("quit", StringComparison.OrdinalIgnoreCase) ||
userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
messages.Add(new ChatMessage
{
Role = "user",
Content = userInput
});
var response = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var choice = response.Choices[0].Message;
if (choice.ToolCalls is { Count: > 0 })
{
messages.Add(choice);
foreach (var toolCall in choice.ToolCalls)
{
var toolArgs = JsonDocument.Parse(
toolCall.FunctionCall.Arguments
).RootElement;
Console.WriteLine(
$" Tool call: {toolCall.FunctionCall.Name}({toolArgs})"
);
var result = ExecuteTool(
toolCall.FunctionCall.Name, toolArgs
);
messages.Add(new ChatMessage
{
Role = "tool",
ToolCallId = toolCall.Id,
Content = result
});
}
var finalResponse = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var answer = finalResponse.Choices[0].Message.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
else
{
var answer = choice.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
}
await model.UnloadAsync();
Console.WriteLine("Model unloaded. Goodbye!");
Jalankan asisten pemanggil alat:
dotnet run
Anda melihat output yang mirip dengan:
Downloading model: 100.00%
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({"location":"current location"})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({"expression":"245 * 38"})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Model memutuskan kapan harus memanggil alat berdasarkan pesan pengguna. Untuk pertanyaan cuaca, ia memanggil get_weather, untuk matematika, ia memanggil calculate, dan untuk pertanyaan umum, ia merespons langsung tanpa panggilan alat apa pun.
Repositori Penampung Sampel
Kode sampel lengkap untuk artikel ini tersedia di repositori GitHub foundry-samples. Untuk mengkloning repositori dan menavigasi ke penggunaan sampel:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/javascript/foundry-local/tutorial-tool-calling
Memasang paket
Jika Anda mengembangkan atau mengirim di Windows, pilih tab Windows. Paket Windows terintegrasi dengan runtime Windows ML — ini menyediakan area permukaan API yang sama dengan luas akselerasi perangkat keras yang lebih luas.
npm install foundry-local-sdk-winml openai
Menentukan alat
Panggilan alat memungkinkan model untuk meminta agar kode Anda menjalankan sebuah fungsi dan mengembalikan hasilnya. Anda menentukan alat yang tersedia sebagai daftar skema JSON yang menjelaskan nama, tujuan, dan parameter setiap fungsi.
Buat file yang disebut
index.js.Tambahkan definisi alat berikut:
// --- Tool definitions --- const tools = [ { type: 'function', function: { name: 'get_weather', description: 'Get the current weather for a location', parameters: { type: 'object', properties: { location: { type: 'string', description: 'The city or location' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: 'Temperature unit' } }, required: ['location'] } } }, { type: 'function', function: { name: 'calculate', description: 'Perform a math calculation', parameters: { type: 'object', properties: { expression: { type: 'string', description: 'The math expression to evaluate' } }, required: ['expression'] } } } ]; // --- Tool implementations --- function getWeather(location, unit = 'celsius') { return { location, temperature: unit === 'celsius' ? 22 : 72, unit, condition: 'Sunny' }; } function calculate(expression) { // Input is validated against a strict allowlist of numeric/math characters, // making this safe from code injection in this tutorial context. const allowed = /^[0-9+\-*/(). ]+$/; if (!allowed.test(expression)) { return { error: 'Invalid expression' }; } try { const result = Function( `"use strict"; return (${expression})` )(); return { expression, result }; } catch (err) { return { error: err.message }; } } const toolFunctions = { get_weather: (args) => getWeather(args.location, args.unit), calculate: (args) => calculate(args.expression) };Setiap definisi alat mencakup
name,descriptionyang membantu model memutuskan kapan harus menggunakannya, danparametersskema yang menjelaskan input yang diharapkan.Tambahkan fungsi JavaScript yang mengimplementasikan setiap alat:
// --- Tool definitions --- const tools = [ { type: 'function', function: { name: 'get_weather', description: 'Get the current weather for a location', parameters: { type: 'object', properties: { location: { type: 'string', description: 'The city or location' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: 'Temperature unit' } }, required: ['location'] } } }, { type: 'function', function: { name: 'calculate', description: 'Perform a math calculation', parameters: { type: 'object', properties: { expression: { type: 'string', description: 'The math expression to evaluate' } }, required: ['expression'] } } } ]; // --- Tool implementations --- function getWeather(location, unit = 'celsius') { return { location, temperature: unit === 'celsius' ? 22 : 72, unit, condition: 'Sunny' }; } function calculate(expression) { // Input is validated against a strict allowlist of numeric/math characters, // making this safe from code injection in this tutorial context. const allowed = /^[0-9+\-*/(). ]+$/; if (!allowed.test(expression)) { return { error: 'Invalid expression' }; } try { const result = Function( `"use strict"; return (${expression})` )(); return { expression, result }; } catch (err) { return { error: err.message }; } } const toolFunctions = { get_weather: (args) => getWeather(args.location, args.unit), calculate: (args) => calculate(args.expression) };Model tidak menjalankan fungsi-fungsi ini secara langsung. Ini mengembalikan permintaan panggilan alat dengan nama fungsi dan argumen, dan kode Anda menjalankan fungsi.
Mengirim pesan yang memicu penggunaan alat
Inisialisasi SDK Lokal Foundry, muat model, dan kirim pesan yang dapat dijawab model dengan memanggil alat.
// --- Main application ---
const manager = FoundryLocalManager.create({
appName: 'foundry_local_samples',
logLevel: 'info'
});
// Download and register all execution providers.
let currentEp = '';
await manager.downloadAndRegisterEps((epName, percent) => {
if (epName !== currentEp) {
if (currentEp !== '') process.stdout.write('\n');
currentEp = epName;
}
process.stdout.write(`\r ${epName.padEnd(30)} ${percent.toFixed(1).padStart(5)}%`);
});
if (currentEp !== '') process.stdout.write('\n');
const model = await manager.catalog.getModel('qwen2.5-0.5b');
await model.download((progress) => {
process.stdout.write(
`\rDownloading model: ${progress.toFixed(2)}%`
);
});
console.log('\nModel downloaded.');
await model.load();
console.log('Model loaded and ready.');
const chatClient = model.createChatClient();
const messages = [
{
role: 'system',
content:
'You are a helpful assistant with access to tools. ' +
'Use them when needed to answer questions accurately.'
}
];
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = (prompt) =>
new Promise((resolve) => {
if (rl.closed) return resolve('quit');
const onClose = () => resolve('quit');
rl.once('close', onClose);
rl.question(prompt, (answer) => {
rl.off('close', onClose);
resolve(answer);
});
});
console.log(
'\nTool-calling assistant ready! Type \'quit\' to exit.\n'
);
while (true) {
const userInput = await askQuestion('You: ');
if (
userInput.trim().toLowerCase() === 'quit' ||
userInput.trim().toLowerCase() === 'exit'
) {
break;
}
messages.push({ role: 'user', content: userInput });
const response = await chatClient.completeChat(
messages, tools
);
const answer = await processToolCalls(
messages, response, chatClient
);
messages.push({ role: 'assistant', content: answer });
console.log(`Assistant: ${answer}\n`);
}
await model.unload();
console.log('Model unloaded. Goodbye!');
rl.close();
Saat model menentukan bahwa alat diperlukan, respons berisi tool_calls alih-alih pesan teks biasa. Langkah selanjutnya menunjukkan cara mendeteksi dan menangani panggilan ini.
Jalankan alat dan kembalikan hasil
Setelah model merespons dengan panggilan alat, Anda mengekstrak nama fungsi dan argumen, menjalankan fungsi, dan mengirim hasilnya kembali.
async function processToolCalls(messages, response, chatClient) {
let choice = response.choices[0]?.message;
while (choice?.tool_calls?.length > 0) {
messages.push(choice);
for (const toolCall of choice.tool_calls) {
const functionName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
console.log(
` Tool call: ${functionName}` +
`(${JSON.stringify(args)})`
);
const result = toolFunctions[functionName](args);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
}
response = await chatClient.completeChat(
messages, tools
);
choice = response.choices[0]?.message;
}
return choice?.content ?? '';
}
Langkah-langkah utama dalam perulangan panggilan alat adalah:
-
Mendeteksi panggilan alat — periksa
response.choices[0]?.message?.tool_calls. - Jalankan fungsi — uraikan argumen dan panggil fungsi lokal Anda.
-
Kembalikan hasil — tambahkan pesan dengan menggunakan peran
tooldan pasangan yang sesuaitool_call_id. - Dapatkan jawaban akhir — model menggunakan hasil alat untuk menghasilkan respons alami.
Menangani loop pemanggilan alat sepenuhnya
Berikut adalah aplikasi lengkap yang menggabungkan definisi alat, inisialisasi SDK, dan perulangan panggilan alat ke dalam satu file yang dapat dijalankan.
Buat file bernama index.js dan tambahkan kode lengkap berikut:
import { FoundryLocalManager } from 'foundry-local-sdk';
import * as readline from 'readline';
// --- Tool definitions ---
const tools = [
{
type: 'function',
function: {
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city or location'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature unit'
}
},
required: ['location']
}
}
},
{
type: 'function',
function: {
name: 'calculate',
description: 'Perform a math calculation',
parameters: {
type: 'object',
properties: {
expression: {
type: 'string',
description:
'The math expression to evaluate'
}
},
required: ['expression']
}
}
}
];
// --- Tool implementations ---
function getWeather(location, unit = 'celsius') {
return {
location,
temperature: unit === 'celsius' ? 22 : 72,
unit,
condition: 'Sunny'
};
}
function calculate(expression) {
// Input is validated against a strict allowlist of numeric/math characters,
// making this safe from code injection in this tutorial context.
const allowed = /^[0-9+\-*/(). ]+$/;
if (!allowed.test(expression)) {
return { error: 'Invalid expression' };
}
try {
const result = Function(
`"use strict"; return (${expression})`
)();
return { expression, result };
} catch (err) {
return { error: err.message };
}
}
const toolFunctions = {
get_weather: (args) => getWeather(args.location, args.unit),
calculate: (args) => calculate(args.expression)
};
async function processToolCalls(messages, response, chatClient) {
let choice = response.choices[0]?.message;
while (choice?.tool_calls?.length > 0) {
messages.push(choice);
for (const toolCall of choice.tool_calls) {
const functionName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
console.log(
` Tool call: ${functionName}` +
`(${JSON.stringify(args)})`
);
const result = toolFunctions[functionName](args);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
}
response = await chatClient.completeChat(
messages, tools
);
choice = response.choices[0]?.message;
}
return choice?.content ?? '';
}
// --- Main application ---
const manager = FoundryLocalManager.create({
appName: 'foundry_local_samples',
logLevel: 'info'
});
// Download and register all execution providers.
let currentEp = '';
await manager.downloadAndRegisterEps((epName, percent) => {
if (epName !== currentEp) {
if (currentEp !== '') process.stdout.write('\n');
currentEp = epName;
}
process.stdout.write(`\r ${epName.padEnd(30)} ${percent.toFixed(1).padStart(5)}%`);
});
if (currentEp !== '') process.stdout.write('\n');
const model = await manager.catalog.getModel('qwen2.5-0.5b');
await model.download((progress) => {
process.stdout.write(
`\rDownloading model: ${progress.toFixed(2)}%`
);
});
console.log('\nModel downloaded.');
await model.load();
console.log('Model loaded and ready.');
const chatClient = model.createChatClient();
const messages = [
{
role: 'system',
content:
'You are a helpful assistant with access to tools. ' +
'Use them when needed to answer questions accurately.'
}
];
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = (prompt) =>
new Promise((resolve) => {
if (rl.closed) return resolve('quit');
const onClose = () => resolve('quit');
rl.once('close', onClose);
rl.question(prompt, (answer) => {
rl.off('close', onClose);
resolve(answer);
});
});
console.log(
'\nTool-calling assistant ready! Type \'quit\' to exit.\n'
);
while (true) {
const userInput = await askQuestion('You: ');
if (
userInput.trim().toLowerCase() === 'quit' ||
userInput.trim().toLowerCase() === 'exit'
) {
break;
}
messages.push({ role: 'user', content: userInput });
const response = await chatClient.completeChat(
messages, tools
);
const answer = await processToolCalls(
messages, response, chatClient
);
messages.push({ role: 'assistant', content: answer });
console.log(`Assistant: ${answer}\n`);
}
await model.unload();
console.log('Model unloaded. Goodbye!');
rl.close();
Jalankan asisten pemanggil alat:
node index.js
Anda melihat output yang mirip dengan:
Downloading model: 100.00%
Model downloaded.
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({"location":"current location"})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({"expression":"245 * 38"})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Model memutuskan kapan harus memanggil alat berdasarkan pesan pengguna. Untuk pertanyaan cuaca, ia memanggil get_weather, untuk matematika, ia memanggil calculate, dan untuk pertanyaan umum, ia merespons langsung tanpa panggilan alat apa pun.
Repositori Penampung Sampel
Kode sampel lengkap untuk artikel ini tersedia di repositori GitHub foundry-samples. Untuk mengkloning repositori dan menavigasi ke penggunaan sampel:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/python/foundry-local/tutorial-tool-calling
Memasang paket
Jika Anda mengembangkan atau mengirim di Windows, pilih tab Windows. Paket Windows terintegrasi dengan runtime Windows ML — ini menyediakan area permukaan API yang sama dengan luas akselerasi perangkat keras yang lebih luas.
pip install foundry-local-sdk-winml openai
Menentukan alat
Panggilan alat memungkinkan model untuk meminta agar kode Anda menjalankan sebuah fungsi dan mengembalikan hasilnya. Anda menentukan alat yang tersedia sebagai daftar skema JSON yang menjelaskan nama, tujuan, dan parameter setiap fungsi.
Buat file yang disebut main.py dan tambahkan definisi alat berikut:
# --- Tool definitions ---
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city or location",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": ("The math expression to evaluate"),
}
},
"required": ["expression"],
},
},
},
]
# --- Tool implementations ---
def get_weather(location, unit="celsius"):
"""Simulate a weather lookup."""
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "Sunny",
}
def calculate(expression):
"""Evaluate a math expression safely."""
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression)
return {"expression": expression, "result": result}
except Exception as e:
return {"error": str(e)}
tool_functions = {"get_weather": get_weather, "calculate": calculate}
Setiap definisi alat mencakup name, description yang membantu model memutuskan kapan harus menggunakannya, dan parameters skema yang menjelaskan input yang diharapkan.
Selanjutnya, tambahkan fungsi Python yang mengimplementasikan setiap alat:
# --- Tool definitions ---
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city or location",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": ("The math expression to evaluate"),
}
},
"required": ["expression"],
},
},
},
]
# --- Tool implementations ---
def get_weather(location, unit="celsius"):
"""Simulate a weather lookup."""
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "Sunny",
}
def calculate(expression):
"""Evaluate a math expression safely."""
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression)
return {"expression": expression, "result": result}
except Exception as e:
return {"error": str(e)}
tool_functions = {"get_weather": get_weather, "calculate": calculate}
Model tidak menjalankan fungsi-fungsi ini secara langsung. Ini mengembalikan permintaan panggilan alat dengan nama fungsi dan argumen, dan kode Anda menjalankan fungsi.
Mengirim pesan yang memicu penggunaan alat
Inisialisasi SDK Lokal Foundry, muat model, dan kirim pesan yang dapat dijawab model dengan memanggil alat.
def main():
# Initialize the Foundry Local SDK
config = Configuration(app_name="foundry_local_samples")
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance
# Download and register all execution providers.
current_ep = ""
def ep_progress(ep_name: str, percent: float):
nonlocal current_ep
if ep_name != current_ep:
if current_ep:
print()
current_ep = ep_name
print(f"\r {ep_name:<30} {percent:5.1f}%", end="", flush=True)
manager.download_and_register_eps(progress_callback=ep_progress)
if current_ep:
print()
# Select and load a model
model = manager.catalog.get_model("qwen2.5-0.5b")
model.download(
lambda progress: print(
f"\rDownloading model: {progress:.2f}%", end="", flush=True
)
)
print()
model.load()
print("Model loaded and ready.")
# Get a chat client
client = model.get_chat_client()
# Conversation with a system prompt
messages = [
{
"role": "system",
"content": "You are a helpful assistant with access to tools. "
"Use them when needed to answer questions accurately.",
}
]
print("\nTool-calling assistant ready! Type 'quit' to exit.\n")
while True:
user_input = input("You: ")
if user_input.strip().lower() in ("quit", "exit"):
break
messages.append({"role": "user", "content": user_input})
response = client.complete_chat(messages, tools=tools)
answer = process_tool_calls(messages, response, client)
messages.append({"role": "assistant", "content": answer})
print(f"Assistant: {answer}\n")
# Clean up
model.unload()
print("Model unloaded. Goodbye!")
Saat model menentukan bahwa alat diperlukan, respons berisi tool_calls alih-alih pesan teks biasa. Langkah selanjutnya menunjukkan cara mendeteksi dan menangani panggilan ini.
Jalankan alat dan kembalikan hasil
Setelah model merespons dengan panggilan alat, Anda mengekstrak nama fungsi dan argumen, menjalankan fungsi, dan mengirim hasilnya kembali.
def process_tool_calls(messages, response, client):
"""Handle tool calls in a loop until the model produces a final answer."""
choice = response.choices[0].message
while choice.tool_calls:
# Convert the assistant message to a dict for the SDK
assistant_msg = {
"role": "assistant",
"content": choice.content,
"tool_calls": [
{
"id": tc.id,
"type": tc.type,
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in choice.tool_calls
],
}
messages.append(assistant_msg)
for tool_call in choice.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f" Tool call: {function_name}({arguments})")
# Execute the function and add the result
func = tool_functions[function_name]
result = func(**arguments)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
}
)
# Send the updated conversation back
response = client.complete_chat(messages, tools=tools)
choice = response.choices[0].message
return choice.content
Langkah-langkah utama dalam perulangan panggilan alat adalah:
-
Mendeteksi panggilan alat — periksa
response.choices[0].message.tool_calls. - Jalankan fungsi — uraikan argumen dan panggil fungsi lokal Anda.
-
Kembalikan hasil — tambahkan pesan dengan menggunakan peran
tooldan pasangan yang sesuaitool_call_id. - Dapatkan jawaban akhir — model menggunakan hasil alat untuk menghasilkan respons alami.
Menangani loop pemanggilan alat sepenuhnya
Berikut adalah aplikasi lengkap yang menggabungkan definisi alat, inisialisasi SDK, dan perulangan panggilan alat ke dalam satu file yang dapat dijalankan.
Buat file bernama main.py dan tambahkan kode lengkap berikut:
import json
from foundry_local_sdk import Configuration, FoundryLocalManager
# --- Tool definitions ---
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city or location",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": ("The math expression to evaluate"),
}
},
"required": ["expression"],
},
},
},
]
# --- Tool implementations ---
def get_weather(location, unit="celsius"):
"""Simulate a weather lookup."""
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "Sunny",
}
def calculate(expression):
"""Evaluate a math expression safely."""
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression)
return {"expression": expression, "result": result}
except Exception as e:
return {"error": str(e)}
tool_functions = {"get_weather": get_weather, "calculate": calculate}
def process_tool_calls(messages, response, client):
"""Handle tool calls in a loop until the model produces a final answer."""
choice = response.choices[0].message
while choice.tool_calls:
# Convert the assistant message to a dict for the SDK
assistant_msg = {
"role": "assistant",
"content": choice.content,
"tool_calls": [
{
"id": tc.id,
"type": tc.type,
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in choice.tool_calls
],
}
messages.append(assistant_msg)
for tool_call in choice.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f" Tool call: {function_name}({arguments})")
# Execute the function and add the result
func = tool_functions[function_name]
result = func(**arguments)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
}
)
# Send the updated conversation back
response = client.complete_chat(messages, tools=tools)
choice = response.choices[0].message
return choice.content
def main():
# Initialize the Foundry Local SDK
config = Configuration(app_name="foundry_local_samples")
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance
# Download and register all execution providers.
current_ep = ""
def ep_progress(ep_name: str, percent: float):
nonlocal current_ep
if ep_name != current_ep:
if current_ep:
print()
current_ep = ep_name
print(f"\r {ep_name:<30} {percent:5.1f}%", end="", flush=True)
manager.download_and_register_eps(progress_callback=ep_progress)
if current_ep:
print()
# Select and load a model
model = manager.catalog.get_model("qwen2.5-0.5b")
model.download(
lambda progress: print(
f"\rDownloading model: {progress:.2f}%", end="", flush=True
)
)
print()
model.load()
print("Model loaded and ready.")
# Get a chat client
client = model.get_chat_client()
# Conversation with a system prompt
messages = [
{
"role": "system",
"content": "You are a helpful assistant with access to tools. "
"Use them when needed to answer questions accurately.",
}
]
print("\nTool-calling assistant ready! Type 'quit' to exit.\n")
while True:
user_input = input("You: ")
if user_input.strip().lower() in ("quit", "exit"):
break
messages.append({"role": "user", "content": user_input})
response = client.complete_chat(messages, tools=tools)
answer = process_tool_calls(messages, response, client)
messages.append({"role": "assistant", "content": answer})
print(f"Assistant: {answer}\n")
# Clean up
model.unload()
print("Model unloaded. Goodbye!")
if __name__ == "__main__":
main()
Jalankan asisten pemanggil alat:
python main.py
Anda melihat output yang mirip dengan:
Downloading model: 100.00%
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({'location': 'current location'})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({'expression': '245 * 38'})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Model memutuskan kapan harus memanggil alat berdasarkan pesan pengguna. Untuk pertanyaan cuaca, ia memanggil get_weather, untuk matematika, ia memanggil calculate, dan untuk pertanyaan umum, ia merespons langsung tanpa panggilan alat apa pun.
Repositori Penampung Sampel
Kode sampel lengkap untuk artikel ini tersedia di repositori GitHub foundry-samples. Untuk mengkloning repositori dan menavigasi ke penggunaan sampel:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/rust/foundry-local/tutorial-tool-calling
Memasang paket
Jika Anda mengembangkan atau mengirim di Windows, pilih tab Windows. Paket Windows terintegrasi dengan runtime Windows ML — ini menyediakan area permukaan API yang sama dengan luas akselerasi perangkat keras yang lebih luas.
cargo add foundry-local-sdk --features winml
cargo add tokio --features full
cargo add tokio-stream anyhow
Menentukan alat
Panggilan alat memungkinkan model untuk meminta agar kode Anda menjalankan sebuah fungsi dan mengembalikan hasilnya. Anda menentukan alat yang tersedia sebagai daftar skema JSON yang menjelaskan nama, tujuan, dan parameter setiap fungsi.
serde_jsonTambahkan dependensi untuk penanganan JSON:cargo add serde_jsonBuka
src/main.rsdan tambahkan definisi alat berikut:// --- Tool definitions --- let tools: Vec<ChatCompletionTools> = serde_json::from_value(json!([ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city or location" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] } } }, { "type": "function", "function": { "name": "calculate", "description": "Perform a math calculation", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "The math expression to evaluate" } }, "required": ["expression"] } } } ]))?;Setiap definisi alat mencakup
name,descriptionyang membantu model memutuskan kapan harus menggunakannya, danparametersskema yang menjelaskan input yang diharapkan.Tambahkan fungsi Rust yang mengimplementasikan setiap alat:
// --- Tool definitions --- let tools: Vec<ChatCompletionTools> = serde_json::from_value(json!([ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city or location" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] } } }, { "type": "function", "function": { "name": "calculate", "description": "Perform a math calculation", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "The math expression to evaluate" } }, "required": ["expression"] } } } ]))?;Model tidak menjalankan fungsi-fungsi ini secara langsung. Ini mengembalikan permintaan panggilan alat dengan nama fungsi dan argumen, dan kode Anda menjalankan fungsi.
Mengirim pesan yang memicu penggunaan alat
Inisialisasi SDK Lokal Foundry, muat model, dan kirim pesan yang dapat dijawab model dengan memanggil alat.
// Initialize the Foundry Local SDK
let manager = FoundryLocalManager::create(
FoundryLocalConfig::new("foundry_local_samples"),
)?;
// Download and register all execution providers.
manager
.download_and_register_eps_with_progress(None, {
let mut current_ep = String::new();
move |ep_name: &str, percent: f64| {
if ep_name != current_ep {
if !current_ep.is_empty() {
println!();
}
current_ep = ep_name.to_string();
}
print!("\r {:<30} {:5.1}%", ep_name, percent);
io::stdout().flush().ok();
}
})
.await?;
println!();
// Select and load a model
let model = manager
.catalog()
.get_model("qwen2.5-0.5b")
.await?;
if !model.is_cached().await? {
println!("Downloading model...");
model
.download(Some(|progress: f64| {
print!("\r {progress:.1}%");
io::stdout().flush().ok();
}))
.await?;
println!();
}
model.load().await?;
println!("Model loaded and ready.");
// Create a chat client
let client = model
.create_chat_client()
.temperature(0.7)
.max_tokens(512)
.tool_choice(ChatToolChoice::Auto);
// Conversation with a system prompt
let mut messages: Vec<ChatCompletionRequestMessage> = vec![
ChatCompletionRequestSystemMessage::from(
"You are a helpful assistant with access to tools. \
Use them when needed to answer questions accurately.",
)
.into(),
];
Saat model menentukan bahwa alat diperlukan, respons berisi tool_calls alih-alih pesan teks biasa. Langkah selanjutnya menunjukkan cara mendeteksi dan menangani panggilan ini.
Jalankan alat dan kembalikan hasil
Setelah model merespons dengan panggilan alat, Anda mengekstrak nama fungsi dan argumen, menjalankan fungsi, dan mengirim hasilnya kembali.
println!(
"\nTool-calling assistant ready! Type 'quit' to exit.\n"
);
let stdin = io::stdin();
loop {
print!("You: ");
io::stdout().flush()?;
let mut input = String::new();
stdin.lock().read_line(&mut input)?;
let input = input.trim();
if input.eq_ignore_ascii_case("quit")
|| input.eq_ignore_ascii_case("exit")
{
break;
}
messages.push(
ChatCompletionRequestUserMessage::from(input).into(),
);
let mut response = client
.complete_chat(&messages, Some(&tools))
.await?;
// Process tool calls in a loop
while response.choices[0].message.tool_calls.is_some() {
let tool_calls = response.choices[0]
.message
.tool_calls
.as_ref()
.unwrap();
// Append the assistant's tool_calls message via JSON
let assistant_msg: ChatCompletionRequestMessage =
serde_json::from_value(json!({
"role": "assistant",
"content": null,
"tool_calls": tool_calls,
}))?;
messages.push(assistant_msg);
for tc_enum in tool_calls {
let tool_call = match tc_enum {
ChatCompletionMessageToolCalls::Function(
tc,
) => tc,
_ => continue,
};
let function_name =
&tool_call.function.name;
let arguments: Value =
serde_json::from_str(
&tool_call.function.arguments,
)?;
println!(
" Tool call: {}({})",
function_name, arguments
);
let result =
execute_tool(function_name, &arguments);
messages.push(
ChatCompletionRequestToolMessage {
content: result.to_string().into(),
tool_call_id: tool_call.id.clone(),
}
.into(),
);
}
response = client
.complete_chat(&messages, Some(&tools))
.await?;
}
let answer = response.choices[0]
.message
.content
.as_deref()
.unwrap_or("");
let assistant_msg: ChatCompletionRequestMessage =
serde_json::from_value(json!({
"role": "assistant",
"content": answer,
}))?;
messages.push(assistant_msg);
println!("Assistant: {}\n", answer);
}
// Clean up
model.unload().await?;
println!("Model unloaded. Goodbye!");
Langkah-langkah utama dalam perulangan panggilan alat adalah:
-
Mendeteksi panggilan alat — periksa
response.choices[0].message.tool_calls. - Jalankan fungsi — uraikan argumen dan panggil fungsi lokal Anda.
-
Kembalikan hasilnya — tambahkan pesan dengan peran
tooldan ID panggilan alat yang cocok. - Dapatkan jawaban akhir — model menggunakan hasil alat untuk menghasilkan respons alami.
Menangani loop pemanggilan alat sepenuhnya
Berikut adalah aplikasi lengkap yang menggabungkan definisi alat, inisialisasi SDK, dan perulangan panggilan alat ke dalam satu file yang dapat dijalankan.
Ganti konten src/main.rs dengan kode lengkap berikut:
use foundry_local_sdk::{
ChatCompletionRequestMessage,
ChatCompletionRequestSystemMessage,
ChatCompletionRequestToolMessage,
ChatCompletionRequestUserMessage,
ChatCompletionMessageToolCalls,
ChatCompletionTools, ChatToolChoice,
FoundryLocalConfig, FoundryLocalManager,
};
use serde_json::{json, Value};
use std::io::{self, BufRead, Write};
// --- Tool implementations ---
fn execute_tool(
name: &str,
arguments: &Value,
) -> Value {
match name {
"get_weather" => {
let location = arguments["location"]
.as_str()
.unwrap_or("unknown");
let unit = arguments["unit"]
.as_str()
.unwrap_or("celsius");
let temp = if unit == "celsius" { 22 } else { 72 };
json!({
"location": location,
"temperature": temp,
"unit": unit,
"condition": "Sunny"
})
}
"calculate" => {
let expression = arguments["expression"]
.as_str()
.unwrap_or("");
let is_valid = expression
.chars()
.all(|c| "0123456789+-*/(). ".contains(c));
if !is_valid {
return json!({"error": "Invalid expression"});
}
match eval_expression(expression) {
Ok(result) => json!({
"expression": expression,
"result": result
}),
Err(e) => json!({"error": e}),
}
}
_ => json!({"error": format!("Unknown function: {}", name)}),
}
}
fn eval_expression(expr: &str) -> Result<f64, String> {
let expr = expr.replace(' ', "");
let chars: Vec<char> = expr.chars().collect();
let mut pos = 0;
let result = parse_add(&chars, &mut pos)?;
if pos < chars.len() {
return Err("Unexpected character".to_string());
}
Ok(result)
}
fn parse_add(
chars: &[char],
pos: &mut usize,
) -> Result<f64, String> {
let mut result = parse_mul(chars, pos)?;
while *pos < chars.len()
&& (chars[*pos] == '+' || chars[*pos] == '-')
{
let op = chars[*pos];
*pos += 1;
let right = parse_mul(chars, pos)?;
result = if op == '+' {
result + right
} else {
result - right
};
}
Ok(result)
}
fn parse_mul(
chars: &[char],
pos: &mut usize,
) -> Result<f64, String> {
let mut result = parse_atom(chars, pos)?;
while *pos < chars.len()
&& (chars[*pos] == '*' || chars[*pos] == '/')
{
let op = chars[*pos];
*pos += 1;
let right = parse_atom(chars, pos)?;
result = if op == '*' {
result * right
} else {
result / right
};
}
Ok(result)
}
fn parse_atom(
chars: &[char],
pos: &mut usize,
) -> Result<f64, String> {
if *pos < chars.len() && chars[*pos] == '(' {
*pos += 1;
let result = parse_add(chars, pos)?;
if *pos < chars.len() && chars[*pos] == ')' {
*pos += 1;
}
return Ok(result);
}
let start = *pos;
while *pos < chars.len()
&& (chars[*pos].is_ascii_digit() || chars[*pos] == '.')
{
*pos += 1;
}
if start == *pos {
return Err("Expected number".to_string());
}
let num_str: String = chars[start..*pos].iter().collect();
num_str.parse::<f64>().map_err(|e| e.to_string())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// --- Tool definitions ---
let tools: Vec<ChatCompletionTools> = serde_json::from_value(json!([
{
"type": "function",
"function": {
"name": "get_weather",
"description":
"Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description":
"The city or location"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description":
"The math expression to evaluate"
}
},
"required": ["expression"]
}
}
}
]))?;
// Initialize the Foundry Local SDK
let manager = FoundryLocalManager::create(
FoundryLocalConfig::new("foundry_local_samples"),
)?;
// Download and register all execution providers.
manager
.download_and_register_eps_with_progress(None, {
let mut current_ep = String::new();
move |ep_name: &str, percent: f64| {
if ep_name != current_ep {
if !current_ep.is_empty() {
println!();
}
current_ep = ep_name.to_string();
}
print!("\r {:<30} {:5.1}%", ep_name, percent);
io::stdout().flush().ok();
}
})
.await?;
println!();
// Select and load a model
let model = manager
.catalog()
.get_model("qwen2.5-0.5b")
.await?;
if !model.is_cached().await? {
println!("Downloading model...");
model
.download(Some(|progress: f64| {
print!("\r {progress:.1}%");
io::stdout().flush().ok();
}))
.await?;
println!();
}
model.load().await?;
println!("Model loaded and ready.");
// Create a chat client
let client = model
.create_chat_client()
.temperature(0.7)
.max_tokens(512)
.tool_choice(ChatToolChoice::Auto);
// Conversation with a system prompt
let mut messages: Vec<ChatCompletionRequestMessage> = vec![
ChatCompletionRequestSystemMessage::from(
"You are a helpful assistant with access to tools. \
Use them when needed to answer questions accurately.",
)
.into(),
];
println!(
"\nTool-calling assistant ready! Type 'quit' to exit.\n"
);
let stdin = io::stdin();
loop {
print!("You: ");
io::stdout().flush()?;
let mut input = String::new();
stdin.lock().read_line(&mut input)?;
let input = input.trim();
if input.eq_ignore_ascii_case("quit")
|| input.eq_ignore_ascii_case("exit")
{
break;
}
messages.push(
ChatCompletionRequestUserMessage::from(input).into(),
);
let mut response = client
.complete_chat(&messages, Some(&tools))
.await?;
// Process tool calls in a loop
while response.choices[0].message.tool_calls.is_some() {
let tool_calls = response.choices[0]
.message
.tool_calls
.as_ref()
.unwrap();
// Append the assistant's tool_calls message via JSON
let assistant_msg: ChatCompletionRequestMessage =
serde_json::from_value(json!({
"role": "assistant",
"content": null,
"tool_calls": tool_calls,
}))?;
messages.push(assistant_msg);
for tc_enum in tool_calls {
let tool_call = match tc_enum {
ChatCompletionMessageToolCalls::Function(
tc,
) => tc,
_ => continue,
};
let function_name =
&tool_call.function.name;
let arguments: Value =
serde_json::from_str(
&tool_call.function.arguments,
)?;
println!(
" Tool call: {}({})",
function_name, arguments
);
let result =
execute_tool(function_name, &arguments);
messages.push(
ChatCompletionRequestToolMessage {
content: result.to_string().into(),
tool_call_id: tool_call.id.clone(),
}
.into(),
);
}
response = client
.complete_chat(&messages, Some(&tools))
.await?;
}
let answer = response.choices[0]
.message
.content
.as_deref()
.unwrap_or("");
let assistant_msg: ChatCompletionRequestMessage =
serde_json::from_value(json!({
"role": "assistant",
"content": answer,
}))?;
messages.push(assistant_msg);
println!("Assistant: {}\n", answer);
}
// Clean up
model.unload().await?;
println!("Model unloaded. Goodbye!");
Ok(())
}
Jalankan asisten pemanggil alat:
cargo run
Anda melihat output yang mirip dengan:
Downloading model: 100.00%
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({"location":"current location"})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({"expression":"245 * 38"})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Model memutuskan kapan harus memanggil alat berdasarkan pesan pengguna. Untuk pertanyaan cuaca, ia memanggil get_weather, untuk matematika, ia memanggil calculate, dan untuk pertanyaan umum, ia merespons langsung tanpa panggilan alat apa pun.
Membersihkan sumber daya
Bobot model akan tetap berada di cache lokal Anda setelah Anda menghapus model. Ini berarti lain kali Anda menjalankan aplikasi, langkah unduhan dilewati dan model dimuat lebih cepat. Tidak diperlukan pembersihan tambahan kecuali Anda ingin mengklaim kembali ruang disk.