< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.AgentFactory
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/AgentFactory.cs
Line coverage
100%
Covered lines: 93
Uncovered lines: 0
Coverable lines: 93
Total lines: 192
Line coverage: 100%
Branch coverage
89%
Covered branches: 43
Total branches: 48
Branch coverage: 89.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%44100%
CreateAgent()100%11100%
CreateAgent(...)100%22100%
CreateAgentFromType(...)100%22100%
CreateAgent(...)83.33%1212100%
GetApplicableTypes(...)100%1212100%
BuildConfiguredOptions()100%22100%
BuildFunctionsCache()100%44100%
BuildFunctionsForType(...)87.5%88100%
BuildFunctionsForTypeViaReflection(...)100%22100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/AgentFactory.cs

#LineLine coverage
 1using Microsoft.Agents.AI;
 2using Microsoft.Extensions.AI;
 3using Microsoft.Extensions.DependencyInjection;
 4
 5using System.Diagnostics.CodeAnalysis;
 6using System.Reflection;
 7
 8namespace NexusLabs.Needlr.AgentFramework;
 9
 10internal sealed class AgentFactory : IAgentFactory
 11{
 12    private readonly IServiceProvider _serviceProvider;
 13    private readonly List<Action<AgentFrameworkConfigureOptions>> _configureCallbacks;
 14    private readonly IReadOnlyList<Type> _functionTypes;
 15    private readonly IReadOnlyDictionary<string, IReadOnlyList<Type>> _functionGroupMap;
 16    private readonly Lazy<AgentFrameworkConfigureOptions> _lazyConfiguredOptions;
 17    private readonly Lazy<IReadOnlyDictionary<Type, IReadOnlyList<AIFunction>>> _lazyFunctionsCache;
 18    private readonly IAIFunctionProvider? _generatedProvider;
 19
 20    private readonly IReadOnlyDictionary<string, Type> _agentTypeMap;
 21
 6522    public AgentFactory(
 6523        IServiceProvider serviceProvider,
 6524        List<Action<AgentFrameworkConfigureOptions>> configureCallbacks,
 6525        IReadOnlyList<Type> functionTypes,
 6526        IReadOnlyDictionary<string, IReadOnlyList<Type>>? functionGroupMap = null,
 6527        IReadOnlyDictionary<string, Type>? agentTypeMap = null,
 6528        IAIFunctionProvider? generatedProvider = null)
 29    {
 6530        _serviceProvider = serviceProvider;
 6531        _configureCallbacks = configureCallbacks;
 6532        _functionTypes = functionTypes;
 6533        _functionGroupMap = functionGroupMap ?? new Dictionary<string, IReadOnlyList<Type>>();
 6534        _agentTypeMap = agentTypeMap ?? new Dictionary<string, Type>();
 6535        _generatedProvider = generatedProvider;
 36
 11837        _lazyConfiguredOptions = new(() => BuildConfiguredOptions());
 11838        _lazyFunctionsCache = new(() => BuildFunctionsCache());
 6539    }
 40
 41    public AIAgent CreateAgent<TAgent>() where TAgent : class
 1742        => CreateAgentFromType(typeof(TAgent));
 43
 44    public AIAgent CreateAgent(string agentClassName)
 45    {
 5246        ArgumentNullException.ThrowIfNull(agentClassName);
 47
 5248        if (!_agentTypeMap.TryGetValue(agentClassName, out var type))
 249            throw new InvalidOperationException(
 250                $"No agent named '{agentClassName}' is registered. " +
 251                $"Ensure the class is decorated with [NeedlrAiAgent] and registered via " +
 252                $"AddAgent<T>(), AddAgentsFromGenerated(), or the source generator [ModuleInitializer].");
 53
 5054        return CreateAgentFromType(type);
 55    }
 56
 57    private AIAgent CreateAgentFromType(Type agentType)
 58    {
 6759        var attr = agentType.GetCustomAttribute<NeedlrAiAgentAttribute>()
 6760            ?? throw new InvalidOperationException(
 6761                $"'{agentType.Name}' is not decorated with [NeedlrAiAgent]. " +
 6762                $"Apply [NeedlrAiAgent] to the class or use CreateAgent(configure) to set options manually.");
 63
 6564        return CreateAgent(opts =>
 6565        {
 6566            opts.Name = agentType.Name;
 6567            opts.Description = attr.Description;
 6568            opts.Instructions = attr.Instructions;
 6569            opts.FunctionTypes = attr.FunctionTypes;
 6570            opts.FunctionGroups = attr.FunctionGroups;
 13071        });
 72    }
 73
 74    public AIAgent CreateAgent(Action<AgentFactoryOptions>? configure = null)
 75    {
 10476        var agentOptions = new AgentFactoryOptions();
 10477        configure?.Invoke(agentOptions);
 78
 10479        var configuredOpts = _lazyConfiguredOptions.Value;
 10480        var allFunctions = _lazyFunctionsCache.Value;
 81
 10482        var applicableTypes = GetApplicableTypes(agentOptions);
 10483        var tools = new List<AITool>();
 84
 31485        foreach (var type in applicableTypes)
 86        {
 5387            if (allFunctions.TryGetValue(type, out var fns))
 88            {
 5089                tools.AddRange(fns);
 90            }
 91        }
 92
 10493        var chatClient = configuredOpts.ChatClientFactory?.Invoke(_serviceProvider)
 10494            ?? _serviceProvider.GetRequiredService<IChatClient>();
 95
 10496        var instructions = agentOptions.Instructions ?? configuredOpts.DefaultInstructions;
 97
 10498        return chatClient.AsAIAgent(
 10499            name: agentOptions.Name,
 104100            description: agentOptions.Description,
 104101            instructions: instructions,
 104102            tools: tools,
 104103            services: _serviceProvider);
 104    }
 105
 106    private IEnumerable<Type> GetApplicableTypes(AgentFactoryOptions agentOptions)
 107    {
 104108        bool hasExplicitScope = agentOptions.FunctionTypes != null || agentOptions.FunctionGroups != null;
 109
 104110        if (!hasExplicitScope)
 66111            return _functionTypes;
 112
 38113        var types = new List<Type>();
 114
 38115        if (agentOptions.FunctionTypes != null)
 24116            types.AddRange(agentOptions.FunctionTypes);
 117
 38118        if (agentOptions.FunctionGroups != null)
 119        {
 58120            foreach (var group in agentOptions.FunctionGroups)
 121            {
 15122                if (_functionGroupMap.TryGetValue(group, out var groupTypes))
 11123                    types.AddRange(groupTypes);
 124            }
 125        }
 126
 38127        return types.Distinct();
 128    }
 129
 130    private AgentFrameworkConfigureOptions BuildConfiguredOptions()
 131    {
 53132        var opts = new AgentFrameworkConfigureOptions
 53133        {
 53134            ServiceProvider = _serviceProvider,
 53135        };
 136
 212137        foreach (var callback in _configureCallbacks)
 138        {
 53139            callback(opts);
 140        }
 141
 53142        return opts;
 143    }
 144
 145    private IReadOnlyDictionary<Type, IReadOnlyList<AIFunction>> BuildFunctionsCache()
 146    {
 53147        var dict = new Dictionary<Type, IReadOnlyList<AIFunction>>();
 148
 468149        foreach (var type in _functionTypes)
 150        {
 181151            var functions = BuildFunctionsForType(type);
 181152            if (functions.Count > 0)
 153            {
 171154                dict[type] = functions;
 155            }
 156        }
 157
 53158        return dict;
 159    }
 160
 161    [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Reflection branch is unreachable when GeneratedAIFun
 162    [UnconditionalSuppressMessage("TrimAnalysis", "IL2026", Justification = "Reflection fallback path is unreachable in 
 163    [UnconditionalSuppressMessage("TrimAnalysis", "IL2067", Justification = "ActivatorUtilities.CreateInstance is only r
 164    private IReadOnlyList<AIFunction> BuildFunctionsForType(Type type)
 165    {
 181166        if (_generatedProvider?.TryGetFunctions(type, _serviceProvider, out var generated) == true)
 1167            return generated!;
 168
 180169        var isStatic = type.IsAbstract && type.IsSealed;
 180170        object? instance = isStatic
 180171            ? null
 180172            : ActivatorUtilities.CreateInstance(_serviceProvider, type);
 173
 180174        return BuildFunctionsForTypeViaReflection(type, isStatic, instance);
 175    }
 176
 177    [RequiresDynamicCode("Reflection-based AIFunction building requires dynamic code generation.")]
 178    [RequiresUnreferencedCode("Reflection-based AIFunction building requires unreferenced code access.")]
 179    private static IReadOnlyList<AIFunction> BuildFunctionsForTypeViaReflection(Type type, bool isStatic, object? instan
 180    {
 180181        var bindingFlags = isStatic
 180182            ? BindingFlags.Public | BindingFlags.Static
 180183            : BindingFlags.Public | BindingFlags.Instance;
 184
 180185        var functions = type.GetMethods(bindingFlags)
 826186            .Where(m => m.IsDefined(typeof(AgentFunctionAttribute), inherit: true))
 170187            .Select(m => AIFunctionFactory.Create(m, target: instance))
 180188            .ToList();
 189
 180190        return functions.AsReadOnly();
 191    }
 192}