llumo 0.2.0__py3-none-any.whl → 0.2.1__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
@@ -12,7 +12,7 @@ import os
12
12
  import itertools
13
13
  import pandas as pd
14
14
  from typing import List, Dict
15
- from .models import AVAILABLEMODELS,getProviderFromModel
15
+ from .models import AVAILABLEMODELS, getProviderFromModel
16
16
  from .execution import ModelExecutor
17
17
  from .helpingFuntions import *
18
18
  from .sockets import LlumoSocketClient
@@ -20,13 +20,15 @@ from .functionCalling import LlumoAgentExecutor
20
20
 
21
21
 
22
22
  # 👇 NEW: Explicitly load .env from the package folder
23
- envPath = os.path.join(os.path.dirname(__file__), '.env')
24
- load_dotenv(dotenv_path=envPath, override=False)# Automatically looks for .env in current directory
23
+ envPath = os.path.join(os.path.dirname(__file__), ".env")
24
+ load_dotenv(
25
+ dotenv_path=envPath, override=False
26
+ ) # Automatically looks for .env in current directory
25
27
 
26
- postUrl = os.getenv("POST_URL")
27
- fetchUrl = os.getenv("FETCH_URL")
28
- validateUrl = os.getenv("VALIDATE_URL")
29
- socketUrl = os.getenv("SOCKET_URL")
28
+ postUrl = "https://app.llumo.ai/api/eval/run-multiple-column"
29
+ fetchUrl = "https://app.llumo.ai/api/eval/fetch-rows-data-by-column"
30
+ validateUrl = "https://app.llumo.ai/api/workspace-details"
31
+ socketUrl = "https://red-skull-service-392377961931.us-central1.run.app/"
30
32
 
31
33
 
32
34
  class LlumoClient:
@@ -35,27 +37,17 @@ class LlumoClient:
35
37
  self.apiKey = api_key
36
38
  self.socket = LlumoSocketClient(socketUrl)
37
39
  self.processMapping = {}
38
-
39
40
 
40
-
41
- def validateApiKey(self, evalName = ""):
41
+ def validateApiKey(self, evalName=""):
42
42
  headers = {
43
43
  "Authorization": f"Bearer {self.apiKey}",
44
44
  "Content-Type": "application/json",
45
45
  }
46
46
  reqBody = {"analytics": [evalName]}
47
47
 
48
- # print(f"Making API key validation request to: {validateUrl}")
49
- # print(f"Request body: {reqBody}")
50
-
51
48
  try:
52
49
  response = requests.post(url=validateUrl, json=reqBody, headers=headers)
53
- # print(response.text)
54
- # Print response info for debugging
55
- # print(f"Response status code: {response.status_code}")
56
- # print(f"Response headers: {response.headers}")
57
50
 
58
- # Try to get at least some of the response content
59
51
  try:
60
52
  response_preview = response.text[:500] # First 500 chars
61
53
  # print(f"Response preview: {response_preview}")
@@ -75,11 +67,6 @@ class LlumoClient:
75
67
  detail=f"Endpoint not found (404): {validateUrl}"
76
68
  )
77
69
 
78
- # if response.status_code >= 500:
79
- # raise LlumoAIError.ServerError(
80
- # detail=f"Server error ({response.status_code})"
81
- # )
82
-
83
70
  if response.status_code != 200:
