Table of Contents

Create an AI Agent with Tools

An AI agent goes beyond simple text generation: it can reason about a problem, decide which tools to call, interpret the results, and iterate until it has a complete answer. This tutorial builds an agent that uses web search, a calculator, and a custom tool you write yourself.


What You Will Build

A research assistant that can:

  1. Search the web for current information (using DuckDuckGo, no API key needed).
  2. Perform calculations when the user's question involves math.
  3. Call a custom tool you define, demonstrating the ITool interface.

Prerequisites

Requirement Minimum
.NET SDK 8.0+
VRAM 4+ GB (for a 4B model with tool-calling support)

You need a model that supports tool calling. Check model.HasToolCalls after loading. Recommended: qwen3:4b or gemma3:4b.


Step 1: Create the Project

dotnet new console -n AgentQuickstart
cd AgentQuickstart
dotnet add package LM-Kit.NET

Step 2: Understand the Agent Architecture

┌──────────────────────────────────────────────────────┐
│                     Agent                            │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │  Identity   │  │   Planning   │  │    Tools     │ │
│  │  (persona)  │  │  (ReAct)     │  │  (registry)  │ │
│  └─────────────┘  └──────────────┘  └──────────────┘ │
└──────────────────────┬───────────────────────────────┘
                       │ Run("user query")
                       ▼
              ┌──────────────────┐
              │  Thought-Action  │ ◄── ReAct loop
              │  Observation     │
              │  ... repeat ...  │
              │  Final Answer    │
              └──────────────────┘
Component Purpose
Agent.CreateBuilder() Fluent API to configure the agent
PlanningStrategy How the agent reasons (None, ReAct, ChainOfThought, etc.)
ToolRegistry Collection of tools the agent can call
BuiltInTools Pre-built tools: WebSearch, Calculator, DateTime, Http, and 50+ more
ITool Interface for custom tools
AgentExecutionResult The agent's response, including tool call history

Step 3: Agent with Built-In Tools

using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

// 1. Load a tool-calling model
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:4b",
    downloadingProgress: (_, len, read) =>
    {
        if (len.HasValue) Console.Write($"\r  Downloading: {(double)read / len.Value * 100:F1}%   ");
        return true;
    },
    loadingProgress: p => { Console.Write($"\r  Loading: {p * 100:F0}%   "); return true; });

Console.WriteLine($"\n  Tool calling supported: {model.HasToolCalls}\n");

// 2. Build the agent
var agent = Agent.CreateBuilder(model)
    .WithPersona("Research Assistant")
    .WithInstruction("You help users find information and answer questions accurately. " +
                     "Use tools when you need current data or calculations. " +
                     "Cite your sources when using web search.")
    .WithPlanning(PlanningStrategy.ReAct)
    .WithTools(tools =>
    {
        tools.Register(BuiltInTools.WebSearch);   // DuckDuckGo, no API key
        tools.Register(BuiltInTools.Calculator);
        tools.Register(BuiltInTools.DateTime);
    })
    .WithMaxIterations(10)
    .Build();

// 3. Interactive loop
Console.WriteLine("Agent ready. Type a question (or 'quit' to exit):\n");

while (true)
{
    Console.ForegroundColor = ConsoleColor.Green;
    Console.Write("You: ");
    Console.ResetColor();

    string? input = Console.ReadLine();
    if (string.IsNullOrWhiteSpace(input) || input.Equals("quit", StringComparison.OrdinalIgnoreCase))
        break;

    var result = await agent.RunAsync(input);

    if (result.IsSuccess)
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.WriteLine($"\nAssistant: {result.Content}");
        Console.ResetColor();

        // Show tool usage
        if (result.ToolCalls.Count > 0)
        {
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Console.WriteLine($"  Tools used: {string.Join(", ", result.ToolCalls.Select(tc => tc.ToolName))}");
            Console.WriteLine($"  Duration: {result.Duration.TotalSeconds:F1}s, Inferences: {result.InferenceCount}");
            Console.ResetColor();
        }
    }
    else
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine($"\nError: {result.Error?.Message}");
        Console.ResetColor();
    }

    Console.WriteLine();
}

Step 4: Add a Custom Tool

Implement the ITool interface to give the agent any capability you want. Here is a tool that looks up stock-like information (simulated):

using System.Text.Json;
using LMKit.Agents.Tools;

public class CompanyLookupTool : ITool
{
    public string Name => "lookup_company";

    public string Description => "Look up key information about a company by ticker symbol";

    public string InputSchema => """
        {
            "type": "object",
            "properties": {
                "ticker": {
                    "type": "string",
                    "description": "Stock ticker symbol (e.g. MSFT, AAPL)"
                }
            },
            "required": ["ticker"]
        }
        """;

