Skip to content

Getting Started with Agents

Overview

LlmTornado.Agents is a powerful framework for building AI agents that can autonomously perform complex tasks. An agent combines an AI model with tools, instructions, and structured output capabilities to structure the functionality of the agent. This guide will help you understand the basics of creating and using agents in your applications.

Agent Orchestration is also provided for coordinating multiple agents to work together on larger tasks to achieve a common goal.

A Chat Runtime is used to manage conversations, maintain context, and handle interactions between users and agents.

Quick Start

Here's a simple example of creating and running an agent:

csharp
using LlmTornado;
using LlmTornado.Agents;
using LlmTornado.Chat.Models;

// Install the package first:
// dotnet add package LlmTornado.Agents

TornadoApi api = new TornadoApi("your-api-key");

// Create a basic agent
TornadoAgent agent = new TornadoAgent(
    client: api,
    model: ChatModel.OpenAi.Gpt41.V41Mini,
    instructions: "You are a helpful assistant."
);

// Run the agent with a simple query
Conversation result = await agent.RunAsync("What is 2+2?");

// Get the response
Console.WriteLine(result.Messages.Last().Content);
// Output: "2+2 equals 4."

Prerequisites

  • Installation of LlmTornado.Agents package via NuGet
  • Basic understanding of the core LlmTornado chat functionality
  • Familiarity with C# async/await patterns
  • An API key from a supported AI provider

From The Ground Up

Lets design a Agent Orchestration System!

Heres whats was needed:

First ❗

Need a system API to talk to Cloud/Local llm providers

LlmTornado the greatest LLM library for C# with a dedicated team of developers keeping this library up to date with all the latest endpoints, providers, and models!

Second ❗

Need To be easily able to define tools, structured output, instructions, model, and settings for the API

LlmTornado.Agents will allow you to easily configure all the latest settings, tools, and capabilities of the API endpoint from both cloud and local resources.

Third ❗

Ability to Run Agent "loop" to allow agents to resolve tools being called that do not require user interaction.

AgentRunner runs the agent loop handling both Response API and Chat API functionality along with guardrails, streaming, tool permissions, and detailed realtime event based run results.

Forth ❗

Achieve basic Statefulness

ChatRuntime Handles agent conversation state through generic interface implementation of IChatRuntimeConfiguration which can take many different forms.

Finally ❗

Implement Node-Based Orchestration

OrchestrationRuntimeConfiguration which implements IChatRuntimeConfiguration to allow configuration of the Orchestration system which involves designing OrchestrationRunnables linked together by OrchestrationAdvancers to create complex node-based agentic work-flows

What is an Agent?

An agent in LlmTornado is an autonomous AI entity that can:

  • Use tools: Execute functions to interact with external systems
  • Make decisions: Determine when and how to use available tools
  • Generate structured output: Return responses in predefined formats
  • Stream responses: Provide real-time feedback during processing

Key Components

TornadoAgent

The core agent class that manages all agent functionality:

csharp
public class TornadoAgent
{
    public TornadoApi Client { get; set; }
    public ChatModel Model { get; set; }
    public string Instructions { get; set; }
    public string Name { get; set; }
    public Type? OutputSchema { get; private set; }
    public List<Delegate>? Tools { get; set; }
    public bool Streaming { get; set; }
}

Agent Configuration Parameters

  • client: The TornadoApi instance for API communication
  • model: The AI model to use (e.g., GPT-4, Claude, Gemini)
  • instructions: System instructions that guide the agent's behavior
  • name: A human-readable name for the agent (default: "Assistant")
  • outputSchema: Optional Type for structured output
  • tools: List of functions the agent can call
  • streaming: Enable real-time response streaming
  • toolPermissionRequired: Dictionary specifying which tools need user permission

Agent Lifecycle

  1. Initialization: Agent is created with initilizer
  2. Execution: Agent processes user input via RunAsync()
  3. Tool Resolution: Agent determines if tools are needed
  4. Response Generation: Agent produces Conversation based on context
  5. State Management: Agent is Stateless but Conversation history can be added to RunAsync()

Basic Usage

