llumo 0.2.14b7__py3-none-any.whl → 0.2.15b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
llumo/client.py CHANGED
@@ -38,7 +38,7 @@ class LlumoClient:
38
38
 
39
39
  def __init__(self, api_key):
40
40
  self.apiKey = api_key
41
- self.socket = LlumoSocketClient(socketUrl)
41
+
42
42
  self.processMapping = {}
43
43
  self.definationMapping = {}
44
44
 
@@ -50,6 +50,7 @@ class LlumoClient:
50
50
  reqBody = {"analytics": [evalName]}
51
51
 
52
52
  try:
53
+ print(reqBody)
53
54
  response = requests.post(url=validateUrl, json=reqBody, headers=headers)
54
55
 
55
56
  except requests.exceptions.RequestException as e:
@@ -581,7 +582,8 @@ class LlumoClient:
581
582
  createExperiment: bool = False,
582
583
  _tocheck=True,
583
584
  ):
584
- dataframe = pd.DataFrame(data)
585
+ self.socket = LlumoSocketClient(socketUrl)
586
+ dataframe = pd.DataFrame(data).astype(str)
585
587
  workspaceID = None
586
588
  email = None
587
589
  socketID = self.socket.connect(timeout=250)
@@ -812,7 +814,7 @@ class LlumoClient:
812
814
  else:
813
815
  return dataframe
814
816
 
815
- def run_sweep(
817
+ def promptSweep(
816
818
  self,
817
819
  templates: List[str],
818
820
  dataset: Dict[str, List[str]],
@@ -821,9 +823,15 @@ class LlumoClient:
821
823
  evals=["Response Correctness"],
822
824
  toEvaluate: bool = False,
823
825
  createExperiment: bool = False,
826
+
827
+
824
828
  ) -> pd.DataFrame:
825
829
 
826
- self.validateApiKey(evalName=" ")
830
+ modelStatus = validateModels(model_aliases=model_aliases)
831
+ if modelStatus["status"]== False:
832
+ raise LlumoAIError.providerError(modelStatus["message"])
833
+
834
+ self.validateApiKey()
827
835
  workspaceID = self.workspaceID
828
836
  email = self.email
829
837
  executor = ModelExecutor(apiKey)
@@ -928,6 +936,7 @@ class LlumoClient:
928
936
  evals=["Final Task Alignment"],
929
937
  prompt_template="Give answer for the given query: {{query}}",
930
938
  createExperiment: bool = False,
939
+
931
940
  ):
932
941
  if model.lower() not in ["openai", "google"]:
933
942
  raise ValueError("Model must be 'openai' or 'google'")
@@ -1002,174 +1011,354 @@ class LlumoClient:
1002
1011
  except Exception as e:
1003
1012
  raise e
1004
1013
 