84
71
  raise LlumoAIError.RequestFailed(
85
72
  detail=f"Unexpected status code: {response.status_code}"
@@ -102,14 +89,10 @@ class LlumoClient:
102
89
  self.workspaceID = data["data"].get("workspaceID")
103
90
  self.evalDefinition = data["data"].get("analyticsMapping")
104
91
  self.socketToken = data["data"].get("token")
105
- self.hasSubscribed = data["data"].get("hasSubscribed",False)
106
- self.trialEndDate = data["data"].get("trialEndDate",None)
92
+ self.hasSubscribed = data["data"].get("hasSubscribed", False)
93
+ self.trialEndDate = data["data"].get("trialEndDate", None)
107
94
  self.subscriptionEndDate = data["data"].get("subscriptionEndDate", None)
108
-
109
- # print(f"API key validation successful:")
110
- # print(f"- Remaining hits: {self.hitsAvailable}")
111
- # print(f"- Workspace ID: {self.workspaceID}")
112
- # print(f"- Token received: {'Yes' if self.socketToken else 'No'}")
95
+ self.email = data["data"].get("email", None)
113
96
 
114
97
  except Exception as e:
115
98
  # print(f"Error extracting data from response: {str(e)}")
@@ -134,7 +117,6 @@ class LlumoClient:
134
117
 
135
118
  except Exception as e:
136
119
  print(f"Error in posting batch: {e}")
137
-
138
120
 
139
121
  def postDataStream(self, batch, workspaceID):
140
122
  payload = {
@@ -155,31 +137,36 @@ class LlumoClient:
155
137
 
156
138
  except Exception as e:
157
139
  print(f"Error in posting batch: {e}")
158
-
159
140
 
160
141
  def AllProcessMapping(self):
161
142
  for batch in self.allBatches:
162
143
  for record in batch:
163
- rowId = record['rowID']
164
- colId = record['columnID']
165
- pid = f'{rowId}-{colId}-{colId}'
144
+ rowId = record["rowID"]
145
+ colId = record["columnID"]
146
+ pid = f"{rowId}-{colId}-{colId}"
166
147
  self.processMapping[pid] = record
167
148
 
168
-
169
- def finalResp(self,results):
149
+ def finalResp(self, results):
170
150
  seen = set()
171
151
  uniqueResults = []
172
152
 
173
153
  for item in results:
174
154
  for rowID in item: # Each item has only one key
175
- # for rowID in item["data"]:
155
+ # for rowID in item["data"]:
176
156
  if rowID not in seen:
177
157
  seen.add(rowID)
178
158
  uniqueResults.append(item)
179
159
 
180
160
  return uniqueResults
181
161
 
182
- def evaluate(self, dataframe, eval ="Response Completeness", prompt_template="", outputColName="output"):
162
+ def evaluate(
163
+ self,
164
+ dataframe,
165
+ eval="Response Completeness",
166
+ prompt_template="",
167
+ outputColName="output",
168
+ createExperiment: bool = False,
169
+ ):
183
170
 
184
171
  results = {}
185
172
  try:
@@ -192,11 +179,12 @@ class LlumoClient:
192
179
  time.sleep(0.1)
193
180
  waited_secs += 0.1
194
181
  if waited_secs >= max_wait_secs:
195
- raise RuntimeError("Timeout waiting for server 'connection-established' event.")
182
+ raise RuntimeError(
183
+ "Timeout waiting for server 'connection-established' event."
184
+ )
196
185
 
197
186
  rowIdMapping = {}
198
187
 
199
-
200
188
  print(f"\n======= Running evaluation for: {eval} =======")
201
189
 
202
190
  try:
@@ -205,8 +193,14 @@ class LlumoClient:
205
193
  if hasattr(e, "response") and getattr(e, "response", None) is not None:
206
194
  pass
207
195
  raise
208
- userHits = checkUserHits(self.workspaceID,self.hasSubscribed,self.trialEndDate,self.subscriptionEndDate,self.hitsAvailable,len(dataframe))
209
-
196
+ userHits = checkUserHits(
197
+ self.workspaceID,
198
+ self.hasSubscribed,
199
+ self.trialEndDate,
200
+ self.subscriptionEndDate,
201
+ self.hitsAvailable,
202
+ len(dataframe),
203
+ )
210
204
 
211
205
  if not userHits["success"]:
212
206
  raise LlumoAIError.InsufficientCredits(userHits["message"])
@@ -219,14 +213,21 @@ class LlumoClient:
219
213
  provider = "OPENAI"
220
214
  evalType = "LLM"
221
215
  workspaceID = self.workspaceID
216
+ email = self.email
222
217
 
223
218
  self.allBatches = []
224
219
  currentBatch = []
225
220
 
226
221
  for index, row in dataframe.iterrows():
227
222
  tools = [row["tools"]] if "tools" in dataframe.columns else []
228
- groundTruth = row["groundTruth"] if "groundTruth" in dataframe.columns else ""
229
- messageHistory = [row["messageHistory"]] if "messageHistory" in dataframe.columns else []
223
+ groundTruth = (
224
+ row["groundTruth"] if "groundTruth" in dataframe.columns else ""
225
+ )
226
+ messageHistory = (
227
+ [row["messageHistory"]]
228
+ if "messageHistory" in dataframe.columns
229
+ else []
230
+ )
230
231
  promptTemplate = prompt_template
231
232
 
232
233
  keys = re.findall(r"{{(.*?)}}", promptTemplate)
@@ -235,9 +236,13 @@ class LlumoClient:
235
236
  raise LlumoAIError.InvalidPromptTemplate()
236
237
 
237
238
  inputDict = {key: row[key] for key in keys if key in row}
238
- output = row[outputColName] if outputColName in dataframe.columns else ""
239
+ output = (
240
+ row[outputColName] if outputColName in dataframe.columns else ""
241
+ )
239
242
 
240
- activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
243
+ activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace(
244
+ "-", ""
245
+ )
241
246
  rowID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
242
247
  columnID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
243
248
 
@@ -270,6 +275,7 @@ class LlumoClient:
270
275
  "rowID": rowID,
271
276
  "playgroundID": activePlayground,
272
277
  "processType": "EVAL",
278
+ "email": email,
273
279
  }
274
280
 
275
281
  query = ""
@@ -282,7 +288,9 @@ class LlumoClient:
282
288
  else:
283
289
  if promptTemplate:
284
290
  tempObj = {key: value}
285
- promptTemplate = getInputPopulatedPrompt(promptTemplate, tempObj)
291
+ promptTemplate = getInputPopulatedPrompt(
292
+ promptTemplate, tempObj
293
+ )
286
294
  else:
287
295
  query += f" {key}: {value}, "
288
296
 
@@ -290,11 +298,17 @@ class LlumoClient:
290
298
  for key, value in inputDict.items():
291
299
  context += f" {key}: {value}, "
292
300
 
293
- templateData["processData"]["executionDependency"]["context"] = context.strip()
294
- templateData["processData"]["executionDependency"]["query"] = query.strip()
301
+ templateData["processData"]["executionDependency"][
302
+ "context"
303
+ ] = context.strip()
304
+ templateData["processData"]["executionDependency"][
305
+ "query"
306
+ ] = query.strip()
295
307
 
296
308
  if promptTemplate and not query.strip():
297
- templateData["processData"]["executionDependency"]["query"] = promptTemplate
309
+ templateData["processData"]["executionDependency"][
310
+ "query"
311
+ ] = promptTemplate
298
312
 
299
313
  currentBatch.append(templateData)
300
314
 
@@ -316,7 +330,10 @@ class LlumoClient:
316
330
  timeout = max(50, min(600, totalItems * 10))
317
331
 
318
332
  self.socket.listenForResults(
319
- min_wait=40, max_wait=timeout, inactivity_timeout=150, expected_results=totalItems
333
+ min_wait=40,
334
+ max_wait=timeout,
335
+ inactivity_timeout=150,
336
+ expected_results=totalItems,
320
337
  )
321
338
 
322
339
  eval_results = self.socket.getReceivedData()
@@ -334,26 +351,33 @@ class LlumoClient:
334
351
  dataframe[evalName] = None
335
352
  for item in records:
336
353
  for compound_key, value in item.items():
337
- # for compound_key, value in item['data'].items():
354
+ # for compound_key, value in item['data'].items():
338
355
 
339
- rowID = compound_key.split('-')[0]
356
+ rowID = compound_key.split("-")[0]
340
357
  # looking for the index of each rowID , in the original dataframe
341
358
  if rowID in rowIdMapping:
342
359
  index = rowIdMapping[rowID]
343
360
  # dataframe.at[index, evalName] = value
344
361
  dataframe.at[index, evalName] = value["value"]
345
- dataframe.at[index, f'{evalName} Reason'] = value["reasoning"]
346
-
362
+ dataframe.at[index, f"{evalName} Reason"] = value["reasoning"]
347
363
 
348
364
  else:
349
365
  pass
350
366
  # print(f"⚠️ Warning: Could not find rowID {rowID} in mapping")
367
+ if createExperiment:
368
+ pd.set_option("future.no_silent_downcasting", True)
369
+ df = dataframe.fillna("Some error occured").astype(object)
351
370
 
352
- return dataframe
371
+ if createPlayground(email, workspaceID, df):
372
+ print(
373
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
374
+ )
375
+ else:
376
+ return dataframe
353
377
 
354
378
  def evaluateCompressor(self, dataframe, prompt_template):
355
379
  results = []
356
-
380
+
357
381
  try:
358
382
  # Connect to socket first
359
383
  # print("Connecting to socket server...")
@@ -366,7 +390,9 @@ class LlumoClient:
366
390
  time.sleep(0.1)
367
391
  waited_secs += 0.1
368
392
  if waited_secs >= max_wait_secs:
369
- raise RuntimeError("Timeout waiting for server 'connection-established' event.")
393
+ raise RuntimeError(
394
+ "Timeout waiting for server 'connection-established' event."
395
+ )
370
396
 
371
397
  # print(f"Connected with socket ID: {socketID}")
372
398
 
@@ -382,10 +408,16 @@ class LlumoClient:
382
408
  raise
383
409
 
384
410
  # check for available hits and trial limit
385
- userHits = checkUserHits(self.workspaceID, self.hasSubscribed, self.trialEndDate, self.subscriptionEndDate,
386
- self.hitsAvailable, len(dataframe))
411
+ userHits = checkUserHits(
412
+ self.workspaceID,
413
+ self.hasSubscribed,
414
+ self.trialEndDate,
415
+ self.subscriptionEndDate,
416
+ self.hitsAvailable,
417
+ len(dataframe),
418
+ )
387
419
 
388
- # do not proceed if subscription or trial limit has exhausted
420
+ # do not proceed if subscription or trial limit has exhausted
389
421
  if not userHits["success"]:
390
422
  raise LlumoAIError.InsufficientCredits(userHits["message"])
391
423
 
@@ -396,7 +428,7 @@ class LlumoClient:
396
428
  provider = "OPENAI"
397
429
  evalType = "LLUMO"
398
430
  workspaceID = self.workspaceID
399
-
431
+ email = self.email
400
432
  # Prepare all batches before sending
401
433
  # print("Preparing batches...")
402
434
  self.allBatches = []
@@ -412,14 +444,22 @@ class LlumoClient:
412
444
  if not all([ky in dataframe.columns for ky in keys]):
413
445
  raise LlumoAIError.InvalidPromptTemplate()
414
446
 
415
- activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
447
+ activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace(
448
+ "-", ""
449
+ )
416
450
  rowID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
417
451
  columnID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
418
452
 
419
- compressed_prompt_id = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
420
- compressed_prompt_output_id = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
453
+ compressed_prompt_id = (
454
+ f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
455
+ )
456
+ compressed_prompt_output_id = (
457
+ f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
458
+ )
421
459
  cost_id = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
422
- cost_saving_id = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
460
+ cost_saving_id = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace(
461
+ "-", ""
462
+ )
423
463
 
424
464
  # Use the server-provided socket ID here
425
465
  templateData = {
@@ -435,7 +475,7 @@ class LlumoClient:
435
475
  "compressed_prompt": compressed_prompt_id,
436
476
  "compressed_prompt_output": compressed_prompt_output_id,
437
477
  "cost": cost_id,
438
- "cost_saving": cost_saving_id
478
+ "cost_saving": cost_saving_id,
439
479
  },
440
480
  "processData": {
441
481
  "rowData": {
@@ -451,15 +491,14 @@ class LlumoClient:
451
491
  "compressed_prompt": compressed_prompt_id,
452
492
  "compressed_prompt_output": compressed_prompt_output_id,
453
493
  "cost": cost_id,
454
- "cost_saving": cost_saving_id
455
- }
494
+ "cost_saving": cost_saving_id,
495
+ },
456
496
  },