Simple Agent

csharp
TornadoApi api = new TornadoApi("your-api-key");

TornadoAgent agent = new TornadoAgent(
    client: api,
    model: ChatModel.OpenAi.Gpt41.V41Mini,
    instructions: "You are a helpful coding assistant specialized in C#."
);

Conversation result = await agent.RunAsync("Explain async/await in C#");
Console.WriteLine(result.Messages.Last().Content);

Agent with Streaming

csharp
TornadoAgent agent = new TornadoAgent(
    client: api,
    model: ChatModel.OpenAi.Gpt41.V41Mini,
    instructions: "You are a helpful assistant.",
    streaming: true
);

// Define streaming event handler
ValueTask streamHandler(AgentRunnerEvents runEvent)
{
    switch (runEvent.EventType)
    {
        case AgentRunnerEventTypes.Streaming:
            if (runEvent is AgentRunnerStreamingEvent streamingEvent)
            {
                if (streamingEvent.ModelStreamingEvent is ModelStreamingOutputTextDeltaEvent deltaTextEvent)
                {
                    Console.Write(deltaTextEvent.DeltaText);
                }
            }
            break;
    }
    return ValueTask.CompletedTask;
}

Conversation result = await agent.RunAsync(
    "Tell me a short story", 
    onAgentRunnerEvent: streamHandler
);

Agent with Tools

csharp
// Define a tool function
[Description("get the weather")]
string GetWeather([Description("cit to get weather from")] string city)
{
    // Simulated weather data
    return $"The weather in {city} is sunny with 22°C.";
}

TornadoAgent agent = new TornadoAgent(
    client: api,
    model: ChatModel.OpenAi.Gpt41.V41Mini,
    instructions: "You are a helpful assistant that can check weather.",
    tools: [ GetWeather ]
);

Conversation result = await agent.RunAsync("What's the weather like in Prague?");
Console.WriteLine(result.Messages.Last().Content);
// Agent will call GetWeather("Prague") and use the result

Agent with Structured Output

csharp
// Define output schema
[Description("Contact info")]
public class ContactInfo
{
    public string Name { get; set; }
    public string Email { get; set; }
    [Description("Contact phone number")]
    public string Phone { get; set; }
}

TornadoAgent agent = new TornadoAgent(
    client: api,
    model: ChatModel.OpenAi.Gpt4.O,
    instructions: "Extract contact information from user messages.",
    outputSchema: typeof(ContactInfo)
);

Conversation result = await agent.RunAsync(
    "Hi, I'm John Doe. You can reach me at john@example.com or +1234567890."
);

// Parse the structured output
string jsonResponse = result.Messages.Last().Content;
ContactInfo? contact = JsonConvert.DeserializeObject<ContactInfo>(jsonResponse);
Console.WriteLine($"Name: {contact?.Name}, Email: {contact?.Email}");

Advanced Usage

Persistent Conversations

Agents can maintain conversation history across multiple interactions:

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

// First interaction
Conversation result = await agent.RunAsync("My name is Alice.");
Console.WriteLine(result.Messages.Last().Content);

// Continue conversation with history
result = await agent.RunAsync(
    "What is my name?", 
    appendMessages: result.Messages.ToList()
);
Console.WriteLine(result.Messages.Last().Content);
// Output: "Your name is Alice."

Saving and Loading Conversations

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

// Run agent and get conversation
Conversation result = await agent.RunAsync("Hello, I'm working on a C# project.");

// Save conversation to file
result.Messages.ToList().SaveConversation("conversation.json");

// Later, load the conversation
List<ChatMessage> messages = new List<ChatMessage>();
await messages.LoadMessagesAsync("conversation.json");

// Resume conversation
result = await agent.RunAsync(
    "Can you remind me what we were discussing?",
    appendMessages: messages
);

Multi-Provider Support

Agents work seamlessly with different AI providers:

csharp
// OpenAI agent
TornadoAgent openAiAgent = new TornadoAgent(
    client: new TornadoApi(new (LLmProviders.OpenAi, "openai-key")),
    model: ChatModel.OpenAi.Gpt41.V41,
    instructions: "You are a helpful assistant."
);

