Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Utwórz asystenta sztucznej inteligencji, który wykracza poza konwersację — może wywoływać funkcje w celu wykonywania akcji. Asystent decyduje, kiedy jest potrzebna funkcja, wykonuje ją i przekaże wynik z powrotem. Wszystkie elementy są uruchamiane lokalnie za pomocą lokalnego zestawu SDK usługi Foundry.
W tym poradniku nauczysz się, jak:
- Konfigurowanie projektu i instalowanie lokalnego zestawu SDK rozwiązania Foundry
- Zdefiniuj narzędzia, które asystent może wywołać
- Wysyłanie komunikatu wyzwalającego użycie narzędzia
- Wykonywanie narzędzia i zwracanie wyników do modelu
- Obsługa pełnej pętli wywołań narzędzi
- Uprzątnij zasoby
Wymagania wstępne
- Komputer Windows, macOS lub Linux z co najmniej 8 GB pamięci RAM.
Repozytorium przykładów
Pełny kod przykładowy do tego artykułu można znaleźć w repozytorium GitHub przykładów platformy Foundry. Aby sklonować repozytorium i przejść do przykładu, użyj następującego polecenia:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/csharp/foundry-local/tutorial-tool-calling
Instalowanie pakietów
Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.
dotnet add package Microsoft.AI.Foundry.Local.WinML
dotnet add package OpenAI
Przykłady języka C# w repozytorium GitHub to wstępnie skonfigurowane projekty. Jeśli tworzysz od podstaw, zapoznaj się z dokumentacją zestawu SDK lokalnego rozwiązania Foundry , aby uzyskać więcej informacji na temat sposobu konfigurowania projektu w języku C# przy użyciu rozwiązania Foundry Local.
Definiowanie narzędzi
Wywołanie narzędzia umożliwia modelowi żądanie, że kod uruchamia funkcję i zwraca wynik. Dostępne narzędzia są definiowane jako lista schematów JSON opisujących nazwy, przeznaczenie i parametry każdej funkcji.
Otwórz
Program.csi dodaj następujące definicje narzędzi:// --- 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}" }); } }Każda definicja narzędzia zawiera element
name,descriptionktóry pomaga modelowi zdecydować, kiedy go używać, oraz schemat opisującyparametersoczekiwane dane wejściowe.Dodaj metody języka C#, które implementują każde narzędzie:
// --- 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 nie uruchamia tych funkcji bezpośrednio. Zwraca żądanie wywołania narzędzia z nazwą funkcji i argumentami, a kod wykonuje funkcję.
Wysyłanie komunikatu wyzwalającego użycie narzędzia
Zainicjuj lokalny zestaw SDK usługi Foundry, załaduj model i wyślij komunikat, na który model może odpowiedzieć, wywołując narzędzie.
// --- 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."
}
};
Gdy model ustali, że potrzebne jest narzędzie, odpowiedź zawiera ToolCalls zamiast zwykłego komunikatu tekstowego. W następnym kroku pokazano, jak wykrywać i obsługiwać te wywołania.
Wykonywanie narzędzia i zwracanie wyników
Gdy model odpowie za pomocą wywołania narzędzia, wyodrębnisz nazwę i argumenty funkcji, uruchom funkcję i wyślij wynik z powrotem.
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!");
Kluczowe kroki w pętli wywoływania narzędzi to:
-
Wykrywanie wywołań narzędzi — sprawdź
response.Choices[0].Message.ToolCalls. - Wykonaj funkcję — przeanalizuj argumenty i wywołaj funkcję lokalną.
-
Zwróć wynik — dodaj komunikat z rolą
tooli pasującymToolCallIdelementem. - Uzyskaj ostateczną odpowiedź — model używa wyniku narzędzia do generowania naturalnej odpowiedzi.
Obsługa pełnej pętli wywołań narzędzi
Oto kompletna aplikacja, która łączy definicje narzędzi, inicjowanie zestawu SDK i pętlę wywoływania narzędzia w jeden plik możliwy do uruchomienia.
Zastąp zawartość Program.cs następującym kompletnym kodem:
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!");
Uruchom asystenta wywoływania narzędzi:
dotnet run
Zobaczysz dane wyjściowe podobne do:
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 decyduje, kiedy wywołać narzędzie na podstawie komunikatu użytkownika. W przypadku pytania o pogodę wywołuje get_weathermetodę , w przypadku matematyki wywołuje calculatemetodę , a w przypadku pytań ogólnych odpowiada bezpośrednio bez żadnych wywołań narzędzi.
Repozytorium przykładów
Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego użycia:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/javascript/foundry-local/tutorial-tool-calling
Instalowanie pakietów
Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.
npm install foundry-local-sdk-winml openai
Definiowanie narzędzi
Wywołanie narzędzia umożliwia modelowi żądanie, że kod uruchamia funkcję i zwraca wynik. Dostępne narzędzia są definiowane jako lista schematów JSON opisujących nazwy, przeznaczenie i parametry każdej funkcji.
Utwórz plik o nazwie
index.js.Dodaj następujące definicje narzędzi:
// --- 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) };Każda definicja narzędzia zawiera element
name,descriptionktóry pomaga modelowi zdecydować, kiedy go używać, oraz schemat opisującyparametersoczekiwane dane wejściowe.Dodaj funkcje języka JavaScript, które implementują każde narzędzie:
// --- 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 nie uruchamia tych funkcji bezpośrednio. Zwraca żądanie wywołania narzędzia z nazwą funkcji i argumentami, a kod wykonuje funkcję.
Wysyłanie komunikatu wyzwalającego użycie narzędzia
Zainicjuj lokalny zestaw SDK usługi Foundry, załaduj model i wyślij komunikat, na który model może odpowiedzieć, wywołując narzędzie.
// --- 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();
Gdy model ustali, że potrzebne jest narzędzie, odpowiedź zawiera tool_calls zamiast zwykłego komunikatu tekstowego. W następnym kroku pokazano, jak wykrywać i obsługiwać te wywołania.
Wykonywanie narzędzia i zwracanie wyników
Gdy model odpowie za pomocą wywołania narzędzia, wyodrębnisz nazwę i argumenty funkcji, uruchom funkcję i wyślij wynik z powrotem.
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 ?? '';
}
Kluczowe kroki w pętli wywoływania narzędzi to:
-
Wykrywanie wywołań narzędzi — sprawdź
response.choices[0]?.message?.tool_calls. - Wykonaj funkcję — przeanalizuj argumenty i wywołaj funkcję lokalną.
-
Zwróć wynik — dodaj komunikat z rolą
tooli pasującymtool_call_idelementem. - Uzyskaj ostateczną odpowiedź — model używa wyniku narzędzia do generowania naturalnej odpowiedzi.
Obsługa pełnej pętli wywołań narzędzi
Oto kompletna aplikacja, która łączy definicje narzędzi, inicjowanie zestawu SDK i pętlę wywoływania narzędzia w jeden plik możliwy do uruchomienia.
Utwórz plik o nazwie index.js i dodaj następujący kompletny kod:
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();
Uruchom asystenta wywoływania narzędzi:
node index.js
Zobaczysz dane wyjściowe podobne do:
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 decyduje, kiedy wywołać narzędzie na podstawie komunikatu użytkownika. W przypadku pytania o pogodę wywołuje get_weathermetodę , w przypadku matematyki wywołuje calculatemetodę , a w przypadku pytań ogólnych odpowiada bezpośrednio bez żadnych wywołań narzędzi.
Repozytorium przykładów
Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego użycia:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/python/foundry-local/tutorial-tool-calling
Instalowanie pakietów
Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.
pip install foundry-local-sdk-winml openai
Definiowanie narzędzi
Wywołanie narzędzia umożliwia modelowi żądanie, że kod uruchamia funkcję i zwraca wynik. Dostępne narzędzia są definiowane jako lista schematów JSON opisujących nazwy, przeznaczenie i parametry każdej funkcji.
Utwórz plik o nazwie main.py i dodaj następujące definicje narzędzi:
# --- 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}
Każda definicja narzędzia zawiera element name, description który pomaga modelowi zdecydować, kiedy go używać, oraz schemat opisujący parameters oczekiwane dane wejściowe.
Następnie dodaj funkcje Python, które implementują każde narzędzie:
# --- 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 nie uruchamia tych funkcji bezpośrednio. Zwraca żądanie wywołania narzędzia z nazwą funkcji i argumentami, a kod wykonuje funkcję.
Wysyłanie komunikatu wyzwalającego użycie narzędzia
Zainicjuj lokalny zestaw SDK usługi Foundry, załaduj model i wyślij komunikat, na który model może odpowiedzieć, wywołując narzędzie.
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!")
Gdy model ustali, że potrzebne jest narzędzie, odpowiedź zawiera tool_calls zamiast zwykłego komunikatu tekstowego. W następnym kroku pokazano, jak wykrywać i obsługiwać te wywołania.
Wykonywanie narzędzia i zwracanie wyników
Gdy model odpowie za pomocą wywołania narzędzia, wyodrębnisz nazwę i argumenty funkcji, uruchom funkcję i wyślij wynik z powrotem.
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
Kluczowe kroki w pętli wywoływania narzędzi to:
-
Wykrywanie wywołań narzędzi — sprawdź
response.choices[0].message.tool_calls. - Wykonaj funkcję — przeanalizuj argumenty i wywołaj funkcję lokalną.
-
Zwróć wynik — dodaj komunikat z rolą
tooli pasującymtool_call_idelementem. - Uzyskaj ostateczną odpowiedź — model używa wyniku narzędzia do generowania naturalnej odpowiedzi.
Obsługa pełnej pętli wywołań narzędzi
Oto kompletna aplikacja, która łączy definicje narzędzi, inicjowanie zestawu SDK i pętlę wywoływania narzędzia w jeden plik możliwy do uruchomienia.
Utwórz plik o nazwie main.py i dodaj następujący kompletny kod:
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()
Uruchom asystenta wywoływania narzędzi:
python main.py
Zobaczysz dane wyjściowe podobne do:
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 decyduje, kiedy wywołać narzędzie na podstawie komunikatu użytkownika. W przypadku pytania o pogodę wywołuje get_weathermetodę , w przypadku matematyki wywołuje calculatemetodę , a w przypadku pytań ogólnych odpowiada bezpośrednio bez żadnych wywołań narzędzi.
Repozytorium przykładów
Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego użycia:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/rust/foundry-local/tutorial-tool-calling
Instalowanie pakietów
Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.
cargo add foundry-local-sdk --features winml
cargo add tokio --features full
cargo add tokio-stream anyhow
Definiowanie narzędzi
Wywołanie narzędzia umożliwia modelowi żądanie, że kod uruchamia funkcję i zwraca wynik. Dostępne narzędzia są definiowane jako lista schematów JSON opisujących nazwy, przeznaczenie i parametry każdej funkcji.
serde_jsonDodaj zależność do obsługi kodu JSON:cargo add serde_jsonOtwórz
src/main.rsi dodaj następujące definicje narzędzi:// --- 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"] } } } ]))?;Każda definicja narzędzia zawiera element
name,descriptionktóry pomaga modelowi zdecydować, kiedy go używać, oraz schemat opisującyparametersoczekiwane dane wejściowe.Dodaj funkcje Rust, które implementują każde narzędzie:
// --- 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 nie uruchamia tych funkcji bezpośrednio. Zwraca żądanie wywołania narzędzia z nazwą funkcji i argumentami, a kod wykonuje funkcję.
Wysyłanie komunikatu wyzwalającego użycie narzędzia
Zainicjuj lokalny zestaw SDK usługi Foundry, załaduj model i wyślij komunikat, na który model może odpowiedzieć, wywołując narzędzie.
// 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(),
];
Gdy model ustali, że potrzebne jest narzędzie, odpowiedź zawiera tool_calls zamiast zwykłego komunikatu tekstowego. W następnym kroku pokazano, jak wykrywać i obsługiwać te wywołania.
Wykonywanie narzędzia i zwracanie wyników
Gdy model odpowie za pomocą wywołania narzędzia, wyodrębnisz nazwę i argumenty funkcji, uruchom funkcję i wyślij wynik z powrotem.
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!");
Kluczowe kroki w pętli wywoływania narzędzi to:
-
Wykrywanie wywołań narzędzi — sprawdź
response.choices[0].message.tool_calls. - Wykonaj funkcję — przeanalizuj argumenty i wywołaj funkcję lokalną.
-
Zwróć wynik — dodaj komunikat z rolą
tooli zgodnym identyfikatorem wywołania narzędzia. - Uzyskaj ostateczną odpowiedź — model używa wyniku narzędzia do generowania naturalnej odpowiedzi.
Obsługa pełnej pętli wywołań narzędzi
Oto kompletna aplikacja, która łączy definicje narzędzi, inicjowanie zestawu SDK i pętlę wywoływania narzędzia w jeden plik możliwy do uruchomienia.
Zastąp zawartość src/main.rs następującym kompletnym kodem:
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(())
}
Uruchom asystenta wywoływania narzędzi:
cargo run
Zobaczysz dane wyjściowe podobne do:
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 decyduje, kiedy wywołać narzędzie na podstawie komunikatu użytkownika. W przypadku pytania o pogodę wywołuje get_weathermetodę , w przypadku matematyki wywołuje calculatemetodę , a w przypadku pytań ogólnych odpowiada bezpośrednio bez żadnych wywołań narzędzi.
Uprzątnij zasoby
Wagi modelu są przechowywane w Twojej lokalnej pamięci podręcznej po rozładowaniu modelu. Oznacza to, że przy następnym uruchomieniu aplikacji krok pobierania zostanie pominięty, a model ładuje się szybciej. Nie jest wymagane żadne dodatkowe czyszczenie, chyba że chcesz odzyskać miejsce na dysku.