457
497
  "workspaceID": workspaceID,
458
- "email": "",
459
- "playgroundID": activePlayground
498
+ "email": email,
499
+ "playgroundID": activePlayground,
460
500
  }
461
501
 
462
-
463
502
  # Build query/context from input
464
503
  query = ""
465
504
  context = ""
@@ -471,7 +510,9 @@ class LlumoClient:
471
510
  context += f" {key}: {value}, "
472
511
  else:
473
512
  if promptTemplate:
474
- populatedPrompt = getInputPopulatedPrompt(promptTemplate, {key: value})
513
+ populatedPrompt = getInputPopulatedPrompt(
514
+ promptTemplate, {key: value}
515
+ )
475
516
  query += f"{populatedPrompt} "
476
517
  else:
477
518
  query += f" {key}: {value}, "
@@ -480,18 +521,22 @@ class LlumoClient:
480
521
  for key, value in inputDict.items():
481
522
  context += f" {key}: {value}, "
482
523
 
483
- templateData["processData"]["rowData"]["context"]["value"] = context.strip()
524
+ templateData["processData"]["rowData"]["context"][
525
+ "value"
526
+ ] = context.strip()
484
527
  templateData["processData"]["rowData"]["query"]["value"] = query.strip()
485
528
 
486
529
  if promptTemplate and not query.strip():
