Skip to content

Commit 40ff539

Browse files
authored
feat: support multi-kind contexts in template interpolation (#48)
This PR allows for Mustache interpolation of multi-kind contexts. Developers can refer to single kinds by their name, e.g. ldctx.user or ldctx.device.
1 parent 8767510 commit 40ff539

File tree

2 files changed

+98
-7
lines changed

2 files changed

+98
-7
lines changed

pkgs/sdk/server-ai/src/LdAiClient.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,25 @@ public ILdAiConfigTracker ModelConfig(string key, Context context, LdAiConfig de
9797

9898
}
9999

100+
101+
private static IDictionary<string, object> AddSingleKindContextAttributes(Context context)
102+
{
103+
var attributes = new Dictionary<string, object>
104+
{
105+
["kind"] = context.Kind.ToString(),
106+
["key"] = context.Key,
107+
["anonymous"] = context.Anonymous
108+
};
109+
110+
foreach (var key in context.OptionalAttributeNames)
111+
{
112+
attributes[key] = ValueToObject(context.GetValue(AttributeRef.FromLiteral(key)));
113+
}
114+
115+
return attributes;
116+
}
117+
118+
100119
/// <summary>
101120
/// Retrieves all attributes from the given context, including private attributes. The attributes
102121
/// are converted into C# primitives recursively.
@@ -105,17 +124,23 @@ public ILdAiConfigTracker ModelConfig(string key, Context context, LdAiConfig de
105124
/// <returns>the attributes</returns>
106125
private static IDictionary<string, object> GetAllAttributes(Context context)
107126
{
108-
var attributes = new Dictionary<string, object>();
109-
foreach (var key in context.OptionalAttributeNames)
127+
if (!context.Multiple)
110128
{
111-
attributes[key] = ValueToObject(context.GetValue(AttributeRef.FromLiteral(key)));
129+
return AddSingleKindContextAttributes(context);
112130
}
113131

114-
attributes["kind"] = context.Kind.ToString();
115-
attributes["key"] = context.Key;
116-
attributes["anonymous"] = context.Anonymous;
132+
var attrs = new Dictionary<string, object>
133+
{
134+
["kind"] = context.Kind,
135+
["key"] = context.FullyQualifiedKey
136+
};
117137

118-
return attributes;
138+
foreach (var kind in context.MultiKindContexts)
139+
{
140+
attrs[kind.Kind.ToString()] = AddSingleKindContextAttributes(kind);
141+
}
142+
143+
return attrs;
119144
}
120145

121146
/// <summary>

pkgs/sdk/server-ai/test/InterpolationTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,70 @@ public void TestInterpolationWithNestedContextAttributes()
170170
var result = Eval("I can ingest over {{ ldctx.stats.power }} tokens per second!", context, null);
171171
Assert.Equal("I can ingest over 9000 tokens per second!", result);
172172
}
173+
174+
[Fact]
175+
public void TestInterpolationWithMultiKindContext()
176+
{
177+
var user = Context.Builder(ContextKind.Default, "123")
178+
.Set("cat_ownership", LdValue.ObjectFrom(new Dictionary<string, LdValue>
179+
{
180+
{ "count", LdValue.Of(12) }
181+
})).Build();
182+
183+
var cat = Context.Builder(ContextKind.Of("cat"), "456")
184+
.Set("health", LdValue.ObjectFrom(new Dictionary<string, LdValue>
185+
{
186+
{ "hunger", LdValue.Of("off the charts") }
187+
})).Build();
188+
189+
var context = Context.MultiBuilder().Add(user).Add(cat).Build();
190+
191+
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);
192+
Assert.Equal("As an owner of 12 cats, I must report that my cat's hunger level is off the charts!", nestedVars);
193+
194+
var canonicalKeys = Eval("multi={{ ldctx.key }} user={{ ldctx.user.key }} cat={{ ldctx.cat.key }}", context, null);
195+
Assert.Equal("multi=cat:456:user:123 user=123 cat=456", canonicalKeys);
196+
}
197+
198+
[Fact]
199+
public void TestInterpolationMultiKindDoesNotHaveAnonymousAttribute()
200+
{
201+
var user = Context.Builder(ContextKind.Default, "123")
202+
.Set("cat_ownership", LdValue.ObjectFrom(new Dictionary<string, LdValue>
203+
{
204+
{ "count", LdValue.Of(12) }
205+
})).Build();
206+
207+
var cat = Context.Builder(ContextKind.Of("cat"), "456")
208+
.Set("health", LdValue.ObjectFrom(new Dictionary<string, LdValue>
209+
{
210+
{ "hunger", LdValue.Of("off the charts") }
211+
})).Build();
212+
213+
var context = Context.MultiBuilder().Add(user).Add(cat).Build();
214+
215+
var result = Eval("anonymous=<{{ ldctx.anonymous }}>", context, null);
216+
Assert.Equal("anonymous=<>", result);
217+
}
218+
219+
[Fact]
220+
public void TestInterpolationMultiKindContextHasKindMulti()
221+
{
222+
var user = Context.Builder(ContextKind.Default, "123")
223+
.Set("cat_ownership", LdValue.ObjectFrom(new Dictionary<string, LdValue>
224+
{
225+
{ "count", LdValue.Of(12) }
226+
})).Build();
227+
228+
var cat = Context.Builder(ContextKind.Of("cat"), "456")
229+
.Set("health", LdValue.ObjectFrom(new Dictionary<string, LdValue>
230+
{
231+
{ "hunger", LdValue.Of("off the charts") }
232+
})).Build();
233+
234+
var context = Context.MultiBuilder().Add(user).Add(cat).Build();
235+
236+
var result = Eval("kind={{ ldctx.kind }}", context, null);
237+
Assert.Equal("kind=multi", result);
238+
}
173239
}

0 commit comments

Comments
 (0)