llumo 0.2.14b7__py3-none-any.whl → 0.2.15b2__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
@@ -10,7 +10,7 @@ import os
10
10
  import itertools
11
11
  import pandas as pd
12
12
  from typing import List, Dict
13
- from .models import AVAILABLEMODELS, getProviderFromModel
13
+ from .models import AVAILABLEMODELS, getProviderFromModel, Provider
14
14
  from .execution import ModelExecutor
15
15
  from .exceptions import LlumoAIError
16
16
  from .helpingFuntions import *
@@ -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
+
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,362 @@ 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))
1083
1077
 
1084
- if len(currentBatch) == 10 or index == len(dataframe) - 1:
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()
1089
+
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=False
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
+
1193
+ provider = getProviderFromModel(model)
1194
+ if provider == Provider.OPENAI:
1195
+ print(validateOpenaiKey(apiKey))
1196
+ elif provider == Provider.GOOGLE:
1197
+ validateGoogleKey(apiKey)
1198
+
1199
+ filled_template = getInputPopulatedPrompt(prompt_template, inputDict)
1200
+ response = executor.execute(provider, model.value, filled_template, apiKey)
1201
+ df.at[indx, f"output_{i}"] = response
1097
1202
  except Exception as e:
1098
- print(f"Error posting batch {cnt + 1}: {str(e)}")
1099
- continue
1203
+ # df.at[indx, f"output_{i}"] = str(e)
1204
+ raise e
1100
1205
 
1101
- # Small delay between batches to prevent overwhelming the server
1102
- time.sleep(1)
1206
+ return df
1103
1207
 
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...")
1208
+ def _evaluateForStream(self, df, evals, modelAliases, prompt_template):
1209
+ dfWithEvals = df.copy()
1109
1210
 
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
- )
1211
+ outputColMapping = {}
1117
1212
 
1118
- # Get results for this evaluation
1119
- eval_results = self.socket.getReceivedData()
1120
- # print(f"Received {len(eval_results)} results for evaluation '{eval}'")
1213
+ for i, model in enumerate(modelAliases, 1):
1214
+ outputColName = f"output_{i}"
1215
+ try:
1216
+
1217
+ res = self.evaluateMultiple(
1218
+ dfWithEvals.to_dict("records"),
1219
+ evals=evals,
1220
+ prompt_template=prompt_template,
1221
+ outputColName=outputColName,
1222
+ _tocheck=False,
1223
+ )
1224
+ for evalMetric in evals:
1225
+ scoreCol = f"{evalMetric}"
1226
+ reasonCol = f"{evalMetric} Reason"
1121
1227
 
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")
1228
+ if scoreCol in res.columns:
1229
+ res = res.rename(columns={scoreCol: f"{scoreCol}_{i}"})
1230
+ if reasonCol in res.columns:
1231
+ res = res.rename(columns={reasonCol: f"{evalMetric}_{i} Reason"})
1125
1232
 
1126
- # print("All evaluations completed successfully")
1233
+ outputColMapping[f"{scoreCol}_{i}"] = outputColName
1127
1234
 