487
- templateData["processData"]["rowData"]["query"]["value"] = promptTemplate
530
+ templateData["processData"]["rowData"]["query"][
531
+ "value"
532
+ ] = promptTemplate
488
533
 
489
534
  # print(templateData)
490
535
  currentBatch.append(templateData)
491
536
 
492
537
  if len(currentBatch) == 10 or index == len(dataframe) - 1:
493
- self.allBatches.append(currentBatch)
494
- currentBatch = []
538
+ self.allBatches.append(currentBatch)
539
+ currentBatch = []
495
540
 
496
541
  # Post all batches
497
542
  total_items = sum(len(batch) for batch in self.allBatches)
@@ -516,7 +561,12 @@ class LlumoClient:
516
561
  # print(f"All batches posted. Waiting up to {timeout} seconds for results...")
517
562
 
518
563
  # Listen for results
519
- self.socket.listenForResults(min_wait=20, max_wait=timeout, inactivity_timeout=30,expected_results=None)
564
+ self.socket.listenForResults(
565
+ min_wait=20,
566
+ max_wait=timeout,
567
+ inactivity_timeout=30,
568
+ expected_results=None,
569
+ )
520
570
 
521
571
  # Get results for this evaluation
522
572
  eval_results = self.socket.getReceivedData()
@@ -539,17 +589,33 @@ class LlumoClient:
539
589
  except Exception as e:
540
590
  print(f"Error disconnecting socket: {e}")
541
591
 
542
- compressed_prompt , compressed_prompt_output , cost , cost_saving = costColumnMapping(results,self.processMapping)
592
+ compressed_prompt, compressed_prompt_output, cost, cost_saving = (
593
+ costColumnMapping(results, self.processMapping)
594
+ )
543
595
  dataframe["compressed_prompt"] = compressed_prompt
544
596
  dataframe["compressed_prompt_output"] = compressed_prompt_output
545
597
  dataframe["cost"] = cost
546
598
  dataframe["cost_saving"] = cost_saving
547
- return dataframe
548
599
 
600
+ return dataframe
549
601
 
550
- def run_sweep(self,templates: List[str], dataset: Dict[str, List[str]], model_aliases: List[AVAILABLEMODELS], apiKey: str, eval = ["Response Correctness"],toEvaluate:bool =False ) -> pd.DataFrame:
602
+ def run_sweep(
603
+ self,
604
+ templates: List[str],
605
+ dataset: Dict[str, List[str]],
606
+ model_aliases: List[AVAILABLEMODELS],
607
+ apiKey: str,
608
+ eval=["Response Correctness"],
609
+ toEvaluate: bool = False,
610
+ createExperiment: bool = False,
611
+ ) -> pd.DataFrame:
612
+ try:
613
+ self.validateApiKey()
614
+ except Exception as e:
615
+ raise "Some error ocuured please check your API key"
616
+ workspaceID = self.workspaceID
617
+ email = self.email
551
618
  executor = ModelExecutor(apiKey)
552
-
553
619
  keys = list(dataset.keys())
554
620
  value_combinations = list(itertools.product(*dataset.values()))
555
621
  combinations = [dict(zip(keys, values)) for values in value_combinations]
@@ -568,65 +634,123 @@ class LlumoClient:
568
634
  "template": template,
569
635
  "prompt": prompt,
570
636
  **combo,
571
- "model": model.value
637
+ "model": model.value,
572
638
  }
573
639
 
574
-
575
640
  try:
576
641
  provider = getProviderFromModel(model)
577
- response = executor.execute(provider, model.value, prompt, apiKey)
642
+ response = executor.execute(
643
+ provider, model.value, prompt, apiKey
644
+ )
578
645
  row["output"] = response
579
646
  except Exception as e:
580
647
  row["output"] = f"Error: {str(e)}"
581
-
648
+
582
649
  results.append(row)
583
- df=pd.DataFrame(results)
650
+ df = pd.DataFrame(results)
584
651
  if toEvaluate:
585
-
586
- res = self.evaluate(df,eval =eval ,prompt_template=str(templates[0]))
587
- return res
588
-
589
- return df
590
-
591
- def evaluateAgents(self, dataframe, model, agents, model_api_key=None,
592
- prompt_template="Give answer for the given query: {{query}}"):
652
+
653
+ res = self.evaluate(df, eval=eval, prompt_template=str(templates[0]))
654
+
655
+ if createExperiment:
656
+ pd.set_option("future.no_silent_downcasting", True)
657
+ res = res.fillna("Some error occured")
658
+ if createPlayground(email, workspaceID, res):
659
+ print(
660
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
661
+ )
662
+ else:
663
+ return res
664
+
665
+ else:
666
+ if createExperiment:
667
+ pd.set_option("future.no_silent_downcasting", True)
668
+ df = df.fillna("Some error occured")
669
+ if createPlayground(email, workspaceID, df):
670
+ print(
671
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
672
+ )
673
+ else:
674
+ return df
675
+
676
+ def evaluateAgents(
677
+ self,
678
+ dataframe,
679
+ model,
680
+ agents,
681
+ model_api_key=None,
682
+ prompt_template="Give answer for the given query: {{query}}",
683
+ createExperiment: bool = False,
684
+ ):
593
685
  if model.lower() not in ["openai", "google"]:
594
686
  raise ValueError("Model must be 'openai' or 'google'")
595
687
 
596
688
  # Run unified agent execution
597
- toolResponseDf = LlumoAgentExecutor.run(dataframe, agents, model=model, model_api_key=model_api_key)
598
- evals = ["Tool Reliability", "Stepwise Progression", "Tool Selection Accuracy", "Final Task Alignment"]
689
+ toolResponseDf = LlumoAgentExecutor.run(
690
+ dataframe, agents, model=model, model_api_key=model_api_key
691
+ )
692
+ evals = [
693
+ "Tool Reliability",
694
+ "Stepwise Progression",
695
+ "Tool Selection Accuracy",
696
+ "Final Task Alignment",
697
+ ]
599
698
 
600
699
  for eval in evals:
601
700
  # Perform evaluation
602
701
  toolResponseDf = self.evaluate(
603
702
  toolResponseDf,
604
- eval = eval,
605
- prompt_template=prompt_template
703
+ eval=eval,
704
+ prompt_template=prompt_template,
705
+ createExperiment=False,
606
706
  )
607
- return toolResponseDf
707
+ if createExperiment:
708
+ pd.set_option("future.no_silent_downcasting", True)
709
+ df = toolResponseDf.fillna("Some error occured")
710
+ if createPlayground(self.email, self.workspaceID, df):
711
+ print(
712
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
713
+ )
714
+ else:
715
+ return toolResponseDf
608
716
 
609
- def evaluateAgentResponses(self, dataframe, prompt_template="Give answer for the given query: {{query}}"):
717
+ def evaluateAgentResponses(
718
+ self,
719
+ dataframe,
720
+ prompt_template="Give answer for the given query: {{query}}",
721
+ createExperiment: bool = False,
722
+ ):
610
723
  try:
611
724
  if "query" and "messageHistory" and "tools" not in dataframe.columns:
612
- raise ValueError("DataFrame must contain 'query', 'messageHistory', and 'tools' columns")
613
- evals = ["Tool Reliability", "Stepwise Progression", "Tool Selection Accuracy", "Final Task Alignment"]
725
+ raise ValueError(
726
+ "DataFrame must contain 'query', 'messageHistory', and 'tools' columns"
727
+ )
728
+ evals = [
729
+ "Tool Reliability",
730
+ "Stepwise Progression",
731
+ "Tool Selection Accuracy",
732
+ "Final Task Alignment",
733
+ ]
614
734
  toolResponseDf = dataframe.copy()
615
735
  for eval in evals:
616
736
  # Perform evaluation
617
737
  toolResponseDf = self.evaluate(
618
- toolResponseDf,
619
- eval = eval,
620
- prompt_template=prompt_template
738
+ toolResponseDf, eval=eval, prompt_template=prompt_template
621
739
  )
622
740
  return toolResponseDf
623
-
741
+
624
742
  except Exception as e:
625
743
  raise e
626
-
627
- def runDataStream(self, dataframe, streamName:str,queryColName:str="query"):
744
+
745
+ def runDataStream(
746
+ self,
747
+ dataframe,
748
+ streamName: str,
749
+ queryColName: str = "query",
750
+ createExperiment: bool = False,
751
+ ):
628
752
  results = {}
629
-
753
+
630
754
  try:
631
755
  socketID = self.socket.connect(timeout=150)
632
756
  # Ensure full connection before proceeding
@@ -636,7 +760,9 @@ class LlumoClient:
636
760
  time.sleep(0.1)
637
761
  waited_secs += 0.1
638
762
  if waited_secs >= max_wait_secs:
639
- raise RuntimeError("Timeout waiting for server 'connection-established' event.")
763
+ raise RuntimeError(
764
+ "Timeout waiting for server 'connection-established' event."
765
+ )
640
766
  # print(f"Connected with socket ID: {socketID}")
641
767
  rowIdMapping = {}
642
768
  try:
@@ -650,25 +776,32 @@ class LlumoClient:
650
776
  print(f"Response content: {e.response.text[:500]}...")
651
777
  raise
652
778
  # check for available hits and trial limit
653
- userHits = checkUserHits(self.workspaceID, self.hasSubscribed, self.trialEndDate, self.subscriptionEndDate,
654
- self.hitsAvailable, len(dataframe))
779
+ userHits = checkUserHits(
780
+ self.workspaceID,
781
+ self.hasSubscribed,
782
+ self.trialEndDate,
783
+ self.subscriptionEndDate,
784
+ self.hitsAvailable,
785
+ len(dataframe),
786
+ )
655
787
 
656
- # do not proceed if subscription or trial limit has exhausted
788
+ # do not proceed if subscription or trial limit has exhausted
657
789
  if not userHits["success"]:
658
790
  raise LlumoAIError.InsufficientCredits(userHits["message"])
659
791
 
660
-
661
-
662
792
  print("====🚀Sit back while we fetch data from the stream 🚀====")
663
793
  workspaceID = self.workspaceID
664
- streamId=getStreamId(workspaceID,self.apiKey,streamName)
794
+ email = self.email
795
+ streamId = getStreamId(workspaceID, self.apiKey, streamName)
665
796
  # Prepare all batches before sending
666
797
  # print("Preparing batches...")
667
798
  self.allBatches = []
668
799
  currentBatch = []
669
800
 
670
801
  for index, row in dataframe.iterrows():
671
- activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
802
+ activePlayground = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace(
803
+ "-", ""
804
+ )
672
805
  rowID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
673
806
  columnID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
674
807
 
@@ -678,28 +811,24 @@ class LlumoClient:
678
811
  "processID": getProcessID(),
679
812
  "socketID": socketID,
680
813
  "processData": {
681
- "executionDependency": {
682
- "query": row[queryColName]
683
- },
684
- "dataStreamID": streamId
814
+ "executionDependency": {"query": row[queryColName]},
815
+ "dataStreamID": streamId,
685
816
  },
686
817
  "workspaceID": workspaceID,
687
- "email": "",
818
+ "email": email,
688
819
  "type": "DATA_STREAM",
689
820
  "playgroundID": activePlayground,
690
821
  "processType": "DATA_STREAM",
691
822
  "rowID": rowID,
692
823
  "columnID": columnID,
693
- "source": "SDK"
694
-
824
+ "source": "SDK",
695
825
  }
696
826
 
697
-
698
827
  currentBatch.append(templateData)
699
828
 
700
829
  if len(currentBatch) == 10 or index == len(dataframe) - 1:
701
- self.allBatches.append(currentBatch)
702
- currentBatch = []
830
+ self.allBatches.append(currentBatch)
831
+ currentBatch = []
703
832
 
704
833
  # Post all batches
705
834
  total_items = sum(len(batch) for batch in self.allBatches)
@@ -724,7 +853,12 @@ class LlumoClient:
724
853
  # print(f"All batches posted. Waiting up to {timeout} seconds for results...")
725
854
 
726
855
  # Listen for results
727
- self.socket.listenForResults(min_wait=20, max_wait=timeout, inactivity_timeout=30,expected_results=None)
856
+ self.socket.listenForResults(
857
+ min_wait=20,
858
+ max_wait=timeout,
859
+ inactivity_timeout=30,
860
+ expected_results=None,
861
+ )
728
862
 
729
863
  # Get results for this evaluation
730
864
  eval_results = self.socket.getReceivedData()
@@ -734,7 +868,6 @@ class LlumoClient:
734
868
  results["Data Stream"] = self.finalResp(eval_results)
735
869
  print(f"=======You are all set! continue your expectations 🚀======\n")
736
870
 
737
-
738
871
  # print("All evaluations completed successfully")
739
872
 
740
873
  except Exception as e:
@@ -752,31 +885,44 @@ class LlumoClient:
752
885
  dataframe[streamName] = None
753
886
  for item in records:
754
887
  for compound_key, value in item.items():
755
- # for compound_key, value in item['data'].items():
888
+ # for compound_key, value in item['data'].items():
756
889
 
757
- rowID = compound_key.split('-')[0]
890
+ rowID = compound_key.split("-")[0]
758
891
  # looking for the index of each rowID , in the original dataframe
759
892
  if rowID in rowIdMapping:
760
893
  index = rowIdMapping[rowID]
761
894
  # dataframe.at[index, evalName] = value
762
895
  dataframe.at[index, streamName] = value["value"]
763
-
764
-
765
896
 
766
897
  else:
767
898
  pass
768
899
  # print(f"⚠️ Warning: Could not find rowID {rowID} in mapping")
769
900
 
770
- return dataframe
771
-
772
-
773
-
774
- def getId(self,workspaceID,streamName):
775
- streamId=getStreamId(workspaceID,self.apiKey,streamName)
776
- return streamId
777
-
778
-
779
-
901
+ if createExperiment:
902
+ pd.set_option("future.no_silent_downcasting", True)
903
+ df = dataframe.fillna("Some error occured").astype(object)
904
+
905
+ if createPlayground(email, workspaceID, df):
906
+ print(
907
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
908
+ )
909
+ else:
910
+ self.latestDataframe = dataframe
911
+ return dataframe
912
+
913
+ def createExperiment(self, dataframe):
914
+ try:
915
+ self.validateApiKey()
916
+
917
+ flag = createPlayground(self.email, self.workspaceID, dataframe)
918
+ if flag:
919
+ print(
920
+ "Your data has been saved in the Llumo Experiment. Visit https://app.llumo.ai/evallm to see the results."
921
+ )
922
+ except Exception as e:
923
+ raise "Some error ocuured please check your API key"
924
+
925
+
780
926
  class SafeDict(dict):
781
927
  def __missing__(self, key):
782
928
  return ""
llumo/helpingFuntions.py CHANGED
@@ -1,18 +1,27 @@
1
1
  import time
2
2
  import uuid
3
3
  import numpy as np
4
- from datetime import datetime
4
+ from datetime import datetime
5
5
  from dateutil import parser
6
6
  import requests
7
7
  import json
8
8
  import base64
9
9
  import os
10
10
  from dotenv import load_dotenv
11
- load_dotenv()
12
11
 
13
- subscriptionUrl = os.getenv("SUBSCRIPTION_URL")
14
- getStreamdataUrl = os.getenv("DATA_STREAM_URL")
15
12
 
