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