1128
- except Exception as e:
1129
- print(f"Error during evaluation: {e}")
1130
- raise
1131
- finally:
1132
- # Always disconnect the socket when done
1235
+ newCols = [col for col in res.columns if col not in dfWithEvals.columns]
1236
+ dfWithEvals = pd.concat([dfWithEvals, res[newCols]], axis=1)
1237
+ except Exception as e:
1238
+ print(f"Evaluation failed for model {model.value}: {str(e)}")
1239
+ return dfWithEvals, outputColMapping
1240
+
1241
+ def runDataStream(
1242
+ self,
1243
+ data,
1244
+ streamName: str,
1245
+ queryColName: str = "query",
1246
+ createExperiment: bool = False,
1247
+ ):
1248
+
1249
+
1250
+ # Copy the original dataframe
1251
+ original_df = pd.DataFrame(data)
1252
+ working_df = original_df.copy()
1253
+
1254
+ # Connect to socket
1255
+ self.socket = LlumoSocketClient(socketUrl)
1256
+ socketID = self.socket.connect(timeout=150)
1257
+ waited_secs = 0
1258
+ while not self.socket._connection_established.is_set():
1259
+ time.sleep(0.1)
1260
+ waited_secs += 0.1
1261
+ if waited_secs >= 20:
1262
+ raise RuntimeError("Timeout waiting for server 'connection-established' event.")
1263
+
1264
+ self.validateApiKey()
1265
+
1266
+ # Check user credits
1267
+ userHits = checkUserHits(
1268
+ self.workspaceID, self.hasSubscribed, self.trialEndDate,
1269
+ self.subscriptionEndDate, self.hitsAvailable, len(working_df)
1270
+ )
1271
+ if not userHits["success"]:
1272
+ raise LlumoAIError.InsufficientCredits(userHits["message"])
1273
+
1274
+ print("====🚀Sit back while we fetch data from the stream 🚀====")
1275
+ workspaceID, email = self.workspaceID, self.email
1276
+ activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
1277
+ streamId = getStreamId(workspaceID, self.apiKey, streamName)
1278
+
1279
+ # Prepare batches
1280
+ rowIdMapping = {}
1281
+ self.allBatches = []
1282
+ currentBatch = []
1283
+
1284
+ expectedResults = len(working_df)
1285
+ timeout = max(100, min(150, expectedResults * 10))
1286
+
1287
+ listener_thread = threading.Thread(
1288
+ target=self.socket.listenForResults,
1289
+ kwargs={
1290
+ "min_wait": 40,
1291
+ "max_wait": timeout,
1292
+ "inactivity_timeout": 10,
1293
+ "expected_results": expectedResults,
1294
+ },
1295
+ daemon=True
1296
+ )
1297
+ listener_thread.start()
1298
+
1299
+ for index, row in working_df.iterrows():
1300
+ rowID, columnID = uuid.uuid4().hex, uuid.uuid4().hex
1301
+ compoundKey = f"{rowID}-{columnID}-{columnID}"
1302
+ rowIdMapping[compoundKey] = {"index": index}
1303
+ templateData = {
1304
+ "processID": getProcessID(),
1305
+ "socketID": socketID,
1306
+ "processData": {
1307
+ "executionDependency": {"query": row[queryColName]},
1308
+ "dataStreamID": streamId,
1309
+ },
1310
+ "workspaceID": workspaceID,
1311
+ "email": email,
1312
+ "type": "DATA_STREAM",
1313
+ "playgroundID": activePlayground,
1314
+ "processType": "DATA_STREAM",
1315
+ "rowID": rowID,
1316
+ "columnID": columnID,
1317
+ "source": "SDK",
1318
+ }
1319
+ currentBatch.append(templateData)
1320
+ if len(currentBatch) == 10 or index == len(working_df) - 1:
1321
+ self.allBatches.append(currentBatch)
1322
+ currentBatch = []
1323
+
1324
+ for batch in tqdm(self.allBatches, desc="Processing Batches", unit="batch", colour="magenta", ncols=80):
1133
1325
  try:
1134
- self.socket.disconnect()
1135
- # print("Socket disconnected")
1326
+ self.postDataStream(batch=batch, workspaceID=workspaceID)
1327
+ time.sleep(3)
1136
1328
  except Exception as e:
1137
- print(f"Error disconnecting socket: {e}")
1329
+ print(f"Error posting batch: {e}")
1330
+ raise
1138
1331
 
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():
1332
+ time.sleep(3)
1333
+ listener_thread.join()
1144
1334
 
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"]
1335
+ rawResults = self.socket.getReceivedData()
1336
+ expectedRowIDs = set(rowIdMapping.keys())
1337
+ receivedRowIDs = {key for item in rawResults for key in item.keys()}
1338
+ missingRowIDs = list(expectedRowIDs - receivedRowIDs)
1151
1339
 
1152
- else:
1153
- pass
1154
- # print(f"⚠️ Warning: Could not find rowID {rowID} in mapping")
1340
+ if missingRowIDs:
1341
+ dataFromDb = fetchData(workspaceID, activePlayground, missingRowIDs)
1342
+ rawResults.extend(dataFromDb)
1155
1343
 
