会話を超える AI アシスタントを構築し、関数を呼び出してアクションを実行できます。 アシスタントは、関数が必要なタイミングを決定して実行し、結果をフィードバックします。 すべてが Foundry Local SDK を使用してローカルで実行されます。
このチュートリアルでは、以下の内容を学習します。
- プロジェクトを設定し、Foundry Local SDK をインストールする
- アシスタントが呼び出すことができるツールを定義する
- ツールの使用をトリガーするメッセージを送信する
- ツールを実行してモデルに結果を返す
- 完全なツール呼び出しループを処理する
- リソースをクリーンアップする
前提条件
- 8 GB 以上の RAM を搭載したWindows、macOS、または Linux コンピューター。
サンプル リポジトリ
この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。
git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/cs/tutorial-tool-calling
パッケージをインストールする
Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。
dotnet add package Microsoft.AI.Foundry.Local.WinML
dotnet add package OpenAI
GitHub リポジトリの C# サンプルは、事前構成済みのプロジェクトです。 最初からビルドする場合は、Foundry Local を使用して C# プロジェクトを設定する方法の詳細については、 Foundry Local SDK リファレンスを参照 してください。
ツールの定義
ツール呼び出しにより、コードで関数を実行し、結果を返すというモデル要求が可能になります。 使用可能なツールは、各関数の名前、目的、およびパラメーターを記述する JSON スキーマの一覧として定義します。
Program.cs開き、次のツール定義を追加します。// --- 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}" }); } }各ツール定義には、
name、モデルが使用するタイミングを決定するのに役立つdescription、および予想される入力を記述するparametersスキーマが含まれます。各ツールを実装する C# メソッドを追加します。
// --- 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}" }); } }モデルでは、これらの関数は直接実行されません。 関数名と引数を含むツール呼び出し要求が返され、コードによって関数が実行されます。
ツールの使用をトリガーするメッセージを送信する
Foundry Local SDK を初期化し、モデルを読み込み、ツールを呼び出してモデルが応答できるメッセージを送信します。
// --- 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."
}
};
ツールが必要であるとモデルが判断すると、応答には通常のテキスト メッセージではなく ToolCalls が含まれます。 次の手順では、これらの呼び出しを検出して処理する方法を示します。
ツールを実行して結果を返す
モデルがツール呼び出しで応答した後、関数の名前と引数を抽出し、関数を実行して、結果を返します。
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!");
ツール呼び出しループの主な手順は次のとおりです。
-
ツールの呼び出しを検出 する —
response.Choices[0].Message.ToolCallsを確認します。 - 関数を実行します 。引数を解析し、ローカル関数を呼び出します。
-
結果を返します 。ロール
toolと一致するToolCallIdを含むメッセージを追加します。 - 最終的な回答を得る — モデルはツールの結果を使用して自然な応答を生成します。
完全なツール呼び出しループを処理する
ツール定義、SDK 初期化、ツール呼び出しループを 1 つの実行可能ファイルに結合する完全なアプリケーションを次に示します。
Program.csの内容を次の完全なコードに置き換えます。
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!");
ツール呼び出しアシスタントを実行します。
dotnet run
次のような出力が表示されます。
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!
モデルは、ユーザーのメッセージに基づいてツールを呼び出すタイミングを決定します。 気象の質問では、 get_weatherを呼び出し、数学では calculateを呼び出し、一般的な質問ではツールの呼び出しなしで直接応答します。
サンプル リポジトリ
この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。
git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/js/tutorial-tool-calling
パッケージをインストールする
Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。
npm install foundry-local-sdk-winml openai
ツールの定義
ツール呼び出しにより、コードで関数を実行し、結果を返すというモデル要求が可能になります。 使用可能なツールは、各関数の名前、目的、およびパラメーターを記述する JSON スキーマの一覧として定義します。
index.jsという名前のファイルを作成します。次のツール定義を追加します。
// --- 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) };各ツール定義には、
name、モデルが使用するタイミングを決定するのに役立つdescription、および予想される入力を記述するparametersスキーマが含まれます。各ツールを実装する JavaScript 関数を追加します。
// --- 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) };モデルでは、これらの関数は直接実行されません。 関数名と引数を含むツール呼び出し要求が返され、コードによって関数が実行されます。
ツールの使用をトリガーするメッセージを送信する
Foundry Local SDK を初期化し、モデルを読み込み、ツールを呼び出してモデルが応答できるメッセージを送信します。
// --- 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) => rl.question(prompt, resolve));
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();
ツールが必要であるとモデルが判断すると、応答には通常のテキスト メッセージではなく tool_calls が含まれます。 次の手順では、これらの呼び出しを検出して処理する方法を示します。
ツールを実行して結果を返す
モデルがツール呼び出しで応答した後、関数の名前と引数を抽出し、関数を実行して、結果を返します。
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 ?? '';
}
ツール呼び出しループの主な手順は次のとおりです。
-
ツールの呼び出しを検出 する —
response.choices[0]?.message?.tool_callsを確認します。 - 関数を実行します 。引数を解析し、ローカル関数を呼び出します。
-
結果を返します 。ロール
toolと一致するtool_call_idを含むメッセージを追加します。 - 最終的な回答を得る — モデルはツールの結果を使用して自然な応答を生成します。
完全なツール呼び出しループを処理する
ツール定義、SDK 初期化、ツール呼び出しループを 1 つの実行可能ファイルに結合する完全なアプリケーションを次に示します。
index.jsという名前のファイルを作成し、次の完全なコードを追加します。
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) => rl.question(prompt, resolve));
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();
ツール呼び出しアシスタントを実行します。
node index.js
次のような出力が表示されます。
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!
モデルは、ユーザーのメッセージに基づいてツールを呼び出すタイミングを決定します。 気象の質問では、 get_weatherを呼び出し、数学では calculateを呼び出し、一般的な質問ではツールの呼び出しなしで直接応答します。
サンプル リポジトリ
この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。
git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/python/tutorial-tool-calling
パッケージをインストールする
Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。
pip install foundry-local-sdk-winml openai
ツールの定義
ツール呼び出しにより、コードで関数を実行し、結果を返すというモデル要求が可能になります。 使用可能なツールは、各関数の名前、目的、およびパラメーターを記述する JSON スキーマの一覧として定義します。
main.pyという名前のファイルを作成し、次のツール定義を追加します。
# --- 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
}
各ツール定義には、 name、モデルが使用するタイミングを決定するのに役立つ description 、および予想される入力を記述する parameters スキーマが含まれます。
次に、各ツールを実装するPython関数を追加します。
# --- 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
}
モデルでは、これらの関数は直接実行されません。 関数名と引数を含むツール呼び出し要求が返され、コードによって関数が実行されます。
ツールの使用をトリガーするメッセージを送信する
Foundry Local SDK を初期化し、モデルを読み込み、ツールを呼び出してモデルが応答できるメッセージを送信します。
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!")
ツールが必要であるとモデルが判断すると、応答には通常のテキスト メッセージではなく tool_calls が含まれます。 次の手順では、これらの呼び出しを検出して処理する方法を示します。
ツールを実行して結果を返す
モデルがツール呼び出しで応答した後、関数の名前と引数を抽出し、関数を実行して、結果を返します。
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
ツール呼び出しループの主な手順は次のとおりです。
-
ツールの呼び出しを検出 する —
response.choices[0].message.tool_callsを確認します。 - 関数を実行します 。引数を解析し、ローカル関数を呼び出します。
-
結果を返します 。ロール
toolと一致するtool_call_idを含むメッセージを追加します。 - 最終的な回答を得る — モデルはツールの結果を使用して自然な応答を生成します。
完全なツール呼び出しループを処理する
ツール定義、SDK 初期化、ツール呼び出しループを 1 つの実行可能ファイルに結合する完全なアプリケーションを次に示します。
main.pyという名前のファイルを作成し、次の完全なコードを追加します。
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()
ツール呼び出しアシスタントを実行します。
python main.py
次のような出力が表示されます。
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!
モデルは、ユーザーのメッセージに基づいてツールを呼び出すタイミングを決定します。 気象の質問では、 get_weatherを呼び出し、数学では calculateを呼び出し、一般的な質問ではツールの呼び出しなしで直接応答します。
サンプル リポジトリ
この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。
git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/rust/tutorial-tool-calling
パッケージをインストールする
Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。
cargo add foundry-local-sdk --features winml
cargo add tokio --features full
cargo add tokio-stream anyhow
ツールの定義
ツール呼び出しにより、コードで関数を実行し、結果を返すというモデル要求が可能になります。 使用可能なツールは、各関数の名前、目的、およびパラメーターを記述する JSON スキーマの一覧として定義します。
JSON 処理の
serde_json依存関係を追加します。cargo add serde_jsonsrc/main.rs開き、次のツール定義を追加します。// --- 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"] } } } ]))?;各ツール定義には、
name、モデルが使用するタイミングを決定するのに役立つdescription、および予想される入力を記述するparametersスキーマが含まれます。各ツールを実装する Rust 関数を追加します。
// --- 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"] } } } ]))?;モデルでは、これらの関数は直接実行されません。 関数名と引数を含むツール呼び出し要求が返され、コードによって関数が実行されます。
ツールの使用をトリガーするメッセージを送信する
Foundry Local SDK を初期化し、モデルを読み込み、ツールを呼び出してモデルが応答できるメッセージを送信します。
// Initialize the Foundry Local SDK
let manager = FoundryLocalManager::create(
FoundryLocalConfig::new("tool-calling-app"),
)?;
// 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(),
];
ツールが必要であるとモデルが判断すると、応答には通常のテキスト メッセージではなく tool_calls が含まれます。 次の手順では、これらの呼び出しを検出して処理する方法を示します。
ツールを実行して結果を返す
モデルがツール呼び出しで応答した後、関数の名前と引数を抽出し、関数を実行して、結果を返します。
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!");
ツール呼び出しループの主な手順は次のとおりです。
-
ツールの呼び出しを検出 する —
response.choices[0].message.tool_callsを確認します。 - 関数を実行します 。引数を解析し、ローカル関数を呼び出します。
-
結果を返します 。ロール
toolと一致するツール呼び出し ID を含むメッセージを追加します。 - 最終的な回答を得る — モデルはツールの結果を使用して自然な応答を生成します。
完全なツール呼び出しループを処理する
ツール定義、SDK 初期化、ツール呼び出しループを 1 つの実行可能ファイルに結合する完全なアプリケーションを次に示します。
src/main.rsの内容を次の完全なコードに置き換えます。
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("tool-calling-app"),
)?;
// 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(())
}
ツール呼び出しアシスタントを実行します。
cargo run
次のような出力が表示されます。
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!
モデルは、ユーザーのメッセージに基づいてツールを呼び出すタイミングを決定します。 気象の質問では、 get_weatherを呼び出し、数学では calculateを呼び出し、一般的な質問ではツールの呼び出しなしで直接応答します。
リソースをクリーンアップする
モデルの重みは、モデルをアンロードした後もローカル キャッシュに残ります。 つまり、次にアプリケーションを実行すると、ダウンロード手順がスキップされ、モデルの読み込みが速くなります。 ディスク領域を再利用しない限り、追加のクリーンアップは必要ありません。