Tool Dynamic Metadata
Tool Metadata provides runtime context that controls dynamic behavior—specifically dynamic descriptions and conditional registration. It allows capabilities to adapt based on runtime state without changing code.
Tool Metadata is a property bag passed to a tool at registration time:
var agent = new AgentBuilder()
WithTools<SearchTools>(new SearchMetadata
{
ProviderName = "Tavily",
MaxResults = 10,
HasAdvancedFeatures = true
})
.Build();The tool can then use these values for:
- Dynamic descriptions:
"Search using {metadata.ProviderName}" - Conditional visibility: Only show functions when
HasAdvancedFeaturesis true
IToolMetadata Interface
All metadata classes must implement IToolMetadata:
public interface IToolMetadata
{
T? GetProperty<T>(string propertyName, T? defaultValue = default);
bool HasProperty(string propertyName);
IEnumerable<string> GetPropertyNames();
}Creating a Metadata Class
public class SearchMetadata : IToolMetadata
{
public string ProviderName { get; set; } = "Default";
public int MaxResults { get; set; } = 10;
public bool HasAdvancedFeatures { get; set; } = false;
public int ProviderCount { get; set; } = 1;
// IToolMetadata implementation
public T? GetProperty<T>(string propertyName, T? defaultValue = default)
{
return propertyName switch
{
nameof(ProviderName) => (T)(object)ProviderName,
nameof(MaxResults) => (T)(object)MaxResults,
nameof(HasAdvancedFeatures) => (T)(object)HasAdvancedFeatures,
nameof(ProviderCount) => (T)(object)ProviderCount,
_ => defaultValue
};
}
public bool HasProperty(string propertyName)
{
return propertyName is nameof(ProviderName)
or nameof(MaxResults)
or nameof(HasAdvancedFeatures)
or nameof(ProviderCount);
}
public IEnumerable<string> GetPropertyNames()
{
yield return nameof(ProviderName);
yield return nameof(MaxResults);
yield return nameof(HasAdvancedFeatures);
yield return nameof(ProviderCount);
}
}Dynamic Descriptions
Use {metadata.PropertyName} syntax in descriptions to interpolate runtime values:
public class SearchToolkit
{
[AIFunction]
[AIDescription("Search the web using {metadata.ProviderName}, returning up to {metadata.MaxResults} results")]
public async Task<string> WebSearch(string query)
{
// Implementation
}
}At registration time:
.WithTools<SearchToolkit>(new SearchMetadata
{
ProviderName = "Tavily",
MaxResults = 20
})Agent sees:
"Search the web using Tavily, returning up to 20 results"
Where Dynamic Descriptions Work
[AIDescription]on functions[AIDescription]on parametersdescriptionin[Toolkit("...")]attributedescriptioninSkillFactory.Create()descriptioninSubAgentFactory.Create()
Conditional Registration
Show or hide capabilities based on metadata properties.
ConditionalFunction
[AIFunction]
[ConditionalFunction("HasAdvancedFeatures")]
[AIDescription("Advanced search with filters and sorting")]
public async Task<string> AdvancedSearch(string query, SearchOptions options)
{
// Only visible when metadata.HasAdvancedFeatures is true
}ConditionalSkill
[Skill]
[ConditionalSkill("HasAdvancedFeatures")]
public Skill AdvancedResearchSkill()
{
// Only visible when metadata.HasAdvancedFeatures is true
return SkillFactory.Create(/* ... */);
}ConditionalSubAgent
[SubAgent]
[ConditionalSubAgent("HasAdvancedFeatures")]
public SubAgent SpecializedAnalyzer()
{
// Only visible when metadata.HasAdvancedFeatures is true
return SubAgentFactory.Create(/* ... */);
}ConditionalParameter
Hide individual parameters based on conditions:
[AIFunction]
[AIDescription("Search across providers")]
public async Task<string> Search(
string query,
[ConditionalParameter("ProviderCount > 1")]
[AIDescription("Which provider to use")]
string? provider = null)
{
// 'provider' parameter only visible when ProviderCount > 1
}Expression Syntax
Conditional attributes support simple expressions:
| Expression | Meaning |
|---|---|
"PropertyName" | Property is truthy (non-null, non-false, non-zero) |
"PropertyName > 1" | Numeric comparison |
"PropertyName >= 2" | Numeric comparison |
"PropertyName == 'value'" | String equality |
[ConditionalFunction("ProviderCount > 1")] // More than one provider
[ConditionalFunction("HasAdvancedFeatures")] // Boolean is true
[ConditionalFunction("Environment == 'prod'")] // String equalsGeneric Attributes (Required for Conditionals)
Important: To use conditional attributes or dynamic descriptions, you must use the generic form of the attribute. Without it, conditions are silently ignored.
// WORKS - generic attribute enables metadata features
[AIFunction<SearchMetadata>]
[ConditionalFunction("HasAdvancedFeatures")]
[AIDescription("Search using {metadata.ProviderName}")]
public async Task<string> Search(string query) { }
// DOESN'T WORK - condition is silently ignored!
[AIFunction]
[ConditionalFunction("HasAdvancedFeatures")] // Has no effect
public async Task<string> Search(string query) { }The generic attribute tells the source generator which metadata type to use for evaluating conditions and interpolating descriptions.
Available Generic Attributes
| Attribute | Use With |
|---|---|
[AIFunction<TMetadata>] | [ConditionalFunction], [ConditionalParameter] |
[Skill<TMetadata>] | [ConditionalSkill] |
[SubAgent<TMetadata>] | [ConditionalSubAgent] |
Compile-Time Validation Bonus
Generic attributes also catch typos at compile time:
[AIFunction<SearchMetadata>]
[AIDescription("Search using {metadata.PrviderName}")] // Compiler error: PrviderName doesn't exist
public async Task<string> Search(string query) { }Example with Full Type Safety
public class SearchMetadata : IToolMetadata
{
public string ProviderName { get; set; } = "Default";
public bool HasFilters { get; set; } = false;
// Implementation...
}
public class SearchToolkit
{
[AIFunction<SearchMetadata>]
[AIDescription("Search using {metadata.ProviderName}")]
[ConditionalFunction("HasFilters")]
public async Task<string> FilteredSearch(
string query,
[ConditionalParameter("HasFilters")]
SearchFilters? filters = null)
{
// Compile-time validated: ProviderName and HasFilters exist
}
}Registration Patterns
Basic Registration
var agent = new AgentBuilder()
.WithTools<SearchToolkit>(new SearchMetadata
{
ProviderName = "Tavily",
HasAdvancedFeatures = true
})
.Build();Environment-Based Registration
var metadata = new SearchMetadata
{
ProviderName = Environment.GetEnvironmentVariable("SEARCH_PROVIDER") ?? "Default",
HasAdvancedFeatures = Environment.GetEnvironmentVariable("ENABLE_ADVANCED") == "true"
};
var agent = new AgentBuilder()
.WithTools<SearchToolkit>(metadata)
.Build();User-Context Registration
public Agent CreateAgentForUser(User user)
{
var metadata = new SearchMetadata
{
HasAdvancedFeatures = user.HasPremiumSubscription,
MaxResults = user.HasPremiumSubscription ? 50 : 10
};
return new AgentBuilder()
.WithTools<SearchToolkit>(metadata)
.Build();
}Complete Example
// Metadata definition
public class WebSearchMetadata : IToolMetadata
{
public string ProviderName { get; set; } = "Bing";
public int MaxResults { get; set; } = 10;
public bool HasImageSearch { get; set; } = false;
public bool HasNewsSearch { get; set; } = false;
public int ProviderCount { get; set; } = 1;
// IToolMetadata implementation...
}
// Toolkit using metadata
[Toolkit("Web search operations using {metadata.ProviderName}")]
public class WebSearchToolkit
{
[AIFunction<WebSearchMetadata>]
[AIDescription("Search the web using {metadata.ProviderName}")]
public async Task<string> WebSearch(
[AIDescription("Search query")] string query,
[ConditionalParameter("ProviderCount > 1")]
[AIDescription("Specific provider to use")]
string? provider = null)
{
// Implementation
}
[AIFunction<WebSearchMetadata>]
[ConditionalFunction("HasImageSearch")]
[AIDescription("Search for images using {metadata.ProviderName}")]
public async Task<string> ImageSearch(string query)
{
// Only visible when HasImageSearch is true
}
[AIFunction<WebSearchMetadata>]
[ConditionalFunction("HasNewsSearch")]
[AIDescription("Search recent news articles")]
public async Task<string> NewsSearch(string query)
{
// Only visible when HasNewsSearch is true
}
[Skill<WebSearchMetadata>]
[ConditionalSkill("HasImageSearch")]
public Skill ImageResearchSkill()
{
return SkillFactory.Create(
name: "Image Research",
description: "Research topics using web and image search",
functionResult: "Image research activated.",
systemPrompt: "Use WebSearch for context, ImageSearch for visuals.",
"WebSearchToolkit.WebSearch",
"WebSearchToolkit.ImageSearch"
);
}
}
// Registration
var agent = new AgentBuilder()
.WithTools<WebSearchToolkit>(new WebSearchMetadata
{
ProviderName = "Google",
MaxResults = 20,
HasImageSearch = true,
HasNewsSearch = false,
ProviderCount = 3
})
.Build();
// Agent sees:
// - WebSearch: "Search the web using Google" (with provider parameter)
// - ImageSearch: "Search for images using Google"
// - ImageResearchSkill
// Agent does NOT see:
// - NewsSearch (HasNewsSearch is false)Best Practices
Use meaningful property names:
HasAdvancedFeaturesis clearer thanflag1.Provide sensible defaults: Metadata should work even if not all properties are set.
Use generics for type safety: Catch typos at compile time, not runtime.
Keep metadata focused: One metadata class per tool; don't share across unrelated tools.
Document property effects: Make it clear which properties affect which capabilities.
// Good: Clear names, documented effects, type-safe
public class SearchMetadata : IToolMetadata
{
/// <summary>
/// Display name of the search provider. Used in function descriptions.
/// </summary>
public string ProviderName { get; set; } = "Default";
/// <summary>
/// When true, enables AdvancedSearch and FilteredSearch functions.
/// </summary>
public bool HasAdvancedFeatures { get; set; } = false;
// Implementation...
}
// Bad: Cryptic names, no defaults, no documentation
public class Meta : IToolMetadata
{
public string p { get; set; }
public bool f1 { get; set; }
public bool f2 { get; set; }
// What do these even mean?
}Next Steps
- 02.1.5 Context Engineering.md - Collapsing and context management