A FunctionInvokingChatClient subclass that emits async events before and after each tool invocation, giving you full observability into the Microsoft.Extensions.AI function-calling pipeline.
dotnet add package PinkRooster.EventEmittingFunctionInvokingChatClient
using Microsoft.Extensions.AI;
// Build a pipeline with event-emitting function invocation
var pipeline = new ChatClientBuilder(innerClient)
.Use(client =>
{
var funcClient = new EventEmittingFunctionInvokingChatClient(client);
funcClient.ToolCallStarting += async (sender, e) =>
{
Console.WriteLine($"Starting: {e.FunctionName} [{e.CallId}]");
await Task.CompletedTask;
};
funcClient.ToolCallCompleted += async (sender, e) =>
{
var icon = e.Succeeded ? "OK" : "FAIL";
Console.WriteLine($"{icon}: {e.FunctionName} in {e.Duration.TotalMilliseconds:F0}ms");
await Task.CompletedTask;
};
return funcClient;
})
.Build();- ToolCallStarting event fires before each function invocation with function name, arguments, call ID, and iteration info
- ToolCallCompleted event fires after each invocation with result/exception, wall-clock duration, and termination status
- Accurate timing that measures only function execution, excluding event handler overhead
- Thread-safe: works correctly with
AllowConcurrentInvocation = true - Robust error handling in the completed event (handler exceptions never swallow function exceptions)
- Overridable
OnCompletedHandlerExceptionfor custom error handling in completed event handlers
| Property | Type | Description |
|---|---|---|
FunctionName |
string |
Name of the function about to be invoked |
CallId |
string |
Unique identifier for this tool call |
Arguments |
AIFunctionArguments? |
Arguments passed to the function |
Iteration |
int |
0-based roundtrip iteration number |
FunctionCallIndex |
int |
0-based index within the current iteration |
FunctionCount |
int |
Total function calls in the current iteration |
IsStreaming |
bool |
Whether this is inside a streaming response |
Context |
FunctionInvocationContext |
Full context for advanced scenarios |
| Property | Type | Description |
|---|---|---|
FunctionName |
string |
Name of the function that was invoked |
CallId |
string |
Unique identifier for this tool call |
Result |
object? |
Return value, or null on failure |
Exception |
Exception? |
Exception thrown, or null on success |
Duration |
TimeSpan |
Wall-clock duration (excluding handler time) |
Succeeded |
bool |
Convenience: true when Exception is null |
TerminationRequested |
bool |
Whether the function requested loop termination |
Context |
FunctionInvocationContext |
Full context for advanced scenarios |
See the Tavily Research Agent for a full working example that uses this library with the Tavily MCP server to build an interactive research assistant.