13
+ load_dotenv()
14
+
15
+ subscriptionUrl = "https://app.llumo.ai/api/workspace/record-extra-usage"
16
+ getStreamdataUrl = "https://app.llumo.ai/api/data-stream/all"
17
+ createPlayUrl = "https://app.llumo.ai/api/New-Eval-API/create-new-eval-playground"
18
+ deletePlayUrl = "https://app.llumo.ai/api/New-Eval-API/new-upload-flow/delete-columnlist-in-playground"
19
+ uploadColList = (
20
+ "https://app.llumo.ai/api/New-Eval-API/new-upload-flow/uploadColumnListInPlayground"
21
+ )
22
+ uploadRowList = (
23
+ "https://app.llumo.ai/api/New-Eval-API/new-upload-flow/uploadRowsInDBPlayground"
24
+ )
16
25
 
17
26
 
18
27
  def getProcessID():
@@ -24,6 +33,7 @@ def getInputPopulatedPrompt(promptTemplate, tempObj):
24
33
  promptTemplate = promptTemplate.replace(f"{{{{{key}}}}}", value)
25
34
  return promptTemplate
26
35
 
36
+
27
37
  def costColumnMapping(costResults, allProcess):
28
38
  # this dict will store cost column data for each row
29
39
  cost_cols = {}
@@ -63,7 +73,14 @@ def costColumnMapping(costResults, allProcess):
63
73
  return compressed_prompt, compressed_prompt_output, cost, cost_saving
64
74
 
65
75
 
66
- def checkUserHits(workspaceID, hasSubscribed, trialEndDate, subscriptionEndDate, remainingHits, datasetLength):
76
+ def checkUserHits(
77
+ workspaceID,
78
+ hasSubscribed,
79
+ trialEndDate,
80
+ subscriptionEndDate,
81
+ remainingHits,
82
+ datasetLength,
83
+ ):
67
84
  # Get the current date (only the date part)
68
85
  current_date = datetime.now().date()
69
86
 
@@ -99,7 +116,9 @@ def checkUserHits(workspaceID, hasSubscribed, trialEndDate, subscriptionEndDate,
99
116
  "Content-Type": "application/json",
100
117
  }
101
118
  reqBody = {"unitsToSet": 1}
102
- responseBody = requests.post(url=subscriptionUrl, json=reqBody, headers=headers)
119
+ responseBody = requests.post(
120
+ url=subscriptionUrl, json=reqBody, headers=headers
121
+ )
103
122
  response = json.loads(responseBody.text)
104
123
 
105
124
  proceed = response.get("execution", "")