1156
- if createExperiment:
1157
- pd.set_option("future.no_silent_downcasting", True)
1158
- df = dataframe.fillna("Some error occured").astype(object)
1344
+ working_df["context"] = None
1345
+ for item in rawResults:
1346
+ for compound_key, value in item.items():
1347
+ if compound_key in rowIdMapping:
1348
+ idx = rowIdMapping[compound_key]["index"]
1349
+ working_df.at[idx, "context"] = value.get("value")
1159
1350
 
1351
+
1352
+
1353
+ self.socket.disconnect()
1354
+
1355
+ # Create experiment if required
1356
+ if createExperiment:
1357
+ df = working_df.fillna("Some error occured").astype(object)
1160
1358
  if createPlayground(
1161
- email,
1162
- workspaceID,
1163
- df,
1164
- queryColName=queryColName,
1165
- dataStreamName=streamId,
1359
+ email, workspaceID, df,
1360
+ queryColName=queryColName,
1361
+ dataStreamName=streamId,
1362
+ definationMapping=self.definationMapping,
1166
1363
  ):
1167
1364
  print(
1168
- "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
1169
- )
1365
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results.")
1170
1366
  else:
1171
- self.latestDataframe = dataframe
1172
- return dataframe
1367
+ self.latestDataframe = working_df
1368
+ return working_df
1369
+
1173
1370
 
1174
1371
  def createExperiment(self, dataframe):
1175
1372
  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/execution.py CHANGED
@@ -25,15 +25,14 @@ class ModelExecutor:
25
25
  return response.choices[0].message.content
26
26
 
27
27
  def _executeGoogle(self, modelName: str, prompt: str,api_key) -> str:
28
-
28
+
29
29
  # Configure GenAI with API Key
30
30
  genai.configure(api_key=api_key)
31
-
31
+
32
32
  # Select Generative Model
33
33
  model = genai.GenerativeModel("gemini-2.0-flash-lite")
34
34
  # Generate Response
35
35
  response = model.generate_content(prompt)
36
36
  return response.text
37
-
38
-
39
-
37
+
38
+
llumo/helpingFuntions.py CHANGED
@@ -8,7 +8,11 @@ import json
8
8
  import base64
9
9
  import os
10
10
  import re
11
+ import openai
12
+ import google.generativeai as genai
11
13
 
14
+
15
+ from .models import _MODEL_METADATA, AVAILABLEMODELS
12
16
  subscriptionUrl = "https://app.llumo.ai/api/workspace/record-extra-usage"
13
17
  getStreamdataUrl = "https://app.llumo.ai/api/data-stream/all"
14
18
  createPlayUrl = "https://app.llumo.ai/api/New-Eval-API/create-new-eval-playground"
@@ -212,7 +216,8 @@ def deleteColumnListInPlayground(workspaceID: str, playgroundID: str):
212
216
  print("❌ Error:", response.status_code, response.text)
213
217
  return None
214
218
 
215
- def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColName=None,outputColName= "output",dataStreamName=None,definationMapping=None):
219
+ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColName=None,
220
+ outputColName= "output",dataStreamName=None,definationMapping=None,evalOutputMap = None):
216
221
  if len(dataframe) > 100:
217
222
  dataframe = dataframe.head(100)
218
223
  print("⚠️ Dataframe truncated to 100 rows for upload.")
@@ -232,11 +237,11 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
232
237
  # Iterate over each column in the dataframe
233
238
  for indx, col in enumerate(dataframe.columns):
234
239
  # Generate a unique column ID using uuid
235
- columnID = str(uuid.uuid4().hex[:8])
240
+ columnID = str(uuid.uuid4().hex[:8])
236
241
 
237
242
  columnIDMapping[col] = columnID
238
243
 
239
-
244
+
240
245
  if col.startswith('output') and promptText!=None:
241
246
  # For output columns, create the prompt template with promptText
242
247
  if promptText:
@@ -248,12 +253,12 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
248
253
 
249
254
  # Loop through each variable and check if it exists as a column name
250
255
  for var in variables:
251
- varName = var.strip()
256
+ varName = var.strip()
252
257
  if varName in columnIDMapping: # Check if the variable is a column name
253
258
  dependencies.append(columnIDMapping[varName]) # Add its columnID