1005
- def runDataStream(
1006
- self,
1007
- data,
1008
- streamName: str,
1009
- queryColName: str = "query",
1010
- createExperiment: bool = False,
1014
+ def ragSweep(
1015
+ self,
1016
+ data,
1017
+ streamName: str,
1018
+ queryColName: str = "query",
1019
+ createExperiment: bool = False,
1020
+ modelAliases=[],
1021
+ apiKey="",
1022
+ prompt_template="Give answer to the given: {{query}} using the context:{{context}}",
1023
+ evals=["Context Utilization"],
1024
+ toEvaluate=False,
1025
+ generateOutput=True
1011
1026
  ):
1012
- results = {}
1013
- dataframe = pd.DataFrame(data)
1014
- try:
1015
- socketID = self.socket.connect(timeout=150)
1016
- # Ensure full connection before proceeding
1017
- max_wait_secs = 20
1018
- waited_secs = 0
1019
- while not self.socket._connection_established.is_set():
1020
- time.sleep(0.1)
1021
- waited_secs += 0.1
1022
- if waited_secs >= max_wait_secs:
1023
- raise RuntimeError(
1024
- "Timeout waiting for server 'connection-established' event."
1025
- )
1026
- # print(f"Connected with socket ID: {socketID}")
1027
- rowIdMapping = {}
1027
+ # Validate required parameters
1028
+ if generateOutput:
1029
+ if not modelAliases:
1030
+ raise ValueError("Model aliases must be provided when generateOutput is True.")
1031
+ if not apiKey or not isinstance(apiKey, str) or apiKey.strip() == "":
1032
+ raise ValueError("Valid API key must be provided when generateOutput is True.")
1033
+
1034
+ modelStatus = validateModels(model_aliases=modelAliases)
1035
+ if modelStatus["status"]== False:
1036
+ if len(modelAliases) == 0:
1037
+ raise LlumoAIError.providerError("No model selected.")
1038
+ else:
1039
+ raise LlumoAIError.providerError(modelStatus["message"])
1028
1040
 
1029
- # print(f"Validating API key...")
1030
- self.validateApiKey()
1031
- # print(f"API key validation successful. Hits available: {self.hitsAvailable}")
1041
+ # Copy the original dataframe
1042
+ original_df = pd.DataFrame(data)
1043
+ working_df = original_df.copy()
1032
1044
 
1033
- # check for available hits and trial limit
1034
- userHits = checkUserHits(
1035
- self.workspaceID,
1036
- self.hasSubscribed,
1037
- self.trialEndDate,
1038
- self.subscriptionEndDate,
1039
- self.hitsAvailable,
1040
- len(dataframe),
1041
- )
1045
+ # Connect to socket
1046
+ self.socket = LlumoSocketClient(socketUrl)
1047
+ socketID = self.socket.connect(timeout=150)
1048
+ waited_secs = 0
1049
+ while not self.socket._connection_established.is_set():
1050
+ time.sleep(0.1)
1051
+ waited_secs += 0.1
1052
+ if waited_secs >= 20:
1053
+ raise RuntimeError("Timeout waiting for server 'connection-established' event.")
1042
1054
 
1043
- # do not proceed if subscription or trial limit has exhausted
1044
- if not userHits["success"]:
1045
- raise LlumoAIError.InsufficientCredits(userHits["message"])
1055
+ self.validateApiKey()
1046
1056
 
1047
- print("====🚀Sit back while we fetch data from the stream 🚀====")
1048
- workspaceID = self.workspaceID
1049
- email = self.email
1050
- streamId = getStreamId(workspaceID, self.apiKey, streamName)
1051
- # Prepare all batches before sending
1052
- # print("Preparing batches...")
1053
- self.allBatches = []
1054
- currentBatch = []
1057
+ # Check user credits
1058
+ userHits = checkUserHits(
1059
+ self.workspaceID, self.hasSubscribed, self.trialEndDate,
1060
+ self.subscriptionEndDate, self.hitsAvailable, len(working_df)
1061
+ )
1062
+ if not userHits["success"]:
1063
+ raise LlumoAIError.InsufficientCredits(userHits["message"])
1055
1064
 
1056
- for index, row in dataframe.iterrows():
1057
- activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace(
1058
- "-", ""
1059
- )
1060
- rowID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
1061
- columnID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
1065
+ print("====🚀Sit back while we fetch data from the stream 🚀====")
1066
+ workspaceID, email = self.workspaceID, self.email
1067
+ activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
1068
+ streamId = getStreamId(workspaceID, self.apiKey, streamName)
1062
1069
 
1063
- rowIdMapping[rowID] = index
1064
- # Use the server-provided socket ID here
1065
- templateData = {
1066
- "processID": getProcessID(),
1067
- "socketID": socketID,
1068
- "processData": {
1069
- "executionDependency": {"query": row[queryColName]},
1070
- "dataStreamID": streamId,
1071
- },
1072
- "workspaceID": workspaceID,
1073
- "email": email,
1074
- "type": "DATA_STREAM",
1075
- "playgroundID": activePlayground,
1076
- "processType": "DATA_STREAM",
1077
- "rowID": rowID,
1078
- "columnID": columnID,
1079
- "source": "SDK",
1080
- }
1070
+ # Prepare batches
1071
+ rowIdMapping = {}
1072
+ self.allBatches = []
1073
+ currentBatch = []
1081
1074
 
1082
- currentBatch.append(templateData)
1075
+ expectedResults = len(working_df)
1076
+ timeout = max(100, min(150, expectedResults * 10))
1077
+
1078
+ listener_thread = threading.Thread(
1079
+ target=self.socket.listenForResults,
1080
+ kwargs={
1081
+ "min_wait": 40,
1082
+ "max_wait": timeout,
1083
+ "inactivity_timeout": 10,
1084
+ "expected_results": expectedResults,
1085
+ },
1086
+ daemon=True
1087
+ )
1088
+ listener_thread.start()
1083
1089
 
1084
- if len(currentBatch) == 10 or index == len(dataframe) - 1:
1090
+ for index, row in working_df.iterrows():
1091
+ rowID, columnID = uuid.uuid4().hex, uuid.uuid4().hex
1092
+ compoundKey = f"{rowID}-{columnID}-{columnID}"
1093
+ rowIdMapping[compoundKey] = {"index": index}
1094
+ templateData = {
1095
+ "processID": getProcessID(),
1096
+ "socketID": socketID,
1097
+ "processData": {
1098
+ "executionDependency": {"query": row[queryColName]},
1099
+ "dataStreamID": streamId,
1100
+ },
1101
+ "workspaceID": workspaceID,
1102
+ "email": email,
1103
+ "type": "DATA_STREAM",
1104
+ "playgroundID": activePlayground,
1105
+ "processType": "DATA_STREAM",
1106
+ "rowID": rowID,
1107
+ "columnID": columnID,
1108
+ "source": "SDK",
1109
+ }
1110
+ currentBatch.append(templateData)
1111
+ if len(currentBatch) == 10 or index == len(working_df) - 1:
1085
1112
  self.allBatches.append(currentBatch)
1086
1113
  currentBatch = []
1087
1114
 
1088
- # Post all batches
1089
- total_items = sum(len(batch) for batch in self.allBatches)
1090
- # print(f"Posting {len(self.allBatches)} batches ({total_items} items total)")
1115
+ for batch in tqdm(self.allBatches, desc="Processing Batches", unit="batch", colour="magenta", ncols=80):
1116
+ try:
1117
+ self.postDataStream(batch=batch, workspaceID=workspaceID)
1118
+ time.sleep(3)
1119
+ except Exception as e:
1120
+ print(f"Error posting batch: {e}")
1121
+ raise
1091
1122
 
1092
- for cnt, batch in enumerate(self.allBatches):
1093
- # print(f"Posting batch {cnt + 1}/{len(self.allBatches)} for eval '{eval}'")
1123
+ time.sleep(3)
1124
+ listener_thread.join()
1125
+
1126
+ rawResults = self.socket.getReceivedData()
1127
+ expectedRowIDs = set(rowIdMapping.keys())
1128
+ receivedRowIDs = {key for item in rawResults for key in item.keys()}
1129
+ missingRowIDs = list(expectedRowIDs - receivedRowIDs)
1130
+
1131
+ if missingRowIDs:
1132
+ dataFromDb = fetchData(workspaceID, activePlayground, missingRowIDs)
1133
+ rawResults.extend(dataFromDb)
1134
+
1135
+ working_df["context"] = None
1136
+ for item in rawResults:
1137
+ for compound_key, value in item.items():
1138
+ if compound_key in rowIdMapping:
1139
+ idx = rowIdMapping[compound_key]["index"]
1140
+ working_df.at[idx, "context"] = value.get("value")
1141
+
1142
+ # Output generation
1143
+ if generateOutput == True:
1144
+ working_df = self._outputForStream(working_df, modelAliases, prompt_template, apiKey)
1145
+
1146
+ # Optional evaluation
1147
+ outputEvalMapping = None
1148
+ if toEvaluate:
1149
+ for evalName in evals:
1150
+
1151
+ # Validate API and dependencies
1152
+ self.validateApiKey(evalName=evalName)
1153
+ metricDependencies = checkDependency(
1154
+ evalName, list(working_df.columns), tocheck=True
1155
+ )
1156
+ if not metricDependencies["status"]:
1157
+ raise LlumoAIError.dependencyError(metricDependencies["message"])
1158
+
1159
+ working_df, outputEvalMapping = self._evaluateForStream(working_df, evals, modelAliases, prompt_template)
1160
+
1161
+
1162
+ self.socket.disconnect()
1163
+
1164
+ # Create experiment if required
1165
+ if createExperiment:
1166
+ df = working_df.fillna("Some error occured").astype(object)
1167
+ if createPlayground(
1168
+ email, workspaceID, df,
1169
+ queryColName=queryColName,
1170
+ dataStreamName=streamId,
1171
+ promptText=prompt_template,
1172
+ definationMapping=self.definationMapping,
1173
+ evalOutputMap=outputEvalMapping
1174
+ ):
1175
+ print(
1176
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results.")
1177
+ else:
1178
+ self.latestDataframe = working_df
1179
+ return working_df
1180
+
1181
+ def _outputForStream(self, df, modelAliases, prompt_template, apiKey):
1182
+ executor = ModelExecutor(apiKey)
1183
+
1184
+ for indx, row in df.iterrows():
1185
+ inputVariables = re.findall(r"{{(.*?)}}", prompt_template)
1186
+ if not all([k in df.columns for k in inputVariables]):
1187
+ raise LlumoAIError.InvalidPromptTemplate()
1188
+
1189
+ inputDict = {key: row[key] for key in inputVariables}
1190
+ for i, model in enumerate(modelAliases, 1):
1094
1191
  try:
1095
- self.postDataStream(batch=batch, workspaceID=workspaceID)
1096
- # print(f"Batch {cnt + 1} posted successfully")
1192
+ provider = getProviderFromModel(model)
1193
+ filled_template = getInputPopulatedPrompt(prompt_template, inputDict)
1194
+ response = executor.execute(provider, model.value, filled_template, apiKey)
1195
+ df.at[indx, f"output_{i}"] = response
1097
1196
  except Exception as e:
1098
- print(f"Error posting batch {cnt + 1}: {str(e)}")
1099
- continue
1197
+ df.at[indx, f"output_{i}"] = str(e)
1198
+ return df
1100
1199
 
1101
- # Small delay between batches to prevent overwhelming the server
1102
- time.sleep(1)
1200
+ def _evaluateForStream(self, df, evals, modelAliases, prompt_template):
1201
+ dfWithEvals = df.copy()
1103
1202
 
1104
- # updating the dict for row column mapping
1105
- self.AllProcessMapping()
1106
- # Calculate a reasonable timeout based on the data size
1107
- timeout = max(60, min(600, total_items * 10))
1108
- # print(f"All batches posted. Waiting up to {timeout} seconds for results...")
1203
+ outputColMapping = {}
1109
1204
 
1110
- # Listen for results
1111
- self.socket.listenForResults(
1112
- min_wait=20,
1113
- max_wait=timeout,
1114
- inactivity_timeout=30,
1115
- expected_results=None,
1116
- )
1205
+ for i, model in enumerate(modelAliases, 1):
1206
+ outputColName = f"output_{i}"
1207
+ try:
1117
1208
 
1118
- # Get results for this evaluation
1119
- eval_results = self.socket.getReceivedData()
1120
- # print(f"Received {len(eval_results)} results for evaluation '{eval}'")
1209
+ res = self.evaluateMultiple(
1210
+ dfWithEvals.to_dict("records"),
1211
+ evals=evals,
1212
+ prompt_template=prompt_template,
1213
+ outputColName=outputColName,
1214
+ _tocheck=False,
1215
+ )
1216
+ for evalMetric in evals:
1217
+ scoreCol = f"{evalMetric}"
1218
+ reasonCol = f"{evalMetric} Reason"
1121
1219
 
1122
- # Add these results to our overall results
1123
- results["Data Stream"] = self.finalResp(eval_results)
1124
- print(f"=======You are all set! continue your expectations 🚀======\n")
1220
+ if scoreCol in res.columns:
1221
+ res = res.rename(columns={scoreCol: f"{scoreCol}_{i}"})
1222
+ if reasonCol in res.columns:
1223
+ res = res.rename(columns={reasonCol: f"{evalMetric}_{i} Reason"})
1125
1224
 
1126
- # print("All evaluations completed successfully")
1225
+ outputColMapping[f"{scoreCol}_{i}"] = outputColName
1127
1226
 
1128
- except Exception as e:
1129
- print(f"Error during evaluation: {e}")
1130
- raise
1131
- finally:
1132
- # Always disconnect the socket when done
1227
+ newCols = [col for col in res.columns if col not in dfWithEvals.columns]
1228
+ dfWithEvals = pd.concat([dfWithEvals, res[newCols]], axis=1)
1229
+ except Exception as e:
1230
+ print(f"Evaluation failed for model {model.value}: {str(e)}")
1231
+ return dfWithEvals, outputColMapping
1232
+
1233
+ def runDataStream(
1234
+ self,
1235
+ data,
1236
+ streamName: str,
1237
+ queryColName: str = "query",
1238
+ createExperiment: bool = False,
1239
+ ):
1240
+
1241
+
1242
+ # Copy the original dataframe
1243
+ original_df = pd.DataFrame(data)
1244
+ working_df = original_df.copy()
1245
+
1246
+ # Connect to socket
1247
+ self.socket = LlumoSocketClient(socketUrl)
1248
+ socketID = self.socket.connect(timeout=150)
1249
+ waited_secs = 0
1250
+ while not self.socket._connection_established.is_set():
1251
+ time.sleep(0.1)
1252
+ waited_secs += 0.1
1253
+ if waited_secs >= 20:
1254
+ raise RuntimeError("Timeout waiting for server 'connection-established' event.")
1255
+
1256
+ self.validateApiKey()
1257
+
1258
+ # Check user credits
1259
+ userHits = checkUserHits(
1260
+ self.workspaceID, self.hasSubscribed, self.trialEndDate,
1261
+ self.subscriptionEndDate, self.hitsAvailable, len(working_df)
1262
+ )
1263
+ if not userHits["success"]:
1264
+ raise LlumoAIError.InsufficientCredits(userHits["message"])
1265
+
1266
+ print("====🚀Sit back while we fetch data from the stream 🚀====")
1267
+ workspaceID, email = self.workspaceID, self.email
1268
+ activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
1269
+ streamId = getStreamId(workspaceID, self.apiKey, streamName)
1270
+
1271
+ # Prepare batches
1272
+ rowIdMapping = {}
1273
+ self.allBatches = []
1274
+ currentBatch = []
1275
+
1276
+ expectedResults = len(working_df)
1277
+ timeout = max(100, min(150, expectedResults * 10))
1278
+
1279
+ listener_thread = threading.Thread(
1280
+ target=self.socket.listenForResults,
1281
+ kwargs={
1282
+ "min_wait": 40,
1283
+ "max_wait": timeout,
1284
+ "inactivity_timeout": 10,
1285
+ "expected_results": expectedResults,
1286
+ },
1287
+ daemon=True
1288
+ )
1289
+ listener_thread.start()
1290
+
1291
+ for index, row in working_df.iterrows():
1292
+ rowID, columnID = uuid.uuid4().hex, uuid.uuid4().hex
1293
+ compoundKey = f"{rowID}-{columnID}-{columnID}"
1294
+ rowIdMapping[compoundKey] = {"index": index}
1295
+ templateData = {
1296
+ "processID": getProcessID(),
1297
+ "socketID": socketID,
1298
+ "processData": {
1299
+ "executionDependency": {"query": row[queryColName]},
1300
+ "dataStreamID": streamId,
1301
+ },
1302
+ "workspaceID": workspaceID,
1303
+ "email": email,
1304
+ "type": "DATA_STREAM",
1305
+ "playgroundID": activePlayground,
1306
+ "processType": "DATA_STREAM",
1307
+ "rowID": rowID,
1308
+ "columnID": columnID,
1309
+ "source": "SDK",
1310
+ }
1311
+ currentBatch.append(templateData)
1312
+ if len(currentBatch) == 10 or index == len(working_df) - 1:
1313
+ self.allBatches.append(currentBatch)
1314
+ currentBatch = []
1315
+
1316
+ for batch in tqdm(self.allBatches, desc="Processing Batches", unit="batch", colour="magenta", ncols=80):
1133
1317
  try:
1134
- self.socket.disconnect()
1135
- # print("Socket disconnected")
1318
+ self.postDataStream(batch=batch, workspaceID=workspaceID)
1319
+ time.sleep(3)
1136
1320
  except Exception as e:
1137
- print(f"Error disconnecting socket: {e}")
1321
+ print(f"Error posting batch: {e}")
1322
+ raise
1138
1323
 
1139
- for streamName, records in results.items():
1140
- dataframe[streamName] = None
1141
- for item in records:
1142
- for compound_key, value in item.items():
1143
- # for compound_key, value in item['data'].items():
1324
+ time.sleep(3)
1325
+ listener_thread.join()
1144
1326
 
1145
- rowID = compound_key.split("-")[0]
1146
- # looking for the index of each rowID , in the original dataframe
1147
- if rowID in rowIdMapping:
1148
- index = rowIdMapping[rowID]
1149
- # dataframe.at[index, evalName] = value
1150
- dataframe.at[index, streamName] = value["value"]
1327
+ rawResults = self.socket.getReceivedData()
1328
+ expectedRowIDs = set(rowIdMapping.keys())
1329
+ receivedRowIDs = {key for item in rawResults for key in item.keys()}
1330
+ missingRowIDs = list(expectedRowIDs - receivedRowIDs)
1151
1331
 
1152
- else:
1153
- pass
1154
- # print(f"⚠️ Warning: Could not find rowID {rowID} in mapping")
1332
+ if missingRowIDs:
1333
+ dataFromDb = fetchData(workspaceID, activePlayground, missingRowIDs)
1334
+ rawResults.extend(dataFromDb)
1155
1335
 
1156
- if createExperiment:
1157
- pd.set_option("future.no_silent_downcasting", True)
1158
- df = dataframe.fillna("Some error occured").astype(object)
1336
+ working_df["context"] = None
1337
+ for item in rawResults:
1338
+ for compound_key, value in item.items():
1339
+ if compound_key in rowIdMapping:
1340
+ idx = rowIdMapping[compound_key]["index"]
1341
+ working_df.at[idx, "context"] = value.get("value")
1159
1342
 
1343
+
1344
+
1345
+ self.socket.disconnect()
1346
+
1347
+ # Create experiment if required
1348
+ if createExperiment:
1349
+ df = working_df.fillna("Some error occured").astype(object)
1160
1350
  if createPlayground(
1161
- email,
1162
- workspaceID,
1163
- df,
1164
- queryColName=queryColName,
1165
- dataStreamName=streamId,
1351
+ email, workspaceID, df,
1352
+ queryColName=queryColName,
1353
+ dataStreamName=streamId,
1354
+ definationMapping=self.definationMapping,
1166
1355
  ):
1167
1356
  print(
1168
- "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
1169
- )
1357
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results.")
1170
1358
  else:
1171
- self.latestDataframe = dataframe
1172
- return dataframe
1359
+ self.latestDataframe = working_df
1360
+ return working_df
1361
+
1173
1362
 
1174
1363
  def createExperiment(self, dataframe):
1175
1364
  try:
llumo/exceptions.py CHANGED
@@ -50,6 +50,10 @@ class LlumoAIError(Exception):
50
50
  def dependencyError(details):
51
51
  return LlumoAIError(details)
52
52
 
53
+ @staticmethod
54
+ def providerError(details):
55
+ return LlumoAIError(details)
56
+
53
57
  # @staticmethod
54
58
  # def dateNotFound():
55
59
  # return LlumoAIError("Trial end date or subscription end date not found for the given user.")
llumo/helpingFuntions.py CHANGED
@@ -9,6 +9,8 @@ import base64
9
9
  import os
10
10
  import re
11
11
 
12
+
13
+ from .models import _MODEL_METADATA, AVAILABLEMODELS
12
14
  subscriptionUrl = "https://app.llumo.ai/api/workspace/record-extra-usage"
13
15
  getStreamdataUrl = "https://app.llumo.ai/api/data-stream/all"
14
16
  createPlayUrl = "https://app.llumo.ai/api/New-Eval-API/create-new-eval-playground"
@@ -212,7 +214,8 @@ def deleteColumnListInPlayground(workspaceID: str, playgroundID: str):
212
214
  print("❌ Error:", response.status_code, response.text)
213
215
  return None
214
216
 
215
- def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColName=None,outputColName= "output",dataStreamName=None,definationMapping=None):
217
+ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColName=None,
218
+ outputColName= "output",dataStreamName=None,definationMapping=None,evalOutputMap = None):
216
219
  if len(dataframe) > 100:
