AIFunctions
AIFunctions are single, focused operations that the agent can call. They are the simplest capability type and the building block for more complex capabilities.
Basic Usage
Mark a method with [AIFunction] to expose it to the agent:
public class CalculatorTool
{
[AIFunction]
public int Add(int a, int b)
{
return a + b;
}
}The agent sees this as a tool called Add that takes two integers and returns an integer.
Descriptions
Function Description
Use [AIDescription] to tell the agent what the function does:
[AIFunction]
[AIDescription("Add two numbers together and return the sum")]
public int Add(int a, int b)
{
return a + b;
}Parameter Descriptions
Apply [AIDescription] to parameters for clarity:
[AIFunction]
[AIDescription("Search the web for information")]
public async Task<string> WebSearch(
[AIDescription("The search query")] string query,
[AIDescription("Maximum number of results (1-10)")] int maxResults = 5)
{
// Implementation
}Return Types
AIFunctions support various return types:
// Synchronous
[AIFunction]
public string GetName() => "HPD-Agent";
// Async
[AIFunction]
public async Task<string> GetNameAsync() => await Task.FromResult("HPD-Agent");
// Complex types (serialized to JSON)
[AIFunction]
public async Task<WeatherResult> GetWeather(string city)
{
return new WeatherResult { Temperature = 72, Condition = "Sunny" };
}
// Void (returns confirmation message)
[AIFunction]
public void LogMessage(string message)
{
Console.WriteLine(message);
}Requiring Permission
Some functions need user approval before execution. Use [RequiresPermission]:
[AIFunction]
[AIDescription("Delete a file from the filesystem")]
[RequiresPermission]
public void DeleteFile(string path)
{
File.Delete(path);
}When the agent calls this function, the user will be prompted to approve or deny the action.
Custom Function Names
Override the default method name:
[AIFunction(Name = "search_web")]
public async Task<string> PerformWebSearch(string query)
{
// Implementation
}The agent sees this as search_web, not PerformWebSearch.
The Toolkit Attribute
When a toolkit has multiple functions, you can group them under a collapsible container using [Toolkit("description")]:
[Toolkit("File operations for reading, writing, and managing files")]
public class FileSystemToolkit
{
[AIFunction]
public string ReadFile(string path) { /* ... */ }
[AIFunction]
public void WriteFile(string path, string content) { /* ... */ }
[AIFunction]
public void DeleteFile(string path) { /* ... */ }
}Note: Providing a description to [Toolkit] enables collapsing. Without a description ([Toolkit] or [Toolkit(Name = "...")]), the toolkit's functions are always visible.
Toolkit with Instructions
The [Toolkit] attribute supports two instruction types that serve different purposes:
[Toolkit(
"Database operations",
FunctionResult: "Available: Query, Insert, Update, Delete. Always use transactions for multiple operations.",
SystemPrompt: "CRITICAL: Never execute DELETE without a WHERE clause."
)]
public class DatabaseToolkit
{
// Functions...
}| Property | Where It Goes | When | Persistence |
|---|---|---|---|
FunctionResult | Tool call result | Once on expansion | In message history |
SystemPrompt | System instructions | Every turn while expanded | Configurable |
FunctionResult: One-time message returned when the container expands. Use for listing available functions or one-time guidance.
SystemPrompt: Injected into system instructions while functions are expanded. Use for critical rules that must be followed every turn.
Dynamic Instructions with Expressions
Reference methods or properties for dynamic instructions:
[Toolkit(
"Search operations",
FunctionResult: nameof(GetAvailableSearchers),
SystemPrompt: nameof(SearchGuidelines)
)]
public class SearchToolkit
{
public static string GetAvailableSearchers() =>
$"Available: {string.Join(", ", _enabledSearchers)}";
public static string SearchGuidelines =>
"Always prefer exact matches over fuzzy matches.";
// Functions...
}→ See 02.1.5 Context Engineering.md for advanced collapsing configuration.
Dependency Injection
Tools can receive services through constructor injection:
public class WeatherTool
{
private readonly IWeatherService _weatherService;
private readonly ILogger<WeatherTool> _logger;
public WeatherTool(IWeatherService weatherService, ILogger<WeatherTool> logger)
{
_weatherService = weatherService;
_logger = logger;
}
[AIFunction]
[AIDescription("Get current weather for a city")]
public async Task<WeatherResult> GetWeather(string city)
{
_logger.LogInformation("Getting weather for {City}", city);
return await _weatherService.GetCurrentWeatherAsync(city);
}
}Register the service provider with the agent:
var services = new ServiceCollection()
.AddSingleton<IWeatherService, WeatherService>()
.AddLogging()
.BuildServiceProvider();
var agent = new AgentBuilder()
.WithServiceProvider(services)
.WithTool<WeatherTool>()
.Build();Typed Metadata
For compile-time validation of dynamic descriptions and conditionals, use the generic attribute:
public class SearchMetadata : IToolMetadata
{
public string ProviderName { get; set; } = "Default";
public bool HasAdvancedFeatures { get; set; } = false;
// IToolMetadata implementation...
}
public class SearchTool
{
[AIFunction<SearchMetadata>]
[AIDescription("Search using {metadata.ProviderName}")]
public async Task<string> Search(string query)
{
// Implementation
}
}→ See 02.1.4 Tool Metadata.md for details on metadata and dynamic descriptions.
Conditional Functions
Show or hide functions based on runtime conditions:
[AIFunction]
[ConditionalFunction("HasAdvancedFeatures")]
[AIDescription("Advanced search with filters")]
public async Task<string> AdvancedSearch(string query, SearchFilters filters)
{
// Only visible when metadata.HasAdvancedFeatures is true
}→ See 02.1.4 Tool Metadata.md for conditional registration details.
Best Practices
Keep functions focused: One function should do one thing well.
Write clear descriptions: The agent relies on descriptions to choose the right function.
Use meaningful parameter names:
cityis better thancorinput.Handle errors gracefully: Return error information rather than throwing exceptions when possible.
Use
[RequiresPermission]for destructive or sensitive operations.Group related functions: Use
[Toolkit("description")]to reduce context clutter.
// Good: Focused, well-described, safe
[AIFunction]
[AIDescription("Search for files matching a pattern in a directory")]
public async Task<string[]> FindFiles(
[AIDescription("Directory to search in")] string directory,
[AIDescription("Glob pattern (e.g., '*.txt')")] string pattern)
{
if (!Directory.Exists(directory))
return Array.Empty<string>();
return Directory.GetFiles(directory, pattern);
}
// Bad: Vague, no descriptions, unsafe
[AIFunction]
public void Process(string x)
{
File.Delete(x); // Destructive without permission!
}Next Steps
- 02.1.2 Skills.md - Group functions into workflows
- 02.1.4 Tool Metadata.md - Dynamic descriptions and conditionals
- 02.1.5 Context Engineering.md - Advanced collapsing