Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Создайте помощник по искусственному интеллекту, который выходит за рамки беседы— он может вызывать функции для выполнения действий. Помощник решает, когда требуется функция, выполняете ее и передаете результат обратно. Все выполняется локально с помощью локального пакета SDK для Foundry.
В этом руководстве вы узнаете, как:
- Настройка проекта и установка локального SDK Foundry
- Определение инструментов, которые помощник может использовать
- Отправка сообщения, которое активирует использование средства
- Выполните инструмент и передайте результаты модели
- Обработка полного цикла вызова инструмента
- Очистите ресурсы
Необходимые условия
- Компьютер Windows, macOS или Linux с не менее 8 ГБ ОЗУ.
Репозиторий примеров
Полный пример кода для этой статьи можно найти в репозитории Foundry samples на GitHub. Чтобы клонировать репозиторий и перейти к примеру, используйте следующую команду:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/csharp/foundry-local/tutorial-tool-calling
Установка пакетов
Если вы разрабатываете или поставляете на Windows, выберите вкладку Windows. Пакет Windows интегрируется со средой выполнения Windows ML — обеспечивает тот же интерфейс API с более широким спектром аппаратного ускорения.
dotnet add package Microsoft.AI.Foundry.Local.WinML
dotnet add package OpenAI
Примеры C# в репозитории GitHub предварительно настроены. Если вы создаете с нуля, ознакомьтесь со справочником по локальному пакету SDK Foundry для получения дополнительных сведений о настройке проекта C# с помощью Foundry Local.
Определение средств
Вызов функции позволяет модели запросить выполнение вашего кода и возврат результата. Доступные средства определяются как список схем JSON, описывающих имя, назначение и параметры каждой функции.
Откройте
Program.csи добавьте следующие определения инструментов:// --- Tool definitions --- List<ToolDefinition> tools = [ new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "get_weather", Description = "Get the current weather for a location", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "location", new PropertyDefinition() { Type = "string", Description = "The city or location" } }, { "unit", new PropertyDefinition() { Type = "string", Description = "Temperature unit (celsius or fahrenheit)" } } }, Required = ["location"] } } }, new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "calculate", Description = "Perform a math calculation", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "expression", new PropertyDefinition() { Type = "string", Description = "The math expression to evaluate" } } }, Required = ["expression"] } } } ]; // --- Tool implementations --- string ExecuteTool(string functionName, JsonElement arguments) { switch (functionName) { case "get_weather": var location = arguments.GetProperty("location") .GetString() ?? "unknown"; var unit = arguments.TryGetProperty("unit", out var u) ? u.GetString() ?? "celsius" : "celsius"; var temp = unit == "celsius" ? 22 : 72; return JsonSerializer.Serialize(new { location, temperature = temp, unit, condition = "Sunny" }); case "calculate": var expression = arguments.GetProperty("expression") .GetString() ?? ""; try { var result = new System.Data.DataTable() .Compute(expression, null); return JsonSerializer.Serialize(new { expression, result = result?.ToString() }); } catch (Exception ex) { return JsonSerializer.Serialize(new { error = ex.Message }); } default: return JsonSerializer.Serialize(new { error = $"Unknown function: {functionName}" }); } }Каждое определение инструмента включает в
nameсебя,descriptionчто помогает модели решать, когда его использовать, иparametersсхему, описывающую ожидаемые входные данные.Добавьте методы C#, реализующие каждое средство:
// --- Tool definitions --- List<ToolDefinition> tools = [ new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "get_weather", Description = "Get the current weather for a location", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "location", new PropertyDefinition() { Type = "string", Description = "The city or location" } }, { "unit", new PropertyDefinition() { Type = "string", Description = "Temperature unit (celsius or fahrenheit)" } } }, Required = ["location"] } } }, new ToolDefinition { Type = "function", Function = new FunctionDefinition() { Name = "calculate", Description = "Perform a math calculation", Parameters = new PropertyDefinition() { Type = "object", Properties = new Dictionary<string, PropertyDefinition>() { { "expression", new PropertyDefinition() { Type = "string", Description = "The math expression to evaluate" } } }, Required = ["expression"] } } } ]; // --- Tool implementations --- string ExecuteTool(string functionName, JsonElement arguments) { switch (functionName) { case "get_weather": var location = arguments.GetProperty("location") .GetString() ?? "unknown"; var unit = arguments.TryGetProperty("unit", out var u) ? u.GetString() ?? "celsius" : "celsius"; var temp = unit == "celsius" ? 22 : 72; return JsonSerializer.Serialize(new { location, temperature = temp, unit, condition = "Sunny" }); case "calculate": var expression = arguments.GetProperty("expression") .GetString() ?? ""; try { var result = new System.Data.DataTable() .Compute(expression, null); return JsonSerializer.Serialize(new { expression, result = result?.ToString() }); } catch (Exception ex) { return JsonSerializer.Serialize(new { error = ex.Message }); } default: return JsonSerializer.Serialize(new { error = $"Unknown function: {functionName}" }); } }Модель не выполняет эти функции напрямую. Он возвращает запрос вызова средства с именем и аргументами функции, а код выполняет функцию.
Отправка сообщения, которое активирует использование средства
Инициализировать локальный пакет SDK Foundry, загрузить модель и отправить сообщение, на которое модель может ответить, вызвав средство.
// --- Main application ---
var config = new Configuration
{
AppName = "foundry_local_samples",
LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information
};
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(
Microsoft.Extensions.Logging.LogLevel.Information
);
});
var logger = loggerFactory.CreateLogger<Program>();
await FoundryLocalManager.CreateAsync(config, logger);
var mgr = FoundryLocalManager.Instance;
// Download and register all execution providers.
var currentEp = "";
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
{
if (epName != currentEp)
{
if (currentEp != "") Console.WriteLine();
currentEp = epName;
}
Console.Write($"\r {epName.PadRight(30)} {percent,6:F1}%");
});
if (currentEp != "") Console.WriteLine();
var catalog = await mgr.GetCatalogAsync();
var model = await catalog.GetModelAsync("qwen2.5-0.5b")
?? throw new Exception("Model not found");
await model.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f) Console.WriteLine();
});
await model.LoadAsync();
Console.WriteLine("Model loaded and ready.");
var chatClient = await model.GetChatClientAsync();
chatClient.Settings.ToolChoice = ToolChoice.Auto;
var messages = new List<ChatMessage>
{
new ChatMessage
{
Role = "system",
Content = "You are a helpful assistant with access to tools. " +
"Use them when needed to answer questions accurately."
}
};
Когда модель определяет, что требуется средство, ответ содержит ToolCalls вместо обычного текстового сообщения. На следующем шаге показано, как обнаруживать и обрабатывать эти вызовы.
Запустите средство и отобразите результаты
После того как модель отвечает вызовом средства, вы извлекаете имя и аргументы функции, запускаете функцию и отправляете результат обратно.
Console.WriteLine("\nTool-calling assistant ready! Type 'quit' to exit.\n");
while (true)
{
Console.Write("You: ");
var userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput) ||
userInput.Equals("quit", StringComparison.OrdinalIgnoreCase) ||
userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
messages.Add(new ChatMessage
{
Role = "user",
Content = userInput
});
var response = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var choice = response.Choices[0].Message;
if (choice.ToolCalls is { Count: > 0 })
{
messages.Add(choice);
foreach (var toolCall in choice.ToolCalls)
{
var toolArgs = JsonDocument.Parse(
toolCall.FunctionCall.Arguments
).RootElement;
Console.WriteLine(
$" Tool call: {toolCall.FunctionCall.Name}({toolArgs})"
);
var result = ExecuteTool(
toolCall.FunctionCall.Name, toolArgs
);
messages.Add(new ChatMessage
{
Role = "tool",
ToolCallId = toolCall.Id,
Content = result
});
}
var finalResponse = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var answer = finalResponse.Choices[0].Message.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
else
{
var answer = choice.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
}
await model.UnloadAsync();
Console.WriteLine("Model unloaded. Goodbye!");
Основные шаги в цикле вызова средства:
-
Обнаружение вызовов инструментов — проверка
response.Choices[0].Message.ToolCalls. - Выполните функцию — синтаксический анализ аргументов и вызов локальной функции.
-
Верните результат — добавьте сообщение с ролью
toolи соответствиемToolCallId. - Получение окончательного ответа — модель использует результат средства для создания естественного ответа.
Обработка полного цикла вызова инструмента
Ниже приведено полное приложение, которое объединяет определения инструментов, инициализацию пакета SDK и цикл вызова средства в один исполняемый файл.
Замените содержимое Program.cs следующим полным кодом:
using System.Text.Json;
using Microsoft.AI.Foundry.Local;
using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels;
using Betalgo.Ranul.OpenAI.ObjectModels.ResponseModels;
using Betalgo.Ranul.OpenAI.ObjectModels.SharedModels;
using Microsoft.Extensions.Logging;
CancellationToken ct = CancellationToken.None;
// --- Tool definitions ---
List<ToolDefinition> tools =
[
new ToolDefinition
{
Type = "function",
Function = new FunctionDefinition()
{
Name = "get_weather",
Description = "Get the current weather for a location",
Parameters = new PropertyDefinition()
{
Type = "object",
Properties = new Dictionary<string, PropertyDefinition>()
{
{ "location", new PropertyDefinition() { Type = "string", Description = "The city or location" } },
{ "unit", new PropertyDefinition() { Type = "string", Description = "Temperature unit (celsius or fahrenheit)" } }
},
Required = ["location"]
}
}
},
new ToolDefinition
{
Type = "function",
Function = new FunctionDefinition()
{
Name = "calculate",
Description = "Perform a math calculation",
Parameters = new PropertyDefinition()
{
Type = "object",
Properties = new Dictionary<string, PropertyDefinition>()
{
{ "expression", new PropertyDefinition() { Type = "string", Description = "The math expression to evaluate" } }
},
Required = ["expression"]
}
}
}
];
// --- Tool implementations ---
string ExecuteTool(string functionName, JsonElement arguments)
{
switch (functionName)
{
case "get_weather":
var location = arguments.GetProperty("location")
.GetString() ?? "unknown";
var unit = arguments.TryGetProperty("unit", out var u)
? u.GetString() ?? "celsius"
: "celsius";
var temp = unit == "celsius" ? 22 : 72;
return JsonSerializer.Serialize(new
{
location,
temperature = temp,
unit,
condition = "Sunny"
});
case "calculate":
var expression = arguments.GetProperty("expression")
.GetString() ?? "";
try
{
var result = new System.Data.DataTable()
.Compute(expression, null);
return JsonSerializer.Serialize(new
{
expression,
result = result?.ToString()
});
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new
{
error = ex.Message
});
}
default:
return JsonSerializer.Serialize(new
{
error = $"Unknown function: {functionName}"
});
}
}
// --- Main application ---
var config = new Configuration
{
AppName = "foundry_local_samples",
LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information
};
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(
Microsoft.Extensions.Logging.LogLevel.Information
);
});
var logger = loggerFactory.CreateLogger<Program>();
await FoundryLocalManager.CreateAsync(config, logger);
var mgr = FoundryLocalManager.Instance;
// Download and register all execution providers.
var currentEp = "";
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
{
if (epName != currentEp)
{
if (currentEp != "") Console.WriteLine();
currentEp = epName;
}
Console.Write($"\r {epName.PadRight(30)} {percent,6:F1}%");
});
if (currentEp != "") Console.WriteLine();
var catalog = await mgr.GetCatalogAsync();
var model = await catalog.GetModelAsync("qwen2.5-0.5b")
?? throw new Exception("Model not found");
await model.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f) Console.WriteLine();
});
await model.LoadAsync();
Console.WriteLine("Model loaded and ready.");
var chatClient = await model.GetChatClientAsync();
chatClient.Settings.ToolChoice = ToolChoice.Auto;
var messages = new List<ChatMessage>
{
new ChatMessage
{
Role = "system",
Content = "You are a helpful assistant with access to tools. " +
"Use them when needed to answer questions accurately."
}
};
Console.WriteLine("\nTool-calling assistant ready! Type 'quit' to exit.\n");
while (true)
{
Console.Write("You: ");
var userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput) ||
userInput.Equals("quit", StringComparison.OrdinalIgnoreCase) ||
userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
messages.Add(new ChatMessage
{
Role = "user",
Content = userInput
});
var response = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var choice = response.Choices[0].Message;
if (choice.ToolCalls is { Count: > 0 })
{
messages.Add(choice);
foreach (var toolCall in choice.ToolCalls)
{
var toolArgs = JsonDocument.Parse(
toolCall.FunctionCall.Arguments
).RootElement;
Console.WriteLine(
$" Tool call: {toolCall.FunctionCall.Name}({toolArgs})"
);
var result = ExecuteTool(
toolCall.FunctionCall.Name, toolArgs
);
messages.Add(new ChatMessage
{
Role = "tool",
ToolCallId = toolCall.Id,
Content = result
});
}
var finalResponse = await chatClient.CompleteChatAsync(
messages, tools, ct
);
var answer = finalResponse.Choices[0].Message.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
else
{
var answer = choice.Content ?? "";
messages.Add(new ChatMessage
{
Role = "assistant",
Content = answer
});
Console.WriteLine($"Assistant: {answer}\n");
}
}
await model.UnloadAsync();
Console.WriteLine("Model unloaded. Goodbye!");
Запустите помощник по вызову инструментов:
dotnet run
Выходные данные похожи на следующие:
Downloading model: 100.00%
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({"location":"current location"})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({"expression":"245 * 38"})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Модель решает, когда следует вызывать средство на основе сообщения пользователя. Для вопроса о погоде он вызывает get_weather, для математики он вызывает calculate, и для общих вопросов он отвечает напрямую без вызовов инструментов.
Репозиторий примеров
Полный исходный код примера для этой статьи доступен в репозитории GitHub foundry-samples. Чтобы клонировать репозиторий и перейти к образцу, используйте:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/javascript/foundry-local/tutorial-tool-calling
Установка пакетов
Если вы разрабатываете или поставляете на Windows, выберите вкладку Windows. Пакет Windows интегрируется со средой выполнения Windows ML — обеспечивает тот же интерфейс API с более широким спектром аппаратного ускорения.
npm install foundry-local-sdk-winml openai
Определение средств
Вызов функции позволяет модели запросить выполнение вашего кода и возврат результата. Доступные средства определяются как список схем JSON, описывающих имя, назначение и параметры каждой функции.
Создайте файл с именем
index.js.Добавьте следующие определения инструментов:
// --- Tool definitions --- const tools = [ { type: 'function', function: { name: 'get_weather', description: 'Get the current weather for a location', parameters: { type: 'object', properties: { location: { type: 'string', description: 'The city or location' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: 'Temperature unit' } }, required: ['location'] } } }, { type: 'function', function: { name: 'calculate', description: 'Perform a math calculation', parameters: { type: 'object', properties: { expression: { type: 'string', description: 'The math expression to evaluate' } }, required: ['expression'] } } } ]; // --- Tool implementations --- function getWeather(location, unit = 'celsius') { return { location, temperature: unit === 'celsius' ? 22 : 72, unit, condition: 'Sunny' }; } function calculate(expression) { // Input is validated against a strict allowlist of numeric/math characters, // making this safe from code injection in this tutorial context. const allowed = /^[0-9+\-*/(). ]+$/; if (!allowed.test(expression)) { return { error: 'Invalid expression' }; } try { const result = Function( `"use strict"; return (${expression})` )(); return { expression, result }; } catch (err) { return { error: err.message }; } } const toolFunctions = { get_weather: (args) => getWeather(args.location, args.unit), calculate: (args) => calculate(args.expression) };Каждое определение инструмента включает в
nameсебя,descriptionчто помогает модели решать, когда его использовать, иparametersсхему, описывающую ожидаемые входные данные.Добавьте функции JavaScript, реализующие каждое средство:
// --- Tool definitions --- const tools = [ { type: 'function', function: { name: 'get_weather', description: 'Get the current weather for a location', parameters: { type: 'object', properties: { location: { type: 'string', description: 'The city or location' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: 'Temperature unit' } }, required: ['location'] } } }, { type: 'function', function: { name: 'calculate', description: 'Perform a math calculation', parameters: { type: 'object', properties: { expression: { type: 'string', description: 'The math expression to evaluate' } }, required: ['expression'] } } } ]; // --- Tool implementations --- function getWeather(location, unit = 'celsius') { return { location, temperature: unit === 'celsius' ? 22 : 72, unit, condition: 'Sunny' }; } function calculate(expression) { // Input is validated against a strict allowlist of numeric/math characters, // making this safe from code injection in this tutorial context. const allowed = /^[0-9+\-*/(). ]+$/; if (!allowed.test(expression)) { return { error: 'Invalid expression' }; } try { const result = Function( `"use strict"; return (${expression})` )(); return { expression, result }; } catch (err) { return { error: err.message }; } } const toolFunctions = { get_weather: (args) => getWeather(args.location, args.unit), calculate: (args) => calculate(args.expression) };Модель не выполняет эти функции напрямую. Он возвращает запрос вызова средства с именем и аргументами функции, а код выполняет функцию.
Отправка сообщения, которое активирует использование средства
Инициализировать локальный пакет SDK Foundry, загрузить модель и отправить сообщение, на которое модель может ответить, вызвав средство.
// --- 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();
Когда модель определяет, что требуется средство, ответ содержит tool_calls вместо обычного текстового сообщения. На следующем шаге показано, как обнаруживать и обрабатывать эти вызовы.
Запустите средство и отобразите результаты
После того как модель отвечает вызовом средства, вы извлекаете имя и аргументы функции, запускаете функцию и отправляете результат обратно.
async function processToolCalls(messages, response, chatClient) {
let choice = response.choices[0]?.message;
while (choice?.tool_calls?.length > 0) {
messages.push(choice);
for (const toolCall of choice.tool_calls) {
const functionName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
console.log(
` Tool call: ${functionName}` +
`(${JSON.stringify(args)})`
);
const result = toolFunctions[functionName](args);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
}
response = await chatClient.completeChat(
messages, tools
);
choice = response.choices[0]?.message;
}
return choice?.content ?? '';
}
Основные шаги в цикле вызова средства:
-
Обнаружение вызовов инструментов — проверка
response.choices[0]?.message?.tool_calls. - Выполните функцию — синтаксический анализ аргументов и вызов локальной функции.
-
Верните результат — добавьте сообщение с ролью
toolи соответствиемtool_call_id. - Получение окончательного ответа — модель использует результат средства для создания естественного ответа.
Обработка полного цикла вызова инструмента
Ниже приведено полное приложение, которое объединяет определения инструментов, инициализацию пакета SDK и цикл вызова средства в один исполняемый файл.
Создайте файл с именем index.js и добавьте следующий полный код:
import { FoundryLocalManager } from 'foundry-local-sdk';
import * as readline from 'readline';
// --- Tool definitions ---
const tools = [
{
type: 'function',
function: {
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city or location'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature unit'
}
},
required: ['location']
}
}
},
{
type: 'function',
function: {
name: 'calculate',
description: 'Perform a math calculation',
parameters: {
type: 'object',
properties: {
expression: {
type: 'string',
description:
'The math expression to evaluate'
}
},
required: ['expression']
}
}
}
];
// --- Tool implementations ---
function getWeather(location, unit = 'celsius') {
return {
location,
temperature: unit === 'celsius' ? 22 : 72,
unit,
condition: 'Sunny'
};
}
function calculate(expression) {
// Input is validated against a strict allowlist of numeric/math characters,
// making this safe from code injection in this tutorial context.
const allowed = /^[0-9+\-*/(). ]+$/;
if (!allowed.test(expression)) {
return { error: 'Invalid expression' };
}
try {
const result = Function(
`"use strict"; return (${expression})`
)();
return { expression, result };
} catch (err) {
return { error: err.message };
}
}
const toolFunctions = {
get_weather: (args) => getWeather(args.location, args.unit),
calculate: (args) => calculate(args.expression)
};
async function processToolCalls(messages, response, chatClient) {
let choice = response.choices[0]?.message;
while (choice?.tool_calls?.length > 0) {
messages.push(choice);
for (const toolCall of choice.tool_calls) {
const functionName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
console.log(
` Tool call: ${functionName}` +
`(${JSON.stringify(args)})`
);
const result = toolFunctions[functionName](args);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
}
response = await chatClient.completeChat(
messages, tools
);
choice = response.choices[0]?.message;
}
return choice?.content ?? '';
}
// --- Main application ---
const manager = FoundryLocalManager.create({
appName: 'foundry_local_samples',
logLevel: 'info'
});
// Download and register all execution providers.
let currentEp = '';
await manager.downloadAndRegisterEps((epName, percent) => {
if (epName !== currentEp) {
if (currentEp !== '') process.stdout.write('\n');
currentEp = epName;
}
process.stdout.write(`\r ${epName.padEnd(30)} ${percent.toFixed(1).padStart(5)}%`);
});
if (currentEp !== '') process.stdout.write('\n');
const model = await manager.catalog.getModel('qwen2.5-0.5b');
await model.download((progress) => {
process.stdout.write(
`\rDownloading model: ${progress.toFixed(2)}%`
);
});
console.log('\nModel downloaded.');
await model.load();
console.log('Model loaded and ready.');
const chatClient = model.createChatClient();
const messages = [
{
role: 'system',
content:
'You are a helpful assistant with access to tools. ' +
'Use them when needed to answer questions accurately.'
}
];
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = (prompt) =>
new Promise((resolve) => {
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();
Запустите помощник по вызову инструментов:
node index.js
Выходные данные похожи на следующие:
Downloading model: 100.00%
Model downloaded.
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({"location":"current location"})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({"expression":"245 * 38"})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Модель решает, когда следует вызывать средство на основе сообщения пользователя. Для вопроса о погоде он вызывает get_weather, для математики он вызывает calculate, и для общих вопросов он отвечает напрямую без вызовов инструментов.
Репозиторий примеров
Полный исходный код примера для этой статьи доступен в репозитории GitHub foundry-samples. Чтобы клонировать репозиторий и перейти к образцу, используйте:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/python/foundry-local/tutorial-tool-calling
Установка пакетов
Если вы разрабатываете или поставляете на Windows, выберите вкладку Windows. Пакет Windows интегрируется со средой выполнения Windows ML — обеспечивает тот же интерфейс API с более широким спектром аппаратного ускорения.
pip install foundry-local-sdk-winml openai
Определение средств
Вызов функции позволяет модели запросить выполнение вашего кода и возврат результата. Доступные средства определяются как список схем JSON, описывающих имя, назначение и параметры каждой функции.
Создайте файл с именем main.py и добавьте следующие определения инструментов:
# --- Tool definitions ---
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city or location",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": ("The math expression to evaluate"),
}
},
"required": ["expression"],
},
},
},
]
# --- Tool implementations ---
def get_weather(location, unit="celsius"):
"""Simulate a weather lookup."""
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "Sunny",
}
def calculate(expression):
"""Evaluate a math expression safely."""
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression)
return {"expression": expression, "result": result}
except Exception as e:
return {"error": str(e)}
tool_functions = {"get_weather": get_weather, "calculate": calculate}
Каждое определение инструмента включает в name себя, description что помогает модели решать, когда его использовать, и parameters схему, описывающую ожидаемые входные данные.
Затем добавьте функции Python, реализующие каждое средство:
# --- Tool definitions ---
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city or location",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": ("The math expression to evaluate"),
}
},
"required": ["expression"],
},
},
},
]
# --- Tool implementations ---
def get_weather(location, unit="celsius"):
"""Simulate a weather lookup."""
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "Sunny",
}
def calculate(expression):
"""Evaluate a math expression safely."""
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression)
return {"expression": expression, "result": result}
except Exception as e:
return {"error": str(e)}
tool_functions = {"get_weather": get_weather, "calculate": calculate}
Модель не выполняет эти функции напрямую. Он возвращает запрос вызова средства с именем и аргументами функции, а код выполняет функцию.
Отправка сообщения, которое активирует использование средства
Инициализировать локальный пакет SDK Foundry, загрузить модель и отправить сообщение, на которое модель может ответить, вызвав средство.
def main():
# Initialize the Foundry Local SDK
config = Configuration(app_name="foundry_local_samples")
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance
# Download and register all execution providers.
current_ep = ""
def ep_progress(ep_name: str, percent: float):
nonlocal current_ep
if ep_name != current_ep:
if current_ep:
print()
current_ep = ep_name
print(f"\r {ep_name:<30} {percent:5.1f}%", end="", flush=True)
manager.download_and_register_eps(progress_callback=ep_progress)
if current_ep:
print()
# Select and load a model
model = manager.catalog.get_model("qwen2.5-0.5b")
model.download(
lambda progress: print(
f"\rDownloading model: {progress:.2f}%", end="", flush=True
)
)
print()
model.load()
print("Model loaded and ready.")
# Get a chat client
client = model.get_chat_client()
# Conversation with a system prompt
messages = [
{
"role": "system",
"content": "You are a helpful assistant with access to tools. "
"Use them when needed to answer questions accurately.",
}
]
print("\nTool-calling assistant ready! Type 'quit' to exit.\n")
while True:
user_input = input("You: ")
if user_input.strip().lower() in ("quit", "exit"):
break
messages.append({"role": "user", "content": user_input})
response = client.complete_chat(messages, tools=tools)
answer = process_tool_calls(messages, response, client)
messages.append({"role": "assistant", "content": answer})
print(f"Assistant: {answer}\n")
# Clean up
model.unload()
print("Model unloaded. Goodbye!")
Когда модель определяет, что требуется средство, ответ содержит tool_calls вместо обычного текстового сообщения. На следующем шаге показано, как обнаруживать и обрабатывать эти вызовы.
Запустите средство и отобразите результаты
После того как модель отвечает вызовом средства, вы извлекаете имя и аргументы функции, запускаете функцию и отправляете результат обратно.
def process_tool_calls(messages, response, client):
"""Handle tool calls in a loop until the model produces a final answer."""
choice = response.choices[0].message
while choice.tool_calls:
# Convert the assistant message to a dict for the SDK
assistant_msg = {
"role": "assistant",
"content": choice.content,
"tool_calls": [
{
"id": tc.id,
"type": tc.type,
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in choice.tool_calls
],
}
messages.append(assistant_msg)
for tool_call in choice.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f" Tool call: {function_name}({arguments})")
# Execute the function and add the result
func = tool_functions[function_name]
result = func(**arguments)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
}
)
# Send the updated conversation back
response = client.complete_chat(messages, tools=tools)
choice = response.choices[0].message
return choice.content
Основные шаги в цикле вызова средства:
-
Обнаружение вызовов инструментов — проверка
response.choices[0].message.tool_calls. - Выполните функцию — синтаксический анализ аргументов и вызов локальной функции.
-
Верните результат — добавьте сообщение с ролью
toolи соответствиемtool_call_id. - Получение окончательного ответа — модель использует результат средства для создания естественного ответа.
Обработка полного цикла вызова инструмента
Ниже приведено полное приложение, которое объединяет определения инструментов, инициализацию пакета SDK и цикл вызова средства в один исполняемый файл.
Создайте файл с именем main.py и добавьте следующий полный код:
import json
from foundry_local_sdk import Configuration, FoundryLocalManager
# --- Tool definitions ---
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city or location",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": ("The math expression to evaluate"),
}
},
"required": ["expression"],
},
},
},
]
# --- Tool implementations ---
def get_weather(location, unit="celsius"):
"""Simulate a weather lookup."""
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "Sunny",
}
def calculate(expression):
"""Evaluate a math expression safely."""
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return {"error": "Invalid expression"}
try:
result = eval(expression)
return {"expression": expression, "result": result}
except Exception as e:
return {"error": str(e)}
tool_functions = {"get_weather": get_weather, "calculate": calculate}
def process_tool_calls(messages, response, client):
"""Handle tool calls in a loop until the model produces a final answer."""
choice = response.choices[0].message
while choice.tool_calls:
# Convert the assistant message to a dict for the SDK
assistant_msg = {
"role": "assistant",
"content": choice.content,
"tool_calls": [
{
"id": tc.id,
"type": tc.type,
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in choice.tool_calls
],
}
messages.append(assistant_msg)
for tool_call in choice.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f" Tool call: {function_name}({arguments})")
# Execute the function and add the result
func = tool_functions[function_name]
result = func(**arguments)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
}
)
# Send the updated conversation back
response = client.complete_chat(messages, tools=tools)
choice = response.choices[0].message
return choice.content
def main():
# Initialize the Foundry Local SDK
config = Configuration(app_name="foundry_local_samples")
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance
# Download and register all execution providers.
current_ep = ""
def ep_progress(ep_name: str, percent: float):
nonlocal current_ep
if ep_name != current_ep:
if current_ep:
print()
current_ep = ep_name
print(f"\r {ep_name:<30} {percent:5.1f}%", end="", flush=True)
manager.download_and_register_eps(progress_callback=ep_progress)
if current_ep:
print()
# Select and load a model
model = manager.catalog.get_model("qwen2.5-0.5b")
model.download(
lambda progress: print(
f"\rDownloading model: {progress:.2f}%", end="", flush=True
)
)
print()
model.load()
print("Model loaded and ready.")
# Get a chat client
client = model.get_chat_client()
# Conversation with a system prompt
messages = [
{
"role": "system",
"content": "You are a helpful assistant with access to tools. "
"Use them when needed to answer questions accurately.",
}
]
print("\nTool-calling assistant ready! Type 'quit' to exit.\n")
while True:
user_input = input("You: ")
if user_input.strip().lower() in ("quit", "exit"):
break
messages.append({"role": "user", "content": user_input})
response = client.complete_chat(messages, tools=tools)
answer = process_tool_calls(messages, response, client)
messages.append({"role": "assistant", "content": answer})
print(f"Assistant: {answer}\n")
# Clean up
model.unload()
print("Model unloaded. Goodbye!")
if __name__ == "__main__":
main()
Запустите помощник по вызову инструментов:
python main.py
Выходные данные похожи на следующие:
Downloading model: 100.00%
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({'location': 'current location'})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({'expression': '245 * 38'})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Модель решает, когда следует вызывать средство на основе сообщения пользователя. Для вопроса о погоде он вызывает get_weather, для математики он вызывает calculate, и для общих вопросов он отвечает напрямую без вызовов инструментов.
Репозиторий примеров
Полный исходный код примера для этой статьи доступен в репозитории GitHub foundry-samples. Чтобы клонировать репозиторий и перейти к образцу, используйте:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/rust/foundry-local/tutorial-tool-calling
Установка пакетов
Если вы разрабатываете или поставляете на Windows, выберите вкладку Windows. Пакет Windows интегрируется со средой выполнения Windows ML — обеспечивает тот же интерфейс API с более широким спектром аппаратного ускорения.
cargo add foundry-local-sdk --features winml
cargo add tokio --features full
cargo add tokio-stream anyhow
Определение средств
Вызов функции позволяет модели запросить выполнение вашего кода и возврат результата. Доступные средства определяются как список схем JSON, описывающих имя, назначение и параметры каждой функции.
serde_jsonДобавьте зависимость для обработки JSON:cargo add serde_jsonОткройте
src/main.rsи добавьте следующие определения инструментов:// --- Tool definitions --- let tools: Vec<ChatCompletionTools> = serde_json::from_value(json!([ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city or location" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] } } }, { "type": "function", "function": { "name": "calculate", "description": "Perform a math calculation", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "The math expression to evaluate" } }, "required": ["expression"] } } } ]))?;Каждое определение инструмента включает в
nameсебя,descriptionчто помогает модели решать, когда его использовать, иparametersсхему, описывающую ожидаемые входные данные.Добавьте функции Rust, реализующие каждое средство:
// --- Tool definitions --- let tools: Vec<ChatCompletionTools> = serde_json::from_value(json!([ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city or location" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] } } }, { "type": "function", "function": { "name": "calculate", "description": "Perform a math calculation", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "The math expression to evaluate" } }, "required": ["expression"] } } } ]))?;Модель не выполняет эти функции напрямую. Он возвращает запрос вызова средства с именем и аргументами функции, а код выполняет функцию.
Отправка сообщения, которое активирует использование средства
Инициализировать локальный пакет SDK Foundry, загрузить модель и отправить сообщение, на которое модель может ответить, вызвав средство.
// 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(),
];
Когда модель определяет, что требуется средство, ответ содержит tool_calls вместо обычного текстового сообщения. На следующем шаге показано, как обнаруживать и обрабатывать эти вызовы.
Запустите средство и отобразите результаты
После того как модель отвечает вызовом средства, вы извлекаете имя и аргументы функции, запускаете функцию и отправляете результат обратно.
println!(
"\nTool-calling assistant ready! Type 'quit' to exit.\n"
);
let stdin = io::stdin();
loop {
print!("You: ");
io::stdout().flush()?;
let mut input = String::new();
stdin.lock().read_line(&mut input)?;
let input = input.trim();
if input.eq_ignore_ascii_case("quit")
|| input.eq_ignore_ascii_case("exit")
{
break;
}
messages.push(
ChatCompletionRequestUserMessage::from(input).into(),
);
let mut response = client
.complete_chat(&messages, Some(&tools))
.await?;
// Process tool calls in a loop
while response.choices[0].message.tool_calls.is_some() {
let tool_calls = response.choices[0]
.message
.tool_calls
.as_ref()
.unwrap();
// Append the assistant's tool_calls message via JSON
let assistant_msg: ChatCompletionRequestMessage =
serde_json::from_value(json!({
"role": "assistant",
"content": null,
"tool_calls": tool_calls,
}))?;
messages.push(assistant_msg);
for tc_enum in tool_calls {
let tool_call = match tc_enum {
ChatCompletionMessageToolCalls::Function(
tc,
) => tc,
_ => continue,
};
let function_name =
&tool_call.function.name;
let arguments: Value =
serde_json::from_str(
&tool_call.function.arguments,
)?;
println!(
" Tool call: {}({})",
function_name, arguments
);
let result =
execute_tool(function_name, &arguments);
messages.push(
ChatCompletionRequestToolMessage {
content: result.to_string().into(),
tool_call_id: tool_call.id.clone(),
}
.into(),
);
}
response = client
.complete_chat(&messages, Some(&tools))
.await?;
}
let answer = response.choices[0]
.message
.content
.as_deref()
.unwrap_or("");
let assistant_msg: ChatCompletionRequestMessage =
serde_json::from_value(json!({
"role": "assistant",
"content": answer,
}))?;
messages.push(assistant_msg);
println!("Assistant: {}\n", answer);
}
// Clean up
model.unload().await?;
println!("Model unloaded. Goodbye!");
Основные шаги в цикле вызова средства:
-
Обнаружение вызовов инструментов — проверка
response.choices[0].message.tool_calls. - Выполните функцию — синтаксический анализ аргументов и вызов локальной функции.
-
Верните результат — добавьте сообщение с ролью
toolи идентификатором вызова соответствующего инструмента. - Получение окончательного ответа — модель использует результат средства для создания естественного ответа.
Обработка полного цикла вызова инструмента
Ниже приведено полное приложение, которое объединяет определения инструментов, инициализацию пакета SDK и цикл вызова средства в один исполняемый файл.
Замените содержимое src/main.rs следующим полным кодом:
use foundry_local_sdk::{
ChatCompletionRequestMessage,
ChatCompletionRequestSystemMessage,
ChatCompletionRequestToolMessage,
ChatCompletionRequestUserMessage,
ChatCompletionMessageToolCalls,
ChatCompletionTools, ChatToolChoice,
FoundryLocalConfig, FoundryLocalManager,
};
use serde_json::{json, Value};
use std::io::{self, BufRead, Write};
// --- Tool implementations ---
fn execute_tool(
name: &str,
arguments: &Value,
) -> Value {
match name {
"get_weather" => {
let location = arguments["location"]
.as_str()
.unwrap_or("unknown");
let unit = arguments["unit"]
.as_str()
.unwrap_or("celsius");
let temp = if unit == "celsius" { 22 } else { 72 };
json!({
"location": location,
"temperature": temp,
"unit": unit,
"condition": "Sunny"
})
}
"calculate" => {
let expression = arguments["expression"]
.as_str()
.unwrap_or("");
let is_valid = expression
.chars()
.all(|c| "0123456789+-*/(). ".contains(c));
if !is_valid {
return json!({"error": "Invalid expression"});
}
match eval_expression(expression) {
Ok(result) => json!({
"expression": expression,
"result": result
}),
Err(e) => json!({"error": e}),
}
}
_ => json!({"error": format!("Unknown function: {}", name)}),
}
}
fn eval_expression(expr: &str) -> Result<f64, String> {
let expr = expr.replace(' ', "");
let chars: Vec<char> = expr.chars().collect();
let mut pos = 0;
let result = parse_add(&chars, &mut pos)?;
if pos < chars.len() {
return Err("Unexpected character".to_string());
}
Ok(result)
}
fn parse_add(
chars: &[char],
pos: &mut usize,
) -> Result<f64, String> {
let mut result = parse_mul(chars, pos)?;
while *pos < chars.len()
&& (chars[*pos] == '+' || chars[*pos] == '-')
{
let op = chars[*pos];
*pos += 1;
let right = parse_mul(chars, pos)?;
result = if op == '+' {
result + right
} else {
result - right
};
}
Ok(result)
}
fn parse_mul(
chars: &[char],
pos: &mut usize,
) -> Result<f64, String> {
let mut result = parse_atom(chars, pos)?;
while *pos < chars.len()
&& (chars[*pos] == '*' || chars[*pos] == '/')
{
let op = chars[*pos];
*pos += 1;
let right = parse_atom(chars, pos)?;
result = if op == '*' {
result * right
} else {
result / right
};
}
Ok(result)
}
fn parse_atom(
chars: &[char],
pos: &mut usize,
) -> Result<f64, String> {
if *pos < chars.len() && chars[*pos] == '(' {
*pos += 1;
let result = parse_add(chars, pos)?;
if *pos < chars.len() && chars[*pos] == ')' {
*pos += 1;
}
return Ok(result);
}
let start = *pos;
while *pos < chars.len()
&& (chars[*pos].is_ascii_digit() || chars[*pos] == '.')
{
*pos += 1;
}
if start == *pos {
return Err("Expected number".to_string());
}
let num_str: String = chars[start..*pos].iter().collect();
num_str.parse::<f64>().map_err(|e| e.to_string())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// --- Tool definitions ---
let tools: Vec<ChatCompletionTools> = serde_json::from_value(json!([
{
"type": "function",
"function": {
"name": "get_weather",
"description":
"Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description":
"The city or location"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform a math calculation",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description":
"The math expression to evaluate"
}
},
"required": ["expression"]
}
}
}
]))?;
// Initialize the Foundry Local SDK
let manager = FoundryLocalManager::create(
FoundryLocalConfig::new("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(())
}
Запустите помощник по вызову инструментов:
cargo run
Выходные данные похожи на следующие:
Downloading model: 100.00%
Model loaded and ready.
Tool-calling assistant ready! Type 'quit' to exit.
You: What's the weather like today?
Tool call: get_weather({"location":"current location"})
Assistant: The weather today is sunny with a temperature of 22°C.
You: What is 245 * 38?
Tool call: calculate({"expression":"245 * 38"})
Assistant: 245 multiplied by 38 equals 9,310.
You: quit
Model unloaded. Goodbye!
Модель решает, когда следует вызывать средство на основе сообщения пользователя. Для вопроса о погоде он вызывает get_weather, для математики он вызывает calculate, и для общих вопросов он отвечает напрямую без вызовов инструментов.
Очистите ресурсы
Вес модели остается в локальном кэше после выгрузки модели. Это означает, что при следующем запуске приложения шаг загрузки пропускается, а модель загружается быстрее. Дополнительная очистка не требуется, если вы не хотите освободить место на диске.