Agentic AIC#verifiedVerified
Tool Use Agent Pattern in C#
Augment an LLM with callable external tools — APIs, code interpreters, databases — so it can take actions and retrieve real-time information beyond its training data.
How to Implement the Tool Use Agent Pattern in C#
1Step 1: Define the tool interface and tool registry
public interface ITool
{
string Name { get; }
string Description { get; }
Task<string> ExecuteAsync(string input);
}
public class ToolRegistry
{
private readonly Dictionary<string, ITool> _tools = [];2Step 2: Register and look up tools by name
public void Register(ITool tool) => _tools[tool.Name] = tool;
public ITool? Get(string name) =>
_tools.GetValueOrDefault(name);
public IReadOnlyList<string> ListNames() =>
_tools.Keys.ToList().AsReadOnly();
}3Step 3: LLM decides which tool to call
public record ToolCall(string ToolName, string Input);
public static class ToolUseLoop
{
public static async Task<string> RunAsync(
string query,
ToolRegistry registry,
Func<string, IReadOnlyList<string>, Task<ToolCall?>> llm)
{
var toolNames = registry.ListNames();
// LLM picks a tool (or null for direct answer)
var call = await llm(query, toolNames);
if (call is null) return "Direct answer (no tool needed)";
var tool = registry.Get(call.ToolName)
?? throw new InvalidOperationException(
$"Unknown tool: {call.ToolName}");
return await tool.ExecuteAsync(call.Input);
}
}using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
// [step] Define robust tool infrastructure with schema validation
public record ToolParameter(string Name, string Type, string Description,
bool Required = true);
public record ToolDefinition(string Name, string Description,
IReadOnlyList<ToolParameter> Parameters);
public interface ITool
{
ToolDefinition Definition { get; }
Task<ToolResult> ExecuteAsync(
JsonDocument args, CancellationToken ct = default);
}
public record ToolResult(
bool Success, string Data, string? Error = null,
Dictionary<string, object>? Metadata = null);
public record ToolCall(string ToolName, JsonDocument Arguments);
// [step] Thread-safe tool registry with middleware support
public sealed class ToolRegistry
{
private readonly Dictionary<string, ITool> _tools = [];
private readonly List<Func<ITool, JsonDocument, CancellationToken,
Task<ToolResult>>> _middleware = [];
private readonly ILogger<ToolRegistry> _logger;
public ToolRegistry(ILogger<ToolRegistry> logger) => _logger = logger;
public ToolRegistry Register(ITool tool)
{
_tools[tool.Definition.Name] = tool;
_logger.LogInformation("Registered tool: {Name}", tool.Definition.Name);
return this;
}
public ToolRegistry UseMiddleware(
Func<ITool, JsonDocument, CancellationToken, Task<ToolResult>> mw)
{
_middleware.Add(mw);
return this;
}
public ITool? Get(string name) => _tools.GetValueOrDefault(name);
public IReadOnlyList<ToolDefinition> ListDefinitions() =>
_tools.Values.Select(t => t.Definition).ToList().AsReadOnly();
}
// [step] Production tool-use orchestrator with retries and logging
public sealed class ToolUseOrchestrator(
ToolRegistry registry, ILogger<ToolUseOrchestrator> logger)
{
public async Task<ToolResult> ExecuteAsync(
ToolCall call, int maxRetries = 2, CancellationToken ct = default)
{
var tool = registry.Get(call.ToolName)
?? throw new InvalidOperationException(
$"Tool \"{call.ToolName}\" not found");
for (var attempt = 0; attempt <= maxRetries; attempt++)
{
ct.ThrowIfCancellationRequested();
try
{
logger.LogDebug("Executing {Tool} (attempt {Attempt})",
call.ToolName, attempt + 1);
var result = await tool.ExecuteAsync(call.Arguments, ct);
if (result.Success)
{
logger.LogInformation("Tool {Tool} succeeded", call.ToolName);
return result;
}
logger.LogWarning("Tool {Tool} returned failure: {Error}",
call.ToolName, result.Error);
}
catch (Exception ex) when (attempt < maxRetries)
{
logger.LogWarning(ex, "Tool {Tool} threw on attempt {Attempt}",
call.ToolName, attempt + 1);
await Task.Delay(TimeSpan.FromMilliseconds(100 * (attempt + 1)), ct);
}
}
return new ToolResult(false, "", "Max retries exhausted");
}
}Tool Use Agent Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Tool Use Agent Pattern in the Real World
“A lawyer (the LLM) in a courtroom knows the law but needs a paralegal team (the tools) to pull case files, run searches, and retrieve exhibits. The lawyer directs which file to fetch, the paralegal returns it, and the lawyer integrates that information into their argument — the lawyer's intelligence is amplified by the support staff's ability to reach into the real world.”