217
220
  dataframe = dataframe.head(100)
218
221
  print("⚠️ Dataframe truncated to 100 rows for upload.")
@@ -232,11 +235,11 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
232
235
  # Iterate over each column in the dataframe
233
236
  for indx, col in enumerate(dataframe.columns):
234
237
  # Generate a unique column ID using uuid
235
- columnID = str(uuid.uuid4().hex[:8])
238
+ columnID = str(uuid.uuid4().hex[:8])
236
239
 
237
240
  columnIDMapping[col] = columnID
238
241
 
239
-
242
+
240
243
  if col.startswith('output') and promptText!=None:
241
244
  # For output columns, create the prompt template with promptText
242
245
  if promptText:
@@ -248,12 +251,12 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
248
251
 
249
252
  # Loop through each variable and check if it exists as a column name
250
253
  for var in variables:
251
- varName = var.strip()
254
+ varName = var.strip()
252
255
  if varName in columnIDMapping: # Check if the variable is a column name
253
256
  dependencies.append(columnIDMapping[varName]) # Add its columnID
254
257
 
255
258
  # Now update the template for the output column
256
-
259
+
257
260
  template={
258
261
  "provider": "OPENAI",
259
262
  "model": "GPT_4o",
@@ -275,8 +278,8 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
275
278
  "type": "PROMPT",
276
279
  "order": indx,
277
280
  }
