-
Notifications
You must be signed in to change notification settings - Fork 855
Description
Description
Microsoft.Extensions.AI version 10.3.0 throws NullReferenceException when calling AIFunctionFactory.Create() with delegates created via DynamicMethod.DefineParameter(). This is a regression - the same code works perfectly in version 10.2.0.
The crash occurs in NullabilityInfoContext.Create(ParameterInfo) when it tries to read RuntimeCustomAttributeData from parameters created with DynamicMethod, which lack complete reflection metadata.
This completely blocks legitimate use cases like:
- MCP (Model Context Protocol) server integration
- Dynamic tool generation from external schemas (OpenAPI, etc.)
- Runtime code generation for AI function calling
DynamicMethod is the only way to create delegates with proper parameter names and types without closure capture (Expression Trees add hidden Closure parameter that breaks AIFunctionFactory validation).
Reproduction Steps
- Install Microsoft.Extensions.AI 10.3.0
- Create a DynamicMethod with parameters:
using System.Reflection;
using System.Reflection.Emit;
using Microsoft.Extensions.AI;
DynamicMethod dynamicMethod = new DynamicMethod(
"TestMethod",
typeof(Task),
new[] { typeof(string), typeof(int) },
typeof(Program).Module);
dynamicMethod.DefineParameter(1, ParameterAttributes.None, "message");
dynamicMethod.DefineParameter(2, ParameterAttributes.None, "count");
ILGenerator il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Call, typeof(Task).GetMethod(nameof(Task.FromResult)).MakeGenericMethod(typeof(object)));
il.Emit(OpCodes.Ret);
Delegate testDelegate = dynamicMethod.CreateDelegate(typeof(Func<string, int, Task>));
- Call AIFunctionFactory.Create:
AIFunction function = AIFunctionFactory.Create(testDelegate, new AIFunctionFactoryOptions
{
Name = "TestTool",
Description = "Test tool"
});
- Observe NullReferenceException
Expected behavior
AIFunctionFactory.Create() should successfully create an AIFunction from the DynamicMethod delegate, just as it did in version 10.2.0.
The function should have proper parameter names ("message", "count") derived from DefineParameter() calls, enabling:
- Correct JSON schema generation
- Proper LLM tool calling
- Meaningful debugging and logging
Actual behavior
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Reflection.RuntimeCustomAttributeData.GetCustomAttributeRecords(RuntimeModule module, Int32 targetToken)
at System.Reflection.RuntimeCustomAttributeData.GetCustomAttributes(RuntimeModule module, Int32 tkTarget)
at System.Reflection.NullabilityInfoContext.Create(ParameterInfo parameterInfo)
at Microsoft.Extensions.AI.AIJsonUtilities.<>c__DisplayClass42_0.g__TransformSchemaNode|0(JsonSchemaExporterContext schemaExporterContext, JsonNode schema)
at System.Text.Json.Schema.JsonSchema.g__CompleteSchema|100_0(JsonNode schema, <>c__DisplayClass100_0&)
at System.Text.Json.Schema.JsonSchema.ToJsonNode(JsonSchemaExporterOptions options)
at System.Text.Json.Schema.JsonSchemaExporter.GetJsonSchemaAsNode(JsonTypeInfo typeInfo, JsonSchemaExporterOptions exporterOptions)
at Microsoft.Extensions.AI.AIJsonUtilities.CreateJsonSchemaCore(...)
at Microsoft.Extensions.AI.AIJsonUtilities.CreateFunctionJsonSchema(...)
at Microsoft.Extensions.AI.AIFunctionFactory.ReflectionAIFunctionDescriptor..ctor(...)
at Microsoft.Extensions.AI.AIFunctionFactory.Create(Delegate method, AIFunctionFactoryOptions options)
The crash occurs because DynamicMethod.DefineParameter() creates ParameterInfo with incomplete RuntimeCustomAttributeData, which NullabilityInfoContext cannot handle.
Regression?
☑ Yes
- Version: Microsoft.Extensions.AI 10.3.0
- Previous working version: Microsoft.Extensions.AI 10.2.0
This is confirmed regression. The exact same code works in 10.2.0 but crashes in 10.3.0.
Known Workarounds
-
WORKAROUND (Current): Downgrade to Microsoft.Extensions.AI 10.2.0
- Status: ✅ Works
- Impact: Cannot use newer features/fixes from 10.3.0
-
ATTEMPTED (Failed): Use Expression Trees instead of DynamicMethod
- Status: ❌ Doesn't work
- Reason: Expression Trees capture closure context, producing delegates with hidden Closure parameter:
Func<Closure, string, int, Task> // Has extra parameter - This fails AIFunctionFactory validation because parameter count doesn't match expected signature
-
ATTEMPTED (Failed): Remove DefineParameter() calls
- Status:
⚠️ Technically works but unacceptable - Reason: Parameters get generic names like "arg0", "arg1" which:
- Breaks LLM tool calling (models need semantic names)
- Breaks JSON schema generation
- Makes debugging impossible
- Status:
- Microsoft.Extensions.AI: 10.3.0 (broken), 10.2.0 (works)
- .NET Version: 10.0.0
- OS: Mageia Linux 9
- Use Case: MCP (Model Context Protocol) server integration
- Architecture: x64
- Exception in BlazorWasm applications due to NullabilityInfoContext usage runtime#102848 (Blazor WASM NullabilityInfoContext crash)
- Consider setting NullabilityInfoContextSupport=true in EF's NuGet package efcore#27474 (EF Core handling IsSupported for mobile)
- dotnet/aspnetcore#59806 (MSAL NullabilityInfoContext_NotSupported)
- MCP server integration (GitHub's @modelcontextprotocol/server-*)
- OpenAPI-to-AIFunction conversion
- Plugin systems with third-party tools
- Multi-language interop (Python/Node.js → .NET)
- Only way to get proper parameter names without closure capture
- Most efficient for runtime code generation (per MSDN docs)
- No assembly/type creation overhead
- JIT-compiled code is GC-reclaimable
Configuration
Other information
ROOT CAUSE:
Version 10.3.0 introduced NullabilityInfoContext.Create(ParameterInfo) call in AIJsonUtilities.CreateJsonSchemaCore to extract nullability metadata. However, ParameterInfo objects from DynamicMethod.DefineParameter() lack complete RuntimeCustomAttributeData, causing the NullReferenceException.SUGGESTED FIX:
Add safety check before calling NullabilityInfoContext.Create():try
{
if (NullabilityInfoContext.IsSupported)
{
nullabilityInfo = nullabilityContext.Create(parameterInfo);
}
}
catch (NullReferenceException)
{
// Parameter from DynamicMethod - treat as non-nullable
}Similar patterns exist in:
IMPACT:
This regression blocks all scenarios requiring runtime tool generation:WHY DYNAMICMETHOD IS ESSENTIAL:
Thank you for your attention to this critical regression.