experimental-ext-interceptors/csharp/sdk/samples/AvatarMoodInterceptorSample/AvatarInterceptors.cs
Peder Holdgaard Pedersen d01a626b76
C# Re-implementation (#5)
Re-implementation of C# interceptors SDK with clearer focus on
server-side / gateway interceptors.
Fixes a lot of issues with the existing implementation.
Still more boilerplate than I'm happy with, but this is better than what
was there. Will refine further in later PRs.
2026-05-14 15:11:41 +02:00

52 lines
1.9 KiB
C#

using System.Text.Json.Nodes;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Interceptors.Protocol;
using ModelContextProtocol.Interceptors.Server;
namespace AvatarMoodInterceptorSample;
/// <summary>
/// A sink interceptor that classifies conversational mood from each
/// <c>llm/completion</c> response using a smaller, secondary model (Haiku) and
/// updates a console avatar. Demonstrates that sink interceptors can do
/// real work — driving downstream state — without gating the main conversation.
/// </summary>
[McpServerInterceptorType]
public sealed class AvatarInterceptors
{
private readonly IChatClient _haiku;
private readonly string _haikuModelId;
private readonly AvatarState _state;
public AvatarInterceptors(IChatClient haiku, string haikuModelId, AvatarState state)
{
_haiku = haiku;
_haikuModelId = haikuModelId;
_state = state;
}
[McpServerInterceptor(
Name = "avatar-mood",
Description = "Classifies conversation mood via a secondary model and updates the avatar state.",
Type = InterceptorType.Sink,
Events = [InterceptionEvents.LlmCompletion],
Phase = InterceptorPhase.Response)]
public SinkInterceptorResult OnLlmCompletion(
JsonNode payload,
string @event,
InterceptorPhase phase,
InvokeInterceptorContext? context,
CancellationToken cancellationToken)
{
// Sink contract: parallel, fire-and-forget, failures swallowed.
// We return immediately so the main conversation never waits on Haiku.
_ = Task.Run(async () =>
{
var verdict = await MoodClassifier.ClassifyAsync(_haiku, payload, cancellationToken);
_state.Update(verdict.Label, verdict.Confidence, _haikuModelId);
AvatarRenderer.Render(_state);
}, cancellationToken);
return new SinkInterceptorResult { Recorded = true };
}
}