278
-
279
- elif col.startswith('Data ') :
281
+
282
+ elif col.startswith('context') and dataStreamName != None :
280
283
  if queryColName and dataStreamName:
281
284
  dependencies = []
282
285
  dependencies.append(columnIDMapping[queryColName])
@@ -286,22 +289,27 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
286
289
  "dataStreamName": dataStreamName,
287
290
  "query": columnIDMapping[queryColName],
288
291
  "columnID": columnID, # Use the generated column ID
289
- "label": "Data stream",
292
+ "label": "context",
290
293
  "type": "DATA_STREAM",
291
294
  "order": indx}
292
295
 
293
- elif col in allEvals and promptText!=None:
294
296
 
297
+ elif any(col.startswith(eval + "_") or col == eval for eval in allEvals) and not " Reason" in col and promptText is not None:
298
+ if evalOutputMap != None:
299
+ outputColName = evalOutputMap[col]
300
+ else:
301
+ outputColName = outputColName
295
302
  dependencies = []
296
303
  variables = re.findall(r'{{(.*?)}}', promptText)
297
304
 
298
305
  # Loop through each variable and check if it exists as a column name
299
306
  for var in variables:
300
- varName = var.strip()
307
+ varName = var.strip()
301
308
  if varName in columnIDMapping: # Check if the variable is a column name
