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