diff --git a/pkgs/sdk/server-ai/src/LdAiClient.cs b/pkgs/sdk/server-ai/src/LdAiClient.cs index ea227e7..522bcb6 100644 --- a/pkgs/sdk/server-ai/src/LdAiClient.cs +++ b/pkgs/sdk/server-ai/src/LdAiClient.cs @@ -97,6 +97,25 @@ public ILdAiConfigTracker ModelConfig(string key, Context context, LdAiConfig de } + + private static IDictionary AddSingleKindContextAttributes(Context context) + { + var attributes = new Dictionary + { + ["kind"] = context.Kind.ToString(), + ["key"] = context.Key, + ["anonymous"] = context.Anonymous + }; + + foreach (var key in context.OptionalAttributeNames) + { + attributes[key] = ValueToObject(context.GetValue(AttributeRef.FromLiteral(key))); + } + + return attributes; + } + + /// /// Retrieves all attributes from the given context, including private attributes. The attributes /// are converted into C# primitives recursively. @@ -105,17 +124,23 @@ public ILdAiConfigTracker ModelConfig(string key, Context context, LdAiConfig de /// the attributes private static IDictionary GetAllAttributes(Context context) { - var attributes = new Dictionary(); - foreach (var key in context.OptionalAttributeNames) + if (!context.Multiple) { - attributes[key] = ValueToObject(context.GetValue(AttributeRef.FromLiteral(key))); + return AddSingleKindContextAttributes(context); } - attributes["kind"] = context.Kind.ToString(); - attributes["key"] = context.Key; - attributes["anonymous"] = context.Anonymous; + var attrs = new Dictionary + { + ["kind"] = context.Kind, + ["key"] = context.FullyQualifiedKey + }; - return attributes; + foreach (var kind in context.MultiKindContexts) + { + attrs[kind.Kind.ToString()] = AddSingleKindContextAttributes(kind); + } + + return attrs; } /// diff --git a/pkgs/sdk/server-ai/test/InterpolationTests.cs b/pkgs/sdk/server-ai/test/InterpolationTests.cs index d7ddf16..eb5c6b4 100644 --- a/pkgs/sdk/server-ai/test/InterpolationTests.cs +++ b/pkgs/sdk/server-ai/test/InterpolationTests.cs @@ -170,4 +170,70 @@ public void TestInterpolationWithNestedContextAttributes() var result = Eval("I can ingest over {{ ldctx.stats.power }} tokens per second!", context, null); Assert.Equal("I can ingest over 9000 tokens per second!", result); } + + [Fact] + public void TestInterpolationWithMultiKindContext() + { + var user = Context.Builder(ContextKind.Default, "123") + .Set("cat_ownership", LdValue.ObjectFrom(new Dictionary + { + { "count", LdValue.Of(12) } + })).Build(); + + var cat = Context.Builder(ContextKind.Of("cat"), "456") + .Set("health", LdValue.ObjectFrom(new Dictionary + { + { "hunger", LdValue.Of("off the charts") } + })).Build(); + + var context = Context.MultiBuilder().Add(user).Add(cat).Build(); + + var nestedVars = Eval("As an owner of {{ ldctx.user.cat_ownership.count }} cats, I must report that my cat's hunger level is {{ ldctx.cat.health.hunger }}!", context, null); + Assert.Equal("As an owner of 12 cats, I must report that my cat's hunger level is off the charts!", nestedVars); + + var canonicalKeys = Eval("multi={{ ldctx.key }} user={{ ldctx.user.key }} cat={{ ldctx.cat.key }}", context, null); + Assert.Equal("multi=cat:456:user:123 user=123 cat=456", canonicalKeys); + } + + [Fact] + public void TestInterpolationMultiKindDoesNotHaveAnonymousAttribute() + { + var user = Context.Builder(ContextKind.Default, "123") + .Set("cat_ownership", LdValue.ObjectFrom(new Dictionary + { + { "count", LdValue.Of(12) } + })).Build(); + + var cat = Context.Builder(ContextKind.Of("cat"), "456") + .Set("health", LdValue.ObjectFrom(new Dictionary + { + { "hunger", LdValue.Of("off the charts") } + })).Build(); + + var context = Context.MultiBuilder().Add(user).Add(cat).Build(); + + var result = Eval("anonymous=<{{ ldctx.anonymous }}>", context, null); + Assert.Equal("anonymous=<>", result); + } + + [Fact] + public void TestInterpolationMultiKindContextHasKindMulti() + { + var user = Context.Builder(ContextKind.Default, "123") + .Set("cat_ownership", LdValue.ObjectFrom(new Dictionary + { + { "count", LdValue.Of(12) } + })).Build(); + + var cat = Context.Builder(ContextKind.Of("cat"), "456") + .Set("health", LdValue.ObjectFrom(new Dictionary + { + { "hunger", LdValue.Of("off the charts") } + })).Build(); + + var context = Context.MultiBuilder().Add(user).Add(cat).Build(); + + var result = Eval("kind={{ ldctx.kind }}", context, null); + Assert.Equal("kind=multi", result); + } }