Last active
June 8, 2026 12:27
-
-
Save artydev/697965fe1d00e2725a505c2cdd941231 to your computer and use it in GitHub Desktop.
lmf2-tools-aot.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.ClientModel; | |
| using System.Text.Json; | |
| using System.Text.Json.Nodes; | |
| using System.Text.Json.Serialization; | |
| using System.Text.RegularExpressions; | |
| using OpenAI; | |
| using OpenAI.Chat; | |
| // create Model for lfm2 cf previous lmf2-tools | |
| // ── Config ─────────────────────────────────────────────────────────────────── | |
| const string BaseUrl = "http://localhost:11434/v1"; | |
| const string Model = "lfm2.5-tools"; | |
| // ── Client ─────────────────────────────────────────────────────────────────── | |
| var client = new OpenAIClient( | |
| credential: new ApiKeyCredential("ollama"), | |
| options: new OpenAIClientOptions { Endpoint = new Uri(BaseUrl) }).GetChatClient(Model); | |
| // ── Tool definitions ───────────────────────────────────────────────────────── | |
| ChatTool[] tools = [ | |
| ChatTool.CreateFunctionTool("get_weather", "Get weather for a city.", BinaryData.FromString("""{"type": "object", "properties": {"location": {"type": "string"}, "unit": {"type": "string"}}, "required": ["location"]}""")), | |
| ChatTool.CreateFunctionTool("calculate", "Evaluate math.", BinaryData.FromString("""{"type": "object", "properties": {"expression": {"type": "string"}}, "required": ["expression"]}""")), | |
| ChatTool.CreateFunctionTool("search_contacts", "Search contacts.", BinaryData.FromString("""{"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}""")), | |
| ]; | |
| // ── Main Loop ──────────────────────────────────────────────────────────────── | |
| Console.WriteLine($"Model: {Model}\nURL: {BaseUrl}"); | |
| string[] testPrompts = ["What's the weather like in Paris?", "What is 10 + 20?", "Find Alice."]; | |
| foreach (var prompt in testPrompts) | |
| { | |
| try { await Run(prompt); } | |
| catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } | |
| } | |
| async Task<string> Run(string userPrompt) | |
| { | |
| Console.WriteLine($"\nUSER: {userPrompt}"); | |
| var options = new ChatCompletionOptions(); | |
| foreach (var tool in tools) options.Tools.Add(tool); | |
| var messages = new List<ChatMessage> { new UserChatMessage(userPrompt) }; | |
| for (int iter = 0; iter < 3; iter++) | |
| { | |
| ChatCompletion response = await client.CompleteChatAsync(messages, options); | |
| if (response.FinishReason == ChatFinishReason.ToolCalls) | |
| { | |
| messages.Add(new AssistantChatMessage(response)); | |
| foreach (var tc in response.ToolCalls) | |
| { | |
| string result = DispatchTool(tc.FunctionName, tc.FunctionArguments.ToString()); | |
| messages.Add(new ToolChatMessage(tc.Id, result)); | |
| } | |
| } | |
| else | |
| { | |
| Console.WriteLine($"ASSISTANT: {response.Content[0].Text}"); | |
| return response.Content[0].Text; | |
| } | |
| } | |
| return ""; | |
| } | |
| // ── Logic Methods ──────────────────────────────────────────────────────────── | |
| string DispatchTool(string name, string argsJson) | |
| { | |
| var args = JsonNode.Parse(argsJson) as JsonObject ?? new JsonObject(); | |
| return name switch | |
| { | |
| "get_weather" => GetWeather(args["location"]?.ToString() ?? "", args["unit"]?.ToString() ?? "celsius"), | |
| "calculate" => Calculate(args["expression"]?.ToString() ?? ""), | |
| "search_contacts" => SearchContacts(args["name"]?.ToString() ?? ""), | |
| _ => "Error" | |
| }; | |
| } | |
| string GetWeather(string location, string unit) | |
| { | |
| var db = new Dictionary<string, (int temp, string cond)>(StringComparer.OrdinalIgnoreCase) { ["paris"] = (18, "Cloudy") }; | |
| // Properly deconstruct the named tuple | |
| var (temp, cond) = db.TryGetValue(location, out var val) ? val : (20, "Unknown"); | |
| return JsonSerializer.Serialize(new WeatherResult(temp, cond, location, unit), AppJsonContext.Default.WeatherResult); | |
| } | |
| string Calculate(string expression) | |
| { | |
| try { return JsonSerializer.Serialize(new CalcResult(expression, MathEval.Evaluate(expression)), AppJsonContext.Default.CalcResult); } | |
| catch { return JsonSerializer.Serialize(new CalcError("Error"), AppJsonContext.Default.CalcError); } | |
| } | |
| string SearchContacts(string name) | |
| { | |
| var results = new Contact[] { new("Alice", "alice@example.com") }.Where(c => c.Name.Contains(name, StringComparison.OrdinalIgnoreCase)).ToArray(); | |
| return JsonSerializer.Serialize(new ContactResult(name, results, results.Length), AppJsonContext.Default.ContactResult); | |
| } | |
| // ── Data & AOT Context ─────────────────────────────────────────────────────── | |
| [JsonSerializable(typeof(WeatherResult))] | |
| [JsonSerializable(typeof(CalcResult))] | |
| [JsonSerializable(typeof(CalcError))] | |
| [JsonSerializable(typeof(ContactResult))] | |
| internal partial class AppJsonContext : JsonSerializerContext { } | |
| internal record WeatherResult(int Temp, string Condition, string Location, string Unit); | |
| internal record CalcResult(string Expression, double Result); | |
| internal record CalcError(string Error); | |
| internal record Contact(string Name, string Email); | |
| internal record ContactResult(string Query, Contact[] Results, int Count); | |
| internal static class MathEval | |
| { | |
| public static double Evaluate(string expr) => new Parser(expr).ParseExpr(); | |
| private class Parser(string s) | |
| { | |
| private readonly string _src = s; // Stores the parameter to satisfy CS9113 | |
| public double ParseExpr() => 10.0; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment