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
dotnet add package Microsoft.AI.Foundry.Local
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 and implementations --- var toolDefinitions = new[] { new { Name = "get_weather", Description = "Get the current weather for a location", Schema = """ { "type": "object", "properties": { "location": { "type": "string", "description": "The city or location" }, "unit": { "type": "string", "description": "Temperature unit", "enum": ["celsius", "fahrenheit"] } }, "required": ["location"] } """ }, new { Name = "calculate", Description = "Perform a math calculation", Schema = """ { "type": "object", "properties": { "expression": { "type": "string", "description": "The math expression to evaluate" } }, "required": ["expression"] } """ } }; 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 and implementations --- var toolDefinitions = new[] { new { Name = "get_weather", Description = "Get the current weather for a location", Schema = """ { "type": "object", "properties": { "location": { "type": "string", "description": "The city or location" }, "unit": { "type": "string", "description": "Temperature unit", "enum": ["celsius", "fahrenheit"] } }, "required": ["location"] } """ }, new { Name = "calculate", Description = "Perform a math calculation", Schema = """ { "type": "object", "properties": { "expression": { "type": "string", "description": "The math expression to evaluate" } }, "required": ["expression"] } """ } }; 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;
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.");
using var chatSession = new ChatSession(model);
chatSession.SetOptions(new RequestOptions { ToolChoice = ToolChoice.Auto });
foreach (var tool in toolDefinitions)
{
chatSession.AddToolDefinition(tool.Name, tool.Description, tool.Schema);
}
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;
}
using var request = new Request();
if (chatSession.TurnCount == 0)
{
request.AddItem(MessageItem.System(
"You are a helpful assistant with access to tools. Use them when needed."));
}
request.AddItem(MessageItem.User(userInput));
using var response = await chatSession.ProcessRequestAsync(request, ct);
var toolCalls = response.OfType<ToolCallItem>()
.Select(call => (call.CallId, call.Name, call.Arguments))
.ToList();
if (toolCalls.Count > 0)
{
using var toolRequest = new Request();
foreach (var toolCall in toolCalls)
{
using var argumentsDocument = JsonDocument.Parse(toolCall.Arguments);
var arguments = argumentsDocument.RootElement;
Console.WriteLine($" Tool call: {toolCall.Name}({arguments})");
var result = ExecuteTool(toolCall.Name, arguments);
toolRequest.AddItem(new ToolResultItem(toolCall.CallId, result));
}
using var finalResponse = await chatSession.ProcessRequestAsync(toolRequest, ct);
var answer = GetResponseText(finalResponse);
Console.WriteLine($"Assistant: {answer}\n");
}
else
{
Console.WriteLine($"Assistant: {GetResponseText(response)}\n");
}
}
chatSession.Dispose();
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 Microsoft.Extensions.Logging;
CancellationToken ct = CancellationToken.None;
// --- Tool definitions and implementations ---
var toolDefinitions = new[]
{
new
{
Name = "get_weather",
Description = "Get the current weather for a location",
Schema =
"""
{
"type": "object",
"properties": {
"location": { "type": "string", "description": "The city or location" },
"unit": {
"type": "string",
"description": "Temperature unit",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
"""
},
new
{
Name = "calculate",
Description = "Perform a math calculation",
Schema =
"""
{
"type": "object",
"properties": {
"expression": { "type": "string", "description": "The math expression to evaluate" }
},
"required": ["expression"]
}
"""
}
};
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;
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.");
using var chatSession = new ChatSession(model);
chatSession.SetOptions(new RequestOptions { ToolChoice = ToolChoice.Auto });
foreach (var tool in toolDefinitions)
{
chatSession.AddToolDefinition(tool.Name, tool.Description, tool.Schema);
}
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;
}
using var request = new Request();
if (chatSession.TurnCount == 0)
{
request.AddItem(MessageItem.System(
"You are a helpful assistant with access to tools. Use them when needed."));
}
request.AddItem(MessageItem.User(userInput));
using var response = await chatSession.ProcessRequestAsync(request, ct);
var toolCalls = response.OfType<ToolCallItem>()
.Select(call => (call.CallId, call.Name, call.Arguments))
.ToList();
if (toolCalls.Count > 0)
{
using var toolRequest = new Request();
foreach (var toolCall in toolCalls)
{
using var argumentsDocument = JsonDocument.Parse(toolCall.Arguments);
var arguments = argumentsDocument.RootElement;
Console.WriteLine($" Tool call: {toolCall.Name}({arguments})");
var result = ExecuteTool(toolCall.Name, arguments);
toolRequest.AddItem(new ToolResultItem(toolCall.CallId, result));
}
using var finalResponse = await chatSession.ProcessRequestAsync(toolRequest, ct);
var answer = GetResponseText(finalResponse);
Console.WriteLine($"Assistant: {answer}\n");
}
else
{
Console.WriteLine($"Assistant: {GetResponseText(response)}\n");
}
}
chatSession.Dispose();
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
npm install foundry-local-sdk 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 = [ { name: 'get_weather', description: 'Get the current weather for a location', jsonSchema: JSON.stringify({ type: 'object', properties: { location: { type: 'string', description: 'The city or location' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: 'Temperature unit' } }, required: ['location'] }) }, { name: 'calculate', description: 'Perform a math calculation', jsonSchema: JSON.stringify({ 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 = [ { name: 'get_weather', description: 'Get the current weather for a location', jsonSchema: JSON.stringify({ type: 'object', properties: { location: { type: 'string', description: 'The city or location' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: 'Temperature unit' } }, required: ['location'] }) }, { name: 'calculate', description: 'Perform a math calculation', jsonSchema: JSON.stringify({ 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 chatSession = new ChatSession(model);
for (const tool of tools) {
chatSession.addToolDefinition(tool);
}
let firstTurn = true;
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;
}
const request = new Request().setOptions({ toolChoice: 'auto' });
if (firstTurn) {
request.addItem(Item.systemMessage(
'You are a helpful assistant with access to tools. ' +
'Use them when needed to answer questions accurately.'
));
firstTurn = false;
}
request.addItem(Item.userMessage(userInput));
const response = await chatSession.processRequest(request);
const answer = await processToolCalls(
response, chatSession
);
console.log(`Assistant: ${answer}\n`);
}
chatSession.dispose();
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(response, chatSession) {
let currentResponse = response;
while (currentResponse.finishReason === 'toolCalls') {
const toolResults = [];
for (const toolCall of currentResponse.output.filter(
(item) => item.type === 'toolCall'
)) {
const functionName = toolCall.name;
const args = JSON.parse(toolCall.arguments);
console.log(
` Tool call: ${functionName}` +
`(${JSON.stringify(args)})`
);
const implementation = toolFunctions[functionName];
const result = implementation
? implementation(args)
: { error: `Unknown tool: ${functionName}` };
toolResults.push(Item.toolResult(
toolCall.callId,
JSON.stringify(result)
));
}
if (toolResults.length === 0) {
throw new Error('The model requested tools but returned no tool calls.');
}
const followUp = new Request()
.setOptions({ toolChoice: 'auto' });
for (const result of toolResults) {
followUp.addItem(result);
}
currentResponse = await chatSession.processRequest(followUp);
}
return currentResponse.output
.map((item) => item.type === 'message'
? item.content ?? item.parts?.map((part) => part.text ?? '').join('') ?? ''
: item.type === 'text' ? item.text : '')
.join('');
}
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 {
ChatSession,
FoundryLocalManager,
Item,
Request
} from 'foundry-local-sdk';
import * as readline from 'readline';
// --- Tool definitions ---
const tools = [
{
name: 'get_weather',
description: 'Get the current weather for a location',
jsonSchema: JSON.stringify({
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city or location'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature unit'
}
},
required: ['location']
})
},
{
name: 'calculate',
description: 'Perform a math calculation',
jsonSchema: JSON.stringify({
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(response, chatSession) {
let currentResponse = response;
while (currentResponse.finishReason === 'toolCalls') {
const toolResults = [];
for (const toolCall of currentResponse.output.filter(
(item) => item.type === 'toolCall'
)) {
const functionName = toolCall.name;
const args = JSON.parse(toolCall.arguments);
console.log(
` Tool call: ${functionName}` +
`(${JSON.stringify(args)})`
);
const implementation = toolFunctions[functionName];
const result = implementation
? implementation(args)
: { error: `Unknown tool: ${functionName}` };
toolResults.push(Item.toolResult(
toolCall.callId,
JSON.stringify(result)
));
}
if (toolResults.length === 0) {
throw new Error('The model requested tools but returned no tool calls.');
}
const followUp = new Request()
.setOptions({ toolChoice: 'auto' });
for (const result of toolResults) {
followUp.addItem(result);
}
currentResponse = await chatSession.processRequest(followUp);
}
return currentResponse.output
.map((item) => item.type === 'message'
? item.content ?? item.parts?.map((part) => part.text ?? '').join('') ?? ''
: item.type === 'text' ? item.text : '')
.join('');
}
// --- 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 chatSession = new ChatSession(model);
for (const tool of tools) {
chatSession.addToolDefinition(tool);
}
let firstTurn = true;
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;
}
const request = new Request().setOptions({ toolChoice: 'auto' });
if (firstTurn) {
request.addItem(Item.systemMessage(
'You are a helpful assistant with access to tools. ' +
'Use them when needed to answer questions accurately.'
));
firstTurn = false;
}
request.addItem(Item.userMessage(userInput));
const response = await chatSession.processRequest(request);
const answer = await processToolCalls(
response, chatSession
);
console.log(`Assistant: ${answer}\n`);
}
chatSession.dispose();
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
pip install foundry-local-sdk 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 = [
{
"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"],
},
},
{
"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(character in allowed for character in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression, {"__builtins__": {}}, {})
return {"expression": expression, "result": result}
except Exception as error:
return {"error": str(error)}
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 = [
{
"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"],
},
},
{
"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(character in allowed for character in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression, {"__builtins__": {}}, {})
return {"expression": expression, "result": result}
except Exception as error:
return {"error": str(error)}
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
# Discover and register execution providers that are not already available.
missing_eps = [ep.name for ep in manager.discover_eps() if not ep.is_registered]
if missing_eps:
manager.download_and_register_eps(missing_eps)
# Select and load a tool-capable model.
model = manager.catalog.get_model("qwen2.5-0.5b")
if model is None:
raise RuntimeError("Model 'qwen2.5-0.5b' was not found in the catalog.")
if not model.is_cached:
model.download(
lambda progress: print(
f"\rDownloading model: {progress:.2f}%", end="", flush=True
)
)
print()
model.load()
print("Model loaded and ready.")
try:
with ChatSession(model) as session:
session.set_options(
RequestOptions(
search=SearchOptions(temperature=0.0, max_output_tokens=512)
)
)
for tool in tools:
session.add_tool_definition(
tool["name"],
tool["description"],
json.dumps(tool["parameters"]),
)
system_prompt = (
"You are a helpful assistant with access to tools. "
"Use them when needed to answer questions accurately."
)
first_turn = True
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
with Request() as request:
if first_turn:
request.add_item(MessageItem.system(system_prompt))
first_turn = False
request.add_item(MessageItem.user(user_input))
with session.process_request(request) as response:
answer, pending_calls = response_content(response)
answer = process_tool_calls(session, answer, pending_calls)
print(f"Assistant: {answer}\n")
finally:
# Clean up
if model.is_loaded:
model.unload()
manager.close()
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(session, answer, pending_calls):
"""Handle tool calls until the stateful session produces a final answer."""
while pending_calls:
with Request() as follow_up:
for call_id, function_name, arguments_json in pending_calls:
arguments = json.loads(arguments_json)
print(f" Tool call: {function_name}({arguments})")
result = tool_functions[function_name](**arguments)
follow_up.add_item(
ToolResultItem(call_id=call_id, result=json.dumps(result))
)
with session.process_request(follow_up) as next_response:
answer, pending_calls = response_content(next_response)
return answer
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 (
ChatSession,
Configuration,
FoundryLocalManager,
MessageItem,
Request,
RequestOptions,
SearchOptions,
TextItem,
ToolCallItem,
ToolResultItem,
)
# --- Tool definitions ---
tools = [
{
"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"],
},
},
{
"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(character in allowed for character in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression, {"__builtins__": {}}, {})
return {"expression": expression, "result": result}
except Exception as error:
return {"error": str(error)}
tool_functions = {"get_weather": get_weather, "calculate": calculate}
def response_content(response):
text_parts = []
tool_calls = []
for item in response:
parts = item.parts if isinstance(item, MessageItem) else [item]
for part in parts:
if isinstance(part, TextItem):
text_parts.append(part.text)
elif isinstance(part, ToolCallItem):
tool_calls.append((part.call_id, part.name, part.arguments))
return "".join(text_parts), tool_calls
def process_tool_calls(session, answer, pending_calls):
"""Handle tool calls until the stateful session produces a final answer."""
while pending_calls:
with Request() as follow_up:
for call_id, function_name, arguments_json in pending_calls:
arguments = json.loads(arguments_json)
print(f" Tool call: {function_name}({arguments})")
result = tool_functions[function_name](**arguments)
follow_up.add_item(
ToolResultItem(call_id=call_id, result=json.dumps(result))
)
with session.process_request(follow_up) as next_response:
answer, pending_calls = response_content(next_response)
return answer
def main():
# Initialize the Foundry Local SDK
config = Configuration(app_name="foundry_local_samples")
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance
# Discover and register execution providers that are not already available.
missing_eps = [ep.name for ep in manager.discover_eps() if not ep.is_registered]
if missing_eps:
manager.download_and_register_eps(missing_eps)
# Select and load a tool-capable model.
model = manager.catalog.get_model("qwen2.5-0.5b")
if model is None:
raise RuntimeError("Model 'qwen2.5-0.5b' was not found in the catalog.")
if not model.is_cached:
model.download(
lambda progress: print(
f"\rDownloading model: {progress:.2f}%", end="", flush=True
)
)
print()
model.load()
print("Model loaded and ready.")
try:
with ChatSession(model) as session:
session.set_options(
RequestOptions(
search=SearchOptions(temperature=0.0, max_output_tokens=512)
)
)
for tool in tools:
session.add_tool_definition(
tool["name"],
tool["description"],
json.dumps(tool["parameters"]),
)
system_prompt = (
"You are a helpful assistant with access to tools. "
"Use them when needed to answer questions accurately."
)
first_turn = True
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
with Request() as request:
if first_turn:
request.add_item(MessageItem.system(system_prompt))
first_turn = False
request.add_item(MessageItem.user(user_input))
with session.process_request(request) as response:
answer, pending_calls = response_content(response)
answer = process_tool_calls(session, answer, pending_calls)
print(f"Assistant: {answer}\n")
finally:
# Clean up
if model.is_loaded:
model.unload()
manager.close()
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
cargo add foundry-local-sdk
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:let weather_schema = json!({ "type": "object", "properties": { "location": {"type": "string", "description": "The city or location"}, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] }); let calculator_schema = json!({ "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:
let weather_schema = json!({ "type": "object", "properties": { "location": {"type": "string", "description": "The city or location"}, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] }); let calculator_schema = json!({ "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.
let manager = FoundryLocalManager::create(FoundryLocalConfig::new("foundry_local_samples"))?;
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 {ep_name:<30} {percent:5.1}%");
io::stdout().flush().ok();
}
})
.await?;
println!();
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?;
let session = ChatSession::new(&model).await?;
session
.set_options(RequestOptions {
search: SearchOptions {
temperature: Some(0.7),
max_output_tokens: Some(512),
..Default::default()
},
tool_choice: Some(ToolChoice::Auto),
..Default::default()
})
.await?;
session
.add_tool_definition(
ToolDefinition::new("get_weather", weather_schema.to_string())
.with_description("Get the current weather for a location."),
)
.await?;
session
.add_tool_definition(
ToolDefinition::new("calculate", calculator_schema.to_string())
.with_description("Perform a math calculation."),
)
.await?;
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;
}
if input.is_empty() {
continue;
}
let mut items = Vec::new();
if session.turn_count() == 0 {
items.push(Item::system_message(vec![Item::text(
"You are a helpful assistant with access to tools. Use them when needed.",
)]));
}
items.push(Item::user_message(vec![Item::text(input)]));
loop {
let response = session
.process_request(Request::from_items(items).with_options(RequestOptions {
search: SearchOptions {
temperature: Some(0.7),
max_output_tokens: Some(512),
..Default::default()
},
tool_choice: Some(ToolChoice::Auto),
..Default::default()
}))
.await?;
let mut tool_results = Vec::new();
let mut answer = String::new();
for item in &response.items {
if let Some(call) = item.as_tool_call() {
let arguments: Value =
serde_json::from_str(&call.arguments).unwrap_or_else(|_| json!({}));
println!(" Tool call: {}({arguments})", call.name);
let result = execute_tool(&call.name, &arguments);
tool_results.push(Item::tool_result(call.call_id.clone(), result.to_string()));
} else if let Some(message) = item.as_message() {
answer.push_str(&message.text());
} else if let Some(text) = item.as_text() {
answer.push_str(text);
}
}
if tool_results.is_empty() {
println!("Assistant: {answer}\n");
break;
}
items = tool_results;
}
}
drop(session);
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::{
ChatSession, FoundryLocalConfig, FoundryLocalManager, Item, Request, RequestOptions,
SearchOptions, ToolChoice, ToolDefinition,
};
use serde_json::{json, Value};
use std::io::{self, BufRead, Write};
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(|character| "0123456789+-*/(). ".contains(character));
if !is_valid {
return json!({"error": "Invalid expression"});
}
match eval_expression(expression) {
Ok(result) => json!({"expression": expression, "result": result}),
Err(error) => json!({"error": error}),
}
}
_ => 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());
}
chars[start..*pos]
.iter()
.collect::<String>()
.parse::<f64>()
.map_err(|error| error.to_string())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let weather_schema = json!({
"type": "object",
"properties": {
"location": {"type": "string", "description": "The city or location"},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
});
let calculator_schema = json!({
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The math expression to evaluate"
}
},
"required": ["expression"]
});
let manager = FoundryLocalManager::create(FoundryLocalConfig::new("foundry_local_samples"))?;
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 {ep_name:<30} {percent:5.1}%");
io::stdout().flush().ok();
}
})
.await?;
println!();
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?;
let session = ChatSession::new(&model).await?;
session
.set_options(RequestOptions {
search: SearchOptions {
temperature: Some(0.7),
max_output_tokens: Some(512),
..Default::default()
},
tool_choice: Some(ToolChoice::Auto),
..Default::default()
})
.await?;
session
.add_tool_definition(
ToolDefinition::new("get_weather", weather_schema.to_string())
.with_description("Get the current weather for a location."),
)
.await?;
session
.add_tool_definition(
ToolDefinition::new("calculate", calculator_schema.to_string())
.with_description("Perform a math calculation."),
)
.await?;
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;
}
if input.is_empty() {
continue;
}
let mut items = Vec::new();
if session.turn_count() == 0 {
items.push(Item::system_message(vec![Item::text(
"You are a helpful assistant with access to tools. Use them when needed.",
)]));
}
items.push(Item::user_message(vec![Item::text(input)]));
loop {
let response = session
.process_request(Request::from_items(items).with_options(RequestOptions {
search: SearchOptions {
temperature: Some(0.7),
max_output_tokens: Some(512),
..Default::default()
},
tool_choice: Some(ToolChoice::Auto),
..Default::default()
}))
.await?;
let mut tool_results = Vec::new();
let mut answer = String::new();
for item in &response.items {
if let Some(call) = item.as_tool_call() {
let arguments: Value =
serde_json::from_str(&call.arguments).unwrap_or_else(|_| json!({}));
println!(" Tool call: {}({arguments})", call.name);
let result = execute_tool(&call.name, &arguments);
tool_results.push(Item::tool_result(call.call_id.clone(), result.to_string()));
} else if let Some(message) = item.as_message() {
answer.push_str(&message.text());
} else if let Some(text) = item.as_text() {
answer.push_str(text);
}
}
if tool_results.is_empty() {
println!("Assistant: {answer}\n");
break;
}
items = tool_results;
}
}
drop(session);
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.
Repozytorium przykładów
Sklonuj repozytorium GitHub z przykładami Foundry i przejdź do przykładu języka C++:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/cpp/foundry-local/tutorial-tool-calling-assistant
Instalowanie pakietów
Przykłady języka C++ wymagają:
- CMake 3.20 lub nowszy.
- Kompilator C++20.
- Interfejs wiersza polecenia NuGet na Twoim
PATH.
Skonfiguruj i skompiluj przykład:
cmake -S . -B build
cmake --build build --config Release
Podczas konfiguracji przykład przywraca pakiet NuGet Microsoft.AI.Foundry.Local.Runtime w wersji 2.2.1 do build/_packages. Pakiet zawiera nagłówki języka C++ i biblioteki natywne dla Windows, Linux i macOS. Nie potrzebujesz oddzielnego pakietu WinML.
Konfiguracja narzędzia CMake wybiera zasoby środowiska uruchomieniowego dla systemu operacyjnego i architektury oraz kopiuje wymagane biblioteki natywne obok pliku wykonywalnego. W systemach macOS i Linux uruchom plik wykonywalny z build. W systemie Windows uruchom plik .exe z build.
Definiowanie narzędzi
Zdefiniuj dostępne narzędzia i funkcje lokalne, które je implementują:
constexpr const char* kTimeSchema = R"({
"type": "object",
"properties": {
"timezone": {"type": "string", "description": "Requested timezone label"}
},
"required": ["timezone"]
})";
std::string GetLocalTime() {
const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm local_time{};
#ifdef _WIN32
localtime_s(&local_time, &now);
#else
localtime_r(&now, &local_time);
#endif
std::ostringstream output;
output << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S");
return R"({"timezone":"local","time":")" + output.str() + R"("})";
}
Wysyłanie komunikatu wyzwalającego użycie narzędzia
Zainicjuj SDK, załaduj model i wyślij żądanie, które może używać narzędzia:
Configuration config("foundry_local_cpp_tool_assistant");
Manager manager(std::move(config));
auto model = manager.GetCatalog().GetModel("qwen2.5-0.5b");
if (!model) {
throw std::runtime_error("Model 'qwen2.5-0.5b' was not found in the catalog.");
}
if (!model->IsCached()) {
model->Download([](float progress) {
std::cout << "\rDownloading model: " << static_cast<int>(progress) << "%" << std::flush;
return 0;
});
std::cout << "\n";
}
model->Load();
Wykonywanie narzędzia i zwracanie wyników
Wykryj wywołanie narzędzia, wykonaj funkcję lokalną, zwróć jej wynik i poproś model o ostateczną odpowiedź:
Request request{
SystemMessage("Use the available tool when current time is requested."),
UserMessage("What time is it?"),
};
request.SetOptions({
.search = {.temperature = 0.0F, .max_output_tokens = 256, .seed = 42},
.tool_choice = FOUNDRY_LOCAL_TOOL_CHOICE_REQUIRED,
});
Response response = session.ProcessRequest(request);
for (int iteration = 0; iteration < 8; ++iteration) {
bool called_tool = false;
for (const Item& item : response.GetItems()) {
if (item.GetType() == FOUNDRY_LOCAL_ITEM_MESSAGE) {
std::cout << "Assistant: ";
PrintAssistant(item);
continue;
}
if (item.GetType() != FOUNDRY_LOCAL_ITEM_TOOL_CALL) {
continue;
}
const auto call = item.GetToolCall();
if (call.name != "get_current_time") {
throw std::runtime_error("The model requested an unknown tool.");
}
Request tool_result;
tool_result.AddItem(Item::ToolResult(std::string(call.call_id), GetLocalTime()));
tool_result.SetOptions({
.search = {.temperature = 0.0F, .max_output_tokens = 256, .seed = 42},
.tool_choice = FOUNDRY_LOCAL_TOOL_CHOICE_NONE,
});
response = session.ProcessRequest(tool_result);
called_tool = true;
break;
}
if (!called_tool) {
break;
}
}
Kompletny kod
#include <foundry_local/foundry_local_cpp.h>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
using namespace foundry_local;
constexpr const char* kTimeSchema = R"({
"type": "object",
"properties": {
"timezone": {"type": "string", "description": "Requested timezone label"}
},
"required": ["timezone"]
})";
std::string GetLocalTime() {
const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm local_time{};
#ifdef _WIN32
localtime_s(&local_time, &now);
#else
localtime_r(&now, &local_time);
#endif
std::ostringstream output;
output << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S");
return R"({"timezone":"local","time":")" + output.str() + R"("})";
}
void PrintAssistant(const Item& message) {
for (const Item& part : message.GetMessage().parts) {
if (part.GetType() == FOUNDRY_LOCAL_ITEM_TEXT) {
std::cout << part.GetText().text;
}
}
std::cout << "\n";
}
int main() {
try {
Configuration config("foundry_local_cpp_tool_assistant");
Manager manager(std::move(config));
auto model = manager.GetCatalog().GetModel("qwen2.5-0.5b");
if (!model) {
throw std::runtime_error("Model 'qwen2.5-0.5b' was not found in the catalog.");
}
if (!model->IsCached()) {
model->Download([](float progress) {
std::cout << "\rDownloading model: " << static_cast<int>(progress) << "%" << std::flush;
return 0;
});
std::cout << "\n";
}
model->Load();
{
ChatSession session(*model);
session.AddToolDefinition(ToolDefinition{
"get_current_time",
"Get the current local date and time.",
kTimeSchema,
});
Request request{
SystemMessage("Use the available tool when current time is requested."),
UserMessage("What time is it?"),
};
request.SetOptions({
.search = {.temperature = 0.0F, .max_output_tokens = 256, .seed = 42},
.tool_choice = FOUNDRY_LOCAL_TOOL_CHOICE_REQUIRED,
});
Response response = session.ProcessRequest(request);
for (int iteration = 0; iteration < 8; ++iteration) {
bool called_tool = false;
for (const Item& item : response.GetItems()) {
if (item.GetType() == FOUNDRY_LOCAL_ITEM_MESSAGE) {
std::cout << "Assistant: ";
PrintAssistant(item);
continue;
}
if (item.GetType() != FOUNDRY_LOCAL_ITEM_TOOL_CALL) {
continue;
}
const auto call = item.GetToolCall();
if (call.name != "get_current_time") {
throw std::runtime_error("The model requested an unknown tool.");
}
Request tool_result;
tool_result.AddItem(Item::ToolResult(std::string(call.call_id), GetLocalTime()));
tool_result.SetOptions({
.search = {.temperature = 0.0F, .max_output_tokens = 256, .seed = 42},
.tool_choice = FOUNDRY_LOCAL_TOOL_CHOICE_NONE,
});
response = session.ProcessRequest(tool_result);
called_tool = true;
break;
}
if (!called_tool) {
break;
}
}
}
model->Unload();
manager.Shutdown();
} catch (const Error& error) {
std::cerr << "Foundry Local error [" << error.Code() << "]: " << error.what() << "\n";
return 1;
} catch (const std::exception& error) {
std::cerr << "Error: " << error.what() << "\n";
return 1;
}
return 0;
}
Uruchom asystenta:
./build/tutorial-tool-calling-assistant
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.