    public Task<string> InvokeAsync(string arguments, CancellationToken cancellationToken = default)
    {
        var args = JsonDocument.Parse(arguments);
        string ticker = args.RootElement.GetProperty("ticker").GetString()!.ToUpperInvariant();

        // In a real app, call a financial data API here
        var data = ticker switch
        {
            "MSFT" => new { name = "Microsoft Corp", sector = "Technology", employees = 228000 },
            "AAPL" => new { name = "Apple Inc", sector = "Technology", employees = 164000 },
            _ => new { name = "Unknown", sector = "N/A", employees = 0 }
        };

        return Task.FromResult(JsonSerializer.Serialize(data));
    }
}

Register it alongside built-in tools:

.WithTools(tools =>
{
    tools.Register(BuiltInTools.WebSearch);
    tools.Register(BuiltInTools.Calculator);
    tools.Register(new CompanyLookupTool());
})

The agent will now call lookup_company when a question involves company information, and fall back to web search for everything else.


Step 5: Alternative: Attribute-Based Tools

For simpler tools, use LMFunctionAttribute instead of implementing ITool:

using System.Text.Json;
using LMKit.Agents.Tools;

public class UtilityTools
{
    [LMFunction("convert_temperature", "Convert temperature between Celsius and Fahrenheit")]
    public string ConvertTemperature(double value, string from_unit)
    {
        double result = from_unit.ToLower() switch
        {
            "celsius" => value * 9.0 / 5.0 + 32,
            "fahrenheit" => (value - 32) * 5.0 / 9.0,
            _ => throw new ArgumentException($"Unknown unit: {from_unit}")
        };

        string toUnit = from_unit.ToLower() == "celsius" ? "fahrenheit" : "celsius";
        return JsonSerializer.Serialize(new { result, unit = toUnit });
    }

    [LMFunction("word_count", "Count words in a text")]
    public int WordCount(string text) => text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}

Register the class (all annotated methods are discovered automatically):

.WithTools(tools =>
{
    tools.Register(new UtilityTools());  // registers both tools
})

Planning Strategies

The planning strategy controls how the agent reasons between tool calls.

Strategy Behavior Best For
None Direct response, no explicit reasoning Simple Q&A, fast responses
ReAct Thought → Action → Observation loop Tool-using agents (recommended default)
ChainOfThought Step-by-step reasoning before answering Math, logic, multi-step analysis
PlanAndExecute Generate full plan, then execute steps Complex multi-step tasks
Reflection Generate, self-critique, refine Tasks requiring accuracy
TreeOfThought Explore multiple reasoning paths Problems with multiple solution approaches

Start with ReAct for any agent that uses tools. It provides the reasoning/action loop that tool-calling agents need.


Configuring Web Search Providers

The default BuiltInTools.WebSearch uses DuckDuckGo (free, no API key). For higher-quality results:

using LMKit.Agents.Tools.BuiltIn.Net;

// Brave Search (free tier: 2,000 queries/month)
var braveSearch = BuiltInTools.CreateWebSearch(
    WebSearchTool.Provider.Brave,
    "your-brave-api-key"
);

// Tavily (optimized for AI/RAG workloads)
var tavilySearch = BuiltInTools.CreateWebSearch(
    WebSearchTool.Provider.Tavily,
    Environment.GetEnvironmentVariable("TAVILY_API_KEY")
);

// Custom options
var customSearch = BuiltInTools.CreateWebSearch(new WebSearchTool.Options
{
    SearchProvider = WebSearchTool.Provider.Brave,
    ApiKey = "your-key",
    DefaultMaxResults = 10,
    Timeout = TimeSpan.FromSeconds(30)
});

Controlling Agent Behavior

MaxIterations

Caps how many reasoning + tool-call cycles the agent can perform before returning:

.WithMaxIterations(15)  // default: 10

If the agent hits the limit, it returns whatever partial answer it has.

Inspecting the Reasoning Trace

var result = await agent.RunAsync("What is the population of France divided by 3?");

// See the agent's internal reasoning (Thought/Action/Observation steps)
if (!string.IsNullOrEmpty(result.ReasoningTrace))
    Console.WriteLine($"Reasoning:\n{result.ReasoningTrace}");

// See each tool call
foreach (var call in result.ToolCalls)
    Console.WriteLine($"  Tool: {call.ToolName}, Status: {call.Type}, Result: {call.ResultJson}");

Common Issues

Problem Cause Fix
Agent never calls tools Model doesn't support tool calling Check model.HasToolCalls. Use qwen3:4b or gemma3:4b
Agent loops without making progress Planning strategy mismatch Use ReAct for tool-using agents. Increase MaxIterations if task is complex
Web search returns empty DuckDuckGo rate-limiting Switch to Brave or Tavily provider, or add a delay between queries
Custom tool errors silently InvokeAsync throws exception The agent sees the error and may retry. Log exceptions inside your tool

Next Steps