302
309
  dependencies.append(columnIDMapping[varName])
303
-
310
+
304
311
  dependencies.append(columnIDMapping[outputColName]) # Add the output column ID
312
+
305
313
  longDef = definationMapping.get(col, {}).get('definition', "")
306
314
  shortDef =definationMapping.get(col, {}).get('briefDefinition', "")
307
315
  enum = col.upper().replace(" ","_")
@@ -341,11 +349,11 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
341
349
  }
342
350
 
343
351
  elif col.endswith(' Reason') and promptText!=None:
344
- continue
352
+ continue
353
+
345
354
 
346
-
347
355
  else:
348
-
356
+
349
357
  template = {
350
358
  "label": col, # Label is the column name
351
359
  "type": "VARIABLE", # Default type for non-output columns
@@ -370,25 +378,27 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
370
378
  row_dict = {}
371
379
 
372
380
  # For each column, we need to map the column ID to the corresponding value in the row
381
+ print(dataframe.columns)
373
382
  for col in dataframe.columns:
374
383
  columnID = columnIDMapping[col]
375
-
376
- if col in allEvals and promptText!=None:
384
+
385
+ if any(col.startswith(eval + "_") or col == eval for eval in allEvals) and not " Reason" in col and promptText!=None:
386
+ print(col)
377
387
  row_dict[columnID] = {
378
-
388
+
379
389
  "value": row[col],
380
390
  "type": "EVAL",
381
391
  "isValid": True,
382
392
  "reasoning": row[col+" Reason"],
383
393
  "edgeCase": "minorHallucinationDetailNotInContext",
384
394
  "kpi": col
385
-
386
- }
395
+
396
+ }
387
397
  elif col.endswith(' Reason') and promptText!=None:
