An AI tool is an API whose first consumer is a model.

Names such as value, date, or id force the model to guess. Give each parameter the same precision you would expect in a public request contract:

using System.ComponentModel;

[Description(
    "Creates a customer appointment and writes it to the calendar.")]
public Task<AppointmentResult> CreateAppointmentAsync(
    [Description(
        "Stable customer identifier from the CRM, not the display name.")]
    string customerId,

    [Description(
        "Appointment start including a UTC offset, for example 2026-08-10T09:00:00+02:00.")]
    DateTimeOffset startsAt,

    [Description(
        "Appointment duration in minutes. Allowed range: 15 to 240.")]
    int durationMinutes,

    [Description(
        "Optional note shown to the customer.")]
    string? notes = null,

    CancellationToken cancellationToken = default)
{
    // Validate and execute through the application service.
}

CancellationToken is infrastructure, not model-supplied input. AIFunctionFactory binds it from the invocation context and does not include it in the generated tool schema.

C# nullability and optionality also shape the contract. string customerId is non-nullable and required. With the default schema generation, string? notes = null permits null and is typically treated as optional because it has a default value. A nullable parameter without a default value can still be required.

Useful tool and parameter descriptions answer concrete questions:

  • What does the tool do?
  • Does it read data or change state?
  • What does each parameter identify?
  • Which format and unit does each value use?
  • Which values are allowed?
  • Is the value supplied by the model, the user, or trusted application state?

Trusted application state such as tenant ID, user ID, or permission scope should usually be resolved at execution time, not supplied by the model as another argument.

Prefer typed parameters and enums when the set of values is genuinely closed. For example, prefer an AppointmentType enum over a free-form string type when the allowed values are fixed. Do not replace a small schema with one long prose description that the application cannot validate.

The generated schema is part of the prompt boundary. Renaming a parameter, dropping a description, or exposing an internal dependency can change tool selection even when the C# method still compiles. Add a contract test for important tools that inspects the generated name, description, required parameters, and JSON schema.

Be precise, but not verbose. Tool names, descriptions, and schemas are sent to the model and count against the request’s token budget.

Descriptions improve the model’s chance of producing the right call. They do not provide authorization, validation, approval, or idempotency. Keep those controls at execution time.

Treat the tool declaration as a real contract: narrow, explicit, testable, and honest about side effects.