254
259
 
255
260
  # Now update the template for the output column
256
-
261
+
257
262
  template={
258
263
  "provider": "OPENAI",
259
264
  "model": "GPT_4o",
@@ -275,8 +280,8 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
275
280
  "type": "PROMPT",
276
281
  "order": indx,
277
282
  }
278
-
279
- elif col.startswith('Data ') :
283
+
284
+ elif col.startswith('context') and dataStreamName != None :
280
285
  if queryColName and dataStreamName:
281
286
  dependencies = []
282
287
  dependencies.append(columnIDMapping[queryColName])
@@ -286,22 +291,27 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
286
291
  "dataStreamName": dataStreamName,
287
292
  "query": columnIDMapping[queryColName],
288
293
  "columnID": columnID, # Use the generated column ID
289
- "label": "Data stream",
294
+ "label": "context",
290
295
  "type": "DATA_STREAM",
291
296
  "order": indx}
292
297
 
293
- elif col in allEvals and promptText!=None:
294
298
 
299
+ elif any(col.startswith(eval + "_") or col == eval for eval in allEvals) and not " Reason" in col and promptText is not None:
300
+ if evalOutputMap != None:
301
+ outputColName = evalOutputMap[col]
302
+ else:
303
+ outputColName = outputColName
295
304
  dependencies = []
296
305
  variables = re.findall(r'{{(.*?)}}', promptText)
297
306
 
298
307
  # Loop through each variable and check if it exists as a column name
299
308
  for var in variables:
300
- varName = var.strip()
309
+ varName = var.strip()
301
310
  if varName in columnIDMapping: # Check if the variable is a column name
302
311
  dependencies.append(columnIDMapping[varName])
303
-
312
+
304
313
  dependencies.append(columnIDMapping[outputColName]) # Add the output column ID
314
+
305
315
  longDef = definationMapping.get(col, {}).get('definition', "")
306
316
  shortDef =definationMapping.get(col, {}).get('briefDefinition', "")
307
317
  enum = col.upper().replace(" ","_")
@@ -341,11 +351,11 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
341
351
  }
342
352
 
343
353
  elif col.endswith(' Reason') and promptText!=None:
344
- continue
354
+ continue
355
+
345
356
 
346
-
347
357
  else:
