-
Notifications
You must be signed in to change notification settings - Fork 310
Expand file tree
/
Copy pathProgram.cs
More file actions
170 lines (135 loc) · 5.12 KB
/
Program.cs
File metadata and controls
170 lines (135 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// <complete_code>
// <imports>
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;
// </imports>
// <init>
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.
// EP packages include dependencies and may be large.
// Download is only required again if a new version of the EP is released.
// For cross platform builds there is no dynamic EP download and this will return immediately.
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
// </init>
// <model_setup>
// 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.");
// </model_setup>
// 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?" }
];
// <tool_definitions>
// 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"]
}
}
}
];
// </tool_definitions>
// <tool_loop>
// 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",
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();
// </tool_loop>
// <cleanup>
// Tidy up - unload the model
await model.UnloadAsync();
// </cleanup>
// </complete_code>