Skip to content

Tornado Runner ​

Overview ​

TornadoRunner provides advanced execution control for agents, including features like guardrails, monitoring, and sophisticated error handling. It's designed for production scenarios requiring robust agent management.

Quick Start ​

csharp
using LlmTornado.Agents;

TornadoAgent agent = new TornadoAgent(api, model);

// TornadoRunner is used internally when calling RunAsync
Conversation result = await agent.RunAsync("Your query here");

Features ​

Event Handling ​

  • Monitor agent execution in real-time
  • Track tool calls and completions
  • Receive streaming updates
  • Log all agent activities

Error Recovery ​

  • Automatic retry on transient failures
  • Graceful degradation
  • Fallback strategies
  • Comprehensive error reporting

Performance Monitoring ​

  • Track execution times
  • Monitor token usage
  • Measure tool call latency
  • Generate performance metrics

Agents Method for Running Tornado-Runner ​

csharp
  public async Task<Conversation> RunAsync(
        string input = "", 
        List<ChatMessage>? appendMessages = null, 
        GuardRailFunction? inputGuardRailFunction = null,
        bool? streaming = null,
        Func<AgentRunnerEvents, ValueTask>? onAgentRunnerEvent = null, 
        int maxTurns = 10, 
        string responseId = "",
        Func<string, ValueTask<bool>>? toolPermissionHandle = null, 
        bool singleTurn = false,
        CancellationToken cancellationToken = default)

string input ​

User Prompt for the agent to process next

List<ChatMessage>? appendMessages ​

Optional list of messages to append to the conversation history for context

GuardRailFunction? inputGuardRailFunction ​

Optional input guardrail to stop execution

bool? streaming ​

Optional enable streaming for the agent run loop (requires OnAgentRunnerEvent set to get streaming results)

⚡ Func<AgentRunnerEvents, ValueTask>? onAgentRunnerEvent ⚡ ​

Detailed Agent loop event system that provides insight into the agent loop as well as all the streaming results and errors.

int maxTurns ​

Max agent loops you want the agent to run on a single turn [Default = 10].

Agent loop:

  • Prompt->Tool Request(turn 1)->Tool Result->Agent Response(turn 2)
  • Prompt->Tool Request(turn 1)->Tool Result->Tool Request(turn 2)->Tool Result->Agent Response(turn 3)

string responseId ​

Optional ResponseId for responseAPI thread usage

Func<string, ValueTask<bool>>? toolPermissionHandle ​

Optional Handle to take care of Passing tool permission requirement to the runner while the loop is stalled. (Only required when you have tool permission = true set on a agent tool)

bool singleTurn ​

Optional debugging feature to stop agent from running tools

CancellationToken cancellationToken ​

Cancallation token to stop the agent execution early.

Advanced Usage ​

Custom Event Handlers ​

csharp
ValueTask HandleRunnerEvents(AgentRunnerEvents runEvent)
{
    switch (runEvent.EventType)
    {
        case AgentRunnerEventTypes.Start:
            Console.WriteLine("Agent started");
            break;
            
        case AgentRunnerEventTypes.ToolCall:
            if (runEvent is AgentRunnerToolCallEvent toolEvent)
            {
                Console.WriteLine($"Tool called: {toolEvent.ToolName}");
            }
            break;
            
        case AgentRunnerEventTypes.Streaming:
            // Handle streaming
            break;
            
        case AgentRunnerEventTypes.Complete:
            Console.WriteLine("Agent completed");
            break;
            
        case AgentRunnerEventTypes.Error:
            if (runEvent is AgentRunnerErrorEvent errorEvent)
            {
                Console.WriteLine($"Error: {errorEvent.Error.Message}");
            }
            break;
    }
    
    return ValueTask.CompletedTask;
}

Conversation result = await agent.RunAsync(
    input,
    onAgentRunnerEvent: HandleRunnerEvents
);

Execution Configuration ​

csharp
Conversation result = await agent.RunAsync(
    input: "Your query",
    appendMessages: previousMessages,
    streaming: true,
    onAgentRunnerEvent: eventHandler
);

Using Guardrails ​

csharp
using LlmTornado.Agents;

public struct IsMath
{
    public string Reasoning { get; set; }
    public bool IsMathRequest { get; set; }
}
    
async ValueTask<GuardRailFunctionOutput> MathGuardRail(string? input = "")
{
    TornadoAgent mathGuardrail = new TornadoAgent(api, ChatModel.OpenAi.Gpt41.V41Mini, instructions: "Check if the user is asking you a Math related question.", outputSchema: typeof(IsMath));

    Conversation result = await TornadoRunner.RunAsync(mathGuardrail, input);

    IsMath? isMath = result.Messages.Last().Content.JsonDecode<IsMath>();

    return new GuardRailFunctionOutput(isMath?.Reasoning ?? "", !isMath?.IsMathRequest ?? false);
}

TornadoAgent agent = new TornadoAgent(
    client: api,
    model: ChatModel.OpenAi.Gpt41.V41,
    instructions: "You are a useful assistant"
);

try
{
    Conversation result = await agent.RunAsync("What is the weather?", inputGuardRailFunction: MathGuardRail);

    Console.WriteLine(result.Messages.Last().Content);
}
catch (GuardRailTriggerException guardRailEx)
{
      Console.WriteLine(guardRailEx.message)
}

Tool Permission ​

csharp
[JsonConverter(typeof(StringEnumConverter))]
public enum Unit
{
    Celsius, 
    Fahrenheit
}

[Description("Get the current weather in a given location")]
public static string GetCurrentWeather(
    [Description("The city and state, e.g. Boston, MA")] string location,
    [Description("unit of temperature measurement in C or F")] Unit unit = Unit.Celsius)
{
    // Call the weather API here.
    return $"31 C";
}

 TornadoAgent agent = new TornadoAgent(
    api, 
    ChatModel.OpenAi.Gpt41.V41Mini, 
    instructions: "You are a useful assistant.",
    tools: [GetCurrentWeather],
    streaming: true,
    toolPermissionRequired:new Dictionary<string, bool>()
        {
            { "GetCurrentWeather", true }
        }
    );

ValueTask runEventHandler(AgentRunnerEvents runEvent)
{
    switch (runEvent.EventType)
    {
        case AgentRunnerEventTypes.Streaming:
            if (runEvent is AgentRunnerStreamingEvent streamingEvent)
            {
                if (streamingEvent.ModelStreamingEvent is ModelStreamingOutputTextDeltaEvent deltaTextEvent)
                {
                    Console.Write(deltaTextEvent.DeltaText); // Write the text delta directly
                }
            }
            break;
        default:
            break;
    }
    return ValueTask.CompletedTask;
}

ValueTask<bool> toolApprovalHandler(string toolRequest)
{
    Console.WriteLine(toolRequest);
    Console.WriteLine("Do you approve? (y/n)");
    string? input = Console.ReadLine();
    return ValueTask.FromResult(input?.ToLower().StartsWith('y') ?? false);
}

Console.WriteLine("[User]: What is the weather in boston?");
Console.Write("[Agent]: ");
Conversation result = await agent.RunAsync(
    input:"What is the weather in boston?", 
    onAgentRunnerEvent:runEventHandler,
    toolPermissionHandle: toolApprovalHandler);

Console.WriteLine(result.Messages.Last().Content);

Best Practices ​

  • Always implement error handling
  • Monitor agent performance metrics
  • Log important events
  • Test with various scenarios
  • Implement timeout mechanisms