388
398
  continue
389
399
  else:# Get the columnID from the mapping
390
400
  row_dict[columnID] = row[col]
391
-
401
+
392
402
  # row_dict[columnID] = row[col] # Directly map the column ID to the row value
393
403
  # Add the row index (if necessary)
394
404
  row_dict["pIndex"] = indx
@@ -440,11 +450,11 @@ def uploadRowsInDBPlayground(payload):
440
450
  return None
441
451
 
442
452
 
443
- def createPlayground(email, workspaceID, df, promptText=None,queryColName=None,dataStreamName=None,definationMapping=None,outputColName="output"):
453
+ def createPlayground(email, workspaceID, df, promptText=None,queryColName=None,dataStreamName=None,definationMapping=None,outputColName="output",evalOutputMap = None):
444
454
 
445
455
  playgroundId = str(createEvalPlayground(email=email, workspaceID=workspaceID))
446
456
  payload1, payload2 = createColumn(
447
- workspaceID=workspaceID, dataframe=df, playgroundID=playgroundId, promptText=promptText,queryColName=queryColName,dataStreamName=dataStreamName,definationMapping=definationMapping,outputColName=outputColName
457
+ workspaceID=workspaceID, dataframe=df, playgroundID=playgroundId, promptText=promptText,queryColName=queryColName,dataStreamName=dataStreamName,definationMapping=definationMapping,outputColName=outputColName,evalOutputMap=evalOutputMap
448
458
  )
449
459
 
450
460
  # Debugging line to check the payload2 structure
@@ -606,3 +616,19 @@ def fetchData(workspaceID, playgroundID, missingList: list):
606
616
  except Exception as e:
607
617
  print(f"An error occurred: {e}")
608
618
  return []
619
+
620
+ def validateModels(model_aliases):
621
+
622
+ selectedProviders = []
623
+ for name in model_aliases:
624
+ for alias ,(provider , modelName ) in _MODEL_METADATA.items():
625
+ if modelName == name:
626
+ selectedProviders.append(provider)
627
+
628
+ if len(set(selectedProviders)) > 1:
629
+ return {"status": False,"message":"All selected models should be of same provider."}
630
+ else:
631
+ return {"status": True,"message":"All selected models are of same provider."}
632
+
633
+
634
+
llumo/models.py CHANGED
@@ -6,35 +6,72 @@ class Provider(str, Enum):
6
6
 
7
7
  # Maps model aliases → (provider, actual model name for API)
