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 +291 -147
- llumo/helpingFuntions.py +179 -10
- {llumo-0.2.0.dist-info → llumo-0.2.2.dist-info}/METADATA +1 -1
- {llumo-0.2.0.dist-info → llumo-0.2.2.dist-info}/RECORD +7 -7
- {llumo-0.2.0.dist-info → llumo-0.2.2.dist-info}/WHEEL +0 -0
- {llumo-0.2.0.dist-info → llumo-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {llumo-0.2.0.dist-info → llumo-0.2.2.dist-info}/top_level.txt +0 -0
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__),
|
24
|
-
load_dotenv(
|
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 =
|
27
|
-
fetchUrl =
|
28
|
-
validateUrl =
|
29
|
-
socketUrl =
|
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[
|
164
|
-
colId = record[
|
165
|
-
pid = f
|
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
|
-
|
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(
|
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(
|
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(
|
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 =
|
229
|
-
|
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 =
|
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(
|
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"][
|
294
|
-
|
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"][
|
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,
|
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
|
-
|
352
|
+
# for compound_key, value in item['data'].items():
|
338
353
|
|
339
|
-
rowID = compound_key.split(
|
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
|
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
|
-
|
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(
|
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(
|
386
|
-
|
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
|
-
|
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 =
|
420
|
-
|
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(
|
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"][
|
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"][
|
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
|
-
|
494
|
-
|
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(
|
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
|
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(
|
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(
|
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
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
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(
|
598
|
-
|
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
|
605
|
-
prompt_template=prompt_template
|
701
|
+
eval=eval,
|
702
|
+
prompt_template=prompt_template,
|
703
|
+
createExperiment=False,
|
606
704
|
)
|
607
|
-
|
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(
|
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(
|
613
|
-
|
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(
|
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(
|
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(
|
654
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
702
|
-
|
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(
|
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
|
-
|
886
|
+
# for compound_key, value in item['data'].items():
|
756
887
|
|
757
|
-
rowID = compound_key.split(
|
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
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
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
|
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(
|
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(
|
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(
|
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,13 +1,13 @@
|
|
1
1
|
llumo/__init__.py,sha256=O04b4yW1BnOvcHzxWFddAKhtdBEhBNhLdb6xgnpHH_Q,205
|
2
|
-
llumo/client.py,sha256=
|
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=
|
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.
|
10
|
-
llumo-0.2.
|
11
|
-
llumo-0.2.
|
12
|
-
llumo-0.2.
|
13
|
-
llumo-0.2.
|
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
|
File without changes
|
File without changes
|