// Anthropic agent
TornadoAgent anthropicAgent = new TornadoAgent(
    client: new TornadoApi(new (LLmProviders.Anthropic, "anthropic-key")),
    model: ChatModel.Anthropic.Claude37.Sonnet,
    instructions: "You are a helpful assistant."
);

// Google agent
TornadoAgent googleAgent = new TornadoAgent(
    client: new TornadoApi(new (LLmProviders.Google, "google-key")),
    model: ChatModel.Google.Gemini.Gemini2Flash001,
    instructions: "You are a helpful assistant."
);

Best Practices

Clear Instructions

  • Be specific about the agent's role and capabilities
  • Provide context about expected behavior
  • Include constraints and limitations
csharp
string instructions = @"
You are a customer support agent for a SaaS product.
- Always be polite and professional
- Provide step-by-step solutions
- If you don't know something, say so and offer to escalate
- Never share sensitive information
";

TornadoAgent agent = new TornadoAgent(api, model, instructions: instructions);

Error Handling

csharp
try
{
    Conversation result = await agent.RunAsync(userInput);
    
    if (result.Messages.Any())
    {
        string response = result.Messages.Last().Content;
        Console.WriteLine(response);
    }
    else
    {
        Console.WriteLine("No response generated");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Agent error: {ex.Message}");
    // Implement retry logic or fallback behavior
}

Tool Design

  • Keep tools focused on single responsibilities
  • Provide clear descriptions
  • Handle errors gracefully within tools
  • Return meaningful results
  • Use description Attribute as much as possible
csharp
[Description("Gets the current time")]
string GetCurrentTime([Description("Input for timezone you want current time of")] string timezone = "UTC")
{
    try
    {
        TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(timezone);
        DateTime time = TimeZoneInfo.ConvertTime(DateTime.Now, tzi);
        return $"Current time in {timezone}: {time:yyyy-MM-dd HH:mm:ss}";
    }
    catch (Exception ex)
    {
        return $"Error getting time for {timezone}: {ex.Message}";
    }
}

Performance Optimization

  • Choose appropriate models for your use case
  • Use streaming for long-running operations
  • Cache agent responses when appropriate
  • Limit conversation history length for long sessions

Common Issues

Issue: Agent Not Using Tools

Problem: Agent doesn't call available tools when expected Solutions:

  • Ensure tool descriptions are clear and relevant
  • Check that the model supports function calling
  • Verify instructions mention tool usage
  • Use models like GPT-4 or Claude 3+ for better tool use

Issue: Conversation Context Lost

Problem: Agent forgets previous messages Solutions:

  • Use appendMessages parameter to maintain history
  • Save and load conversations for persistence
  • Consider token limits and trim old messages if needed

Issue: Streaming Not Working

Problem: Streaming responses not appearing Solutions:

  • Set streaming: true in constructor or RunAsync
  • Implement AgentRunnerEvents handler correctly
  • Check event type filtering in handler
  • Ensure console output is flushed

Issue: Structured Output Not Matching Schema

Problem: Output doesn't conform to expected format Solutions:

  • Verify model supports structured output (GPT-4, etc.)
  • Check schema is properly defined
  • Use clear instructions about output format
  • Consider using JSON mode fallback

API Reference

TornadoAgent Constructor

csharp
public TornadoAgent(
    TornadoApi client,
    ChatModel model,
    string name = "Assistant",
    string instructions = "You are a helpful assistant",
    Type? outputSchema = null,
    List<Delegate>? tools = null,
    List<MCPServer>? mcpServers = null,
    bool streaming = false,
    Dictionary<string, bool>? toolPermissionRequired = null
)

Key Methods

  • RunAsync(string input, ...) - Execute agent with user input
  • UpdateOutputSchema(Type? newSchema) - Change output schema after initilization

Key Properties

  • Client - TornadoApi instance
  • Model - Current ChatModel
  • Instructions - System instructions
  • Tools - Available tool functions
  • Streaming - Enable/disable streaming
  • OutputSchema - Current output schema type