8
8
  _MODEL_METADATA = {
9
- "GPT_4": (Provider.OPENAI, "gpt-4"),
10
- "GPT_4_32K": (Provider.OPENAI, "gpt-4-32k"),
11
- "GPT_35T": (Provider.OPENAI, "gpt-3.5-turbo"),
12
- "GPT_35T_INS": (Provider.OPENAI, "gpt-3.5-turbo-instruct"),
13
- "GPT_35T_16K": (Provider.OPENAI, "gpt-3.5-turbo-16k"),
14
- "GPT_35_TURBO": (Provider.OPENAI, "gpt-3.5-turbo"),
15
-
16
- "GOOGLE_15_FLASH": (Provider.GOOGLE, "gemini-1.5-flash-latest"),
17
- "GEMINI_PRO": (Provider.GOOGLE, "gemini-pro"),
18
- "TEXT_BISON": (Provider.GOOGLE, "text-bison-001"),
19
- "CHAT_BISON": (Provider.GOOGLE, "chat-bison-001"),
20
- "TEXT_BISON_32K": (Provider.GOOGLE, "text-bison-32k"),
21
- "TEXT_UNICORN": (Provider.GOOGLE, "text-unicorn-experimental"),
9
+ "GPT_4O": (Provider.OPENAI, "GPT_4O"),
10
+ "GPT_4_5": (Provider.OPENAI, "GPT_4_5"),
11
+ "GPT_4": (Provider.OPENAI, "GPT_4"),
12
+ "GPT_4_32K": (Provider.OPENAI, "GPT_4_32K"),
13
+ "GPT_3_5_Turbo": (Provider.OPENAI, "GPT_35T"),
14
+ "GPT_3_5_Turbo_Instruct": (Provider.OPENAI, "GPT_35T_INS"),
15
+ "GPT_3_5_Turbo_16K": (Provider.OPENAI, "GPT_35T_16K"),
16
+ "GPT_4_o_Mini": (Provider.OPENAI, "GPT_4O_MINI"),
17
+ "o4_MINI": (Provider.OPENAI, "O4_MINI"),
18
+ "o4_MINI_HIGH": (Provider.OPENAI, "O4_MINI_HIGH"),
19
+ "GPT_4_1": (Provider.OPENAI, "GPT_4_1"),
20
+ "GPT_4_1_Mini": (Provider.OPENAI, "GPT_4_1_MINI"),
21
+ "GPT_4_1_nano": (Provider.OPENAI, "GPT_4_1_NANO"),
22
+ "o3": (Provider.OPENAI, "O3"),
23
+ "o3_MINI": (Provider.OPENAI, "O3_MINI"),
24
+ "o1": (Provider.OPENAI, "O1"),
25
+ "o1_MINI": (Provider.OPENAI, "O1_MINI"),
26
+
27
+
28
+ "Gemini_2_5_Pro": (Provider.GOOGLE, "GEMINI_2_5_PRO"),
29
+ "Gemini_2_5_Flash": (Provider.GOOGLE, "GEMINI_2_5_FLASH"),
30
+ "Gemini_2_0": (Provider.GOOGLE, "GEMINI_2_0"),
31
+ "Gemini_2_0_Flash": (Provider.GOOGLE, "GEMINI_2_0_FLASH"),
32
+ "Gemini_Pro": (Provider.GOOGLE, "GEMINI_PRO"),
33
+ "Text_Bison": (Provider.GOOGLE, "TEXT_BISON"),
34
+ "Chat_Bison": (Provider.GOOGLE, "CHAT_BISON"),
35
+ "Text_Bison_32k": (Provider.GOOGLE, "TEXT_BISON_32K"),
36
+ "Text_Unicorn": (Provider.GOOGLE, "TEXT_UNICORN"),
37
+ "Google_1_5_Flash": (Provider.GOOGLE, "GOOGLE_15_FLASH"),
38
+ "Gemma_3_9B": (Provider.GOOGLE, "GEMMA_3_9B"),
39
+ "Gemma_3_27B": (Provider.GOOGLE, "GEMMA_3_27B"),
22
40
  }
23
41
 
24
42
  class AVAILABLEMODELS(str, Enum):
25
- GPT_4 = "gpt-4"
26
- GPT_4_32K = "gpt-4-32k"
27
- GPT_35T = "gpt-3.5-turbo"
28
- GPT_35T_INS = "gpt-3.5-turbo-instruct"
29
- GPT_35T_16K = "gpt-3.5-turbo-16k"
30
- GPT_35_TURBO = "gpt-3.5-turbo"
31
-
32
- GOOGLE_15_FLASH = "gemini-1.5-flash-latest"
33
- GEMINI_PRO = ""
34
- TEXT_BISON = "text-bison-001"
35
- CHAT_BISON = "chat-bison-001"
36
- TEXT_BISON_32K = "text-bison-32k"
37
- TEXT_UNICORN = "text-unicorn-experimental"
43
+ GPT_4o= "GPT_4O",
44
+ GPT_4o_Mini= "GPT_4O_MINI",
45
+ GPT_4_5= "GPT_4_5",
46
+ GPT_4= "GPT_4",
47
+ GPT_4_32K= "GPT_4_32K",
48
+ GPT_3_5_Turbo= "GPT_35T",
49
+ GPT_3_5_Turbo_Instruct= "GPT_35T_INS",
50
+ GPT_3_5_Turbo_16K= "GPT_35T_16K",
51
+ GPT_4_o_Mini= "GPT_4O_MINI",
52
+ o4_MINI = "O4_MINI",
53
+ o4_MINI_HIGH = "O4_MINI_HIGH",
54
+ GPT_4_1 = "GPT_4_1",
55
+ GPT_4_1_Mini = "GPT_4_1_MINI",
56
+ GPT_4_1_nano = "GPT_4_1_NANO",
57
+ o3 = "O3",
58
+ o3_MINI = "O3_MINI",
59
+ o1 = "O1",
60
+ o1_MINI = "O1_MINI",
61
+
62
+ Gemini_2_5_Pro = "GEMINI_2_5_PRO",
63
+ Gemini_2_5_Flash = "GEMINI_2_5_FLASH",
64
+ Gemini_2_0 = "GEMINI_2_0",
65
+ Gemini_2_0_Flash = "GEMINI_2_0_FLASH",
66
+ Gemini_Pro = "GEMINI_PRO",
67
+ Text_Bison = "TEXT_BISON",
68
+ Chat_Bison = "CHAT_BISON",
69
+ Text_Bison_32k = "TEXT_BISON_32K",
70
+ Text_Unicorn = "TEXT_UNICORN",
71
+ Google_1_5_Flash = "GOOGLE_15_FLASH",
72
+ Gemma_3_9B = "GEMMA_3_9B",
73
+ Gemma_3_27B = "GEMMA_3_27B",
74
+
38
75
 
