{"repo": "achieveai/lmdotnettools", "files": {"example/LmConfigUsageExample/OpenAiGrokAgenticExample.cs": {"5df9a37e189f520689e30ebef085dd06c50d907c": {"t": 1785745808.2934623, "n": 17722, "h": "362d5ae78d9ce7a6", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Required API keys by model:\");\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools using FunctionRegistry =====\n var registry = new FunctionRegistry();\n\n // get_weather\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n async (args, _, _) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = stringArray[Random.Shared.Next(4)],\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(weather));\n },\n providerName: \"Example\"\n );\n\n // get_time\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(time));\n },\n providerName: \"Example\"\n );\n\n // calculate\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression }));\n }\n catch (Exception ex)\n {\n return ToolHandlerResult.FromError(JsonSerializer.Serialize(new { error = ex.Message }));\n }\n },\n providerName: \"Example\"\n );\n\n // Build middleware and handlers from registry\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\n .WithMessageTransformation()\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\n .WithMiddleware(toolCallMiddleware);\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n var turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = modelId,\n // Temperature = temperature,\n RunId = Guid.NewGuid().ToString(),\n ParentRunId = null,\n ThreadId = Guid.NewGuid().ToString(),\n }\n );\n\n // Collect messages from stream and execute tool calls in parallel as they arrive\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n var pendingToolCalls = new Dictionary>();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time and handle tool calls\n switch (message)\n {\n case ReasoningMessage reasoningMsg:\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\n {\n Console.Write(reasoningMsg.Reasoning);\n _ = textContent.Append(reasoningMsg.Reasoning);\n }\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n _ = textContent.Append(textMsg.Text);\n }\n break;\n\n case ToolCallMessage toolCall:\n // Start executing tool call immediately when we receive it\n _logger.LogInformation(\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\n toolCall.FunctionName,\n toolCall.ToolCallId\n );\n\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n\n default:\n // Ignore other message types\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls and Await Results =====\n if (pendingToolCalls.Count == 0)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Await Tool Results and Add to Conversation =====\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\n\n try\n {\n // Await all pending tool executions\n _ = await Task.WhenAll(pendingToolCalls.Values);\n\n // Collect results and add to conversation\n foreach (var kvp in pendingToolCalls)\n {\n var result = await kvp.Value;\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\n\n // Add each tool result to conversation history\n conversationHistory.Add(result);\n }\n\n _logger.LogInformation(\"Tool execution completed.\");\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Methods =====\n\n /// \n /// Executes a single tool call and returns the result message.\n /// \n private async Task ExecuteToolCallAsync(\n ToolCallMessage toolCall,\n IDictionary handlers\n )\n {\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\n var functionName = toolCall.FunctionName ?? \"unknown\";\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\n\n try\n {\n if (handlers.TryGetValue(functionName, out var handler))\n {\n var ctx = new ToolCallContext { ToolCallId = toolCall.ToolCallId };\n var result = await handler(functionArgs, ctx, CancellationToken.None);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = result.ResultText,\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n else\n {\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "26335e3cc6208c4d2aec959abf40b43d4a60047c": {"t": 1785756728.6472123, "n": 17722, "h": "362d5ae78d9ce7a6", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Required API keys by model:\");\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools using FunctionRegistry =====\n var registry = new FunctionRegistry();\n\n // get_weather\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n async (args, _, _) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = stringArray[Random.Shared.Next(4)],\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(weather));\n },\n providerName: \"Example\"\n );\n\n // get_time\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(time));\n },\n providerName: \"Example\"\n );\n\n // calculate\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression }));\n }\n catch (Exception ex)\n {\n return ToolHandlerResult.FromError(JsonSerializer.Serialize(new { error = ex.Message }));\n }\n },\n providerName: \"Example\"\n );\n\n // Build middleware and handlers from registry\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\n .WithMessageTransformation()\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\n .WithMiddleware(toolCallMiddleware);\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n var turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = modelId,\n // Temperature = temperature,\n RunId = Guid.NewGuid().ToString(),\n ParentRunId = null,\n ThreadId = Guid.NewGuid().ToString(),\n }\n );\n\n // Collect messages from stream and execute tool calls in parallel as they arrive\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n var pendingToolCalls = new Dictionary>();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time and handle tool calls\n switch (message)\n {\n case ReasoningMessage reasoningMsg:\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\n {\n Console.Write(reasoningMsg.Reasoning);\n _ = textContent.Append(reasoningMsg.Reasoning);\n }\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n _ = textContent.Append(textMsg.Text);\n }\n break;\n\n case ToolCallMessage toolCall:\n // Start executing tool call immediately when we receive it\n _logger.LogInformation(\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\n toolCall.FunctionName,\n toolCall.ToolCallId\n );\n\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n\n default:\n // Ignore other message types\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls and Await Results =====\n if (pendingToolCalls.Count == 0)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Await Tool Results and Add to Conversation =====\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\n\n try\n {\n // Await all pending tool executions\n _ = await Task.WhenAll(pendingToolCalls.Values);\n\n // Collect results and add to conversation\n foreach (var kvp in pendingToolCalls)\n {\n var result = await kvp.Value;\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\n\n // Add each tool result to conversation history\n conversationHistory.Add(result);\n }\n\n _logger.LogInformation(\"Tool execution completed.\");\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Methods =====\n\n /// \n /// Executes a single tool call and returns the result message.\n /// \n private async Task ExecuteToolCallAsync(\n ToolCallMessage toolCall,\n IDictionary handlers\n )\n {\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\n var functionName = toolCall.FunctionName ?? \"unknown\";\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\n\n try\n {\n if (handlers.TryGetValue(functionName, out var handler))\n {\n var ctx = new ToolCallContext { ToolCallId = toolCall.ToolCallId };\n var result = await handler(functionArgs, ctx, CancellationToken.None);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = result.ResultText,\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n else\n {\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "56588f18cc14eb718666114e86024ad32c2b7a6d": {"t": 1785756730.2002761, "n": 18173, "h": "77c43f364c259cca", "c": "using System.Text.Json;\r\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\r\nusing AchieveAi.LmDotnetTools.LmCore.Core;\r\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\r\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\r\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nnamespace LmConfigUsageExample;\r\n\r\n/// \r\n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\r\n/// with the new middleware chain architecture.\r\n///\r\n/// Middleware Chain:\r\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\r\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\r\n///\r\n/// Agentic Loop:\r\n/// 1. Call middleware chain (streaming)\r\n/// 2. Collect all messages from the stream\r\n/// 3. Find ToolsCallMessage in the messages\r\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\r\n/// 5. Add ToolsCallResultMessage to conversation\r\n/// 6. Loop again\r\n/// 7. If no ToolsCallMessage, end the loop\r\n/// \r\npublic class OpenAiGrokAgenticExample\r\n{\r\n private readonly IModelResolver _modelResolver;\r\n private readonly IProviderAgentFactory _agentFactory;\r\n private readonly ILogger _logger;\r\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\r\n\r\n public OpenAiGrokAgenticExample(\r\n IModelResolver modelResolver,\r\n IProviderAgentFactory agentFactory,\r\n ILogger logger\r\n )\r\n {\r\n _modelResolver = modelResolver;\r\n _agentFactory = agentFactory;\r\n _logger = logger;\r\n }\r\n\r\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\r\n {\r\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\r\n\r\n // ===== Step 1: Resolve the specified model =====\r\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\r\n if (resolution == null)\r\n {\r\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\r\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Required API keys by model:\");\r\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\r\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\r\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\r\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\r\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\r\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\r\n return;\r\n }\r\n\r\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\r\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\r\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\r\n\r\n // ===== Step 3: Define Tools using FunctionRegistry =====\r\n var registry = new FunctionRegistry();\r\n\r\n // get_weather\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_weather\",\r\n Description = \"Get the current weather for a location\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"location\",\r\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n new FunctionParameterContract\r\n {\r\n Name = \"unit\",\r\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = false,\r\n },\r\n ],\r\n },\r\n async (args, _, _) =>\r\n {\r\n var weatherArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var weather = new\r\n {\r\n location = weatherArgs?.Location,\r\n temperature = Random.Shared.Next(60, 85),\r\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\r\n condition = stringArray[Random.Shared.Next(4)],\r\n };\r\n\r\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(weather));\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // get_time\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_time\",\r\n Description = \"Get the current time for a timezone\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"timezone\",\r\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args, _, _) =>\r\n {\r\n var timeArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var time = new\r\n {\r\n timezone = timeArgs?.Timezone,\r\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\r\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\r\n };\r\n\r\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(time));\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // calculate\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"calculate\",\r\n Description = \"Perform a mathematical calculation\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"expression\",\r\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args, _, _) =>\r\n {\r\n var calcArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\r\n\r\n await Task.Delay(50); // Simulate processing\r\n\r\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\r\n try\r\n {\r\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\r\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression }));\r\n }\r\n catch (Exception ex)\r\n {\r\n return ToolHandlerResult.FromError(JsonSerializer.Serialize(new { error = ex.Message }));\r\n }\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // Build middleware and handlers from registry\r\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\r\n\r\n // ===== Step 4: Create Provider Agent =====\r\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\r\n .WithMessageTransformation()\r\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\r\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\r\n .WithMiddleware(toolCallMiddleware);\r\n\r\n // ===== Step 6: Start Conversation =====\r\n var conversationHistory = new List\r\n {\r\n new TextMessage { Text = prompt, Role = Role.User },\r\n };\r\n\r\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\r\n\r\n // ===== Step 7: Agentic Loop =====\r\n var turnCount = 0;\r\n\r\n while (turnCount < maxTurns)\r\n {\r\n turnCount++;\r\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\r\n\r\n // Call LLM through middleware chain (streaming)\r\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\r\n conversationHistory,\r\n new GenerateReplyOptions\r\n {\r\n ModelId = modelId,\r\n // Temperature = temperature,\r\n RunId = Guid.NewGuid().ToString(),\r\n ParentRunId = null,\r\n ThreadId = Guid.NewGuid().ToString(),\r\n }\r\n );\r\n\r\n // Collect messages from stream and execute tool calls in parallel as they arrive\r\n var messages = new List();\r\n var textContent = new System.Text.StringBuilder();\r\n var pendingToolCalls = new Dictionary>();\r\n\r\n await foreach (var message in streamTask)\r\n {\r\n messages.Add(message);\r\n\r\n // Display streaming content in real-time and handle tool calls\r\n switch (message)\r\n {\r\n case ReasoningMessage reasoningMsg:\r\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\r\n {\r\n Console.Write(reasoningMsg.Reasoning);\r\n _ = textContent.Append(reasoningMsg.Reasoning);\r\n }\r\n break;\r\n\r\n case TextMessage textMsg:\r\n if (!string.IsNullOrEmpty(textMsg.Text))\r\n {\r\n Console.Write(textMsg.Text);\r\n _ = textContent.Append(textMsg.Text);\r\n }\r\n break;\r\n\r\n case ToolCallMessage toolCall:\r\n // Start executing tool call immediately when we receive it\r\n _logger.LogInformation(\r\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\r\n toolCall.FunctionName,\r\n toolCall.ToolCallId\r\n );\r\n\r\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\r\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\r\n break;\r\n\r\n case UsageMessage usage:\r\n _logger.LogDebug(\r\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\r\n usage.Usage.PromptTokens,\r\n usage.Usage.CompletionTokens\r\n );\r\n break;\r\n\r\n default:\r\n // Ignore other message types\r\n break;\r\n }\r\n }\r\n\r\n Console.WriteLine(); // New line after streaming\r\n\r\n // Add all messages to conversation history\r\n conversationHistory.AddRange(messages);\r\n\r\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\r\n\r\n // ===== Step 8: Check for Tool Calls and Await Results =====\r\n if (pendingToolCalls.Count == 0)\r\n {\r\n // No tool calls, conversation complete\r\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\r\n\r\n if (textContent.Length > 0)\r\n {\r\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\r\n }\r\n\r\n break;\r\n }\r\n\r\n // ===== Step 9: Await Tool Results and Add to Conversation =====\r\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\r\n\r\n try\r\n {\r\n // Await all pending tool executions\r\n _ = await Task.WhenAll(pendingToolCalls.Values);\r\n\r\n // Collect results and add to conversation\r\n foreach (var kvp in pendingToolCalls)\r\n {\r\n var result = await kvp.Value;\r\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\r\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\r\n\r\n // Add each tool result to conversation history\r\n conversationHistory.Add(result);\r\n }\r\n\r\n _logger.LogInformation(\"Tool execution completed.\");\r\n Console.WriteLine(); // Spacing\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Tool execution failed\");\r\n\r\n // Add error message to conversation\r\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\r\n conversationHistory.Add(errorMessage);\r\n }\r\n }\r\n\r\n if (turnCount >= maxTurns)\r\n {\r\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\r\n }\r\n\r\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\r\n }\r\n\r\n // ===== Helper Methods =====\r\n\r\n /// \r\n /// Executes a single tool call and returns the result message.\r\n /// \r\n private async Task ExecuteToolCallAsync(\r\n ToolCallMessage toolCall,\r\n IDictionary handlers\r\n )\r\n {\r\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\r\n var functionName = toolCall.FunctionName ?? \"unknown\";\r\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\r\n\r\n try\r\n {\r\n if (handlers.TryGetValue(functionName, out var handler))\r\n {\r\n var ctx = new ToolCallContext { ToolCallId = toolCall.ToolCallId };\r\n var result = await handler(functionArgs, ctx, CancellationToken.None);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = result.ResultText,\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n else\r\n {\r\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n\r\n // ===== Helper Classes =====\r\n\r\n private class WeatherArgs\r\n {\r\n public string? Location { get; set; }\r\n public string? Unit { get; set; }\r\n }\r\n\r\n private class TimeArgs\r\n {\r\n public string? Timezone { get; set; }\r\n }\r\n\r\n private class CalculateArgs\r\n {\r\n public string? Expression { get; set; }\r\n }\r\n\r\n // Simple expression evaluator (for demo purposes only - unsafe in production)\r\n private static double EvaluateSimpleExpression(string expression)\r\n {\r\n expression = expression.Replace(\" \", \"\");\r\n\r\n // Very basic calculator - only handles +, -, *, /\r\n if (expression.Contains('+'))\r\n {\r\n var parts = expression.Split('+');\r\n return double.Parse(parts[0]) + double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('-'))\r\n {\r\n var parts = expression.Split('-');\r\n return double.Parse(parts[0]) - double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('*'))\r\n {\r\n var parts = expression.Split('*');\r\n return double.Parse(parts[0]) * double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('/'))\r\n {\r\n var parts = expression.Split('/');\r\n return double.Parse(parts[0]) / double.Parse(parts[1]);\r\n }\r\n\r\n return double.Parse(expression);\r\n }\r\n}\r\n"}, "f7662cabb85b75d89939f274482822aa22c3113f": {"t": 1785756731.7211182, "n": 18060, "h": "b06dc06feecc4a8b", "c": "using System.Text.Json;\r\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\r\nusing AchieveAi.LmDotnetTools.LmCore.Core;\r\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\r\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\r\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nnamespace LmConfigUsageExample;\r\n\r\n/// \r\n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\r\n/// with the new middleware chain architecture.\r\n///\r\n/// Middleware Chain:\r\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\r\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\r\n///\r\n/// Agentic Loop:\r\n/// 1. Call middleware chain (streaming)\r\n/// 2. Collect all messages from the stream\r\n/// 3. Find ToolsCallMessage in the messages\r\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\r\n/// 5. Add ToolsCallResultMessage to conversation\r\n/// 6. Loop again\r\n/// 7. If no ToolsCallMessage, end the loop\r\n/// \r\npublic class OpenAiGrokAgenticExample\r\n{\r\n private readonly IModelResolver _modelResolver;\r\n private readonly IProviderAgentFactory _agentFactory;\r\n private readonly ILogger _logger;\r\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\r\n\r\n public OpenAiGrokAgenticExample(\r\n IModelResolver modelResolver,\r\n IProviderAgentFactory agentFactory,\r\n ILogger logger\r\n )\r\n {\r\n _modelResolver = modelResolver;\r\n _agentFactory = agentFactory;\r\n _logger = logger;\r\n }\r\n\r\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\r\n {\r\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\r\n\r\n // ===== Step 1: Resolve the specified model =====\r\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\r\n if (resolution == null)\r\n {\r\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\r\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Required API keys by model:\");\r\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\r\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\r\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\r\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\r\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\r\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\r\n return;\r\n }\r\n\r\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\r\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\r\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\r\n\r\n // ===== Step 3: Define Tools using FunctionRegistry =====\r\n var registry = new FunctionRegistry();\r\n\r\n // get_weather\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_weather\",\r\n Description = \"Get the current weather for a location\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"location\",\r\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n new FunctionParameterContract\r\n {\r\n Name = \"unit\",\r\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = false,\r\n },\r\n ],\r\n },\r\n async (args, _, _) =>\r\n {\r\n var weatherArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var weather = new\r\n {\r\n location = weatherArgs?.Location,\r\n temperature = Random.Shared.Next(60, 85),\r\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\r\n condition = stringArray[Random.Shared.Next(4)],\r\n };\r\n\r\n return JsonSerializer.Serialize(weather);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // get_time\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_time\",\r\n Description = \"Get the current time for a timezone\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"timezone\",\r\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args, _, _) =>\r\n {\r\n var timeArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var time = new\r\n {\r\n timezone = timeArgs?.Timezone,\r\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\r\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\r\n };\r\n\r\n return JsonSerializer.Serialize(time);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // calculate\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"calculate\",\r\n Description = \"Perform a mathematical calculation\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"expression\",\r\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args, _, _) =>\r\n {\r\n var calcArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\r\n\r\n await Task.Delay(50); // Simulate processing\r\n\r\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\r\n try\r\n {\r\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\r\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\r\n }\r\n catch (Exception ex)\r\n {\r\n return JsonSerializer.Serialize(new { error = ex.Message });\r\n }\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // Build middleware and handlers from registry\r\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\r\n\r\n // ===== Step 4: Create Provider Agent =====\r\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\r\n .WithMessageTransformation()\r\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\r\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\r\n .WithMiddleware(toolCallMiddleware);\r\n\r\n // ===== Step 6: Start Conversation =====\r\n var conversationHistory = new List\r\n {\r\n new TextMessage { Text = prompt, Role = Role.User },\r\n };\r\n\r\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\r\n\r\n // ===== Step 7: Agentic Loop =====\r\n var turnCount = 0;\r\n\r\n while (turnCount < maxTurns)\r\n {\r\n turnCount++;\r\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\r\n\r\n // Call LLM through middleware chain (streaming)\r\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\r\n conversationHistory,\r\n new GenerateReplyOptions\r\n {\r\n ModelId = modelId,\r\n // Temperature = temperature,\r\n RunId = Guid.NewGuid().ToString(),\r\n ParentRunId = null,\r\n ThreadId = Guid.NewGuid().ToString(),\r\n }\r\n );\r\n\r\n // Collect messages from stream and execute tool calls in parallel as they arrive\r\n var messages = new List();\r\n var textContent = new System.Text.StringBuilder();\r\n var pendingToolCalls = new Dictionary>();\r\n\r\n await foreach (var message in streamTask)\r\n {\r\n messages.Add(message);\r\n\r\n // Display streaming content in real-time and handle tool calls\r\n switch (message)\r\n {\r\n case ReasoningMessage reasoningMsg:\r\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\r\n {\r\n Console.Write(reasoningMsg.Reasoning);\r\n _ = textContent.Append(reasoningMsg.Reasoning);\r\n }\r\n break;\r\n\r\n case TextMessage textMsg:\r\n if (!string.IsNullOrEmpty(textMsg.Text))\r\n {\r\n Console.Write(textMsg.Text);\r\n _ = textContent.Append(textMsg.Text);\r\n }\r\n break;\r\n\r\n case ToolCallMessage toolCall:\r\n // Start executing tool call immediately when we receive it\r\n _logger.LogInformation(\r\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\r\n toolCall.FunctionName,\r\n toolCall.ToolCallId\r\n );\r\n\r\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\r\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\r\n break;\r\n\r\n case UsageMessage usage:\r\n _logger.LogDebug(\r\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\r\n usage.Usage.PromptTokens,\r\n usage.Usage.CompletionTokens\r\n );\r\n break;\r\n\r\n default:\r\n // Ignore other message types\r\n break;\r\n }\r\n }\r\n\r\n Console.WriteLine(); // New line after streaming\r\n\r\n // Add all messages to conversation history\r\n conversationHistory.AddRange(messages);\r\n\r\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\r\n\r\n // ===== Step 8: Check for Tool Calls and Await Results =====\r\n if (pendingToolCalls.Count == 0)\r\n {\r\n // No tool calls, conversation complete\r\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\r\n\r\n if (textContent.Length > 0)\r\n {\r\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\r\n }\r\n\r\n break;\r\n }\r\n\r\n // ===== Step 9: Await Tool Results and Add to Conversation =====\r\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\r\n\r\n try\r\n {\r\n // Await all pending tool executions\r\n _ = await Task.WhenAll(pendingToolCalls.Values);\r\n\r\n // Collect results and add to conversation\r\n foreach (var kvp in pendingToolCalls)\r\n {\r\n var result = await kvp.Value;\r\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\r\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\r\n\r\n // Add each tool result to conversation history\r\n conversationHistory.Add(result);\r\n }\r\n\r\n _logger.LogInformation(\"Tool execution completed.\");\r\n Console.WriteLine(); // Spacing\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Tool execution failed\");\r\n\r\n // Add error message to conversation\r\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\r\n conversationHistory.Add(errorMessage);\r\n }\r\n }\r\n\r\n if (turnCount >= maxTurns)\r\n {\r\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\r\n }\r\n\r\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\r\n }\r\n\r\n // ===== Helper Methods =====\r\n\r\n /// \r\n /// Executes a single tool call and returns the result message.\r\n /// \r\n private async Task ExecuteToolCallAsync(\r\n ToolCallMessage toolCall,\r\n IDictionary handlers\r\n )\r\n {\r\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\r\n var functionName = toolCall.FunctionName ?? \"unknown\";\r\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\r\n\r\n try\r\n {\r\n if (handlers.TryGetValue(functionName, out var handler))\r\n {\r\n var ctx = new ToolCallContext { ToolCallId = toolCall.ToolCallId };\r\n var result = await handler(functionArgs, ctx, CancellationToken.None);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = result.ResultText,\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n else\r\n {\r\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n\r\n // ===== Helper Classes =====\r\n\r\n private class WeatherArgs\r\n {\r\n public string? Location { get; set; }\r\n public string? Unit { get; set; }\r\n }\r\n\r\n private class TimeArgs\r\n {\r\n public string? Timezone { get; set; }\r\n }\r\n\r\n private class CalculateArgs\r\n {\r\n public string? Expression { get; set; }\r\n }\r\n\r\n // Simple expression evaluator (for demo purposes only - unsafe in production)\r\n private static double EvaluateSimpleExpression(string expression)\r\n {\r\n expression = expression.Replace(\" \", \"\");\r\n\r\n // Very basic calculator - only handles +, -, *, /\r\n if (expression.Contains('+'))\r\n {\r\n var parts = expression.Split('+');\r\n return double.Parse(parts[0]) + double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('-'))\r\n {\r\n var parts = expression.Split('-');\r\n return double.Parse(parts[0]) - double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('*'))\r\n {\r\n var parts = expression.Split('*');\r\n return double.Parse(parts[0]) * double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('/'))\r\n {\r\n var parts = expression.Split('/');\r\n return double.Parse(parts[0]) / double.Parse(parts[1]);\r\n }\r\n\r\n return double.Parse(expression);\r\n }\r\n}\r\n"}, "274826859b8a5fca8b68a12e8d83e762390ca0aa": {"t": 1785756733.3940406, "n": 18027, "h": "25ca1fc6d9afe726", "c": "using System.Text.Json;\r\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\r\nusing AchieveAi.LmDotnetTools.LmCore.Core;\r\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\r\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\r\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nnamespace LmConfigUsageExample;\r\n\r\n/// \r\n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\r\n/// with the new middleware chain architecture.\r\n///\r\n/// Middleware Chain:\r\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\r\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\r\n///\r\n/// Agentic Loop:\r\n/// 1. Call middleware chain (streaming)\r\n/// 2. Collect all messages from the stream\r\n/// 3. Find ToolsCallMessage in the messages\r\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\r\n/// 5. Add ToolsCallResultMessage to conversation\r\n/// 6. Loop again\r\n/// 7. If no ToolsCallMessage, end the loop\r\n/// \r\npublic class OpenAiGrokAgenticExample\r\n{\r\n private readonly IModelResolver _modelResolver;\r\n private readonly IProviderAgentFactory _agentFactory;\r\n private readonly ILogger _logger;\r\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\r\n\r\n public OpenAiGrokAgenticExample(\r\n IModelResolver modelResolver,\r\n IProviderAgentFactory agentFactory,\r\n ILogger logger\r\n )\r\n {\r\n _modelResolver = modelResolver;\r\n _agentFactory = agentFactory;\r\n _logger = logger;\r\n }\r\n\r\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\r\n {\r\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\r\n\r\n // ===== Step 1: Resolve the specified model =====\r\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\r\n if (resolution == null)\r\n {\r\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\r\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Required API keys by model:\");\r\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\r\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\r\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\r\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\r\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\r\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\r\n return;\r\n }\r\n\r\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\r\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\r\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\r\n\r\n // ===== Step 3: Define Tools using FunctionRegistry =====\r\n var registry = new FunctionRegistry();\r\n\r\n // get_weather\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_weather\",\r\n Description = \"Get the current weather for a location\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"location\",\r\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n new FunctionParameterContract\r\n {\r\n Name = \"unit\",\r\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = false,\r\n },\r\n ],\r\n },\r\n async (args, _) =>\r\n {\r\n var weatherArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var weather = new\r\n {\r\n location = weatherArgs?.Location,\r\n temperature = Random.Shared.Next(60, 85),\r\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\r\n condition = stringArray[Random.Shared.Next(4)],\r\n };\r\n\r\n return JsonSerializer.Serialize(weather);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // get_time\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_time\",\r\n Description = \"Get the current time for a timezone\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"timezone\",\r\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args, _) =>\r\n {\r\n var timeArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var time = new\r\n {\r\n timezone = timeArgs?.Timezone,\r\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\r\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\r\n };\r\n\r\n return JsonSerializer.Serialize(time);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // calculate\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"calculate\",\r\n Description = \"Perform a mathematical calculation\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"expression\",\r\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args, _) =>\r\n {\r\n var calcArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\r\n\r\n await Task.Delay(50); // Simulate processing\r\n\r\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\r\n try\r\n {\r\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\r\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\r\n }\r\n catch (Exception ex)\r\n {\r\n return JsonSerializer.Serialize(new { error = ex.Message });\r\n }\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // Build middleware and handlers from registry\r\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\r\n\r\n // ===== Step 4: Create Provider Agent =====\r\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\r\n .WithMessageTransformation()\r\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\r\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\r\n .WithMiddleware(toolCallMiddleware);\r\n\r\n // ===== Step 6: Start Conversation =====\r\n var conversationHistory = new List\r\n {\r\n new TextMessage { Text = prompt, Role = Role.User },\r\n };\r\n\r\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\r\n\r\n // ===== Step 7: Agentic Loop =====\r\n var turnCount = 0;\r\n\r\n while (turnCount < maxTurns)\r\n {\r\n turnCount++;\r\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\r\n\r\n // Call LLM through middleware chain (streaming)\r\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\r\n conversationHistory,\r\n new GenerateReplyOptions\r\n {\r\n ModelId = modelId,\r\n // Temperature = temperature,\r\n RunId = Guid.NewGuid().ToString(),\r\n ParentRunId = null,\r\n ThreadId = Guid.NewGuid().ToString(),\r\n }\r\n );\r\n\r\n // Collect messages from stream and execute tool calls in parallel as they arrive\r\n var messages = new List();\r\n var textContent = new System.Text.StringBuilder();\r\n var pendingToolCalls = new Dictionary>();\r\n\r\n await foreach (var message in streamTask)\r\n {\r\n messages.Add(message);\r\n\r\n // Display streaming content in real-time and handle tool calls\r\n switch (message)\r\n {\r\n case ReasoningMessage reasoningMsg:\r\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\r\n {\r\n Console.Write(reasoningMsg.Reasoning);\r\n _ = textContent.Append(reasoningMsg.Reasoning);\r\n }\r\n break;\r\n\r\n case TextMessage textMsg:\r\n if (!string.IsNullOrEmpty(textMsg.Text))\r\n {\r\n Console.Write(textMsg.Text);\r\n _ = textContent.Append(textMsg.Text);\r\n }\r\n break;\r\n\r\n case ToolCallMessage toolCall:\r\n // Start executing tool call immediately when we receive it\r\n _logger.LogInformation(\r\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\r\n toolCall.FunctionName,\r\n toolCall.ToolCallId\r\n );\r\n\r\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\r\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\r\n break;\r\n\r\n case UsageMessage usage:\r\n _logger.LogDebug(\r\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\r\n usage.Usage.PromptTokens,\r\n usage.Usage.CompletionTokens\r\n );\r\n break;\r\n\r\n default:\r\n // Ignore other message types\r\n break;\r\n }\r\n }\r\n\r\n Console.WriteLine(); // New line after streaming\r\n\r\n // Add all messages to conversation history\r\n conversationHistory.AddRange(messages);\r\n\r\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\r\n\r\n // ===== Step 8: Check for Tool Calls and Await Results =====\r\n if (pendingToolCalls.Count == 0)\r\n {\r\n // No tool calls, conversation complete\r\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\r\n\r\n if (textContent.Length > 0)\r\n {\r\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\r\n }\r\n\r\n break;\r\n }\r\n\r\n // ===== Step 9: Await Tool Results and Add to Conversation =====\r\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\r\n\r\n try\r\n {\r\n // Await all pending tool executions\r\n _ = await Task.WhenAll(pendingToolCalls.Values);\r\n\r\n // Collect results and add to conversation\r\n foreach (var kvp in pendingToolCalls)\r\n {\r\n var result = await kvp.Value;\r\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\r\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\r\n\r\n // Add each tool result to conversation history\r\n conversationHistory.Add(result);\r\n }\r\n\r\n _logger.LogInformation(\"Tool execution completed.\");\r\n Console.WriteLine(); // Spacing\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Tool execution failed\");\r\n\r\n // Add error message to conversation\r\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\r\n conversationHistory.Add(errorMessage);\r\n }\r\n }\r\n\r\n if (turnCount >= maxTurns)\r\n {\r\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\r\n }\r\n\r\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\r\n }\r\n\r\n // ===== Helper Methods =====\r\n\r\n /// \r\n /// Executes a single tool call and returns the result message.\r\n /// \r\n private async Task ExecuteToolCallAsync(\r\n ToolCallMessage toolCall,\r\n IDictionary handlers\r\n )\r\n {\r\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\r\n var functionName = toolCall.FunctionName ?? \"unknown\";\r\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\r\n\r\n try\r\n {\r\n if (handlers.TryGetValue(functionName, out var handler))\r\n {\r\n var ctx = new ToolCallContext { ToolCallId = toolCall.ToolCallId };\r\n var result = await handler(functionArgs, ctx);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = result.ResultText,\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n else\r\n {\r\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n\r\n // ===== Helper Classes =====\r\n\r\n private class WeatherArgs\r\n {\r\n public string? Location { get; set; }\r\n public string? Unit { get; set; }\r\n }\r\n\r\n private class TimeArgs\r\n {\r\n public string? Timezone { get; set; }\r\n }\r\n\r\n private class CalculateArgs\r\n {\r\n public string? Expression { get; set; }\r\n }\r\n\r\n // Simple expression evaluator (for demo purposes only - unsafe in production)\r\n private static double EvaluateSimpleExpression(string expression)\r\n {\r\n expression = expression.Replace(\" \", \"\");\r\n\r\n // Very basic calculator - only handles +, -, *, /\r\n if (expression.Contains('+'))\r\n {\r\n var parts = expression.Split('+');\r\n return double.Parse(parts[0]) + double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('-'))\r\n {\r\n var parts = expression.Split('-');\r\n return double.Parse(parts[0]) - double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('*'))\r\n {\r\n var parts = expression.Split('*');\r\n return double.Parse(parts[0]) * double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('/'))\r\n {\r\n var parts = expression.Split('/');\r\n return double.Parse(parts[0]) / double.Parse(parts[1]);\r\n }\r\n\r\n return double.Parse(expression);\r\n }\r\n}\r\n"}, "5e0ab63291333a85068466185c71ee6d9d2a9c64": {"t": 1785756734.7543228, "n": 17954, "h": "4df478a19f30dcdf", "c": "using System.Text.Json;\r\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\r\nusing AchieveAi.LmDotnetTools.LmCore.Core;\r\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\r\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\r\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nnamespace LmConfigUsageExample;\r\n\r\n/// \r\n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\r\n/// with the new middleware chain architecture.\r\n///\r\n/// Middleware Chain:\r\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\r\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\r\n///\r\n/// Agentic Loop:\r\n/// 1. Call middleware chain (streaming)\r\n/// 2. Collect all messages from the stream\r\n/// 3. Find ToolsCallMessage in the messages\r\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\r\n/// 5. Add ToolsCallResultMessage to conversation\r\n/// 6. Loop again\r\n/// 7. If no ToolsCallMessage, end the loop\r\n/// \r\npublic class OpenAiGrokAgenticExample\r\n{\r\n private readonly IModelResolver _modelResolver;\r\n private readonly IProviderAgentFactory _agentFactory;\r\n private readonly ILogger _logger;\r\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\r\n\r\n public OpenAiGrokAgenticExample(\r\n IModelResolver modelResolver,\r\n IProviderAgentFactory agentFactory,\r\n ILogger logger\r\n )\r\n {\r\n _modelResolver = modelResolver;\r\n _agentFactory = agentFactory;\r\n _logger = logger;\r\n }\r\n\r\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\r\n {\r\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\r\n\r\n // ===== Step 1: Resolve the specified model =====\r\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\r\n if (resolution == null)\r\n {\r\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\r\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Required API keys by model:\");\r\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\r\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\r\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\r\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\r\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\r\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\r\n return;\r\n }\r\n\r\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\r\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\r\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\r\n\r\n // ===== Step 3: Define Tools using FunctionRegistry =====\r\n var registry = new FunctionRegistry();\r\n\r\n // get_weather\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_weather\",\r\n Description = \"Get the current weather for a location\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"location\",\r\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n new FunctionParameterContract\r\n {\r\n Name = \"unit\",\r\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = false,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var weatherArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var weather = new\r\n {\r\n location = weatherArgs?.Location,\r\n temperature = Random.Shared.Next(60, 85),\r\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\r\n condition = stringArray[Random.Shared.Next(4)],\r\n };\r\n\r\n return JsonSerializer.Serialize(weather);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // get_time\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_time\",\r\n Description = \"Get the current time for a timezone\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"timezone\",\r\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var timeArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var time = new\r\n {\r\n timezone = timeArgs?.Timezone,\r\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\r\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\r\n };\r\n\r\n return JsonSerializer.Serialize(time);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // calculate\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"calculate\",\r\n Description = \"Perform a mathematical calculation\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"expression\",\r\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var calcArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\r\n\r\n await Task.Delay(50); // Simulate processing\r\n\r\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\r\n try\r\n {\r\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\r\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\r\n }\r\n catch (Exception ex)\r\n {\r\n return JsonSerializer.Serialize(new { error = ex.Message });\r\n }\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // Build middleware and handlers from registry\r\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\r\n\r\n // ===== Step 4: Create Provider Agent =====\r\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\r\n .WithMessageTransformation()\r\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\r\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\r\n .WithMiddleware(toolCallMiddleware);\r\n\r\n // ===== Step 6: Start Conversation =====\r\n var conversationHistory = new List\r\n {\r\n new TextMessage { Text = prompt, Role = Role.User },\r\n };\r\n\r\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\r\n\r\n // ===== Step 7: Agentic Loop =====\r\n var turnCount = 0;\r\n\r\n while (turnCount < maxTurns)\r\n {\r\n turnCount++;\r\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\r\n\r\n // Call LLM through middleware chain (streaming)\r\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\r\n conversationHistory,\r\n new GenerateReplyOptions\r\n {\r\n ModelId = modelId,\r\n // Temperature = temperature,\r\n RunId = Guid.NewGuid().ToString(),\r\n ParentRunId = null,\r\n ThreadId = Guid.NewGuid().ToString(),\r\n }\r\n );\r\n\r\n // Collect messages from stream and execute tool calls in parallel as they arrive\r\n var messages = new List();\r\n var textContent = new System.Text.StringBuilder();\r\n var pendingToolCalls = new Dictionary>();\r\n\r\n await foreach (var message in streamTask)\r\n {\r\n messages.Add(message);\r\n\r\n // Display streaming content in real-time and handle tool calls\r\n switch (message)\r\n {\r\n case ReasoningMessage reasoningMsg:\r\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\r\n {\r\n Console.Write(reasoningMsg.Reasoning);\r\n _ = textContent.Append(reasoningMsg.Reasoning);\r\n }\r\n break;\r\n\r\n case TextMessage textMsg:\r\n if (!string.IsNullOrEmpty(textMsg.Text))\r\n {\r\n Console.Write(textMsg.Text);\r\n _ = textContent.Append(textMsg.Text);\r\n }\r\n break;\r\n\r\n case ToolCallMessage toolCall:\r\n // Start executing tool call immediately when we receive it\r\n _logger.LogInformation(\r\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\r\n toolCall.FunctionName,\r\n toolCall.ToolCallId\r\n );\r\n\r\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\r\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\r\n break;\r\n\r\n case UsageMessage usage:\r\n _logger.LogDebug(\r\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\r\n usage.Usage.PromptTokens,\r\n usage.Usage.CompletionTokens\r\n );\r\n break;\r\n\r\n default:\r\n // Ignore other message types\r\n break;\r\n }\r\n }\r\n\r\n Console.WriteLine(); // New line after streaming\r\n\r\n // Add all messages to conversation history\r\n conversationHistory.AddRange(messages);\r\n\r\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\r\n\r\n // ===== Step 8: Check for Tool Calls and Await Results =====\r\n if (pendingToolCalls.Count == 0)\r\n {\r\n // No tool calls, conversation complete\r\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\r\n\r\n if (textContent.Length > 0)\r\n {\r\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\r\n }\r\n\r\n break;\r\n }\r\n\r\n // ===== Step 9: Await Tool Results and Add to Conversation =====\r\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\r\n\r\n try\r\n {\r\n // Await all pending tool executions\r\n _ = await Task.WhenAll(pendingToolCalls.Values);\r\n\r\n // Collect results and add to conversation\r\n foreach (var kvp in pendingToolCalls)\r\n {\r\n var result = await kvp.Value;\r\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\r\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\r\n\r\n // Add each tool result to conversation history\r\n conversationHistory.Add(result);\r\n }\r\n\r\n _logger.LogInformation(\"Tool execution completed.\");\r\n Console.WriteLine(); // Spacing\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Tool execution failed\");\r\n\r\n // Add error message to conversation\r\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\r\n conversationHistory.Add(errorMessage);\r\n }\r\n }\r\n\r\n if (turnCount >= maxTurns)\r\n {\r\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\r\n }\r\n\r\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\r\n }\r\n\r\n // ===== Helper Methods =====\r\n\r\n /// \r\n /// Executes a single tool call and returns the result message.\r\n /// \r\n private async Task ExecuteToolCallAsync(\r\n ToolCallMessage toolCall,\r\n IDictionary>> handlers\r\n )\r\n {\r\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\r\n var functionName = toolCall.FunctionName ?? \"unknown\";\r\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\r\n\r\n try\r\n {\r\n if (handlers.TryGetValue(functionName, out var handler))\r\n {\r\n var result = await handler(functionArgs);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = result.ResultText,\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n else\r\n {\r\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n\r\n // ===== Helper Classes =====\r\n\r\n private class WeatherArgs\r\n {\r\n public string? Location { get; set; }\r\n public string? Unit { get; set; }\r\n }\r\n\r\n private class TimeArgs\r\n {\r\n public string? Timezone { get; set; }\r\n }\r\n\r\n private class CalculateArgs\r\n {\r\n public string? Expression { get; set; }\r\n }\r\n\r\n // Simple expression evaluator (for demo purposes only - unsafe in production)\r\n private static double EvaluateSimpleExpression(string expression)\r\n {\r\n expression = expression.Replace(\" \", \"\");\r\n\r\n // Very basic calculator - only handles +, -, *, /\r\n if (expression.Contains('+'))\r\n {\r\n var parts = expression.Split('+');\r\n return double.Parse(parts[0]) + double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('-'))\r\n {\r\n var parts = expression.Split('-');\r\n return double.Parse(parts[0]) - double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('*'))\r\n {\r\n var parts = expression.Split('*');\r\n return double.Parse(parts[0]) * double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('/'))\r\n {\r\n var parts = expression.Split('/');\r\n return double.Parse(parts[0]) / double.Parse(parts[1]);\r\n }\r\n\r\n return double.Parse(expression);\r\n }\r\n}\r\n"}, "7d6fbf36d4f47012f995e306cfb47494fe174abc": {"t": 1785756736.4656126, "n": 17935, "h": "d15755920836bbbd", "c": "using System.Text.Json;\r\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\r\nusing AchieveAi.LmDotnetTools.LmCore.Core;\r\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\r\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\r\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nnamespace LmConfigUsageExample;\r\n\r\n/// \r\n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\r\n/// with the new middleware chain architecture.\r\n///\r\n/// Middleware Chain:\r\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\r\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\r\n///\r\n/// Agentic Loop:\r\n/// 1. Call middleware chain (streaming)\r\n/// 2. Collect all messages from the stream\r\n/// 3. Find ToolsCallMessage in the messages\r\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\r\n/// 5. Add ToolsCallResultMessage to conversation\r\n/// 6. Loop again\r\n/// 7. If no ToolsCallMessage, end the loop\r\n/// \r\npublic class OpenAiGrokAgenticExample\r\n{\r\n private readonly IModelResolver _modelResolver;\r\n private readonly IProviderAgentFactory _agentFactory;\r\n private readonly ILogger _logger;\r\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\r\n\r\n public OpenAiGrokAgenticExample(\r\n IModelResolver modelResolver,\r\n IProviderAgentFactory agentFactory,\r\n ILogger logger\r\n )\r\n {\r\n _modelResolver = modelResolver;\r\n _agentFactory = agentFactory;\r\n _logger = logger;\r\n }\r\n\r\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\r\n {\r\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\r\n\r\n // ===== Step 1: Resolve the specified model =====\r\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\r\n if (resolution == null)\r\n {\r\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\r\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Required API keys by model:\");\r\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\r\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\r\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\r\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\r\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\r\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\r\n return;\r\n }\r\n\r\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\r\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\r\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\r\n\r\n // ===== Step 3: Define Tools using FunctionRegistry =====\r\n var registry = new FunctionRegistry();\r\n\r\n // get_weather\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_weather\",\r\n Description = \"Get the current weather for a location\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"location\",\r\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n new FunctionParameterContract\r\n {\r\n Name = \"unit\",\r\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = false,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var weatherArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var weather = new\r\n {\r\n location = weatherArgs?.Location,\r\n temperature = Random.Shared.Next(60, 85),\r\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\r\n condition = stringArray[Random.Shared.Next(4)],\r\n };\r\n\r\n return JsonSerializer.Serialize(weather);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // get_time\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_time\",\r\n Description = \"Get the current time for a timezone\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"timezone\",\r\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var timeArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var time = new\r\n {\r\n timezone = timeArgs?.Timezone,\r\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\r\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\r\n };\r\n\r\n return JsonSerializer.Serialize(time);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // calculate\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"calculate\",\r\n Description = \"Perform a mathematical calculation\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"expression\",\r\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var calcArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\r\n\r\n await Task.Delay(50); // Simulate processing\r\n\r\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\r\n try\r\n {\r\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\r\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\r\n }\r\n catch (Exception ex)\r\n {\r\n return JsonSerializer.Serialize(new { error = ex.Message });\r\n }\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // Build middleware and handlers from registry\r\n var (toolCallMiddleware, functionHandlers, _) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\r\n\r\n // ===== Step 4: Create Provider Agent =====\r\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\r\n .WithMessageTransformation()\r\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\r\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\r\n .WithMiddleware(toolCallMiddleware);\r\n\r\n // ===== Step 6: Start Conversation =====\r\n var conversationHistory = new List\r\n {\r\n new TextMessage { Text = prompt, Role = Role.User },\r\n };\r\n\r\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\r\n\r\n // ===== Step 7: Agentic Loop =====\r\n var turnCount = 0;\r\n\r\n while (turnCount < maxTurns)\r\n {\r\n turnCount++;\r\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\r\n\r\n // Call LLM through middleware chain (streaming)\r\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\r\n conversationHistory,\r\n new GenerateReplyOptions\r\n {\r\n ModelId = modelId,\r\n // Temperature = temperature,\r\n RunId = Guid.NewGuid().ToString(),\r\n ParentRunId = null,\r\n ThreadId = Guid.NewGuid().ToString(),\r\n }\r\n );\r\n\r\n // Collect messages from stream and execute tool calls in parallel as they arrive\r\n var messages = new List();\r\n var textContent = new System.Text.StringBuilder();\r\n var pendingToolCalls = new Dictionary>();\r\n\r\n await foreach (var message in streamTask)\r\n {\r\n messages.Add(message);\r\n\r\n // Display streaming content in real-time and handle tool calls\r\n switch (message)\r\n {\r\n case ReasoningMessage reasoningMsg:\r\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\r\n {\r\n Console.Write(reasoningMsg.Reasoning);\r\n _ = textContent.Append(reasoningMsg.Reasoning);\r\n }\r\n break;\r\n\r\n case TextMessage textMsg:\r\n if (!string.IsNullOrEmpty(textMsg.Text))\r\n {\r\n Console.Write(textMsg.Text);\r\n _ = textContent.Append(textMsg.Text);\r\n }\r\n break;\r\n\r\n case ToolCallMessage toolCall:\r\n // Start executing tool call immediately when we receive it\r\n _logger.LogInformation(\r\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\r\n toolCall.FunctionName,\r\n toolCall.ToolCallId\r\n );\r\n\r\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\r\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\r\n break;\r\n\r\n case UsageMessage usage:\r\n _logger.LogDebug(\r\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\r\n usage.Usage.PromptTokens,\r\n usage.Usage.CompletionTokens\r\n );\r\n break;\r\n\r\n default:\r\n // Ignore other message types\r\n break;\r\n }\r\n }\r\n\r\n Console.WriteLine(); // New line after streaming\r\n\r\n // Add all messages to conversation history\r\n conversationHistory.AddRange(messages);\r\n\r\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\r\n\r\n // ===== Step 8: Check for Tool Calls and Await Results =====\r\n if (pendingToolCalls.Count == 0)\r\n {\r\n // No tool calls, conversation complete\r\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\r\n\r\n if (textContent.Length > 0)\r\n {\r\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\r\n }\r\n\r\n break;\r\n }\r\n\r\n // ===== Step 9: Await Tool Results and Add to Conversation =====\r\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\r\n\r\n try\r\n {\r\n // Await all pending tool executions\r\n _ = await Task.WhenAll(pendingToolCalls.Values);\r\n\r\n // Collect results and add to conversation\r\n foreach (var kvp in pendingToolCalls)\r\n {\r\n var result = await kvp.Value;\r\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\r\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\r\n\r\n // Add each tool result to conversation history\r\n conversationHistory.Add(result);\r\n }\r\n\r\n _logger.LogInformation(\"Tool execution completed.\");\r\n Console.WriteLine(); // Spacing\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Tool execution failed\");\r\n\r\n // Add error message to conversation\r\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\r\n conversationHistory.Add(errorMessage);\r\n }\r\n }\r\n\r\n if (turnCount >= maxTurns)\r\n {\r\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\r\n }\r\n\r\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\r\n }\r\n\r\n // ===== Helper Methods =====\r\n\r\n /// \r\n /// Executes a single tool call and returns the result message.\r\n /// \r\n private async Task ExecuteToolCallAsync(\r\n ToolCallMessage toolCall,\r\n IDictionary>> handlers\r\n )\r\n {\r\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\r\n var functionName = toolCall.FunctionName ?? \"unknown\";\r\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\r\n\r\n try\r\n {\r\n if (handlers.TryGetValue(functionName, out var handler))\r\n {\r\n var result = await handler(functionArgs);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = result,\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n else\r\n {\r\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n\r\n // ===== Helper Classes =====\r\n\r\n private class WeatherArgs\r\n {\r\n public string? Location { get; set; }\r\n public string? Unit { get; set; }\r\n }\r\n\r\n private class TimeArgs\r\n {\r\n public string? Timezone { get; set; }\r\n }\r\n\r\n private class CalculateArgs\r\n {\r\n public string? Expression { get; set; }\r\n }\r\n\r\n // Simple expression evaluator (for demo purposes only - unsafe in production)\r\n private static double EvaluateSimpleExpression(string expression)\r\n {\r\n expression = expression.Replace(\" \", \"\");\r\n\r\n // Very basic calculator - only handles +, -, *, /\r\n if (expression.Contains('+'))\r\n {\r\n var parts = expression.Split('+');\r\n return double.Parse(parts[0]) + double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('-'))\r\n {\r\n var parts = expression.Split('-');\r\n return double.Parse(parts[0]) - double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('*'))\r\n {\r\n var parts = expression.Split('*');\r\n return double.Parse(parts[0]) * double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('/'))\r\n {\r\n var parts = expression.Split('/');\r\n return double.Parse(parts[0]) / double.Parse(parts[1]);\r\n }\r\n\r\n return double.Parse(expression);\r\n }\r\n}\r\n"}, "f6fbc09259cc79ab57c0060c74e0e8c3dfb6a1cc": {"t": 1785756737.8071604, "n": 17932, "h": "d73298ad311ea5a9", "c": "using System.Text.Json;\r\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\r\nusing AchieveAi.LmDotnetTools.LmCore.Core;\r\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\r\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\r\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nnamespace LmConfigUsageExample;\r\n\r\n/// \r\n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\r\n/// with the new middleware chain architecture.\r\n///\r\n/// Middleware Chain:\r\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\r\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\r\n///\r\n/// Agentic Loop:\r\n/// 1. Call middleware chain (streaming)\r\n/// 2. Collect all messages from the stream\r\n/// 3. Find ToolsCallMessage in the messages\r\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\r\n/// 5. Add ToolsCallResultMessage to conversation\r\n/// 6. Loop again\r\n/// 7. If no ToolsCallMessage, end the loop\r\n/// \r\npublic class OpenAiGrokAgenticExample\r\n{\r\n private readonly IModelResolver _modelResolver;\r\n private readonly IProviderAgentFactory _agentFactory;\r\n private readonly ILogger _logger;\r\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\r\n\r\n public OpenAiGrokAgenticExample(\r\n IModelResolver modelResolver,\r\n IProviderAgentFactory agentFactory,\r\n ILogger logger\r\n )\r\n {\r\n _modelResolver = modelResolver;\r\n _agentFactory = agentFactory;\r\n _logger = logger;\r\n }\r\n\r\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\r\n {\r\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\r\n\r\n // ===== Step 1: Resolve the specified model =====\r\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\r\n if (resolution == null)\r\n {\r\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\r\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Required API keys by model:\");\r\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\r\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\r\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\r\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\r\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\r\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\r\n _logger.LogWarning(\"\");\r\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\r\n return;\r\n }\r\n\r\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\r\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\r\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\r\n\r\n // ===== Step 3: Define Tools using FunctionRegistry =====\r\n var registry = new FunctionRegistry();\r\n\r\n // get_weather\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_weather\",\r\n Description = \"Get the current weather for a location\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"location\",\r\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n new FunctionParameterContract\r\n {\r\n Name = \"unit\",\r\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = false,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var weatherArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var weather = new\r\n {\r\n location = weatherArgs?.Location,\r\n temperature = Random.Shared.Next(60, 85),\r\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\r\n condition = stringArray[Random.Shared.Next(4)],\r\n };\r\n\r\n return JsonSerializer.Serialize(weather);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // get_time\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"get_time\",\r\n Description = \"Get the current time for a timezone\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"timezone\",\r\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var timeArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\r\n\r\n await Task.Delay(100); // Simulate API call\r\n\r\n var time = new\r\n {\r\n timezone = timeArgs?.Timezone,\r\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\r\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\r\n };\r\n\r\n return JsonSerializer.Serialize(time);\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // calculate\r\n _ = registry.AddFunction(\r\n new FunctionContract\r\n {\r\n Name = \"calculate\",\r\n Description = \"Perform a mathematical calculation\",\r\n Parameters =\r\n [\r\n new FunctionParameterContract\r\n {\r\n Name = \"expression\",\r\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\r\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\r\n IsRequired = true,\r\n },\r\n ],\r\n },\r\n async (args) =>\r\n {\r\n var calcArgs = JsonSerializer.Deserialize(args);\r\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\r\n\r\n await Task.Delay(50); // Simulate processing\r\n\r\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\r\n try\r\n {\r\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\r\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\r\n }\r\n catch (Exception ex)\r\n {\r\n return JsonSerializer.Serialize(new { error = ex.Message });\r\n }\r\n },\r\n providerName: \"Example\"\r\n );\r\n\r\n // Build middleware and handlers from registry\r\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\r\n\r\n // ===== Step 4: Create Provider Agent =====\r\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\r\n .WithMessageTransformation()\r\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\r\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\r\n .WithMiddleware(toolCallMiddleware);\r\n\r\n // ===== Step 6: Start Conversation =====\r\n var conversationHistory = new List\r\n {\r\n new TextMessage { Text = prompt, Role = Role.User },\r\n };\r\n\r\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\r\n\r\n // ===== Step 7: Agentic Loop =====\r\n var turnCount = 0;\r\n\r\n while (turnCount < maxTurns)\r\n {\r\n turnCount++;\r\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\r\n\r\n // Call LLM through middleware chain (streaming)\r\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\r\n conversationHistory,\r\n new GenerateReplyOptions\r\n {\r\n ModelId = modelId,\r\n // Temperature = temperature,\r\n RunId = Guid.NewGuid().ToString(),\r\n ParentRunId = null,\r\n ThreadId = Guid.NewGuid().ToString(),\r\n }\r\n );\r\n\r\n // Collect messages from stream and execute tool calls in parallel as they arrive\r\n var messages = new List();\r\n var textContent = new System.Text.StringBuilder();\r\n var pendingToolCalls = new Dictionary>();\r\n\r\n await foreach (var message in streamTask)\r\n {\r\n messages.Add(message);\r\n\r\n // Display streaming content in real-time and handle tool calls\r\n switch (message)\r\n {\r\n case ReasoningMessage reasoningMsg:\r\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\r\n {\r\n Console.Write(reasoningMsg.Reasoning);\r\n _ = textContent.Append(reasoningMsg.Reasoning);\r\n }\r\n break;\r\n\r\n case TextMessage textMsg:\r\n if (!string.IsNullOrEmpty(textMsg.Text))\r\n {\r\n Console.Write(textMsg.Text);\r\n _ = textContent.Append(textMsg.Text);\r\n }\r\n break;\r\n\r\n case ToolCallMessage toolCall:\r\n // Start executing tool call immediately when we receive it\r\n _logger.LogInformation(\r\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\r\n toolCall.FunctionName,\r\n toolCall.ToolCallId\r\n );\r\n\r\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\r\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\r\n break;\r\n\r\n case UsageMessage usage:\r\n _logger.LogDebug(\r\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\r\n usage.Usage.PromptTokens,\r\n usage.Usage.CompletionTokens\r\n );\r\n break;\r\n\r\n default:\r\n // Ignore other message types\r\n break;\r\n }\r\n }\r\n\r\n Console.WriteLine(); // New line after streaming\r\n\r\n // Add all messages to conversation history\r\n conversationHistory.AddRange(messages);\r\n\r\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\r\n\r\n // ===== Step 8: Check for Tool Calls and Await Results =====\r\n if (pendingToolCalls.Count == 0)\r\n {\r\n // No tool calls, conversation complete\r\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\r\n\r\n if (textContent.Length > 0)\r\n {\r\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\r\n }\r\n\r\n break;\r\n }\r\n\r\n // ===== Step 9: Await Tool Results and Add to Conversation =====\r\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\r\n\r\n try\r\n {\r\n // Await all pending tool executions\r\n _ = await Task.WhenAll(pendingToolCalls.Values);\r\n\r\n // Collect results and add to conversation\r\n foreach (var kvp in pendingToolCalls)\r\n {\r\n var result = await kvp.Value;\r\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\r\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\r\n\r\n // Add each tool result to conversation history\r\n conversationHistory.Add(result);\r\n }\r\n\r\n _logger.LogInformation(\"Tool execution completed.\");\r\n Console.WriteLine(); // Spacing\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Tool execution failed\");\r\n\r\n // Add error message to conversation\r\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\r\n conversationHistory.Add(errorMessage);\r\n }\r\n }\r\n\r\n if (turnCount >= maxTurns)\r\n {\r\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\r\n }\r\n\r\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\r\n }\r\n\r\n // ===== Helper Methods =====\r\n\r\n /// \r\n /// Executes a single tool call and returns the result message.\r\n /// \r\n private async Task ExecuteToolCallAsync(\r\n ToolCallMessage toolCall,\r\n IDictionary>> handlers\r\n )\r\n {\r\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\r\n var functionName = toolCall.FunctionName ?? \"unknown\";\r\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\r\n\r\n try\r\n {\r\n if (handlers.TryGetValue(functionName, out var handler))\r\n {\r\n var result = await handler(functionArgs);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = result,\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n else\r\n {\r\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n catch (Exception ex)\r\n {\r\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\r\n return new ToolCallResultMessage\r\n {\r\n ToolCallId = toolCallId,\r\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\r\n Role = Role.User,\r\n FromAgent = toolCall.FromAgent,\r\n GenerationId = toolCall.GenerationId,\r\n };\r\n }\r\n }\r\n\r\n // ===== Helper Classes =====\r\n\r\n private class WeatherArgs\r\n {\r\n public string? Location { get; set; }\r\n public string? Unit { get; set; }\r\n }\r\n\r\n private class TimeArgs\r\n {\r\n public string? Timezone { get; set; }\r\n }\r\n\r\n private class CalculateArgs\r\n {\r\n public string? Expression { get; set; }\r\n }\r\n\r\n // Simple expression evaluator (for demo purposes only - unsafe in production)\r\n private static double EvaluateSimpleExpression(string expression)\r\n {\r\n expression = expression.Replace(\" \", \"\");\r\n\r\n // Very basic calculator - only handles +, -, *, /\r\n if (expression.Contains('+'))\r\n {\r\n var parts = expression.Split('+');\r\n return double.Parse(parts[0]) + double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('-'))\r\n {\r\n var parts = expression.Split('-');\r\n return double.Parse(parts[0]) - double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('*'))\r\n {\r\n var parts = expression.Split('*');\r\n return double.Parse(parts[0]) * double.Parse(parts[1]);\r\n }\r\n if (expression.Contains('/'))\r\n {\r\n var parts = expression.Split('/');\r\n return double.Parse(parts[0]) / double.Parse(parts[1]);\r\n }\r\n\r\n return double.Parse(expression);\r\n }\r\n}\r\n"}, "03bb4344e55549129a4e557c4ee5443fba7e6a90": {"t": 1785756739.5514913, "n": 17482, "h": "97402165d2b8d8fe", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Required API keys by model:\");\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools using FunctionRegistry =====\n var registry = new FunctionRegistry();\n\n // get_weather\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n async (args) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = stringArray[Random.Shared.Next(4)],\n };\n\n return JsonSerializer.Serialize(weather);\n },\n providerName: \"Example\"\n );\n\n // get_time\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return JsonSerializer.Serialize(time);\n },\n providerName: \"Example\"\n );\n\n // calculate\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\n }\n catch (Exception ex)\n {\n return JsonSerializer.Serialize(new { error = ex.Message });\n }\n },\n providerName: \"Example\"\n );\n\n // Build middleware and handlers from registry\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\n .WithMessageTransformation()\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\n .WithMiddleware(toolCallMiddleware);\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n var turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = modelId,\n // Temperature = temperature,\n RunId = Guid.NewGuid().ToString(),\n ParentRunId = null,\n ThreadId = Guid.NewGuid().ToString(),\n }\n );\n\n // Collect messages from stream and execute tool calls in parallel as they arrive\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n var pendingToolCalls = new Dictionary>();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time and handle tool calls\n switch (message)\n {\n case ReasoningMessage reasoningMsg:\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\n {\n Console.Write(reasoningMsg.Reasoning);\n _ = textContent.Append(reasoningMsg.Reasoning);\n }\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n _ = textContent.Append(textMsg.Text);\n }\n break;\n\n case ToolCallMessage toolCall:\n // Start executing tool call immediately when we receive it\n _logger.LogInformation(\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\n toolCall.FunctionName,\n toolCall.ToolCallId\n );\n\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n\n default:\n // Ignore other message types\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls and Await Results =====\n if (pendingToolCalls.Count == 0)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Await Tool Results and Add to Conversation =====\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\n\n try\n {\n // Await all pending tool executions\n _ = await Task.WhenAll(pendingToolCalls.Values);\n\n // Collect results and add to conversation\n foreach (var kvp in pendingToolCalls)\n {\n var result = await kvp.Value;\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\n\n // Add each tool result to conversation history\n conversationHistory.Add(result);\n }\n\n _logger.LogInformation(\"Tool execution completed.\");\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Methods =====\n\n /// \n /// Executes a single tool call and returns the result message.\n /// \n private async Task ExecuteToolCallAsync(\n ToolCallMessage toolCall,\n IDictionary>> handlers\n )\n {\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\n var functionName = toolCall.FunctionName ?? \"unknown\";\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\n\n try\n {\n if (handlers.TryGetValue(functionName, out var handler))\n {\n var result = await handler(functionArgs);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = result,\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n else\n {\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "256b7b69ce737c03b0d8b71dfdfe48640140eabd": {"t": 1785756741.2986786, "n": 17356, "h": "337d65351e93f18b", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Required API keys by model:\");\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools using FunctionRegistry =====\n var registry = new FunctionRegistry();\n\n // get_weather\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n async (args) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = new[] { \"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\" }[Random.Shared.Next(4)],\n };\n\n return JsonSerializer.Serialize(weather);\n },\n providerName: \"Example\"\n );\n\n // get_time\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return JsonSerializer.Serialize(time);\n },\n providerName: \"Example\"\n );\n\n // calculate\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\n }\n catch (Exception ex)\n {\n return JsonSerializer.Serialize(new { error = ex.Message });\n }\n },\n providerName: \"Example\"\n );\n\n // Build middleware and handlers from registry\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\n .WithMessageTransformation()\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\n .WithMiddleware(toolCallMiddleware);\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n var turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = modelId,\n // Temperature = temperature,\n RunId = Guid.NewGuid().ToString(),\n ParentRunId = null,\n ThreadId = Guid.NewGuid().ToString(),\n }\n );\n\n // Collect messages from stream and execute tool calls in parallel as they arrive\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n var pendingToolCalls = new Dictionary>();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time and handle tool calls\n switch (message)\n {\n case ReasoningMessage reasoningMsg:\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\n {\n Console.Write(reasoningMsg.Reasoning);\n _ = textContent.Append(reasoningMsg.Reasoning);\n }\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n _ = textContent.Append(textMsg.Text);\n }\n break;\n\n case ToolCallMessage toolCall:\n // Start executing tool call immediately when we receive it\n _logger.LogInformation(\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\n toolCall.FunctionName,\n toolCall.ToolCallId\n );\n\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls and Await Results =====\n if (pendingToolCalls.Count == 0)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Await Tool Results and Add to Conversation =====\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\n\n try\n {\n // Await all pending tool executions\n _ = await Task.WhenAll(pendingToolCalls.Values);\n\n // Collect results and add to conversation\n foreach (var kvp in pendingToolCalls)\n {\n var result = await kvp.Value;\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\n\n // Add each tool result to conversation history\n conversationHistory.Add(result);\n }\n\n _logger.LogInformation(\"Tool execution completed.\");\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Methods =====\n\n /// \n /// Executes a single tool call and returns the result message.\n /// \n private async Task ExecuteToolCallAsync(\n ToolCallMessage toolCall,\n IDictionary>> handlers\n )\n {\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\n var functionName = toolCall.FunctionName ?? \"unknown\";\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\n\n try\n {\n if (handlers.TryGetValue(functionName, out var handler))\n {\n var result = await handler(functionArgs);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = result,\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n else\n {\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "e0a2749ec68628b6e6b3026f258b8d8451deea67": {"t": 1785756742.7041762, "n": 17313, "h": "e9fca20009040825", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Required API keys by model:\");\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools using FunctionRegistry =====\n var registry = new FunctionRegistry();\n\n // get_weather\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n async (args) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = new[] { \"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\" }[Random.Shared.Next(4)],\n };\n\n return JsonSerializer.Serialize(weather);\n },\n providerName: \"Example\"\n );\n\n // get_time\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return JsonSerializer.Serialize(time);\n },\n providerName: \"Example\"\n );\n\n // calculate\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\n }\n catch (Exception ex)\n {\n return JsonSerializer.Serialize(new { error = ex.Message });\n }\n },\n providerName: \"Example\"\n );\n\n // Build middleware and handlers from registry\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\n .WithMessageTransformation()\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\n .WithMiddleware(toolCallMiddleware);\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n var turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = modelId,\n // Temperature = temperature,\n RunId = Guid.NewGuid().ToString(),\n ParentRunId = null,\n ThreadId = Guid.NewGuid().ToString(),\n }\n );\n\n // Collect messages from stream and execute tool calls in parallel as they arrive\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n var pendingToolCalls = new Dictionary>();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time and handle tool calls\n switch (message)\n {\n case ReasoningMessage reasoningMsg:\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\n {\n Console.Write(reasoningMsg.Reasoning);\n _ = textContent.Append(reasoningMsg.Reasoning);\n }\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n _ = textContent.Append(textMsg.Text);\n }\n break;\n\n case ToolCallMessage toolCall:\n // Start executing tool call immediately when we receive it\n _logger.LogInformation(\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\n toolCall.FunctionName,\n toolCall.ToolCallId\n );\n\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls and Await Results =====\n if (pendingToolCalls.Count == 0)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Await Tool Results and Add to Conversation =====\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\n\n try\n {\n // Await all pending tool executions\n _ = await Task.WhenAll(pendingToolCalls.Values);\n\n // Collect results and add to conversation\n foreach (var kvp in pendingToolCalls)\n {\n var result = await kvp.Value;\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\n\n // Add each tool result to conversation history\n conversationHistory.Add(result);\n }\n\n _logger.LogInformation(\"Tool execution completed.\");\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Methods =====\n\n /// \n /// Executes a single tool call and returns the result message.\n /// \n private async Task ExecuteToolCallAsync(\n ToolCallMessage toolCall,\n IDictionary>> handlers\n )\n {\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\n var functionName = toolCall.FunctionName ?? \"unknown\";\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\n\n try\n {\n if (handlers.TryGetValue(functionName, out var handler))\n {\n var result = await handler(functionArgs);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = result,\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n else\n {\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "0830e654192e285f2269a0e39a8d0683c3019c8a": {"t": 1785756744.4616446, "n": 15316, "h": "410ae0460f177633", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmConfig.Services;\nusing AchieveAi.LmDotnetTools.LmCore.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"grok-4.1\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools =====\n var functions = new[]\n {\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n };\n\n var functionMap = new Dictionary>>\n {\n [\"get_weather\"] = async (args) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = new[] { \"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\" }[Random.Shared.Next(4)],\n };\n\n return JsonSerializer.Serialize(weather);\n },\n [\"get_time\"] = async (args) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return JsonSerializer.Serialize(time);\n },\n [\"calculate\"] = async (args) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\n }\n catch (Exception ex)\n {\n return JsonSerializer.Serialize(new { error = ex.Message });\n }\n },\n };\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution);\n\n // ===== Step 5: Build Middleware Chain =====\n // Chain structure (innermost to outermost):\n // Provider Agent -> ToolCallInjectionMiddleware -> MessageUpdateJoinerMiddleware\n // -> JsonFragmentUpdateMiddleware -> MessageTransformationMiddleware\n\n // Build middleware chain by wrapping one at a time (innermost first)\n IStreamingAgent agentWithMiddleware = providerAgent;\n\n // 1. ToolCallInjectionMiddleware (innermost - closest to provider)\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new ToolCallInjectionMiddleware(functions: functions, name: \"ToolCallInjection\")\n );\n\n // 2. MessageUpdateJoinerMiddleware\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\")\n );\n\n // 3. JsonFragmentUpdateMiddleware\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new JsonFragmentUpdateMiddleware()\n );\n\n // 4. MessageTransformationMiddleware (outermost)\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new MessageTransformationMiddleware(name: \"MessageTransformation\")\n );\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n int turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await agentWithMiddleware.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions { ModelId = modelId, Temperature = temperature }\n );\n\n // Collect messages from stream\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time\n switch (message)\n {\n case TextUpdateMessage textUpdate:\n Console.Write(textUpdate.Text);\n textContent.Append(textUpdate.Text);\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n textContent.Append(textMsg.Text);\n }\n break;\n\n case ReasoningUpdateMessage reasoningUpdate:\n // Optionally display reasoning\n break;\n\n case ToolCallUpdateMessage toolCallUpdate:\n // Display tool call progress\n if (!string.IsNullOrEmpty(toolCallUpdate.FunctionName))\n {\n _logger.LogDebug(\n \"[Tool Update] {FunctionName}: {Args}\",\n toolCallUpdate.FunctionName,\n toolCallUpdate.FunctionArgs\n );\n }\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls =====\n // Look for ToolsCallMessage (plural) in the messages\n var toolCallMessage = messages.OfType().FirstOrDefault();\n\n if (toolCallMessage == null)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Execute Tools =====\n _logger.LogInformation(\"Executing {Count} tool call(s)...\", toolCallMessage.ToolCalls.Count);\n\n foreach (var toolCall in toolCallMessage.ToolCalls)\n {\n _logger.LogInformation(\n \" [{Idx}] {Name} with args: {Args}\",\n toolCall.ToolCallIdx,\n toolCall.FunctionName,\n toolCall.FunctionArgs\n );\n }\n\n try\n {\n // Execute tools using ToolCallExecutor\n var toolResult = await ToolCallExecutor.ExecuteAsync(toolCallMessage, functionMap, logger: _logger);\n\n // Log tool results\n _logger.LogInformation(\"Tool execution completed:\");\n foreach (var result in toolResult.ToolCallResults)\n {\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", result.ToolCallId, resultPreview);\n }\n\n // Add tool results to conversation\n conversationHistory.Add(toolResult);\n\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "f5a5da908baf1680e1c826a97888e7cc787823fc": {"t": 1785756746.1042001, "n": 15816, "h": "b062e03e322d8aa2", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmConfig.Services;\nusing AchieveAi.LmDotnetTools.LmCore.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt)\n {\n _logger.LogInformation(\"=== OpenAI Grok4.1 Agentic Loop Example ===\\n\");\n\n // ===== Step 1: Check Grok4.1 availability =====\n var isAvailable = await _modelResolver.IsProviderAvailableAsync(\"xAI\");\n if (!isAvailable)\n {\n _logger.LogWarning(\"xAI provider is not available. Set XAI_API_KEY environment variable.\");\n return;\n }\n\n // ===== Step 2: Resolve Grok4.1 model =====\n var resolution = await _modelResolver.ResolveProviderAsync(\"grok-4.1\");\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve grok-4.1 model\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools =====\n var functions = new[]\n {\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false\n }\n ]\n },\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true\n }\n ]\n },\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true\n }\n ]\n }\n };\n\n var functionMap = new Dictionary>>\n {\n [\"get_weather\"] = async (args) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = new[] { \"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\" }[Random.Shared.Next(4)]\n };\n\n return JsonSerializer.Serialize(weather);\n },\n [\"get_time\"] = async (args) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\")\n };\n\n return JsonSerializer.Serialize(time);\n },\n [\"calculate\"] = async (args) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression });\n }\n catch (Exception ex)\n {\n return JsonSerializer.Serialize(new { error = ex.Message });\n }\n }\n };\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution);\n\n // ===== Step 5: Build Middleware Chain =====\n // Chain structure (innermost to outermost):\n // Provider Agent -> ToolCallInjectionMiddleware -> MessageUpdateJoinerMiddleware\n // -> JsonFragmentUpdateMiddleware -> MessageTransformationMiddleware\n\n // Build middleware chain by wrapping one at a time (innermost first)\n IStreamingAgent agentWithMiddleware = providerAgent;\n\n // 1. ToolCallInjectionMiddleware (innermost - closest to provider)\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new ToolCallInjectionMiddleware(functions: functions, name: \"ToolCallInjection\")\n );\n\n // 2. MessageUpdateJoinerMiddleware\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\")\n );\n\n // 3. JsonFragmentUpdateMiddleware\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new JsonFragmentUpdateMiddleware()\n );\n\n // 4. MessageTransformationMiddleware (outermost)\n agentWithMiddleware = new MiddlewareWrappingStreamingAgent(\n agentWithMiddleware,\n new MessageTransformationMiddleware(name: \"MessageTransformation\")\n );\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage\n {\n Text = prompt,\n Role = Role.User\n }\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n int turnCount = 0;\n const int maxTurns = 10; // Safety limit\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await agentWithMiddleware.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = \"grok-4.1\",\n Temperature = 0.7f\n }\n );\n\n // Collect messages from stream\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time\n switch (message)\n {\n case TextUpdateMessage textUpdate:\n Console.Write(textUpdate.Text);\n textContent.Append(textUpdate.Text);\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n textContent.Append(textMsg.Text);\n }\n break;\n\n case ReasoningUpdateMessage reasoningUpdate:\n // Optionally display reasoning\n break;\n\n case ToolCallUpdateMessage toolCallUpdate:\n // Display tool call progress\n if (!string.IsNullOrEmpty(toolCallUpdate.FunctionName))\n {\n _logger.LogDebug(\n \"[Tool Update] {FunctionName}: {Args}\",\n toolCallUpdate.FunctionName,\n toolCallUpdate.FunctionArgs\n );\n }\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls =====\n // Look for ToolsCallMessage (plural) in the messages\n var toolCallMessage = messages.OfType().FirstOrDefault();\n\n if (toolCallMessage == null)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Execute Tools =====\n _logger.LogInformation(\n \"Executing {Count} tool call(s)...\",\n toolCallMessage.ToolCalls.Count\n );\n\n foreach (var toolCall in toolCallMessage.ToolCalls)\n {\n _logger.LogInformation(\n \" [{Idx}] {Name} with args: {Args}\",\n toolCall.ToolCallIdx,\n toolCall.FunctionName,\n toolCall.FunctionArgs\n );\n }\n\n try\n {\n // Execute tools using ToolCallExecutor\n var toolResult = await ToolCallExecutor.ExecuteAsync(\n toolCallMessage,\n functionMap,\n logger: _logger\n );\n\n // Log tool results\n _logger.LogInformation(\"Tool execution completed:\");\n foreach (var result in toolResult.ToolCallResults)\n {\n var resultPreview = result.Result.Length > 100\n ? result.Result[..100] + \"...\"\n : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", result.ToolCallId, resultPreview);\n }\n\n // Add tool results to conversation\n conversationHistory.Add(toolResult);\n\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage\n {\n Text = $\"Error executing tools: {ex.Message}\",\n Role = Role.User\n };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "ddd4d029a734677e4b28624bd4663e1143d96e86": {"t": 1786067473.079505, "n": 17722, "h": "362d5ae78d9ce7a6", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Required API keys by model:\");\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools using FunctionRegistry =====\n var registry = new FunctionRegistry();\n\n // get_weather\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n async (args, _, _) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = stringArray[Random.Shared.Next(4)],\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(weather));\n },\n providerName: \"Example\"\n );\n\n // get_time\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(time));\n },\n providerName: \"Example\"\n );\n\n // calculate\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression }));\n }\n catch (Exception ex)\n {\n return ToolHandlerResult.FromError(JsonSerializer.Serialize(new { error = ex.Message }));\n }\n },\n providerName: \"Example\"\n );\n\n // Build middleware and handlers from registry\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\n .WithMessageTransformation()\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\n .WithMiddleware(toolCallMiddleware);\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n var turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = modelId,\n // Temperature = temperature,\n RunId = Guid.NewGuid().ToString(),\n ParentRunId = null,\n ThreadId = Guid.NewGuid().ToString(),\n }\n );\n\n // Collect messages from stream and execute tool calls in parallel as they arrive\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n var pendingToolCalls = new Dictionary>();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time and handle tool calls\n switch (message)\n {\n case ReasoningMessage reasoningMsg:\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\n {\n Console.Write(reasoningMsg.Reasoning);\n _ = textContent.Append(reasoningMsg.Reasoning);\n }\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n _ = textContent.Append(textMsg.Text);\n }\n break;\n\n case ToolCallMessage toolCall:\n // Start executing tool call immediately when we receive it\n _logger.LogInformation(\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\n toolCall.FunctionName,\n toolCall.ToolCallId\n );\n\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n\n default:\n // Ignore other message types\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls and Await Results =====\n if (pendingToolCalls.Count == 0)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Await Tool Results and Add to Conversation =====\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\n\n try\n {\n // Await all pending tool executions\n _ = await Task.WhenAll(pendingToolCalls.Values);\n\n // Collect results and add to conversation\n foreach (var kvp in pendingToolCalls)\n {\n var result = await kvp.Value;\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\n\n // Add each tool result to conversation history\n conversationHistory.Add(result);\n }\n\n _logger.LogInformation(\"Tool execution completed.\");\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Methods =====\n\n /// \n /// Executes a single tool call and returns the result message.\n /// \n private async Task ExecuteToolCallAsync(\n ToolCallMessage toolCall,\n IDictionary handlers\n )\n {\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\n var functionName = toolCall.FunctionName ?? \"unknown\";\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\n\n try\n {\n if (handlers.TryGetValue(functionName, out var handler))\n {\n var ctx = new ToolCallContext { ToolCallId = toolCall.ToolCallId };\n var result = await handler(functionArgs, ctx, CancellationToken.None);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = result.ResultText,\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n else\n {\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}, "3a25297b94f73fcda85e6e48bc8b48c2ee8e662d": {"t": 1786068324.15928, "n": 17722, "h": "362d5ae78d9ce7a6", "c": "using System.Text.Json;\nusing AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing AchieveAi.LmDotnetTools.LmCore.Middleware;\nusing AchieveAi.LmDotnetTools.LmCore.Utils;\nusing Microsoft.Extensions.Logging;\n\nnamespace LmConfigUsageExample;\n\n/// \n/// Example demonstrating an agentic loop using OpenAI provider connected to Grok4.1\n/// with the new middleware chain architecture.\n///\n/// Middleware Chain:\n/// Provider Agent => MessageTransformationMiddleware => JsonFragmentUpdateMiddleware\n/// => MessageUpdateJoinerMiddleware => ToolCallInjectionMiddleware\n///\n/// Agentic Loop:\n/// 1. Call middleware chain (streaming)\n/// 2. Collect all messages from the stream\n/// 3. Find ToolsCallMessage in the messages\n/// 4. If found, use ToolCallExecutor to execute tool calls asynchronously\n/// 5. Add ToolsCallResultMessage to conversation\n/// 6. Loop again\n/// 7. If no ToolsCallMessage, end the loop\n/// \npublic class OpenAiGrokAgenticExample\n{\n private readonly IModelResolver _modelResolver;\n private readonly IProviderAgentFactory _agentFactory;\n private readonly ILogger _logger;\n private static readonly string[] stringArray = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\n\n public OpenAiGrokAgenticExample(\n IModelResolver modelResolver,\n IProviderAgentFactory agentFactory,\n ILogger logger\n )\n {\n _modelResolver = modelResolver;\n _agentFactory = agentFactory;\n _logger = logger;\n }\n\n public async Task RunAsync(string prompt, string modelId = \"x-ai/grok-4.1-fast\", float temperature = 0.7f, int maxTurns = 10)\n {\n _logger.LogInformation(\"=== Agentic Loop Example with {ModelId} ===\\n\", modelId);\n\n // ===== Step 1: Resolve the specified model =====\n var resolution = await _modelResolver.ResolveProviderAsync(modelId);\n if (resolution == null)\n {\n _logger.LogError(\"Failed to resolve model: {ModelId}\", modelId);\n _logger.LogWarning(\"Make sure the model exists in models.json and the provider has an API key set.\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Required API keys by model:\");\n _logger.LogWarning(\" grok-4.1, x-ai/* -> XAI_API_KEY\");\n _logger.LogWarning(\" gpt-4.1*, openai/* -> OPENAI_API_KEY\");\n _logger.LogWarning(\" claude-3-*, anthropic/* -> ANTHROPIC_API_KEY\");\n _logger.LogWarning(\" openrouter/* -> OPENROUTER_API_KEY\");\n _logger.LogWarning(\" deepseek/* -> DEEPSEEK_API_KEY\");\n _logger.LogWarning(\" claude-sonnet-4-5 -> ClaudeAgentSDK (no API key needed if Claude Code authenticated)\");\n _logger.LogWarning(\"\");\n _logger.LogWarning(\"Use '--list-providers' to see provider status.\");\n return;\n }\n\n _logger.LogInformation(\"✓ Resolved model: {ModelName}\", resolution.EffectiveModelName);\n _logger.LogInformation(\" Provider: {ProviderName}\", resolution.EffectiveProviderName);\n _logger.LogInformation(\" Endpoint: {Endpoint}\\n\", resolution.Connection.EndpointUrl);\n\n // ===== Step 3: Define Tools using FunctionRegistry =====\n var registry = new FunctionRegistry();\n\n // get_weather\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_weather\",\n Description = \"Get the current weather for a location\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"location\",\n Description = \"The city name (e.g., 'San Francisco', 'New York')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n new FunctionParameterContract\n {\n Name = \"unit\",\n Description = \"Temperature unit: 'celsius' or 'fahrenheit'\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = false,\n },\n ],\n },\n async (args, _, _) =>\n {\n var weatherArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting weather for {Location}\", weatherArgs?.Location);\n\n await Task.Delay(100); // Simulate API call\n\n var weather = new\n {\n location = weatherArgs?.Location,\n temperature = Random.Shared.Next(60, 85),\n unit = weatherArgs?.Unit ?? \"fahrenheit\",\n condition = stringArray[Random.Shared.Next(4)],\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(weather));\n },\n providerName: \"Example\"\n );\n\n // get_time\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"get_time\",\n Description = \"Get the current time for a timezone\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"timezone\",\n Description = \"Timezone (e.g., 'PST', 'EST', 'UTC')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var timeArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Getting time for {Timezone}\", timeArgs?.Timezone);\n\n await Task.Delay(100); // Simulate API call\n\n var time = new\n {\n timezone = timeArgs?.Timezone,\n time = DateTime.UtcNow.ToString(\"HH:mm:ss\"),\n date = DateTime.UtcNow.ToString(\"yyyy-MM-dd\"),\n };\n\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(time));\n },\n providerName: \"Example\"\n );\n\n // calculate\n _ = registry.AddFunction(\n new FunctionContract\n {\n Name = \"calculate\",\n Description = \"Perform a mathematical calculation\",\n Parameters =\n [\n new FunctionParameterContract\n {\n Name = \"expression\",\n Description = \"Mathematical expression (e.g., '2 + 2', '10 * 5')\",\n ParameterType = SchemaHelper.CreateJsonSchemaFromType(typeof(string)),\n IsRequired = true,\n },\n ],\n },\n async (args, _, _) =>\n {\n var calcArgs = JsonSerializer.Deserialize(args);\n _logger.LogInformation(\"[TOOL] Calculating: {Expression}\", calcArgs?.Expression);\n\n await Task.Delay(50); // Simulate processing\n\n // Simple eval-like calculation (for demo purposes only - unsafe in production)\n try\n {\n var result = EvaluateSimpleExpression(calcArgs?.Expression ?? \"0\");\n return ToolHandlerResult.FromText(JsonSerializer.Serialize(new { result, expression = calcArgs?.Expression }));\n }\n catch (Exception ex)\n {\n return ToolHandlerResult.FromError(JsonSerializer.Serialize(new { error = ex.Message }));\n }\n },\n providerName: \"Example\"\n );\n\n // Build middleware and handlers from registry\n var (toolCallMiddleware, functionHandlers) = registry.BuildToolCallComponents(name: \"ToolCallInjection\");\n\n // ===== Step 4: Create Provider Agent =====\n var providerAgent = _agentFactory.CreateStreamingAgent(resolution)\n .WithMessageTransformation()\n .WithMiddleware(new JsonFragmentUpdateMiddleware())\n .WithMiddleware(new MessageUpdateJoinerMiddleware(name: \"MessageJoiner\"))\n .WithMiddleware(toolCallMiddleware);\n\n // ===== Step 6: Start Conversation =====\n var conversationHistory = new List\n {\n new TextMessage { Text = prompt, Role = Role.User },\n };\n\n _logger.LogInformation(\"User: {Prompt}\\n\", prompt);\n\n // ===== Step 7: Agentic Loop =====\n var turnCount = 0;\n\n while (turnCount < maxTurns)\n {\n turnCount++;\n _logger.LogInformation(\"=== Turn {Turn} ===\", turnCount);\n\n // Call LLM through middleware chain (streaming)\n var streamTask = await providerAgent.GenerateReplyStreamingAsync(\n conversationHistory,\n new GenerateReplyOptions\n {\n ModelId = modelId,\n // Temperature = temperature,\n RunId = Guid.NewGuid().ToString(),\n ParentRunId = null,\n ThreadId = Guid.NewGuid().ToString(),\n }\n );\n\n // Collect messages from stream and execute tool calls in parallel as they arrive\n var messages = new List();\n var textContent = new System.Text.StringBuilder();\n var pendingToolCalls = new Dictionary>();\n\n await foreach (var message in streamTask)\n {\n messages.Add(message);\n\n // Display streaming content in real-time and handle tool calls\n switch (message)\n {\n case ReasoningMessage reasoningMsg:\n if (!string.IsNullOrEmpty(reasoningMsg.Reasoning))\n {\n Console.Write(reasoningMsg.Reasoning);\n _ = textContent.Append(reasoningMsg.Reasoning);\n }\n break;\n\n case TextMessage textMsg:\n if (!string.IsNullOrEmpty(textMsg.Text))\n {\n Console.Write(textMsg.Text);\n _ = textContent.Append(textMsg.Text);\n }\n break;\n\n case ToolCallMessage toolCall:\n // Start executing tool call immediately when we receive it\n _logger.LogInformation(\n \"[Tool Call] Starting execution: {FunctionName} (id: {ToolCallId})\",\n toolCall.FunctionName,\n toolCall.ToolCallId\n );\n\n var executionTask = ExecuteToolCallAsync(toolCall, functionHandlers);\n pendingToolCalls[toolCall.ToolCallId ?? $\"call_{pendingToolCalls.Count}\"] = executionTask;\n break;\n\n case UsageMessage usage:\n _logger.LogDebug(\n \"[Usage] Prompt: {Prompt}, Completion: {Completion}\",\n usage.Usage.PromptTokens,\n usage.Usage.CompletionTokens\n );\n break;\n\n default:\n // Ignore other message types\n break;\n }\n }\n\n Console.WriteLine(); // New line after streaming\n\n // Add all messages to conversation history\n conversationHistory.AddRange(messages);\n\n _logger.LogInformation(\"Received {Count} message(s)\", messages.Count);\n\n // ===== Step 8: Check for Tool Calls and Await Results =====\n if (pendingToolCalls.Count == 0)\n {\n // No tool calls, conversation complete\n _logger.LogInformation(\"✓ No tool calls - conversation complete\\n\");\n\n if (textContent.Length > 0)\n {\n _logger.LogInformation(\"Assistant: {Response}\", textContent.ToString());\n }\n\n break;\n }\n\n // ===== Step 9: Await Tool Results and Add to Conversation =====\n _logger.LogInformation(\"Awaiting {Count} tool call result(s)...\", pendingToolCalls.Count);\n\n try\n {\n // Await all pending tool executions\n _ = await Task.WhenAll(pendingToolCalls.Values);\n\n // Collect results and add to conversation\n foreach (var kvp in pendingToolCalls)\n {\n var result = await kvp.Value;\n var resultPreview = result.Result.Length > 100 ? result.Result[..100] + \"...\" : result.Result;\n _logger.LogInformation(\" Result for {ToolCallId}: {Result}\", kvp.Key, resultPreview);\n\n // Add each tool result to conversation history\n conversationHistory.Add(result);\n }\n\n _logger.LogInformation(\"Tool execution completed.\");\n Console.WriteLine(); // Spacing\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Tool execution failed\");\n\n // Add error message to conversation\n var errorMessage = new TextMessage { Text = $\"Error executing tools: {ex.Message}\", Role = Role.User };\n conversationHistory.Add(errorMessage);\n }\n }\n\n if (turnCount >= maxTurns)\n {\n _logger.LogWarning(\"⚠ Max turns reached - stopping conversation\");\n }\n\n _logger.LogInformation(\"\\n=== Agentic Loop Complete ===\");\n }\n\n // ===== Helper Methods =====\n\n /// \n /// Executes a single tool call and returns the result message.\n /// \n private async Task ExecuteToolCallAsync(\n ToolCallMessage toolCall,\n IDictionary handlers\n )\n {\n var toolCallId = toolCall.ToolCallId ?? $\"call_{toolCall.Index}\";\n var functionName = toolCall.FunctionName ?? \"unknown\";\n var functionArgs = toolCall.FunctionArgs ?? \"{}\";\n\n try\n {\n if (handlers.TryGetValue(functionName, out var handler))\n {\n var ctx = new ToolCallContext { ToolCallId = toolCall.ToolCallId };\n var result = await handler(functionArgs, ctx, CancellationToken.None);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = result.ResultText,\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n else\n {\n _logger.LogWarning(\"No handler found for function: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = $\"Unknown function: {functionName}\" }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Error executing tool call: {FunctionName}\", functionName);\n return new ToolCallResultMessage\n {\n ToolCallId = toolCallId,\n Result = JsonSerializer.Serialize(new { error = ex.Message }),\n Role = Role.User,\n FromAgent = toolCall.FromAgent,\n GenerationId = toolCall.GenerationId,\n };\n }\n }\n\n // ===== Helper Classes =====\n\n private class WeatherArgs\n {\n public string? Location { get; set; }\n public string? Unit { get; set; }\n }\n\n private class TimeArgs\n {\n public string? Timezone { get; set; }\n }\n\n private class CalculateArgs\n {\n public string? Expression { get; set; }\n }\n\n // Simple expression evaluator (for demo purposes only - unsafe in production)\n private static double EvaluateSimpleExpression(string expression)\n {\n expression = expression.Replace(\" \", \"\");\n\n // Very basic calculator - only handles +, -, *, /\n if (expression.Contains('+'))\n {\n var parts = expression.Split('+');\n return double.Parse(parts[0]) + double.Parse(parts[1]);\n }\n if (expression.Contains('-'))\n {\n var parts = expression.Split('-');\n return double.Parse(parts[0]) - double.Parse(parts[1]);\n }\n if (expression.Contains('*'))\n {\n var parts = expression.Split('*');\n return double.Parse(parts[0]) * double.Parse(parts[1]);\n }\n if (expression.Contains('/'))\n {\n var parts = expression.Split('/');\n return double.Parse(parts[0]) / double.Parse(parts[1]);\n }\n\n return double.Parse(expression);\n }\n}\n"}}, "tests/LmConfig.Tests/UnifiedAgentTests.cs": {"5df9a37e189f520689e30ebef085dd06c50d907c": {"t": 1785745826.36667, "n": 20653, "h": "2ac87503efdbef2b", "c": "using AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmConfig.Capabilities;\nusing AchieveAi.LmDotnetTools.LmConfig.Models;\nusing AchieveAi.LmDotnetTools.LmCore.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Options;\nusing Moq;\n\nnamespace LmConfig.Tests;\n\npublic class UnifiedAgentTests\n{\n private static ServiceCollection CreateServices(AppConfig config)\n {\n var services = new ServiceCollection();\n services.AddLogging();\n services.AddSingleton(Options.Create(config));\n services.AddSingleton<\n AchieveAi.LmDotnetTools.LmConfig.Http.IHttpHandlerBuilder,\n AchieveAi.LmDotnetTools.LmConfig.Http.HandlerBuilder>();\n return services;\n }\n\n private static async IAsyncEnumerable CreateEmptyAsyncEnumerable()\n {\n await Task.CompletedTask; // Make it async\n yield break; // Return empty sequence\n }\n\n [Fact]\n public void UnifiedAgent_ShouldImplementIAgent()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"test-model\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"TestProvider\",\n ModelName = \"test-model-name\",\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 1.0, CompletionPerMillion = 2.0 },\n Tags = [\"test\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"TestProvider\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://test.example.com\",\n ApiKeyEnvironmentVariable = \"TEST_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n var services = CreateServices(config);\n services.AddSingleton();\n services.AddSingleton();\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n\n // Act\n var agent = serviceProvider.GetRequiredService();\n\n // Assert\n Assert.NotNull(agent);\n Assert.IsAssignableFrom(agent);\n Assert.IsAssignableFrom(agent);\n }\n\n [Fact]\n public async Task ModelResolver_ShouldResolveProvider()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"test-model\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"TestProvider\",\n ModelName = \"test-model-name\",\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 1.0, CompletionPerMillion = 2.0 },\n Tags = [\"test\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"TestProvider\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://test.example.com\",\n ApiKeyEnvironmentVariable = \"TEST_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n // Set up the required environment variable for the test\n Environment.SetEnvironmentVariable(\"TEST_API_KEY\", \"test-api-key\");\n\n try\n {\n var services = CreateServices(config);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var resolver = serviceProvider.GetRequiredService();\n\n // Act\n var resolution = await resolver.ResolveProviderAsync(\"test-model\");\n\n // Assert\n Assert.NotNull(resolution);\n Assert.Equal(\"test-model\", resolution.Model.Id);\n Assert.Equal(\"TestProvider\", resolution.Provider.Name);\n Assert.Equal(\"test-model-name\", resolution.Provider.ModelName);\n Assert.NotNull(resolution.Connection);\n Assert.Equal(\"https://test.example.com\", resolution.Connection.EndpointUrl);\n }\n finally\n {\n // Clean up the environment variable\n Environment.SetEnvironmentVariable(\"TEST_API_KEY\", null);\n }\n }\n\n [Fact]\n public async Task ModelResolver_ShouldReturnNullForUnknownModel()\n {\n // Arrange\n var config = new AppConfig\n {\n Models = [],\n ProviderRegistry = new Dictionary(),\n };\n\n var services = CreateServices(config);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var resolver = serviceProvider.GetRequiredService();\n\n // Act\n var resolution = await resolver.ResolveProviderAsync(\"unknown-model\");\n\n // Assert\n Assert.Null(resolution);\n }\n\n [Fact]\n public async Task UnifiedAgent_ShouldUpdateModelIdWhenForwardingToProviderAgent()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"gpt-4.1\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"OpenRouter\",\n ModelName = \"openai/gpt-4.1\", // Different from the model ID\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 2.0, CompletionPerMillion = 8.0 },\n Tags = [\"openai-compatible\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"OpenRouter\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://openrouter.ai/api/v1\",\n ApiKeyEnvironmentVariable = \"OPENROUTER_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n var services = CreateServices(config);\n\n // Create a mock model resolver that returns a predefined resolution\n var mockModelResolver = new Mock();\n var expectedResolution = new ProviderResolution\n {\n Model = config.Models[0],\n Provider = config.Models[0].Providers[0],\n Connection = config.ProviderRegistry[\"OpenRouter\"],\n };\n\n mockModelResolver\n .Setup(r =>\n r.ResolveProviderAsync(\n It.IsAny(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .ReturnsAsync(expectedResolution);\n\n // Create a mock agent factory that captures the options passed to it\n var mockAgentFactory = new Mock();\n var mockAgent = new Mock();\n\n GenerateReplyOptions? capturedOptions = null;\n\n mockAgent\n .Setup(a =>\n a.GenerateReplyAsync(\n It.IsAny>(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .Callback, GenerateReplyOptions?, CancellationToken>(\n (messages, options, ct) => capturedOptions = options\n )\n .ReturnsAsync(\n [\n new TextMessage { Role = Role.Assistant, Text = \"Test response\" },\n ]\n );\n\n mockAgentFactory.Setup(f => f.CreateAgent(It.IsAny())).Returns(mockAgent.Object);\n\n services.AddSingleton(mockModelResolver.Object);\n services.AddSingleton(mockAgentFactory.Object);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var unifiedAgent = serviceProvider.GetRequiredService();\n\n var messages = new List\n {\n new TextMessage { Role = Role.User, Text = \"Hello, world!\" },\n };\n\n var originalOptions = new GenerateReplyOptions\n {\n ModelId = \"gpt-4.1\", // User specifies the logical model ID\n Temperature = 0.7f,\n };\n\n // Act\n await unifiedAgent.GenerateReplyAsync(messages, originalOptions);\n\n // Assert\n Assert.NotNull(capturedOptions);\n Assert.Equal(\"openai/gpt-4.1\", capturedOptions.ModelId); // Should be updated to provider's model name\n Assert.Equal(0.7f, capturedOptions.Temperature); // Other properties should be preserved\n\n // Verify the mock was called\n mockAgent.Verify(\n a =>\n a.GenerateReplyAsync(\n It.IsAny>(),\n It.Is(o => o.ModelId == \"openai/gpt-4.1\"),\n It.IsAny()\n ),\n Times.Once\n );\n }\n\n [Fact]\n public async Task UnifiedAgent_ShouldUpdateModelIdForStreamingRequests()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"claude-3-sonnet\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"OpenRouter\",\n ModelName = \"anthropic/claude-3-sonnet\", // Different from the model ID\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 3.0, CompletionPerMillion = 15.0 },\n Tags = [\"openai-compatible\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"OpenRouter\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://openrouter.ai/api/v1\",\n ApiKeyEnvironmentVariable = \"OPENROUTER_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n var services = CreateServices(config);\n\n // Create a mock model resolver that returns a predefined resolution\n var mockModelResolver = new Mock();\n var expectedResolution = new ProviderResolution\n {\n Model = config.Models[0],\n Provider = config.Models[0].Providers[0],\n Connection = config.ProviderRegistry[\"OpenRouter\"],\n };\n\n mockModelResolver\n .Setup(r =>\n r.ResolveProviderAsync(\n It.IsAny(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .ReturnsAsync(expectedResolution);\n\n // Create a mock agent factory that captures the options passed to it\n var mockAgentFactory = new Mock();\n var mockAgent = new Mock();\n\n GenerateReplyOptions? capturedOptions = null;\n\n mockAgent\n .Setup(a =>\n a.GenerateReplyStreamingAsync(\n It.IsAny>(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .Callback, GenerateReplyOptions?, CancellationToken>(\n (messages, options, ct) => capturedOptions = options\n )\n .ReturnsAsync(CreateEmptyAsyncEnumerable());\n\n mockAgentFactory.Setup(f => f.CreateStreamingAgent(It.IsAny())).Returns(mockAgent.Object);\n\n services.AddSingleton(mockModelResolver.Object);\n services.AddSingleton(mockAgentFactory.Object);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var unifiedAgent = serviceProvider.GetRequiredService();\n\n var messages = new List\n {\n new TextMessage { Role = Role.User, Text = \"Hello, world!\" },\n };\n\n var originalOptions = new GenerateReplyOptions\n {\n ModelId = \"claude-3-sonnet\", // User specifies the logical model ID\n Temperature = 0.5f,\n MaxToken = 2000,\n };\n\n // Act\n await unifiedAgent.GenerateReplyStreamingAsync(messages, originalOptions);\n\n // Assert\n Assert.NotNull(capturedOptions);\n Assert.Equal(\"anthropic/claude-3-sonnet\", capturedOptions.ModelId); // Should be updated to provider's model name\n Assert.Equal(0.5f, capturedOptions.Temperature); // Other properties should be preserved\n Assert.Equal(2000, capturedOptions.MaxToken); // Other properties should be preserved\n\n // Verify the mock was called\n mockAgent.Verify(\n a =>\n a.GenerateReplyStreamingAsync(\n It.IsAny>(),\n It.Is(o => o.ModelId == \"anthropic/claude-3-sonnet\"),\n It.IsAny()\n ),\n Times.Once\n );\n }\n\n [Fact]\n public async Task UnifiedAgent_ShouldCreateOptionsWithEffectiveModelNameWhenNullOptions()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"deepseek-r1\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"DeepSeek\",\n ModelName = \"deepseek-reasoner\", // Different from the model ID\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 0.55, CompletionPerMillion = 2.19 },\n Tags = [\"reasoning\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"DeepSeek\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://api.deepseek.com/v1\",\n ApiKeyEnvironmentVariable = \"DEEPSEEK_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n var services = CreateServices(config);\n\n // Create a mock model resolver that returns a predefined resolution\n var mockModelResolver = new Mock();\n var expectedResolution = new ProviderResolution\n {\n Model = config.Models[0],\n Provider = config.Models[0].Providers[0],\n Connection = config.ProviderRegistry[\"DeepSeek\"],\n };\n\n mockModelResolver\n .Setup(r =>\n r.ResolveProviderAsync(\n It.IsAny(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .ReturnsAsync(expectedResolution);\n\n // Create a mock agent factory that captures the options passed to it\n var mockAgentFactory = new Mock();\n var mockAgent = new Mock();\n\n GenerateReplyOptions? capturedOptions = null;\n\n mockAgent\n .Setup(a =>\n a.GenerateReplyAsync(\n It.IsAny>(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .Callback, GenerateReplyOptions?, CancellationToken>(\n (messages, options, ct) => capturedOptions = options\n )\n .ReturnsAsync(\n [\n new TextMessage { Role = Role.Assistant, Text = \"Test response\" },\n ]\n );\n\n mockAgentFactory.Setup(f => f.CreateAgent(It.IsAny())).Returns(mockAgent.Object);\n\n services.AddSingleton(mockModelResolver.Object);\n services.AddSingleton(mockAgentFactory.Object);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var unifiedAgent = serviceProvider.GetRequiredService();\n\n var messages = new List\n {\n new TextMessage { Role = Role.User, Text = \"Hello, world!\" },\n };\n\n // Pass null options but the agent should still work by getting model from context\n // We need to test this differently since we can't pass null options and expect it to work\n // Let's test with minimal options instead\n var minimalOptions = new GenerateReplyOptions { ModelId = \"deepseek-r1\" };\n\n // Act\n await unifiedAgent.GenerateReplyAsync(messages, minimalOptions);\n\n // Assert\n Assert.NotNull(capturedOptions);\n Assert.Equal(\"deepseek-reasoner\", capturedOptions.ModelId); // Should be updated to provider's model name\n\n // Verify the mock was called\n mockAgent.Verify(\n a =>\n a.GenerateReplyAsync(\n It.IsAny>(),\n It.Is(o => o.ModelId == \"deepseek-reasoner\"),\n It.IsAny()\n ),\n Times.Once\n );\n }\n}\n"}, "988e64991217e152367177f40ce7cb270d94cb51": {"t": 1785756730.171627, "n": 20653, "h": "2ac87503efdbef2b", "c": "using AchieveAi.LmDotnetTools.LmConfig.Agents;\nusing AchieveAi.LmDotnetTools.LmConfig.Capabilities;\nusing AchieveAi.LmDotnetTools.LmConfig.Models;\nusing AchieveAi.LmDotnetTools.LmCore.Agents;\nusing AchieveAi.LmDotnetTools.LmCore.Core;\nusing AchieveAi.LmDotnetTools.LmCore.Messages;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Options;\nusing Moq;\n\nnamespace LmConfig.Tests;\n\npublic class UnifiedAgentTests\n{\n private static ServiceCollection CreateServices(AppConfig config)\n {\n var services = new ServiceCollection();\n services.AddLogging();\n services.AddSingleton(Options.Create(config));\n services.AddSingleton<\n AchieveAi.LmDotnetTools.LmConfig.Http.IHttpHandlerBuilder,\n AchieveAi.LmDotnetTools.LmConfig.Http.HandlerBuilder>();\n return services;\n }\n\n private static async IAsyncEnumerable CreateEmptyAsyncEnumerable()\n {\n await Task.CompletedTask; // Make it async\n yield break; // Return empty sequence\n }\n\n [Fact]\n public void UnifiedAgent_ShouldImplementIAgent()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"test-model\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"TestProvider\",\n ModelName = \"test-model-name\",\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 1.0, CompletionPerMillion = 2.0 },\n Tags = [\"test\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"TestProvider\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://test.example.com\",\n ApiKeyEnvironmentVariable = \"TEST_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n var services = CreateServices(config);\n services.AddSingleton();\n services.AddSingleton();\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n\n // Act\n var agent = serviceProvider.GetRequiredService();\n\n // Assert\n Assert.NotNull(agent);\n Assert.IsAssignableFrom(agent);\n Assert.IsAssignableFrom(agent);\n }\n\n [Fact]\n public async Task ModelResolver_ShouldResolveProvider()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"test-model\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"TestProvider\",\n ModelName = \"test-model-name\",\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 1.0, CompletionPerMillion = 2.0 },\n Tags = [\"test\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"TestProvider\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://test.example.com\",\n ApiKeyEnvironmentVariable = \"TEST_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n // Set up the required environment variable for the test\n Environment.SetEnvironmentVariable(\"TEST_API_KEY\", \"test-api-key\");\n\n try\n {\n var services = CreateServices(config);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var resolver = serviceProvider.GetRequiredService();\n\n // Act\n var resolution = await resolver.ResolveProviderAsync(\"test-model\");\n\n // Assert\n Assert.NotNull(resolution);\n Assert.Equal(\"test-model\", resolution.Model.Id);\n Assert.Equal(\"TestProvider\", resolution.Provider.Name);\n Assert.Equal(\"test-model-name\", resolution.Provider.ModelName);\n Assert.NotNull(resolution.Connection);\n Assert.Equal(\"https://test.example.com\", resolution.Connection.EndpointUrl);\n }\n finally\n {\n // Clean up the environment variable\n Environment.SetEnvironmentVariable(\"TEST_API_KEY\", null);\n }\n }\n\n [Fact]\n public async Task ModelResolver_ShouldReturnNullForUnknownModel()\n {\n // Arrange\n var config = new AppConfig\n {\n Models = [],\n ProviderRegistry = new Dictionary(),\n };\n\n var services = CreateServices(config);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var resolver = serviceProvider.GetRequiredService();\n\n // Act\n var resolution = await resolver.ResolveProviderAsync(\"unknown-model\");\n\n // Assert\n Assert.Null(resolution);\n }\n\n [Fact]\n public async Task UnifiedAgent_ShouldUpdateModelIdWhenForwardingToProviderAgent()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"gpt-4.1\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"OpenRouter\",\n ModelName = \"openai/gpt-4.1\", // Different from the model ID\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 2.0, CompletionPerMillion = 8.0 },\n Tags = [\"openai-compatible\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"OpenRouter\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://openrouter.ai/api/v1\",\n ApiKeyEnvironmentVariable = \"OPENROUTER_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n var services = CreateServices(config);\n\n // Create a mock model resolver that returns a predefined resolution\n var mockModelResolver = new Mock();\n var expectedResolution = new ProviderResolution\n {\n Model = config.Models[0],\n Provider = config.Models[0].Providers[0],\n Connection = config.ProviderRegistry[\"OpenRouter\"],\n };\n\n mockModelResolver\n .Setup(r =>\n r.ResolveProviderAsync(\n It.IsAny(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .ReturnsAsync(expectedResolution);\n\n // Create a mock agent factory that captures the options passed to it\n var mockAgentFactory = new Mock();\n var mockAgent = new Mock();\n\n GenerateReplyOptions? capturedOptions = null;\n\n mockAgent\n .Setup(a =>\n a.GenerateReplyAsync(\n It.IsAny>(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .Callback, GenerateReplyOptions?, CancellationToken>(\n (messages, options, ct) => capturedOptions = options\n )\n .ReturnsAsync(\n [\n new TextMessage { Role = Role.Assistant, Text = \"Test response\" },\n ]\n );\n\n mockAgentFactory.Setup(f => f.CreateAgent(It.IsAny())).Returns(mockAgent.Object);\n\n services.AddSingleton(mockModelResolver.Object);\n services.AddSingleton(mockAgentFactory.Object);\n services.AddSingleton();\n\n var serviceProvider = services.BuildServiceProvider();\n var unifiedAgent = serviceProvider.GetRequiredService();\n\n var messages = new List\n {\n new TextMessage { Role = Role.User, Text = \"Hello, world!\" },\n };\n\n var originalOptions = new GenerateReplyOptions\n {\n ModelId = \"gpt-4.1\", // User specifies the logical model ID\n Temperature = 0.7f,\n };\n\n // Act\n await unifiedAgent.GenerateReplyAsync(messages, originalOptions);\n\n // Assert\n Assert.NotNull(capturedOptions);\n Assert.Equal(\"openai/gpt-4.1\", capturedOptions.ModelId); // Should be updated to provider's model name\n Assert.Equal(0.7f, capturedOptions.Temperature); // Other properties should be preserved\n\n // Verify the mock was called\n mockAgent.Verify(\n a =>\n a.GenerateReplyAsync(\n It.IsAny>(),\n It.Is(o => o.ModelId == \"openai/gpt-4.1\"),\n It.IsAny()\n ),\n Times.Once\n );\n }\n\n [Fact]\n public async Task UnifiedAgent_ShouldUpdateModelIdForStreamingRequests()\n {\n // Arrange\n var config = new AppConfig\n {\n Models =\n [\n new ModelConfig\n {\n Id = \"claude-3-sonnet\",\n Capabilities = new ModelCapabilities\n {\n TokenLimits = new TokenLimits\n {\n MaxContextTokens = 4000,\n MaxOutputTokens = 1000,\n RecommendedMaxPromptTokens = 3000,\n },\n },\n Providers =\n [\n new ProviderConfig\n {\n Name = \"OpenRouter\",\n ModelName = \"anthropic/claude-3-sonnet\", // Different from the model ID\n Priority = 1,\n Pricing = new PricingConfig { PromptPerMillion = 3.0, CompletionPerMillion = 15.0 },\n Tags = [\"openai-compatible\"],\n },\n ],\n },\n ],\n ProviderRegistry = new Dictionary\n {\n [\"OpenRouter\"] = new ProviderConnectionInfo\n {\n EndpointUrl = \"https://openrouter.ai/api/v1\",\n ApiKeyEnvironmentVariable = \"OPENROUTER_API_KEY\",\n Compatibility = \"OpenAI\",\n },\n },\n };\n\n var services = CreateServices(config);\n\n // Create a mock model resolver that returns a predefined resolution\n var mockModelResolver = new Mock();\n var expectedResolution = new ProviderResolution\n {\n Model = config.Models[0],\n Provider = config.Models[0].Providers[0],\n Connection = config.ProviderRegistry[\"OpenRouter\"],\n };\n\n mockModelResolver\n .Setup(r =>\n r.ResolveProviderAsync(\n It.IsAny(),\n It.IsAny(),\n It.IsAny()\n )\n )\n .ReturnsAsync(expectedResolution);\n\n // Create a mock agent factory that captures the options passed to it\n var mockAgentFactory = new Mock();\n var mockAgent = new Mock