Custom tools — typed
Inherit from ToolBase<TArgs> and you get:
- A typed argument POCO (no manual JSON parsing).
- An auto-generated JSON Schema from your POCO's
[Description]attributes (no manual schema writing). - The framework feeds the schema to the LLM and deserialises the call's argument blob back to your POCO.
This is the recommended path for 99% of custom tools.
A complete example
A weather tool that calls a fictional weather API and returns the current temperature in Celsius.
using System.ComponentModel;
using LogicGrid.Core.Tools;
public sealed class WeatherArgs
{
[Description("City name, e.g. 'Tokyo'")]
public string City { get; set; } = "";
[Description("Units. 'C' (default) or 'F'.")]
public string Units { get; set; } = "C";
}
public sealed class WeatherTool : ToolBase<WeatherArgs>
{
private readonly HttpClient _http;
private readonly string _apiKey;
public WeatherTool(HttpClient http, string apiKey)
{
_http = http;
_apiKey = apiKey;
}
public override string Name => "get_weather";
public override string Description =>
"Get the current temperature in a city. " +
"Use when the user asks about weather.";
public override async Task<string> ExecuteAsync(
WeatherArgs args, CancellationToken ct = default)
{
var url = $"https://api.example.com/weather" +
$"?city={Uri.EscapeDataString(args.City)}" +
$"&units={args.Units}" +
$"&key={_apiKey}";
using var response = await _http.GetAsync(url, ct);
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
return body; // already JSON; the LLM parses it
}
}
Wire it onto an agent:
var weather = new WeatherTool(new HttpClient(), Environment.GetEnvironmentVariable("WEATHER_KEY")!);
IAgent travel = new Agent<string>(
"Travel",
"Helps plan trips.",
"You are a helpful travel assistant. Use tools when you need real data.",
llm,
tools: new ToolBase[] { weather });
Console.WriteLine(
await travel.RunAsync("Should I bring a coat to Reykjavík tomorrow?", new AgentContext()));
Errors
If your ExecuteAsync throws, LogicGrid:
- Fires
ToolCallFailedEvent(visible in the trace and logs). - Re-throws.
The Agent/AgentBase retry policy then decides whether to call
the LLM again with the failure context.
If you'd rather have the LLM recover from the error itself, catch the exception and return a string describing it — the LLM will read it and try again with different args.
public override async Task<string> ExecuteAsync(WeatherArgs args, CancellationToken ct)
{
try
{
// …
}
catch (HttpRequestException ex)
{
return $"Weather API call failed: {ex.Message}. Try a different city or check the spelling.";
}
}
Keep your tools cheap
Tools are called inside the agent's loop. A 5-second tool call inside a 15-loop agent is 75 seconds of wall clock. Aim for sub-second latency on the happy path; cache aggressively when the same args will be called repeatedly.