39
76
  def getProviderFromModel(model: AVAILABLEMODELS) -> Provider:
40
77
  for alias, (provider, apiName) in _MODEL_METADATA.items():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llumo
3
- Version: 0.2.14b7
3
+ Version: 0.2.15b1
4
4
  Summary: Python SDK for interacting with the Llumo ai API.
5
5
  Home-page: https://www.llumo.ai/
6
6
  Author: Llumo
@@ -0,0 +1,13 @@
1
+ llumo/__init__.py,sha256=O04b4yW1BnOvcHzxWFddAKhtdBEhBNhLdb6xgnpHH_Q,205
2
+ llumo/client.py,sha256=XljwD5mZxjyrXHhu8YhN0cGsd-O_LyKbPzrhS8zbqZo,53778
3
+ llumo/exceptions.py,sha256=Vp_MnanHbnd1Yjuoi6WLrKiwwZbJL3znCox2URMmGU4,2032
4
+ llumo/execution.py,sha256=x88wQV8eL99wNN5YtjFaAMCIfN1PdfQVlAZQb4vzgQ0,1413
5
+ llumo/functionCalling.py,sha256=D5jYapu1rIvdIJNUYPYMTyhQ1H-6nkwoOLMi6eekfUE,7241
6
+ llumo/helpingFuntions.py,sha256=0-ZwG0fnbfP4DP1JTMewM8LdXzz_-p1gRqhPsX0Zmpk,22785
7
+ llumo/models.py,sha256=aVEZsOOoQx5LeNtwSyBxqvrINq0izH3QWu_YjsMPE6o,2910
8
+ llumo/sockets.py,sha256=I2JO_eNEctRo_ikgvFVp5zDd-m0VDu04IEUhhsa1Tic,5950
9
+ llumo-0.2.15b1.dist-info/licenses/LICENSE,sha256=tF9yAcfPV9xGT3ViWmC8hPvOo8BEk4ZICbUfcEo8Dlk,182
10
+ llumo-0.2.15b1.dist-info/METADATA,sha256=yDLkiD46Qq44PA3ylKK2dzsXZmnuE23yxH0RmoqizOk,1521
11
+ llumo-0.2.15b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ llumo-0.2.15b1.dist-info/top_level.txt,sha256=d5zUTMI99llPtLRB8rtSrqELm_bOqX-bNC5IcwlDk88,6
13
+ llumo-0.2.15b1.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- llumo/__init__.py,sha256=O04b4yW1BnOvcHzxWFddAKhtdBEhBNhLdb6xgnpHH_Q,205
2
- llumo/client.py,sha256=HpvUyucrGPbcPQMz_cTRDcEsBFpmNt8jfW1zJU4Nyss,46781
3
- llumo/exceptions.py,sha256=i3Qv4_g7XjRuho7-b7ybjw2bwSh_NhvICR6ZAgiLQX8,1944
4
- llumo/execution.py,sha256=x88wQV8eL99wNN5YtjFaAMCIfN1PdfQVlAZQb4vzgQ0,1413
5
- llumo/functionCalling.py,sha256=D5jYapu1rIvdIJNUYPYMTyhQ1H-6nkwoOLMi6eekfUE,7241
6
- llumo/helpingFuntions.py,sha256=RgWok8DoE1R-Tc0kJ9B5En6LEUEk5EvQU8iJiGPbUsw,21911
7
- llumo/models.py,sha256=YH-qAMnShmUpmKE2LQAzQdpRsaXkFSlOqMxHwU4zBUI,1560
8
- llumo/sockets.py,sha256=I2JO_eNEctRo_ikgvFVp5zDd-m0VDu04IEUhhsa1Tic,5950
9
- llumo-0.2.14b7.dist-info/licenses/LICENSE,sha256=tF9yAcfPV9xGT3ViWmC8hPvOo8BEk4ZICbUfcEo8Dlk,182
10
- llumo-0.2.14b7.dist-info/METADATA,sha256=kdeDmcNgV8uRyH7gXhhAqeb3se5U_Gqo3bA3Cf4SLlM,1521
11
- llumo-0.2.14b7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- llumo-0.2.14b7.dist-info/top_level.txt,sha256=d5zUTMI99llPtLRB8rtSrqELm_bOqX-bNC5IcwlDk88,6
13
- llumo-0.2.14b7.dist-info/RECORD,,