Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Foundry Local can make use of tool calling, a technique where you prompt the model with definitions of available tools that together with a text prompt, allow the model to work out which tools should be called and with what input data. The application then calls those tools and adds the results to a subsequent model prompt to answer the user's query.
The tools can perform functions that the model doesn't have access to, such as getting the current weather, or reading files on the local file system, or accessing a user's address book (providing the application has permission to do so).
This guide shows you how to use this feature of Foundry Local.
Models that support tool calling
Using the Foundry Local CLI, you can run the foundry model list command to see which models support tool calling.
In the Task column, you can see that the tools task indicates that tool calling is supported.
Prerequisites
- .NET 8.0 SDK or later installed.
Samples repository
The complete sample code for this article is available in the foundry-samples GitHub repository. To clone the repository and navigate to the sample use:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/csharp/foundry-local/tool-calling-foundry-local-sdk
Install packages
If you're developing or shipping on Windows, select the Windows tab. The Windows package integrates with the Windows ML runtime — it provides the same API surface area with a wider breadth of hardware acceleration.
dotnet add package Microsoft.AI.Foundry.Local.WinML
dotnet add package OpenAI
The C# samples in the GitHub repository are preconfigured projects. If you're building from scratch, you should read the Foundry Local SDK reference for more details on how to set up your C# project with Foundry Local.
Understanding tool choice settings
The tool choice parameter controls whether and how the model invokes the tools you provide. Tool choice is sent as part of the chat completion request alongside your tool definitions.
Different models have different capabilities when it comes to tool calling, but in general you can expect the following behavior for each option:
| Option | Value | Behavior | Reliability |
|---|---|---|---|
| Auto | "auto" |
The model decides whether to call a tool or respond directly, based on the user's message and the available tool definitions. | Reliable across all tool-calling models |
| None | "none" |
The model won't call any tools, even if tools are provided in the request. | Reliable across all tool-calling models |
| Required | "required" |
The model must call at least one tool. | Best-effort |
| Specific function | {"type": "function", "function": {"name": "my_function"}} |
The model must call the specified function. | Best-effort |
Use native chat completions with tool calling
Copy and paste the following code into a C# file named Program.cs:
using Microsoft.AI.Foundry.Local;
using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels;
using Betalgo.Ranul.OpenAI.ObjectModels.ResponseModels;
using Betalgo.Ranul.OpenAI.ObjectModels.SharedModels;
using System.Text.Json;
CancellationToken ct = new CancellationToken();
var config = new Configuration
{
AppName = "foundry_local_samples",
LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information
};
// Initialize the singleton instance.
await FoundryLocalManager.CreateAsync(config, Utils.GetAppLogger());
var mgr = FoundryLocalManager.Instance;
// Ensure that any Execution Provider (EP) downloads run and are completed.
// 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();
// Get the model catalog
var catalog = await mgr.GetCatalogAsync();
// Get a model using an alias.
var model = await catalog.GetModelAsync("qwen2.5-0.5b") ?? throw new Exception("Model not found");
// Download the model (the method skips download if already cached)
await model.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f)
{
Console.WriteLine();
}
});
// Load the model
Console.Write($"Loading model {model.Id}...");
await model.LoadAsync();
Console.WriteLine("done.");
// Get a chat client
var chatClient = await model.GetChatClientAsync();
chatClient.Settings.ToolChoice = ToolChoice.Required; // Force the model to make a tool call
// Prepare messages
List<ChatMessage> messages =
[
new ChatMessage { Role = "system", Content = "You are a helpful AI assistant. If necessary, you can use any provided tools to answer the question." },
new ChatMessage { Role = "user", Content = "What is the answer to 7 multiplied by 6?" }
];
// Prepare tools
List<ToolDefinition> tools =
[
new ToolDefinition
{
Type = "function",
Function = new FunctionDefinition()
{
Name = "multiply_numbers",
Description = "A tool for multiplying two numbers.",
Parameters = new PropertyDefinition()
{
Type = "object",
Properties = new Dictionary<string, PropertyDefinition>()
{
{ "first", new PropertyDefinition() { Type = "integer", Description = "The first number in the operation" } },
{ "second", new PropertyDefinition() { Type = "integer", Description = "The second number in the operation" } }
},
Required = ["first", "second"]
}
}
}
];
// Get a streaming chat completion response
var toolCallResponses = new List<ChatCompletionCreateResponse>();
Console.WriteLine("Chat completion response:");
var streamingResponse = chatClient.CompleteChatStreamingAsync(messages, tools, ct);
await foreach (var chunk in streamingResponse)
{
var content = chunk.Choices[0].Message.Content;
Console.Write(content);
Console.Out.Flush();
if (chunk.Choices[0].FinishReason == "tool_calls")
{
toolCallResponses.Add(chunk);
}
}
Console.WriteLine();
// Invoke tools called and append responses to the chat
foreach (var chunk in toolCallResponses)
{
var call = chunk?.Choices[0].Message.ToolCalls?[0].FunctionCall;
if (call?.Name == "multiply_numbers")
{
var arguments = JsonSerializer.Deserialize<Dictionary<string, int>>(call.Arguments!)!;
var first = arguments["first"];
var second = arguments["second"];
Console.WriteLine($"\nInvoking tool: {call?.Name} with arguments {first} and {second}");
var result = Utils.MultiplyNumbers(first, second);
Console.WriteLine($"Tool response: {result.ToString()}");
var response = new ChatMessage
{
Role = "tool",
ToolCallId = chunk!.Choices[0].Message.ToolCalls![0].Id,
Content = result.ToString(),
};
messages.Add(response);
}
}
Console.WriteLine("\nTool calls completed. Prompting model to continue conversation...\n");
// Prompt the model to continue the conversation after the tool call
messages.Add(new ChatMessage { Role = "system", Content = "Respond only with the answer generated by the tool." });
// Set tool calling back to auto so that the model can decide whether to call
// the tool again or continue the conversation based on the new user prompt
chatClient.Settings.ToolChoice = ToolChoice.Auto;
// Run the next turn of the conversation
Console.WriteLine("Chat completion response:");
streamingResponse = chatClient.CompleteChatStreamingAsync(messages, tools, ct);
await foreach (var chunk in streamingResponse)
{
var content = chunk.Choices[0].Message.Content;
Console.Write(content);
Console.Out.Flush();
}
Console.WriteLine();
// Tidy up - unload the model
await model.UnloadAsync();
Run the native chat completions example
dotnet run
Use OpenAI Web server for tool calling
If you prefer to use the OpenAI SDKs to call the Foundry Local web service, use the following example that demonstrates how to handle tool calling in that scenario.
Tip
Use options.ToolChoice = ChatToolChoice.CreateAutoChoice(); (the default) for the most reliable behavior. Write clear tool names and descriptions so the model calls the correct tool on its own.
using Microsoft.AI.Foundry.Local;
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
using System.Text.Json;
var config = new Configuration
{
AppName = "foundry_local_samples",
LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information,
Web = new Configuration.WebService
{
Urls = "http://127.0.0.1:52495"
}
};
// Initialize the singleton instance.
await FoundryLocalManager.CreateAsync(config, Utils.GetAppLogger());
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();
// Get the model catalog
var catalog = await mgr.GetCatalogAsync();
// Get a model using an alias
var model = await catalog.GetModelAsync("qwen2.5-0.5b") ?? throw new Exception("Model not found");
// Download the model (the method skips download if already cached)
await model.DownloadAsync(progress =>
{
Console.Write($"\rDownloading model: {progress:F2}%");
if (progress >= 100f)
{
Console.WriteLine();
}
});
// Load the model
Console.Write($"Loading model {model.Id}...");
await model.LoadAsync();
Console.WriteLine("done.");
// Start the web service
Console.Write($"Starting web service on {config.Web.Urls}...");
await mgr.StartWebServiceAsync();
Console.WriteLine("done.");
// <<<<<< OPEN AI SDK USAGE >>>>>>
// Use the OpenAI SDK to call the local Foundry web service
ApiKeyCredential key = new ApiKeyCredential("notneeded");
OpenAIClient client = new OpenAIClient(key, new OpenAIClientOptions
{
Endpoint = new Uri(config.Web.Urls + "/v1"),
});
// Get chat client
var chatClient = client.GetChatClient(model.Id);
// Prepare messages
var messages = new List<ChatMessage>
{
ChatMessage.CreateSystemMessage("You are a helpful AI assistant. If necessary, you can use any provided tools to answer the question."),
ChatMessage.CreateUserMessage("What is the answer to 7 multiplied by 6?")
};
// Prepare tools
var tools = new List<ChatTool>
{
ChatTool.CreateFunctionTool(
functionName: "multiply_numbers",
functionDescription: "A tool for multiplying two numbers.",
functionParameters: BinaryData.FromString("""
{
"type": "object",
"properties": {
"first": { "type": "number", "description": "The first number in the operation" },
"second": { "type": "number", "description": "The second number in the operation" }
},
"required": ["first", "second"]
}
""")
)
};
// Prepare chat completion options
var options = new ChatCompletionOptions
{
ToolChoice = ChatToolChoice.CreateRequiredChoice() // Force the model to make a tool call
};
foreach (var tool in tools)
{
options.Tools.Add(tool);
}
// Get a streaming chat completion response
var completionUpdates = chatClient.CompleteChatStreaming(messages, options);
var toolCalls = new List<StreamingChatToolCallUpdate>();
Console.Write($"[ASSISTANT]: ");
foreach (var completionUpdate in completionUpdates)
{
if (completionUpdate.ContentUpdate.Count > 0)
{
Console.Write(completionUpdate.ContentUpdate[0].Text);
}
if (completionUpdate.FinishReason == ChatFinishReason.ToolCalls)
{
foreach (var toolCall in completionUpdate.ToolCallUpdates)
{
toolCalls.Add(toolCall);
}
}
}
Console.WriteLine();
// Invoke tools called and append responses to the chat
foreach (var toolCall in toolCalls)
{
if (toolCall.FunctionName == "multiply_numbers")
{
var arguments = JsonDocument.Parse(toolCall.FunctionArgumentsUpdate.ToString()).RootElement;
var first = arguments.GetProperty("first").GetInt32();
var second = arguments.GetProperty("second").GetInt32();
Console.WriteLine($"\nInvoking tool: {toolCall.FunctionName} with arguments {first} and {second}");
var result = Utils.MultiplyNumbers(first, second);
Console.WriteLine($"Tool response: {result.ToString()}");
messages.Add(ChatMessage.CreateToolMessage(toolCallId: "abcd1234", content: result.ToString()));
}
}
Console.WriteLine("\nTool calls completed. Prompting model to continue conversation...\n");
// Prompt the model to continue the conversation after the tool call
messages.Add(ChatMessage.CreateSystemMessage("Respond only with the answer generated by the tool."));
// Set tool calling back to auto so that the model can decide whether to call
// the tool again or continue the conversation based on the new user prompt
options.ToolChoice = ChatToolChoice.CreateAutoChoice();
// Run the next turn of the conversation
Console.WriteLine("Chat completion response:");
completionUpdates = chatClient.CompleteChatStreaming(messages, options);
Console.Write($"[ASSISTANT]: ");
foreach (var completionUpdate in completionUpdates)
{
if (completionUpdate.ContentUpdate.Count > 0)
{
Console.Write(completionUpdate.ContentUpdate[0].Text);
}
}
Console.WriteLine();
// <<<<<< END OPEN AI SDK USAGE >>>>>>
// Tidy up
// Stop the web service and unload model
await mgr.StopWebServiceAsync();
await model.UnloadAsync();
Run the OpenAI web service example
dotnet run
Prerequisites
- Node.js 20 or later installed.
Samples repository
The complete sample code for this article is available in the foundry-samples GitHub repository. To clone the repository and navigate to the sample use:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/javascript/foundry-local/tool-calling-foundry-local
Install packages
If you're developing or shipping on Windows, select the Windows tab. The Windows package integrates with the Windows ML runtime — it provides the same API surface area with a wider breadth of hardware acceleration.
npm install foundry-local-sdk-winml openai
Understanding tool choice settings
The tool choice parameter controls whether and how the model invokes the tools you provide. Tool choice is sent as part of the chat completion request alongside your tool definitions.
Different models have different capabilities when it comes to tool calling, but in general you can expect the following behavior for each option:
| Option | Value | Behavior | Reliability |
|---|---|---|---|
| Auto | "auto" |
The model decides whether to call a tool or respond directly, based on the user's message and the available tool definitions. | Reliable across all tool-calling models |
| None | "none" |
The model doesn't invoke any tools, even if tools are provided in the request. | Reliable across all tool-calling models |
| Required | "required" |
The model must call at least one tool. | Best-effort |
| Specific function | {"type": "function", "function": {"name": "my_function"}} |
The model must call the specified function. | Best-effort |
Use chat completions with tool calling
Copy and paste the following code into a JavaScript file named app.js:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { OpenAI } from "openai";
import { FoundryLocalManager } from "foundry-local-sdk";
// By using an alias, the most suitable model will be downloaded
// to your end-user's device.
// TIP: You can find a list of available models by running the
// following command in your terminal: `foundry model list`.
const alias = "qwen2.5-0.5b";
function multiplyNumbers(first, second) {
return first * second;
}
async function runToolCallingExample() {
let manager = null;
let model = null;
try {
console.log("Initializing Foundry Local SDK...");
manager = FoundryLocalManager.create({
appName: "foundry_local_samples",
serviceEndpoint: "http://localhost:5000",
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 catalog = manager.catalog;
model = await catalog.getModel(alias);
if (!model) {
throw new Error(`Model ${alias} not found`);
}
console.log(`Loading model ${model.id}...`);
await model.download();
await model.load();
console.log('✓ Model loaded');
manager.startWebService();
const endpoint = manager.urls[0];
if (!endpoint) {
throw new Error("Foundry Local web service did not return an endpoint.");
}
const openai = new OpenAI({
baseURL: `${endpoint.replace(/\/$/, "")}/v1`,
apiKey: "local"
});
// Prepare messages
const messages = [
{
role: "system",
content: "You are a helpful AI assistant. If necessary, you can use any provided tools to answer the question."
},
{ role: "user", content: "What is the answer to 7 multiplied by 6?" }
];
// Prepare tools
const tools = [
{
type: "function",
function: {
name: "multiply_numbers",
description: "A tool for multiplying two numbers.",
parameters: {
type: "object",
properties: {
first: {
type: "integer",
description: "The first number in the operation"
},
second: {
type: "integer",
description: "The second number in the operation"
}
},
required: ["first", "second"]
}
}
}
];
// Start the conversation
console.log("Chat completion response:");
const toolCallResponses = [];
const firstStream = await openai.chat.completions.create({
model: model.id,
messages,
tools,
tool_choice: "required",
stream: true
});
for await (const chunk of firstStream) {
const content = chunk.choices?.[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
toolCallResponses.push(chunk);
}
}
console.log();
// Invoke tools called and append responses to the chat
for (const chunk of toolCallResponses) {
const toolCalls = chunk.choices?.[0]?.message?.tool_calls ?? chunk.choices?.[0]?.delta?.tool_calls ?? [];
for (const toolCall of toolCalls) {
if (toolCall.function?.name === "multiply_numbers") {
const args = JSON.parse(toolCall.function.arguments || "{}");
const first = args.first;
const second = args.second;
console.log(`\nInvoking tool: ${toolCall.function.name} with arguments ${first} and ${second}`);
const result = multiplyNumbers(first, second);
console.log(`Tool response: ${result}`);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: result.toString()
});
}
}
}
console.log("\nTool calls completed. Prompting model to continue conversation...\n");
// Prompt the model to continue the conversation after the tool call
messages.push({
role: "system",
content: "Respond only with the answer generated by the tool."
});
// Run the next turn of the conversation
console.log("Chat completion response:");
const secondStream = await openai.chat.completions.create({
model: model.id,
messages,
tools,
tool_choice: "auto",
stream: true
});
for await (const chunk of secondStream) {
const content = chunk.choices?.[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
}
console.log();
} finally {
if (model) {
try {
if (await model.isLoaded()) {
await model.unload();
}
} catch (cleanupError) {
console.warn("Cleanup warning while unloading model:", cleanupError);
}
}
if (manager) {
try {
manager.stopWebService();
} catch (cleanupError) {
console.warn("Cleanup warning while stopping service:", cleanupError);
}
}
}
}
await runToolCallingExample().catch((error) => {
console.error("Error running sample:", error);
process.exitCode = 1;
});
To run the application, execute the following command in your terminal:
node app.js
Prerequisites
- Python 3.11 or later installed.
Samples repository
The complete sample code for this article is available in the foundry-samples GitHub repository. To clone the repository and navigate to the sample use:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/python/foundry-local/tool-calling
Install packages
If you're developing or shipping on Windows, select the Windows tab. The Windows package integrates with the Windows ML runtime — it provides the same API surface area with a wider breadth of hardware acceleration.
pip install foundry-local-sdk-winml openai
Install the OpenAI SDK:
pip install openai
Understanding tool choice settings
The tool choice parameter controls whether and how the model invokes the tools you provide. The parameter is sent as part of the chat completion request alongside your tool definitions.
Different models have different capabilities when it comes to tool calling, but in general you can expect the following behavior for each option:
| Option | Value | Behavior | Reliability |
|---|---|---|---|
| Auto | "auto" |
The model decides whether to call a tool or respond directly, based on the user's message and the available tool definitions. | Reliable across all tool-calling models |
| None | "none" |
The model won't call any tools, even if tools are provided in the request. | Reliable across all tool-calling models |
| Required | "required" |
The model must call at least one tool. | Best-effort |
| Specific function | {"type": "function", "function": {"name": "my_function"}} |
The model must call the specified function. | Best-effort |
Use chat completions with tool calling
Copy and paste the following code into a Python file named app.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.",
},
{
"role": "user",
"content": "What is the weather in Seattle and what is 42 * 17?",
},
]
print("Sending request with tools...")
response = client.complete_chat(messages, tools=tools)
answer = process_tool_calls(messages, response, client)
print(f"\nAssistant: {answer}")
# Clean up
model.unload()
print("Model unloaded.")
if __name__ == "__main__":
main()
To run the application, execute the following command in your terminal:
python app.py
Prerequisites
- Rust and Cargo installed (Rust 1.70.0 or later).
Samples repository
The complete sample code for this article is available in the foundry-samples GitHub repository. To clone the repository and navigate to the sample use:
git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/rust/foundry-local/tool-calling-foundry-local
Install packages
If you're developing or shipping on Windows, select the Windows tab. The Windows package integrates with the Windows ML runtime — it provides the same API surface area with a wider breadth of hardware acceleration.
cargo add foundry-local-sdk --features winml
cargo add tokio --features full
cargo add tokio-stream anyhow
Add extra dependencies for HTTP and JSON:
cargo add anyhow reqwest --features reqwest/json
cargo add serde serde_json --features serde/derive
Understanding tool choice settings
The tool choice parameter controls whether and how the model invokes the tools you provide. Tool choice is sent as part of the chat completion request alongside your tool definitions.
Different models have different capabilities when it comes to tool calling, but in general you can expect the following behavior for each option:
| Option | Value | Behavior | Reliability |
|---|---|---|---|
| Auto | "auto" |
The model decides whether to call a tool or respond directly, based on the user's message and the available tool definitions. | Reliable across all tool-calling models |
| None | "none" |
The model doesn't call any tools, even if tools are provided in the request. | Reliable across all tool-calling models |
| Required | "required" |
The model must call at least one tool. | Best effort (tool call could be ignored by smaller models) |
| Specific function | {"type": "function", "function": {"name": "my_function"}} |
The model must call the specified function. | Best-effort (tool call could be ignored by smaller models) |
Use chat completions with tool calling
Replace the contents of src/main.rs with the following code:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
use std::io::{self, Write};
use serde_json::{json, Value};
use tokio_stream::StreamExt;
use foundry_local_sdk::{
ChatCompletionRequestMessage, ChatCompletionRequestSystemMessage,
ChatCompletionRequestToolMessage, ChatCompletionRequestUserMessage, ChatCompletionTools,
ChatToolChoice, FinishReason, FoundryLocalConfig, FoundryLocalManager,
};
// By using an alias, the most suitable model variant will be downloaded
// to your end-user's device.
const ALIAS: &str = "qwen2.5-0.5b";
/// A simple tool that multiplies two numbers.
fn multiply_numbers(first: f64, second: f64) -> f64 {
first * second
}
/// Dispatch a tool call by name and parsed arguments.
fn invoke_tool(name: &str, args: &Value) -> String {
match name {
"multiply_numbers" => {
let first = args.get("first").and_then(|v| v.as_f64()).unwrap_or(0.0);
let second = args.get("second").and_then(|v| v.as_f64()).unwrap_or(0.0);
let result = multiply_numbers(first, second);
result.to_string()
}
_ => format!("Unknown tool: {name}"),
}
}
/// Accumulated state from a streaming response that contains tool calls.
#[derive(Default)]
struct ToolCallState {
tool_calls: Vec<Value>,
current_tool_id: String,
current_tool_name: String,
current_tool_args: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Tool Calling with Foundry Local");
println!("===============================\n");
// ── 1. Initialise the manager ────────────────────────────────────────
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!();
// ── 2. Load a model──────────────────────────────────────────────────
let model = manager.catalog().get_model(ALIAS).await?;
println!("Model: {} (id: {})", model.alias(), model.id());
if !model.is_cached().await? {
println!("Downloading model...");
model
.download(Some(|progress: f64| {
print!("\r {progress:.1}%");
io::stdout().flush().ok();
}))
.await?;
println!();
}
println!("Loading model...");
model.load().await?;
println!("✓ Model loaded\n");
// ── 3. Create a chat clientwith tool_choice = required ──────────────
let client = model.create_chat_client()
.max_tokens(512)
.tool_choice(ChatToolChoice::Required);
// Define the multiply_numbers tool.
let tools: Vec<ChatCompletionTools> = serde_json::from_value(json!([{
"type": "function",
"function": {
"name": "multiply_numbers",
"description": "A tool for multiplying two numbers.",
"parameters": {
"type": "object",
"properties": {
"first": {
"type": "integer",
"description": "The first number in the operation"
},
"second": {
"type": "integer",
"description": "The second number in the operation"
}
},
"required": ["first", "second"]
}
}
}]))?;
// Prepare the initial conversation.
let mut messages: Vec<ChatCompletionRequestMessage> = vec![
ChatCompletionRequestSystemMessage::from(
"You are a helpful AI assistant. If necessary, you can use any provided tools to answer the question.",
)
.into(),
ChatCompletionRequestUserMessage::from("What is the answer to 7 multiplied by 6?").into(),
];
// ── 4. First streaming call – expect tool_calls ──────────────────────
println!("Chat completion response:");
let mut state = ToolCallState::default();
let mut stream = client
.complete_streaming_chat(&messages, Some(&tools))
.await?;
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
if let Some(choice) = chunk.choices.first() {
// Accumulate streamed content (if any).
if let Some(ref content) = choice.delta.content {
print!("{content}");
io::stdout().flush().ok();
}
// Accumulate tool call fragments.
if let Some(ref tool_calls) = choice.delta.tool_calls {
for tc in tool_calls {
if let Some(ref id) = tc.id {
state.current_tool_id = id.clone();
}
if let Some(ref func) = tc.function {
if let Some(ref name) = func.name {
state.current_tool_name = name.clone();
}
if let Some(ref args) = func.arguments {
state.current_tool_args.push_str(args);
}
}
}
}
// When the model signals finish_reason = ToolCalls, finalise.
if choice.finish_reason == Some(FinishReason::ToolCalls) {
let tc = json!({
"id": state.current_tool_id.clone(),
"type": "function",
"function": {
"name": state.current_tool_name.clone(),
"arguments": state.current_tool_args.clone(),
}
});
state.tool_calls.push(tc);
}
}
}
println!();
// ── 5. Execute the tool(s)and append results ────────────────────────
for tc in &state.tool_calls {
let func = &tc["function"];
let name = func["name"].as_str().unwrap_or_default();
let args_str = func["arguments"].as_str().unwrap_or("{}");
let args: Value = serde_json::from_str(args_str).unwrap_or(json!({}));
println!("\nInvoking tool: {name} with arguments {args}");
let result = invoke_tool(name, &args);
println!("Tool response: {result}");
// Append the assistant's tool_calls message and the tool result.
let assistant_msg: ChatCompletionRequestMessage = serde_json::from_value(json!({
"role": "assistant",
"content": null,
"tool_calls": [tc],
}))?;
messages.push(assistant_msg);
messages.push(
ChatCompletionRequestToolMessage {
content: result.into(),
tool_call_id: tc["id"].as_str().unwrap_or_default().to_string(),
}
.into(),
);
}
// ── 6. Continue the conversation with auto tool_choice ───────────────
println!("\nTool calls completed. Prompting model to continue conversation...\n");
messages.push(
ChatCompletionRequestSystemMessage::from(
"Respond only with the answer generated by the tool.",
)
.into(),
);
let client = client.tool_choice(ChatToolChoice::Auto);
print!("Chat completion response: ");
let mut stream = client
.complete_streaming_chat(&messages, Some(&tools))
.await?;
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
if let Some(choice) = chunk.choices.first() {
if let Some(ref content) = choice.delta.content {
print!("{content}");
io::stdout().flush().ok();
}
}
}
println!("\n");
// ── 7. Clean up──────────────────────────────────────────────────────
println!("Unloading model...");
model.unload().await?;
println!("Done.");
Ok(())
}
To run the application, execute the following command in your terminal:
cargo run