ToolsDefining tools
TOOLS

Defining tools

Create a complete C# tool with metadata, input validation, and structured output.

This example counts text locally. It introduces the tool contract without depending on a remote API or database.

Install the package

.NET CLI
dotnet add package Runiq.AI.Agents

The tool types use the Runiq.AI.Agents.Tools namespace.

Implement the tool

Create Tools/TextStatsTool.cs in your application:

C#
using Runiq.AI.Agents.Tools;

/// <summary>Counts UTF-16 code units and whitespace-separated words.</summary>
[RuniqTool(
    name: "text_stats",
    description: "Counts characters and whitespace-separated words in supplied text.")]
public sealed class TextStatsTool : IRuniqTool<TextStatsInput, TextStatsOutput>
{
    /// <summary>Calculates text statistics after validating the input.</summary>
    /// <param name="input">The text to measure.</param>
    /// <param name="cancellationToken">Cancels the operation.</param>
    /// <returns>The character and word counts.</returns>
    public Task<TextStatsOutput> ExecuteAsync(
        TextStatsInput input,
        CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ArgumentNullException.ThrowIfNull(input);
        ArgumentException.ThrowIfNullOrWhiteSpace(input.Text);

        var words = input.Text.Split(
            (char[]?)null,
            StringSplitOptions.RemoveEmptyEntries);

        return Task.FromResult(
            new TextStatsOutput(input.Text.Length, words.Length));
    }
}

/// <summary>Supplies the text to measure.</summary>
/// <param name="Text">Non-empty text.</param>
public sealed record TextStatsInput(string Text);

/// <summary>Contains the measured text statistics.</summary>
/// <param name="CharacterCount">Number of UTF-16 code units, including whitespace.</param>
/// <param name="WordCount">Number of whitespace-separated segments.</param>
public sealed record TextStatsOutput(int CharacterCount, int WordCount);

For Hello Runiq, the result is 11 characters and 2 words. Character count follows .NET string.Length; it is not a count of visible Unicode characters. Word count is a whitespace split, not linguistic tokenization.

Name and describe the operation

The runtime name is text_stats, not the C# class name. Reference that name in instructions.

Describe what the operation returns and which input it needs. Prefer “Counts characters and whitespace-separated words in supplied text” over “Text helper.”

Registration requirements

RequirementReason
A concrete tool classThe runtime creates an instance.
Exactly one IRuniqTool<TInput, TOutput> contractInput and output types must be unambiguous.
[RuniqTool] metadataThe runtime needs a name and description.
A public ExecuteAsync method with input and cancellation parametersThe current invoker locates this method through reflection.
Distinct names for different registered tool typesThe host rejects name collisions.

The same tool type can be shared by multiple agents. Attach it only once to each agent.

Design input and output

Use explicit properties rather than a generic JSON string inside the input. Deserialization provides a typed value, but does not replace business validation. Check required values, ranges, and permissions in your implementation.

Return the data the caller needs. In an agent run, this output is returned to the model; in direct Studio testing, it is shown as the tool result.

For an operation with no arguments, use EmptyToolInput. It still needs the metadata attribute and the same execution signature.

Use application services

Tools can constructor-inject registered services. Runiq creates the tool with ActivatorUtilities, resolving constructor dependencies from the invocation's service provider. Register those dependencies before building the host.

Keep data access and business logic in those services. See Patterns for organization and lifetime considerations.

Next: Attach or register the tool →

On this page