348
-
358
+
349
359
  template = {
350
360
  "label": col, # Label is the column name
351
361
  "type": "VARIABLE", # Default type for non-output columns
@@ -370,25 +380,27 @@ def createColumn(workspaceID, dataframe, playgroundID, promptText=None,queryColN
370
380
  row_dict = {}
371
381
 
372
382
  # For each column, we need to map the column ID to the corresponding value in the row
383
+
373
384
  for col in dataframe.columns:
374
385
  columnID = columnIDMapping[col]
375
-
376
- if col in allEvals and promptText!=None:
386
+
387
+ if any(col.startswith(eval + "_") or col == eval for eval in allEvals) and not " Reason" in col and promptText!=None:
388
+
377
389
  row_dict[columnID] = {
378
-
390
+
379
391
  "value": row[col],
380
392
  "type": "EVAL",
381
393
  "isValid": True,
382
394
  "reasoning": row[col+" Reason"],
383
395
  "edgeCase": "minorHallucinationDetailNotInContext",
384
396
  "kpi": col
385
-
386
- }
397
+
398
+ }
387
399
  elif col.endswith(' Reason') and promptText!=None:
388
400
  continue
389
401
  else:# Get the columnID from the mapping
390
402
  row_dict[columnID] = row[col]
391
-
403
+
392
404
  # row_dict[columnID] = row[col] # Directly map the column ID to the row value
393
405
  # Add the row index (if necessary)
394
406
  row_dict["pIndex"] = indx
@@ -440,11 +452,11 @@ def uploadRowsInDBPlayground(payload):
440
452
  return None
441
453
 
442
454
 
443
- def createPlayground(email, workspaceID, df, promptText=None,queryColName=None,dataStreamName=None,definationMapping=None,outputColName="output"):
455
+ def createPlayground(email, workspaceID, df, promptText=None,queryColName=None,dataStreamName=None,definationMapping=None,outputColName="output",evalOutputMap = None):
444
456
 
445
457
  playgroundId = str(createEvalPlayground(email=email, workspaceID=workspaceID))
446
458
  payload1, payload2 = createColumn(
447
- workspaceID=workspaceID, dataframe=df, playgroundID=playgroundId, promptText=promptText,queryColName=queryColName,dataStreamName=dataStreamName,definationMapping=definationMapping,outputColName=outputColName
459
+ workspaceID=workspaceID, dataframe=df, playgroundID=playgroundId, promptText=promptText,queryColName=queryColName,dataStreamName=dataStreamName,definationMapping=definationMapping,outputColName=outputColName,evalOutputMap=evalOutputMap
448
460
  )
449
461
 
450
462
  # Debugging line to check the payload2 structure
@@ -606,3 +618,36 @@ def fetchData(workspaceID, playgroundID, missingList: list):
606
618
  except Exception as e:
607
619
  print(f"An error occurred: {e}")
608
620
  return []
621
+
622
+ def validateModels(model_aliases):
623
+
624
+ selectedProviders = []
625
+ for name in model_aliases:
626
+ for alias ,(provider , modelName ) in _MODEL_METADATA.items():
627
+ if modelName == name:
628
+ selectedProviders.append(provider)
629
+
630
+ if len(set(selectedProviders)) > 1:
631
+ return {"status": False,"message":"All selected models should be of same provider."}
632
+ else:
633
+ return {"status": True,"message":"All selected models are of same provider."}
634
+
635
+
636
+
637
+ def validateOpenaiKey(api_key):
638
+ try:
639
+ client = openai.OpenAI(api_key=api_key)
640
+ _ = client.models.list() # Light call to list models
641
+ except openai.AuthenticationError:
642
+ raise ValueError("❌ Invalid OpenAI API key.")
643
+ except Exception as e:
644
+ raise RuntimeError(f"⚠️ Error validating OpenAI key: {e}")
645
+
646
+ def validateGoogleKey(api_key):
647
+ try:
648
+ genai.configure(api_key=api_key)
649
+ _ = genai.GenerativeModel("gemini-2.0").generate_content("test")
650
+ except Exception as e:
651
+ if "PERMISSION_DENIED" in str(e) or "API key not valid" in str(e):
652
+ raise ValueError("❌ Invalid Google API key.")
653
+ raise RuntimeError(f"⚠️ Error validating Gemini key: {e}")
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.15b2
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=60RSxhk-9wzK9KgBz8dfbUd3-AaKiljxqbHI5UL8GIw,54021
3
+ llumo/exceptions.py,sha256=Vp_MnanHbnd1Yjuoi6WLrKiwwZbJL3znCox2URMmGU4,2032
4
+ llumo/execution.py,sha256=nWbJ7AvWuUPcOb6i-JzKRna_PvF-ewZTiK8skS-5n3w,1380
5
+ llumo/functionCalling.py,sha256=D5jYapu1rIvdIJNUYPYMTyhQ1H-6nkwoOLMi6eekfUE,7241
6
+ llumo/helpingFuntions.py,sha256=BZfUIgTO0PJchppHn0wDRF1wcYSuMST5ry95HBPN5SQ,23534
7
+ llumo/models.py,sha256=aVEZsOOoQx5LeNtwSyBxqvrINq0izH3QWu_YjsMPE6o,2910
8
+ llumo/sockets.py,sha256=I2JO_eNEctRo_ikgvFVp5zDd-m0VDu04IEUhhsa1Tic,5950
9
+ llumo-0.2.15b2.dist-info/licenses/LICENSE,sha256=tF9yAcfPV9xGT3ViWmC8hPvOo8BEk4ZICbUfcEo8Dlk,182
10
+ llumo-0.2.15b2.dist-info/METADATA,sha256=vbXwSwhuxnO0CSMz4uJ45AepuwVMl7irZlHmYkqRYbY,1521
11
+ llumo-0.2.15b2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ llumo-0.2.15b2.dist-info/top_level.txt,sha256=d5zUTMI99llPtLRB8rtSrqELm_bOqX-bNC5IcwlDk88,6
13
+ llumo-0.2.15b2.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,,