1
1
package com .launchdarkly .sdk .server .ai ;
2
2
3
+ import java .util .ArrayList ;
4
+ import java .util .HashMap ;
5
+ import java .util .List ;
6
+ import java .util .Optional ;
7
+
3
8
import com .launchdarkly .logging .LDLogger ;
4
9
import com .launchdarkly .sdk .server .interfaces .LDClientInterface ;
10
+ import com .launchdarkly .sdk .LDValue ;
11
+ import com .launchdarkly .sdk .LDValueType ;
12
+ import com .launchdarkly .sdk .server .ai .datamodel .AiConfig ;
13
+ import com .launchdarkly .sdk .server .ai .datamodel .Message ;
14
+ import com .launchdarkly .sdk .server .ai .datamodel .Meta ;
15
+ import com .launchdarkly .sdk .server .ai .datamodel .Model ;
16
+ import com .launchdarkly .sdk .server .ai .datamodel .Provider ;
17
+ import com .launchdarkly .sdk .server .ai .datamodel .Role ;
5
18
import com .launchdarkly .sdk .server .ai .interfaces .LDAiClientInterface ;
6
19
7
20
/**
8
- * The LaunchDarkly AI client. The client is capable of retrieving AI Configs from LaunchDarkly,
9
- * and generating events specific to usage of the AI Config when interacting with model providers.
21
+ * The LaunchDarkly AI client. The client is capable of retrieving AI Configs
22
+ * from LaunchDarkly,
23
+ * and generating events specific to usage of the AI Config when interacting
24
+ * with model providers.
10
25
*/
11
26
public class LDAiClient implements LDAiClientInterface {
12
27
private LDClientInterface client ;
@@ -15,14 +30,151 @@ public class LDAiClient implements LDAiClientInterface {
15
30
/**
16
31
* Creates a {@link LDAiClient}
17
32
*
18
- * @param client LaunchDarkly Java Server SDK
33
+ * @param client LaunchDarkly Java Server SDK
19
34
*/
20
35
public LDAiClient (LDClientInterface client ) {
21
- if (client == null ) {
22
- //Error
36
+ if (client == null ) {
37
+ // Error
23
38
} else {
24
39
this .client = client ;
25
40
this .logger = client .getLogger ();
26
41
}
27
42
}
43
+
44
+ /**
45
+ * Method to convert the JSON variable into the AiConfig object
46
+ *
47
+ * Currently, I am doing this in a mutable way, just to verify the logic convert
48
+ * LDValue into AiConfig.
49
+ * It is possible we need a builder to create immutable version of this for ease
50
+ * of use in a later PR.
51
+ *
52
+ * @param value
53
+ * @param key
54
+ */
55
+ protected AiConfig parseAiConfig (LDValue value , String key ) {
56
+ AiConfig result = new AiConfig ();
57
+
58
+ try {
59
+ // Verify the whole value is a JSON object
60
+ if (value == null || value .getType () != LDValueType .OBJECT ) {
61
+ throw new AiConfigParseException ("Input to parseAiConfig must be a JSON object" );
62
+ }
63
+
64
+ // Convert the _meta JSON object into Meta
65
+ LDValue valueMeta = value .get ("_ldMeta" );
66
+ if (valueMeta == LDValue .ofNull () || valueMeta .getType () != LDValueType .OBJECT ) {
67
+ throw new AiConfigParseException ("_ldMeta must be a JSON object" );
68
+ }
69
+
70
+ Meta meta = new Meta ();
71
+ // TODO: Do we expect customer calling this to build default value?
72
+ // If we do, then some of the values would be null
73
+ meta .setEnabled (ldValueNullCheck (valueMeta .get ("enabled" )).booleanValue ());
74
+ meta .setVariationKey (ldValueNullCheck (valueMeta .get ("variationKey" )).stringValue ());
75
+ Optional <Integer > version = Optional .of (valueMeta .get ("version" ).intValue ());
76
+ meta .setVersion (version );
77
+ result .setMeta (meta );
78
+
79
+ // Convert the optional messages from an JSON array of JSON objects into Message
80
+ // Q: Does it even make sense to have 0 messages?
81
+ LDValue valueMessages = value .get ("messages" );
82
+ if (valueMeta == LDValue .ofNull () || valueMessages .getType () != LDValueType .ARRAY ) {
83
+ throw new AiConfigParseException ("messages must be a JSON array" );
84
+ }
85
+
86
+ List <Message > messages = new ArrayList <Message >();
87
+ for (LDValue valueMessage : valueMessages .values ()) {
88
+ if (valueMessage == LDValue .ofNull () || valueMessage .getType () != LDValueType .OBJECT ) {
89
+ throw new AiConfigParseException ("individual message must be a JSON object" );
90
+ }
91
+
92
+ Message message = new Message ();
93
+ message .setContent (ldValueNullCheck (valueMessage .get ("content" )).stringValue ());
94
+ // TODO: For absolute safety, we need to check this is one out of the three
95
+ // possible enum
96
+ message .setRole (Role .valueOf (valueMessage .get ("role" ).stringValue ().toUpperCase ()));
97
+ messages .add (message );
98
+ }
99
+ result .setMessages (messages );
100
+
101
+ // Convert the optional model from an JSON object of with parameters and custom
102
+ // into Model
103
+ LDValue valueModel = value .get ("model" );
104
+ if (valueModel == LDValue .ofNull () || valueModel .getType () != LDValueType .OBJECT ) {
105
+ throw new AiConfigParseException ("model must be a JSON object" );
106
+ }
107
+
108
+ Model model = new Model ();
109
+ model .setName (ldValueNullCheck (valueModel .get ("name" )).stringValue ());
110
+
111
+ LDValue valueParameters = valueModel .get ("parameters" );
112
+ if (valueParameters .getType () != LDValueType .NULL ) {
113
+ if (valueParameters .getType () != LDValueType .OBJECT ) {
114
+ throw new AiConfigParseException ("non-null parameters must be a JSON object" );
115
+ }
116
+
117
+ HashMap <String , LDValue > parameters = new HashMap <>();
118
+ for (String k : valueParameters .keys ()) {
119
+ parameters .put (k , valueParameters .get (k ));
120
+ }
121
+ model .setParameters (parameters );
122
+ } else {
123
+ // Parameters is optional - so we can just set null and proceed
124
+
125
+ // TODO: Mustash validation somewhere
126
+ model .setParameters (null );
127
+ }
128
+
129
+ LDValue valueCustom = valueModel .get ("custom" );
130
+ if (valueCustom .getType () != LDValueType .NULL ) {
131
+ if (valueCustom .getType () != LDValueType .OBJECT ) {
132
+ throw new AiConfigParseException ("non-null custom must be a JSON object" );
133
+ }
134
+
135
+ HashMap <String , LDValue > custom = new HashMap <>();
136
+ for (String k : valueCustom .keys ()) {
137
+ custom .put (k , valueCustom .get (k ));
138
+ }
139
+ model .setCustom (custom );
140
+ } else {
141
+ // Custom is optional - we can just set null and proceed
142
+ model .setCustom (null );
143
+ }
144
+ result .setModel (model );
145
+
146
+ // Convert the optional provider from an JSON object of with name into Provider
147
+ LDValue valueProvider = value .get ("provider" );
148
+ if (valueProvider .getType () != LDValueType .NULL ) {
149
+ if (valueProvider .getType () != LDValueType .OBJECT ) {
150
+ throw new AiConfigParseException ("non-null provider must be a JSON object" );
151
+ }
152
+
153
+ Provider provider = new Provider ();
154
+ provider .setName (ldValueNullCheck (valueProvider .get ("name" )).stringValue ());
155
+ result .setProvider (provider );
156
+ } else {
157
+ // Provider is optional - we can just set null and proceed
158
+ result .setProvider (null );
159
+ }
160
+ } catch (AiConfigParseException e ) {
161
+ // logger.error(e.getMessage());
162
+ return null ;
163
+ }
164
+
165
+ return result ;
166
+ }
167
+
168
+ protected <T > T ldValueNullCheck (T ldValue ) throws AiConfigParseException {
169
+ if (ldValue == LDValue .ofNull ()) {
170
+ throw new AiConfigParseException ("Unexpected Null value for non-optional field" );
171
+ }
172
+ return ldValue ;
173
+ }
174
+
175
+ class AiConfigParseException extends Exception {
176
+ AiConfigParseException (String exceptionMessage ) {
177
+ super (exceptionMessage );
178
+ }
179
+ }
28
180
}
0 commit comments