@@ -110,6 +129,7 @@ def checkUserHits(workspaceID, hasSubscribed, trialEndDate, subscriptionEndDate,
110
129
 
111
130
  return {"success": True, "message": "Access granted."}
112
131
 
132
+
113
133
  def getStreamId(workspaceID: str, token, dataStreamName):
114
134
  headers = {
115
135
  "Authorization": f"Bearer {token}",
@@ -123,15 +143,164 @@ def getStreamId(workspaceID: str, token, dataStreamName):
123
143
  data = responseJson.get("data", [])
124
144
 
125
145
  # Find stream by name
126
- matchedStream = next((stream for stream in data if stream.get("name") == dataStreamName), None)
146
+ matchedStream = next(
147
+ (stream for stream in data if stream.get("name") == dataStreamName), None
148
+ )
127
149
 
128
150
  if matchedStream:
129
-
151
+
130
152
  return matchedStream.get("dataStreamID")
131
-
153
+
132
154
  else:
133
155
  print(f"No stream found with name: {dataStreamName}")
134
156
  return None
135
157
  else:
136
158
  print("Error:", response.status_code, response.text)
137
- return None
159
+ return None
160
+
161
+
162
+ def createEvalPlayground(email: str, workspaceID: str):
163
+ url = createPlayUrl
164
+ headers = {
165
+ "Content-Type": "application/json",
166
+ }
167
+ payload = {
168
+ "email": email,
169
+ "workspaceID": workspaceID,
170
+ }
171
+
172
+ response = requests.post(url, json=payload, headers=headers)
173
+
174
+ if response.status_code == 200:
175
+ try:
176
+ responseJson = response.json()
177
+ # print(responseJson)
178
+ return responseJson.get("data", {}).get("playgroundID", None)
179
+
180
+ except Exception as e:
181
+ print("Failed to parse JSON:", e)
182
+ return None
183
+ else:
184
+ print("Error:", response.status_code, response.text)
185
+ return None
186
+
187
+
188
+ def deleteColumnListInPlayground(workspaceID: str, playgroundID: str):
189
+ url = deletePlayUrl
190
+ headers = {
191
+ "Content-Type": "application/json",
192
+ }
193
+ payload = {
194
+ "workspaceID": workspaceID,
195
+ "playgroundID": playgroundID,
196
+ }
197
+
198
+ response = requests.post(url, json=payload, headers=headers)
199
+
200
+ if response.status_code == 200:
201
+ try:
202
+
203
+ return response.json()
204
+ except Exception as e:
205
+ print("⚠️ Failed to parse JSON:", e)
206
+ return None
207
+ else:
208
+ print("❌ Error:", response.status_code, response.text)
209
+ return None
210
+
211
+
212
+ def createColumn(workspaceID, dataframe, playgroundID):
213
+
214
+ if len(dataframe) > 100:
215
+ dataframe = dataframe.head(100)
216
+ print("⚠️ Dataframe truncated to 100 rows for upload.")
217
+
218
+ playgroundID = playgroundID
219
+
220
+ coltemplate = {
221
+ "workspaceID": workspaceID,
222
+ "playgroundID": playgroundID,
223
+ "columnListToUpload": [],
224
+ }
225
+
226
+ for indx, col in enumerate(dataframe.columns):
227
+ template = {
228
+ "label": col,
229
+ "type": "VARIABLE",
230
+ "variableType": "STRING",
231
+ "order": indx,
232
+ "columnID": col,
233
+ }
234
+ coltemplate["columnListToUpload"].append(template)
235
+
236
+ rowTemplate = {
237
+ "workspaceID": workspaceID,
238
+ "playgroundID": playgroundID,
239
+ "dataToUploadList": [],
240
+ "columnList": coltemplate["columnListToUpload"],
241
+ }
242
+
243
+ for indx, row in dataframe.iterrows():
244
+ row_dict = row.to_dict()
245
+ row_dict["pIndex"] = indx
246
+ rowTemplate["dataToUploadList"].append(row_dict)
247
+
248
+ return coltemplate, rowTemplate
249
+
250
+
251
+ def uploadColumnListInPlayground(payload):
252
+ url = uploadColList
253
+ headers = {
254
+ "Content-Type": "application/json",
255
+ }
256
+ payload = payload
257
+
258
+ response = requests.post(url, json=payload, headers=headers)
259
+
260
+ if response.status_code == 200:
261
+ try:
262
+
263
+ return response.json()
264
+ except Exception as e:
265
+ print("⚠️ Failed to parse JSON:", e)
266
+ return None
267
+ else:
268
+ print("❌ Error:", response.status_code, response.text)
269
+ return None
270
+
271
+
272
+ def uploadRowsInDBPlayground(payload):
273
+ url = uploadRowList
274
+ headers = {
275
+ "Content-Type": "application/json",
276
+ }
277
+
278
+ payload = payload
279
+
280
+ response = requests.post(url, json=payload, headers=headers)
281
+
282
+ if response.status_code == 200:
283
+ try:
284
+
285
+ return response.json()
286
+ except Exception as e:
287
+ print("⚠️ Failed to parse JSON:", e)
288
+ return None
289
+ else:
290
+ print("❌ Error:", response.status_code, response.text)
291
+ return None
292
+
293
+
294
+ def createPlayground(email, workspaceID, df):
295
+ playgroundId = str(createEvalPlayground(email=email, workspaceID=workspaceID))
296
+ payload1, payload2 = createColumn(
297
+ workspaceID=workspaceID, dataframe=df, playgroundID=playgroundId
298
+ )
299
+ deleteExistingRows = deleteColumnListInPlayground(
300
+ workspaceID=workspaceID, playgroundID=playgroundId
301
+ )
302
+ colListUpload = uploadColumnListInPlayground(payload=payload1)
303
+ rowListUpload = uploadRowsInDBPlayground(payload=payload2)
304
+
305
+ if rowListUpload:
306
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llumo
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Python SDK for interacting with the Llumo ai API.
5
5
  Home-page: https://www.llumo.ai/
6
6
  Author: Llumo
@@ -1,13 +1,13 @@
1
1
  llumo/__init__.py,sha256=O04b4yW1BnOvcHzxWFddAKhtdBEhBNhLdb6xgnpHH_Q,205
2
- llumo/client.py,sha256=F4zabvAjLnu0N61qSw5DqerNlV5ybC2DbxaI55olldg,31916
2
+ llumo/client.py,sha256=YBTWDVqJTBh0IKNDXzD8vvFgUoi8uuZDbA-Po72cfxI,35849
3
3
  llumo/exceptions.py,sha256=iCj7HhtO_ckC2EaVBdXbAudNpuMDsYmmMEV5lwynZ-E,1854
4
4
  llumo/execution.py,sha256=x88wQV8eL99wNN5YtjFaAMCIfN1PdfQVlAZQb4vzgQ0,1413
5
5
  llumo/functionCalling.py,sha256=QtuTtyoz5rnfNUrNT1kzegNPOrMFjrlgxZfwTqRMdiA,7190
6
- llumo/helpingFuntions.py,sha256=9w1J4wMnJV1v_5yFMyxIHcvc16sEq5MZzOFBPagij5Q,4467
6
+ llumo/helpingFuntions.py,sha256=mDGOjeqJI1NOdoaT5FK3tlhWwhpZnk1nCnZOan0AFk0,8886
7
7
  llumo/models.py,sha256=YH-qAMnShmUpmKE2LQAzQdpRsaXkFSlOqMxHwU4zBUI,1560
8
8
  llumo/sockets.py,sha256=Qxxqtx3Hg07HLhA4QfcipK1ChiOYhHZBu02iA6MfYlQ,5579
9
- llumo-0.2.0.dist-info/licenses/LICENSE,sha256=tF9yAcfPV9xGT3ViWmC8hPvOo8BEk4ZICbUfcEo8Dlk,182
10
- llumo-0.2.0.dist-info/METADATA,sha256=z8Ec84QAe1khloO38UkicxqVGukFid_3aeptYpHra0I,426
11
- llumo-0.2.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
12
- llumo-0.2.0.dist-info/top_level.txt,sha256=d5zUTMI99llPtLRB8rtSrqELm_bOqX-bNC5IcwlDk88,6
13
- llumo-0.2.0.dist-info/RECORD,,
9
+ llumo-0.2.1.dist-info/licenses/LICENSE,sha256=tF9yAcfPV9xGT3ViWmC8hPvOo8BEk4ZICbUfcEo8Dlk,182
10
+ llumo-0.2.1.dist-info/METADATA,sha256=E2hanseFfMf8jIbj54qVdZUgKu2JTOu_DV3nweGG_zs,426
11
+ llumo-0.2.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
12
+ llumo-0.2.1.dist-info/top_level.txt,sha256=d5zUTMI99llPtLRB8rtSrqELm_bOqX-bNC5IcwlDk88,6
13
+ llumo-0.2.1.dist-info/RECORD,,
File without changes