A tool schema can require that amount is a number. It cannot decide whether the refund is allowed.
Arguments produced by a model remain untrusted after JSON parsing and C# binding. Validate them where the application owns the operation:
public sealed class RefundTools(
OrderStore orders,
RefundAuthorization authorization,
RefundService refunds,
AuthenticatedCallerContext caller)
{
public async Task<RefundResult> IssueRefundAsync(
string orderId,
decimal amount,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(orderId) || amount <= 0)
{
return RefundResult.Rejected("Invalid refund request.");
}
Order? order = await orders.FindAsync(
caller.TenantId,
orderId,
cancellationToken);
if (order is null || amount > order.RefundableAmount)
{
return RefundResult.Rejected("Refund is not allowed.");
}
if (!await authorization.CanIssueRefundAsync(
caller,
order,
amount,
cancellationToken))
{
return RefundResult.Rejected("Refund is not allowed.");
}
return await refunds.IssueAsync(
order,
amount,
cancellationToken);
}
}
Only orderId and amount come from the model. The tenant and identity come from the authenticated caller context. The order lookup is scoped to that tenant, and the authorization service decides whether this caller may refund this order.
Parsing rejects malformed JSON. C# binding rejects values that cannot be converted to the expected parameter types. The generated schema describes the argument structure the model should produce. OpenAI’s strict schema mode can improve adherence, but even strict mode does not validate business rules or grant authority.
Keep trusted context out of the model-controlled argument list. Resolve the authenticated caller context and application services through the tool instance or dependency injection.
The RefundableAmount check provides a useful early rejection, but the value can change before the write. RefundService must enforce the remaining refundable amount atomically while it commits the refund. Otherwise, two concurrent calls can both pass the check.
For state-changing tools, define idempotency and approval rules as well. Structurally valid arguments can still describe a duplicate or high-impact operation.
Validate model-controlled arguments before the tenant-scoped read. Recheck mutable business invariants atomically when committing the side effect.