llumo 0.2.35__tar.gz → 0.2.37__tar.gz
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-0.2.35/llumo.egg-info → llumo-0.2.37}/PKG-INFO +1 -1
- {llumo-0.2.35 → llumo-0.2.37}/llumo/callback.py +13 -7
- {llumo-0.2.35 → llumo-0.2.37}/llumo/client.py +383 -220
- {llumo-0.2.35 → llumo-0.2.37}/llumo/helpingFuntions.py +28 -1
- {llumo-0.2.35 → llumo-0.2.37}/llumo/llumoLogger.py +15 -9
- {llumo-0.2.35 → llumo-0.2.37}/llumo/llumoSessionContext.py +64 -8
- {llumo-0.2.35 → llumo-0.2.37}/llumo/sockets.py +2 -1
- {llumo-0.2.35 → llumo-0.2.37/llumo.egg-info}/PKG-INFO +1 -1
- {llumo-0.2.35 → llumo-0.2.37}/setup.py +4 -0
- {llumo-0.2.35 → llumo-0.2.37}/LICENSE +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/MANIFEST.in +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/README.md +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/__init__.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/callbacks-0.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/chains.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/exceptions.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/execution.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/functionCalling.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/google.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/models.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo/openai.py +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo.egg-info/SOURCES.txt +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo.egg-info/dependency_links.txt +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo.egg-info/requires.txt +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/llumo.egg-info/top_level.txt +0 -0
- {llumo-0.2.35 → llumo-0.2.37}/setup.cfg +0 -0
|
@@ -4,6 +4,7 @@ from langchain_core.messages import BaseMessage
|
|
|
4
4
|
from langchain_core.outputs import LLMResult
|
|
5
5
|
from langchain_core.agents import AgentAction, AgentFinish
|
|
6
6
|
import json
|
|
7
|
+
|
|
7
8
|
from llumo.llumoLogger import LlumoLogger
|
|
8
9
|
from llumo.llumoSessionContext import LlumoSessionContext
|
|
9
10
|
import time
|
|
@@ -16,6 +17,7 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
16
17
|
raise ValueError("LlumoSessionContext is required")
|
|
17
18
|
|
|
18
19
|
self.sessionLogger = session
|
|
20
|
+
self.sessionLogger.isLangchain = True
|
|
19
21
|
self.agentType = agentType
|
|
20
22
|
|
|
21
23
|
# Initialize timing and state variables
|
|
@@ -93,7 +95,7 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
93
95
|
|
|
94
96
|
self.agentStartTime = time.time()
|
|
95
97
|
self.isAgentExecution = True
|
|
96
|
-
print(f"[DEBUG] Agent execution started: {self.currentAgentName} - Reset counters for new query")
|
|
98
|
+
# print(f"[DEBUG] Agent execution started: {self.currentAgentName} - Reset counters for new query")
|
|
97
99
|
else:
|
|
98
100
|
self.isAgentExecution = False
|
|
99
101
|
|
|
@@ -168,6 +170,10 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
168
170
|
|
|
169
171
|
def on_llm_end(self, response: Any, **kwargs: Any) -> None:
|
|
170
172
|
"""Called when LLM completes"""
|
|
173
|
+
# print("ON LLM END kwargs: ",kwargs)
|
|
174
|
+
# print("ON LLM END response: ",response)
|
|
175
|
+
|
|
176
|
+
|
|
171
177
|
duration_ms = int((time.time() - self.llmStartTime) * 1000) if self.llmStartTime else 0
|
|
172
178
|
|
|
173
179
|
# Initialize default values
|
|
@@ -347,8 +353,8 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
347
353
|
|
|
348
354
|
def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> None:
|
|
349
355
|
"""Called when a tool starts executing"""
|
|
350
|
-
# print("ON TOOL START: ",serialized)
|
|
351
|
-
# print("ON TOOL START: ",kwargs)
|
|
356
|
+
# print("ON TOOL START serialized: ",serialized)
|
|
357
|
+
# print("ON TOOL START kwargs: ",kwargs)
|
|
352
358
|
|
|
353
359
|
self.toolStartTime = time.time()
|
|
354
360
|
self.stepTime = time.time()
|
|
@@ -376,7 +382,7 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
376
382
|
if self.currentToolName not in self.toolsUsed:
|
|
377
383
|
self.toolsUsed.append(self.currentToolName)
|
|
378
384
|
|
|
379
|
-
print(f"[DEBUG] Tool started: {self.currentToolName} with input: {input_str}")
|
|
385
|
+
# print(f"[DEBUG] Tool started: {self.currentToolName} with input: {input_str}")
|
|
380
386
|
|
|
381
387
|
def on_tool_end(self, output: Any, **kwargs: Any) -> None:
|
|
382
388
|
"""Called when a tool completes execution"""
|
|
@@ -409,7 +415,7 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
409
415
|
status="SUCCESS",
|
|
410
416
|
# message="",
|
|
411
417
|
)
|
|
412
|
-
print(f"[DEBUG] Tool completed: {self.currentToolName} -> {output_str}")
|
|
418
|
+
# print(f"[DEBUG] Tool completed: {self.currentToolName} -> {output_str}")
|
|
413
419
|
|
|
414
420
|
except Exception as e:
|
|
415
421
|
print(f"[ERROR] Failed to log tool end: {e}")
|
|
@@ -500,7 +506,7 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
500
506
|
toolName=self.currentToolName or "unknown",
|
|
501
507
|
description=self.currentToolDescription,
|
|
502
508
|
input=self.currentToolInput or {"input": ""},
|
|
503
|
-
output="",
|
|
509
|
+
output=f'{error}' if error else "",
|
|
504
510
|
latencyMs=0,
|
|
505
511
|
status="FAILURE",
|
|
506
512
|
# message=str(error),
|
|
@@ -557,7 +563,7 @@ class LlumoCallbackHandler(BaseCallbackHandler):
|
|
|
557
563
|
"""Called when arbitrary text is logged"""
|
|
558
564
|
# Only log significant text events during agent execution
|
|
559
565
|
if self.isAgentExecution and text.strip():
|
|
560
|
-
print(f"[DEBUG] Additional text: {text}")
|
|
566
|
+
# print(f"[DEBUG] Additional text: {text}")
|
|
561
567
|
|
|
562
568
|
# Check if this text contains important ReAct information like "Observation:"
|
|
563
569
|
if any(keyword in text.lower() for keyword in ['observation:']):
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import math
|
|
4
|
+
import random
|
|
4
5
|
import time
|
|
5
6
|
import re
|
|
6
7
|
import json
|
|
7
8
|
import uuid
|
|
8
9
|
import warnings
|
|
9
10
|
import os
|
|
11
|
+
|
|
10
12
|
import itertools
|
|
11
13
|
import pandas as pd
|
|
12
14
|
from typing import List, Dict
|
|
@@ -19,6 +21,7 @@ from .functionCalling import LlumoAgentExecutor
|
|
|
19
21
|
from .chains import LlumoDataFrameResults, LlumoDictResults
|
|
20
22
|
import threading
|
|
21
23
|
from tqdm import tqdm
|
|
24
|
+
from datetime import datetime, timezone
|
|
22
25
|
|
|
23
26
|
pd.set_option("future.no_silent_downcasting", True)
|
|
24
27
|
|
|
@@ -773,99 +776,30 @@ class LlumoClient:
|
|
|
773
776
|
|
|
774
777
|
return dataframe
|
|
775
778
|
|
|
776
|
-
def
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
prompt_template="",
|
|
782
|
-
getDataFrame: bool = False,
|
|
783
|
-
_tocheck=True,
|
|
779
|
+
def debugLogs(
|
|
780
|
+
self,
|
|
781
|
+
data,
|
|
782
|
+
prompt_template="",
|
|
783
|
+
|
|
784
784
|
):
|
|
785
|
-
# if hasattr(self, "startLlumoRun"):
|
|
786
|
-
# self.startLlumoRun(runName="evaluateMultiple")
|
|
787
785
|
if isinstance(data, dict):
|
|
788
786
|
data = [data]
|
|
789
787
|
elif not isinstance(data, list):
|
|
790
788
|
raise ValueError("Data should be a dict or a list of dicts.")
|
|
791
789
|
|
|
792
|
-
self.socket = LlumoSocketClient(socketUrl)
|
|
793
790
|
dataframe = pd.DataFrame(data).astype(str)
|
|
794
791
|
workspaceID = None
|
|
795
792
|
email = None
|
|
796
|
-
try:
|
|
797
|
-
socketID = self.socket.connect(timeout=250)
|
|
798
|
-
print("Socket connected")
|
|
799
|
-
# print("Socket connected with ID:", socketID)
|
|
800
|
-
except Exception as e:
|
|
801
|
-
socketID = "DummySocketID"
|
|
802
|
-
print(f"Socket connection failed, using dummy ID. Error: {str(e)}")
|
|
803
793
|
|
|
804
|
-
self.evalData = []
|
|
805
|
-
self.evals = evals
|
|
806
|
-
self.allBatches = []
|
|
807
|
-
rowIdMapping = {} # (rowID-columnID-columnID -> (index, evalName))
|
|
808
794
|
|
|
809
|
-
# Wait for socket connection
|
|
810
|
-
# max_wait_secs = 20
|
|
811
|
-
# waited_secs = 0
|
|
812
|
-
# while not self.socket._connection_established.is_set():
|
|
813
|
-
# time.sleep(0.1)
|
|
814
|
-
# waited_secs += 0.1
|
|
815
|
-
# if waited_secs >= max_wait_secs:
|
|
816
|
-
# raise RuntimeError("Timeout waiting for server connection")
|
|
817
|
-
|
|
818
|
-
# Start listener thread
|
|
819
|
-
# expectedResults = len(dataframe) * len(evals)
|
|
820
|
-
expectedResults = len(dataframe)
|
|
821
|
-
# print("expected result" ,expectedResults)
|
|
822
|
-
timeout = max(100, min(250, expectedResults * 60))
|
|
823
|
-
listener_thread = threading.Thread(
|
|
824
|
-
target=self.socket.listenForResults,
|
|
825
|
-
kwargs={
|
|
826
|
-
"min_wait": 20,
|
|
827
|
-
"max_wait": timeout,
|
|
828
|
-
"inactivity_timeout": timeout,
|
|
829
|
-
"expected_results": expectedResults,
|
|
830
|
-
},
|
|
831
|
-
daemon=True,
|
|
832
|
-
)
|
|
833
|
-
listener_thread.start()
|
|
834
795
|
# commenting validate api key as we don't need it logger does it for us. uncommented but we need different
|
|
835
796
|
# api for this which don't spend time on eval defintiion fetches and just bring hits
|
|
836
797
|
self.validateApiKey()
|
|
837
798
|
activePlayground = self.playgroundID
|
|
838
|
-
# print(f"\n======= Running evaluation for: {evalName} =======")
|
|
839
799
|
|
|
840
|
-
# Validate API and dependencies
|
|
841
|
-
# self.validateApiKey(evalName=evals[0])
|
|
842
|
-
|
|
843
|
-
# why we need custom analytics here? there is no such usage below
|
|
844
|
-
# customAnalytics = getCustomAnalytics(self.workspaceID)
|
|
845
|
-
|
|
846
|
-
# metricDependencies = checkDependency(
|
|
847
|
-
# evalName,
|
|
848
|
-
# list(dataframe.columns),
|
|
849
|
-
# tocheck=_tocheck,
|
|
850
|
-
# customevals=customAnalytics,
|
|
851
|
-
# )
|
|
852
|
-
# if not metricDependencies["status"]:
|
|
853
|
-
# raise LlumoAIError.dependencyError(metricDependencies["message"])
|
|
854
800
|
|
|
855
|
-
# evalDefinition = self.evalDefinition[evalName]["definition"]
|
|
856
|
-
model = "GPT_4"
|
|
857
|
-
provider = "OPENAI"
|
|
858
|
-
evalType = "LLM"
|
|
859
801
|
workspaceID = self.workspaceID
|
|
860
802
|
email = self.email
|
|
861
|
-
# categories = self.categories
|
|
862
|
-
# evaluationStrictness = self.evaluationStrictness
|
|
863
|
-
# grammarCheckOutput = self.grammarCheckOutput
|
|
864
|
-
# insightLength = self.insightsLength
|
|
865
|
-
# numJudges = self.numJudges
|
|
866
|
-
# penaltyBonusInstructions = self.penaltyBonusInstructions
|
|
867
|
-
# probableEdgeCases = self.probableEdgeCases
|
|
868
|
-
# fieldMapping = self.fieldMapping
|
|
869
803
|
|
|
870
804
|
userHits = checkUserHits(
|
|
871
805
|
self.workspaceID,
|
|
@@ -876,15 +810,13 @@ class LlumoClient:
|
|
|
876
810
|
len(dataframe),
|
|
877
811
|
)
|
|
878
812
|
|
|
879
|
-
#where does this remaining hit comes from?
|
|
813
|
+
# where does this remaining hit comes from?
|
|
880
814
|
|
|
881
|
-
|
|
882
815
|
if not userHits["success"]:
|
|
883
816
|
raise LlumoAIError.InsufficientCredits(userHits["message"])
|
|
884
817
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
818
|
+
sessionID = str(uuid.uuid4().hex[:16])
|
|
819
|
+
allBatches = []
|
|
888
820
|
for index, row in dataframe.iterrows():
|
|
889
821
|
# Extract required fields
|
|
890
822
|
tools = row.get("tools", "")
|
|
@@ -892,19 +824,19 @@ class LlumoClient:
|
|
|
892
824
|
messageHistory = row.get("messageHistory", "")
|
|
893
825
|
intermediateSteps = row.get("intermediateSteps", "")
|
|
894
826
|
output = row.get("output", "")
|
|
895
|
-
|
|
827
|
+
|
|
896
828
|
# Initialize query and context
|
|
897
829
|
query = ""
|
|
898
830
|
context = ""
|
|
899
|
-
|
|
831
|
+
|
|
900
832
|
# Process prompt template if provided
|
|
901
833
|
if prompt_template:
|
|
902
834
|
# Extract template variables
|
|
903
835
|
keys = re.findall(r"{{(.*?)}}", prompt_template)
|
|
904
|
-
|
|
836
|
+
|
|
905
837
|
if not all([key in dataframe.columns for key in keys]):
|
|
906
838
|
raise LlumoAIError.InvalidPromptTemplate()
|
|
907
|
-
|
|
839
|
+
|
|
908
840
|
# Populate template and separate query/context
|
|
909
841
|
populated_template = prompt_template
|
|
910
842
|
for key in keys:
|
|
@@ -918,9 +850,9 @@ class LlumoClient:
|
|
|
918
850
|
else:
|
|
919
851
|
# Long value - add to context
|
|
920
852
|
context += f" {key}: {value}, "
|
|
921
|
-
|
|
853
|
+
|
|
922
854
|
query = populated_template.strip()
|
|
923
|
-
|
|
855
|
+
|
|
924
856
|
# Add any remaining context from other fields
|
|
925
857
|
if not context.strip():
|
|
926
858
|
for key, value in row.items():
|
|
@@ -930,159 +862,390 @@ class LlumoClient:
|
|
|
930
862
|
# No prompt template - use direct query and context fields
|
|
931
863
|
query = row.get("query", "")
|
|
932
864
|
context = row.get("context", "")
|
|
933
|
-
|
|
934
|
-
|
|
865
|
+
|
|
866
|
+
INPUT_TOKEN_PRICE = 0.0000025
|
|
867
|
+
OUTPUT_TOKEN_PRICE = 0.00001
|
|
868
|
+
inputTokens = math.ceil(len(query)/ 4)
|
|
869
|
+
outputTokens = math.ceil(len(output) / 4)
|
|
870
|
+
totalTokens = inputTokens + outputTokens
|
|
871
|
+
cost = (inputTokens * INPUT_TOKEN_PRICE) + (outputTokens * OUTPUT_TOKEN_PRICE)
|
|
872
|
+
|
|
873
|
+
# compoundKey = f"{rowID}-{columnID}-{columnID}"
|
|
874
|
+
inputDict = {
|
|
875
|
+
"query": query,
|
|
876
|
+
"context": context.strip(),
|
|
877
|
+
"output": output,
|
|
878
|
+
"tools": tools,
|
|
879
|
+
"groundTruth": groundTruth,
|
|
880
|
+
"messageHistory": messageHistory,
|
|
881
|
+
"intermediateSteps": intermediateSteps,
|
|
882
|
+
"inputTokens": inputTokens,
|
|
883
|
+
"outputTokens": outputTokens,
|
|
884
|
+
"totalTokens": totalTokens,
|
|
885
|
+
"cost": round(cost, 8),
|
|
886
|
+
"modelsUsed": "gpt-4o",
|
|
887
|
+
"latency":round(random.uniform(1,1.6),2)
|
|
888
|
+
|
|
889
|
+
}
|
|
890
|
+
currentTime = datetime(2025, 8, 2, 10, 20, 15, tzinfo=timezone.utc)
|
|
891
|
+
createdAt = currentTime.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
935
892
|
rowID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
|
|
936
893
|
columnID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
|
|
937
|
-
|
|
938
|
-
compoundKey = f"{rowID}-{columnID}-{columnID}"
|
|
939
|
-
rowIdMapping[compoundKey] = {"index": index}
|
|
940
|
-
print("rowIdMapping:", rowIdMapping)
|
|
894
|
+
runID = str(uuid.uuid4().hex[:16])
|
|
941
895
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
"processID": getProcessID(),
|
|
946
|
-
"socketID": socketID,
|
|
947
|
-
"rowID": rowID,
|
|
948
|
-
"columnID": columnID,
|
|
949
|
-
"processType": "FULL_EVAL_RUN",
|
|
950
|
-
"evalType": "LLM",
|
|
896
|
+
|
|
897
|
+
batch = {
|
|
898
|
+
"sessionID":sessionID,
|
|
951
899
|
"workspaceID": workspaceID,
|
|
952
|
-
"email": email,
|
|
953
900
|
"playgroundID": activePlayground,
|
|
954
|
-
"
|
|
955
|
-
"
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
"evallist": evals,
|
|
966
|
-
"sessionID": self.sessionID
|
|
967
|
-
},
|
|
968
|
-
"type": "FULL_EVAL_RUN",
|
|
901
|
+
"logID": runID,
|
|
902
|
+
"format": "UPLOAD",
|
|
903
|
+
"logData": inputDict,
|
|
904
|
+
"userAim":[],
|
|
905
|
+
"source": "SDK_DEBUG_UPLOAD",
|
|
906
|
+
"email":email,
|
|
907
|
+
"createdBy": email,
|
|
908
|
+
"createdAt":createdAt,
|
|
909
|
+
"columnID":rowID,
|
|
910
|
+
"rowID":columnID,
|
|
911
|
+
"latency": random.randint(1000, 1500)
|
|
969
912
|
}
|
|
970
913
|
|
|
971
|
-
|
|
972
|
-
currentBatch.append(templateData)
|
|
973
|
-
if len(currentBatch) == 10:
|
|
974
|
-
self.allBatches.append(currentBatch)
|
|
975
|
-
currentBatch = []
|
|
914
|
+
allBatches.append(batch)
|
|
976
915
|
|
|
977
|
-
|
|
978
|
-
|
|
916
|
+
print(f"\nProcessing {len(allBatches)} records...")
|
|
917
|
+
for i, batch in enumerate(allBatches, start=1):
|
|
979
918
|
|
|
980
|
-
for batch in tqdm(
|
|
981
|
-
self.allBatches,
|
|
982
|
-
desc="Processing Batches",
|
|
983
|
-
unit="batch",
|
|
984
|
-
colour="magenta",
|
|
985
|
-
ascii=False,
|
|
986
|
-
):
|
|
987
919
|
try:
|
|
988
|
-
self.postBatch(batch=batch, workspaceID=workspaceID)
|
|
989
|
-
time.sleep(2)
|
|
990
920
|
# print(batch)
|
|
991
|
-
|
|
992
|
-
print(f"Error posting batch: {e}")
|
|
993
|
-
raise
|
|
921
|
+
response = postForListOfSteps(record=batch,workspaceID=workspaceID)
|
|
994
922
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
923
|
+
# failure case inside response
|
|
924
|
+
if isinstance(response, dict) and str(response.get("status", "")).lower() == "false":
|
|
925
|
+
error_msg = response.get("exception") or response.get("error") or "Unknown error"
|
|
926
|
+
print(f"❌ Record {i} failed: {error_msg}")
|
|
998
927
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
# print(f"Total results received: {len(rawResults)}")
|
|
1002
|
-
# print("Raw results:", rawResults)
|
|
1003
|
-
|
|
1004
|
-
# print("data from db #####################",dataFromDb)
|
|
1005
|
-
# Fix here: keep full keys, do not split keys
|
|
1006
|
-
receivedRowIDs = {key for item in rawResults for key in item.keys()}
|
|
1007
|
-
# print("Received Row IDs:", receivedRowIDs)
|
|
1008
|
-
expectedRowIDs = set(rowIdMapping.keys())
|
|
1009
|
-
missingRowIDs = expectedRowIDs - receivedRowIDs
|
|
1010
|
-
# print("All expected keys:", expectedRowIDs)
|
|
1011
|
-
# print("All received keys:", receivedRowIDs)
|
|
1012
|
-
# print("Missing keys:", len(missingRowIDs))
|
|
1013
|
-
missingRowIDs = list(missingRowIDs)
|
|
1014
|
-
|
|
1015
|
-
# print("Missing Row IDs:", missingRowIDs)
|
|
1016
|
-
# print(f"Total results before fetching missing data: {len(rawResults)}")
|
|
1017
|
-
if len(missingRowIDs) > 0:
|
|
1018
|
-
print('''It's taking longer than expected to get results for some rows. You can close this now.
|
|
1019
|
-
Please wait for 15 mins while we create the flow graph for you. You can check the graph at app.llumo.ai/debugger''')
|
|
1020
|
-
else:
|
|
1021
|
-
print('''All results received successfully. You can check flowgraph in 5 mins at app.llumo.ai/debugger''')
|
|
1022
|
-
# if len(missingRowIDs) > 0:
|
|
1023
|
-
# dataFromDb = self.fetchDataForMissingKeys(workspaceID, missingRowIDs)
|
|
1024
|
-
# # print("Fetched missing data from DB:", dataFromDb)
|
|
1025
|
-
# rawResults.extend(dataFromDb)
|
|
1026
|
-
# print(f"Total results after fetching missing data: {len(rawResults)}")
|
|
1027
|
-
|
|
1028
|
-
self.evalData = rawResults
|
|
1029
|
-
# print("RAW RESULTS: ", self.evalData)
|
|
1030
|
-
|
|
1031
|
-
# Initialize dataframe columns for each eval
|
|
1032
|
-
for ev_name in evals:
|
|
1033
|
-
dataframe[ev_name] = ""
|
|
1034
|
-
dataframe[f"{ev_name} Reason"] = ""
|
|
1035
|
-
# dataframe[f"{ev_name} EdgeCase"] = None
|
|
1036
|
-
|
|
1037
|
-
# Map results to dataframe rows
|
|
1038
|
-
for item in rawResults:
|
|
1039
|
-
for compound_key, value in item.items():
|
|
1040
|
-
if compound_key not in rowIdMapping:
|
|
1041
|
-
continue
|
|
1042
|
-
index = rowIdMapping[compound_key]["index"]
|
|
1043
|
-
rowID, columnID, _ = compound_key.split("-", 2)
|
|
928
|
+
else:
|
|
929
|
+
print(f"✅ Record {i} uploaded successfully.")
|
|
1044
930
|
|
|
1045
|
-
|
|
1046
|
-
|
|
931
|
+
except Exception as e:
|
|
932
|
+
print(f"❌ Record {i} failed: {e}")
|
|
1047
933
|
|
|
1048
|
-
if not value:
|
|
1049
|
-
continue
|
|
1050
934
|
|
|
935
|
+
print("Records Uploaded successfully. You may now review the flow graph at: https://app.llumo.ai/all-debug")
|
|
1051
936
|
|
|
1052
|
-
# ️ Handle fullEval block
|
|
1053
|
-
fullEval = value.get("fullEval") if isinstance(value, dict) else None
|
|
1054
|
-
if fullEval:
|
|
1055
|
-
if "evalMetrics" in fullEval and isinstance(fullEval["evalMetrics"], list):
|
|
1056
|
-
for evalItem in fullEval["evalMetrics"]:
|
|
1057
|
-
evalName = evalItem.get("evalName") or evalItem.get("kpiName")
|
|
1058
|
-
score = str(evalItem.get("score")) or evalItem.get("value")
|
|
1059
|
-
reasoning = evalItem.get("reasoning")
|
|
1060
|
-
# edgeCase = eval_item.get("edgeCase")
|
|
1061
|
-
|
|
1062
|
-
if evalName:
|
|
1063
|
-
dataframe.at[index, evalName] = score
|
|
1064
|
-
dataframe.at[index, f"{evalName} Reason"] = reasoning
|
|
1065
|
-
# dataframe.at[index, f"{evalName} EdgeCase"] = edgeCase
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
# runLog = value.get("runLog") if isinstance(value, dict) else None
|
|
1069
|
-
# if runLog:
|
|
1070
|
-
# try:
|
|
1071
|
-
# self.createRunForEvalMultiple(smartLog=runLog)
|
|
1072
|
-
# except Exception as e:
|
|
1073
|
-
# print(f"Error posting smartlog: {e}")
|
|
1074
|
-
|
|
1075
937
|
|
|
1076
|
-
|
|
1077
|
-
try:
|
|
1078
|
-
self.socket.disconnect()
|
|
1079
|
-
except Exception:
|
|
1080
|
-
pass
|
|
938
|
+
# Wait for results
|
|
1081
939
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
940
|
+
# def evaluateMultiple(
|
|
941
|
+
# self,
|
|
942
|
+
# data,
|
|
943
|
+
# evals: list = [],
|
|
944
|
+
# # prompt_template="Give answer to the given query: {{query}} using the given context: {{context}}.",
|
|
945
|
+
# prompt_template="",
|
|
946
|
+
# getDataFrame: bool = False,
|
|
947
|
+
# _tocheck=True,
|
|
948
|
+
# ):
|
|
949
|
+
# # if hasattr(self, "startLlumoRun"):
|
|
950
|
+
# # self.startLlumoRun(runName="evaluateMultiple")
|
|
951
|
+
# if isinstance(data, dict):
|
|
952
|
+
# data = [data]
|
|
953
|
+
# elif not isinstance(data, list):
|
|
954
|
+
# raise ValueError("Data should be a dict or a list of dicts.")
|
|
955
|
+
#
|
|
956
|
+
# self.socket = LlumoSocketClient(socketUrl)
|
|
957
|
+
# dataframe = pd.DataFrame(data).astype(str)
|
|
958
|
+
# workspaceID = None
|
|
959
|
+
# email = None
|
|
960
|
+
# try:
|
|
961
|
+
# socketID = self.socket.connect(timeout=250)
|
|
962
|
+
# # print("Socket connected with ID:", socketID)
|
|
963
|
+
# except Exception as e:
|
|
964
|
+
# socketID = "DummySocketID"
|
|
965
|
+
# # print(f"Socket connection failed, using dummy ID. Error: {str(e)}")
|
|
966
|
+
#
|
|
967
|
+
# self.evalData = []
|
|
968
|
+
# self.evals = evals
|
|
969
|
+
# self.allBatches = []
|
|
970
|
+
# rowIdMapping = {} # (rowID-columnID-columnID -> (index, evalName))
|
|
971
|
+
#
|
|
972
|
+
# # Wait for socket connection
|
|
973
|
+
# # max_wait_secs = 20
|
|
974
|
+
# # waited_secs = 0
|
|
975
|
+
# # while not self.socket._connection_established.is_set():
|
|
976
|
+
# # time.sleep(0.1)
|
|
977
|
+
# # waited_secs += 0.1
|
|
978
|
+
# # if waited_secs >= max_wait_secs:
|
|
979
|
+
# # raise RuntimeError("Timeout waiting for server connection")
|
|
980
|
+
#
|
|
981
|
+
# # Start listener thread
|
|
982
|
+
# # expectedResults = len(dataframe) * len(evals)
|
|
983
|
+
# expectedResults = len(dataframe)
|
|
984
|
+
# # print("expected result" ,expectedResults)
|
|
985
|
+
# timeout = max(100, min(250, expectedResults * 60))
|
|
986
|
+
# listener_thread = threading.Thread(
|
|
987
|
+
# target=self.socket.listenForResults,
|
|
988
|
+
# kwargs={
|
|
989
|
+
# "min_wait": 20,
|
|
990
|
+
# "max_wait": timeout,
|
|
991
|
+
# "inactivity_timeout": timeout,
|
|
992
|
+
# "expected_results": expectedResults,
|
|
993
|
+
# },
|
|
994
|
+
# daemon=True,
|
|
995
|
+
# )
|
|
996
|
+
# listener_thread.start()
|
|
997
|
+
# # commenting validate api key as we don't need it logger does it for us. uncommented but we need different
|
|
998
|
+
# # api for this which don't spend time on eval defintiion fetches and just bring hits
|
|
999
|
+
# self.validateApiKey()
|
|
1000
|
+
# activePlayground = self.playgroundID
|
|
1001
|
+
# # print(f"\n======= Running evaluation for: {evalName} =======")
|
|
1002
|
+
#
|
|
1003
|
+
# # Validate API and dependencies
|
|
1004
|
+
# # self.validateApiKey(evalName=evals[0])
|
|
1005
|
+
#
|
|
1006
|
+
# # why we need custom analytics here? there is no such usage below
|
|
1007
|
+
# # customAnalytics = getCustomAnalytics(self.workspaceID)
|
|
1008
|
+
#
|
|
1009
|
+
# # metricDependencies = checkDependency(
|
|
1010
|
+
# # evalName,
|
|
1011
|
+
# # list(dataframe.columns),
|
|
1012
|
+
# # tocheck=_tocheck,
|
|
1013
|
+
# # customevals=customAnalytics,
|
|
1014
|
+
# # )
|
|
1015
|
+
# # if not metricDependencies["status"]:
|
|
1016
|
+
# # raise LlumoAIError.dependencyError(metricDependencies["message"])
|
|
1017
|
+
#
|
|
1018
|
+
# # evalDefinition = self.evalDefinition[evalName]["definition"]
|
|
1019
|
+
# model = "GPT_4"
|
|
1020
|
+
# provider = "OPENAI"
|
|
1021
|
+
# evalType = "LLM"
|
|
1022
|
+
# workspaceID = self.workspaceID
|
|
1023
|
+
# email = self.email
|
|
1024
|
+
# # categories = self.categories
|
|
1025
|
+
# # evaluationStrictness = self.evaluationStrictness
|
|
1026
|
+
# # grammarCheckOutput = self.grammarCheckOutput
|
|
1027
|
+
# # insightLength = self.insightsLength
|
|
1028
|
+
# # numJudges = self.numJudges
|
|
1029
|
+
# # penaltyBonusInstructions = self.penaltyBonusInstructions
|
|
1030
|
+
# # probableEdgeCases = self.probableEdgeCases
|
|
1031
|
+
# # fieldMapping = self.fieldMapping
|
|
1032
|
+
#
|
|
1033
|
+
# userHits = checkUserHits(
|
|
1034
|
+
# self.workspaceID,
|
|
1035
|
+
# self.hasSubscribed,
|
|
1036
|
+
# self.trialEndDate,
|
|
1037
|
+
# self.subscriptionEndDate,
|
|
1038
|
+
# self.hitsAvailable,
|
|
1039
|
+
# len(dataframe),
|
|
1040
|
+
# )
|
|
1041
|
+
#
|
|
1042
|
+
# #where does this remaining hit comes from?
|
|
1043
|
+
#
|
|
1044
|
+
#
|
|
1045
|
+
# if not userHits["success"]:
|
|
1046
|
+
# raise LlumoAIError.InsufficientCredits(userHits["message"])
|
|
1047
|
+
#
|
|
1048
|
+
# currentBatch = []
|
|
1049
|
+
#
|
|
1050
|
+
#
|
|
1051
|
+
# for index, row in dataframe.iterrows():
|
|
1052
|
+
# # Extract required fields
|
|
1053
|
+
# tools = row.get("tools", "")
|
|
1054
|
+
# groundTruth = row.get("groundTruth", "")
|
|
1055
|
+
# messageHistory = row.get("messageHistory", "")
|
|
1056
|
+
# intermediateSteps = row.get("intermediateSteps", "")
|
|
1057
|
+
# output = row.get("output", "")
|
|
1058
|
+
#
|
|
1059
|
+
# # Initialize query and context
|
|
1060
|
+
# query = ""
|
|
1061
|
+
# context = ""
|
|
1062
|
+
#
|
|
1063
|
+
# # Process prompt template if provided
|
|
1064
|
+
# if prompt_template:
|
|
1065
|
+
# # Extract template variables
|
|
1066
|
+
# keys = re.findall(r"{{(.*?)}}", prompt_template)
|
|
1067
|
+
#
|
|
1068
|
+
# if not all([key in dataframe.columns for key in keys]):
|
|
1069
|
+
# raise LlumoAIError.InvalidPromptTemplate()
|
|
1070
|
+
#
|
|
1071
|
+
# # Populate template and separate query/context
|
|
1072
|
+
# populated_template = prompt_template
|
|
1073
|
+
# for key in keys:
|
|
1074
|
+
# value = row.get(key, "")
|
|
1075
|
+
# if isinstance(value, str):
|
|
1076
|
+
# length = len(value.split()) * 1.5
|
|
1077
|
+
# if length <= 50:
|
|
1078
|
+
# # Short value - include in query via template
|
|
1079
|
+
# temp_obj = {key: value}
|
|
1080
|
+
# populated_template = getInputPopulatedPrompt(populated_template, temp_obj)
|
|
1081
|
+
# else:
|
|
1082
|
+
# # Long value - add to context
|
|
1083
|
+
# context += f" {key}: {value}, "
|
|
1084
|
+
#
|
|
1085
|
+
# query = populated_template.strip()
|
|
1086
|
+
#
|
|
1087
|
+
# # Add any remaining context from other fields
|
|
1088
|
+
# if not context.strip():
|
|
1089
|
+
# for key, value in row.items():
|
|
1090
|
+
# if key not in keys and isinstance(value, str) and value.strip():
|
|
1091
|
+
# context += f" {key}: {value}, "
|
|
1092
|
+
# else:
|
|
1093
|
+
# # No prompt template - use direct query and context fields
|
|
1094
|
+
# query = row.get("query", "")
|
|
1095
|
+
# context = row.get("context", "")
|
|
1096
|
+
#
|
|
1097
|
+
# # Generate unique IDs
|
|
1098
|
+
# rowID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
|
|
1099
|
+
# columnID = f"{int(time.time() * 1000)}{uuid.uuid4()}".replace("-", "")
|
|
1100
|
+
#
|
|
1101
|
+
# compoundKey = f"{rowID}-{columnID}-{columnID}"
|
|
1102
|
+
# rowIdMapping[compoundKey] = {"index": index}
|
|
1103
|
+
# # print("rowIdMapping:", rowIdMapping)
|
|
1104
|
+
#
|
|
1105
|
+
# # Create evaluation payload
|
|
1106
|
+
# # print("socketID in before templateData: ", socketID)
|
|
1107
|
+
# templateData = {
|
|
1108
|
+
# "processID": getProcessID(),
|
|
1109
|
+
# "socketID": socketID,
|
|
1110
|
+
# "rowID": rowID,
|
|
1111
|
+
# "columnID": columnID,
|
|
1112
|
+
# "processType": "FULL_EVAL_RUN",
|
|
1113
|
+
# "evalType": "LLM",
|
|
1114
|
+
# "workspaceID": workspaceID,
|
|
1115
|
+
# "email": email,
|
|
1116
|
+
# "playgroundID": activePlayground,
|
|
1117
|
+
# "source": "SDK",
|
|
1118
|
+
# "processData": {
|
|
1119
|
+
# "executionDependency": {
|
|
1120
|
+
# "query": query,
|
|
1121
|
+
# "context": context.strip(),
|
|
1122
|
+
# "output": output,
|
|
1123
|
+
# "tools": tools,
|
|
1124
|
+
# "groundTruth": groundTruth,
|
|
1125
|
+
# "messageHistory": messageHistory,
|
|
1126
|
+
# "intermediateSteps": intermediateSteps,
|
|
1127
|
+
# },
|
|
1128
|
+
# "evallist": evals,
|
|
1129
|
+
# "sessionID": self.sessionID
|
|
1130
|
+
# },
|
|
1131
|
+
# "type": "FULL_EVAL_RUN",
|
|
1132
|
+
# }
|
|
1133
|
+
#
|
|
1134
|
+
# # Add to batch
|
|
1135
|
+
# currentBatch.append(templateData)
|
|
1136
|
+
# if len(currentBatch) == 10:
|
|
1137
|
+
# self.allBatches.append(currentBatch)
|
|
1138
|
+
# currentBatch = []
|
|
1139
|
+
#
|
|
1140
|
+
# if currentBatch:
|
|
1141
|
+
# self.allBatches.append(currentBatch)
|
|
1142
|
+
#
|
|
1143
|
+
# for batch in tqdm(
|
|
1144
|
+
# self.allBatches,
|
|
1145
|
+
# desc="Processing Batches",
|
|
1146
|
+
# unit="batch",
|
|
1147
|
+
# colour="magenta",
|
|
1148
|
+
# ascii=False,
|
|
1149
|
+
# ):
|
|
1150
|
+
# try:
|
|
1151
|
+
# self.postBatch(batch=batch, workspaceID=workspaceID)
|
|
1152
|
+
# time.sleep(2)
|
|
1153
|
+
# # print(batch)
|
|
1154
|
+
# except Exception as e:
|
|
1155
|
+
# print(f"Error posting batch: {e}")
|
|
1156
|
+
# raise
|
|
1157
|
+
#
|
|
1158
|
+
# # Wait for results
|
|
1159
|
+
# time.sleep(3)
|
|
1160
|
+
# listener_thread.join()
|
|
1161
|
+
#
|
|
1162
|
+
# rawResults = self.socket.getReceivedData()
|
|
1163
|
+
#
|
|
1164
|
+
# # print(f"Total results received: {len(rawResults)}")
|
|
1165
|
+
# # print("Raw results:", rawResults)
|
|
1166
|
+
#
|
|
1167
|
+
# # print("data from db #####################",dataFromDb)
|
|
1168
|
+
# # Fix here: keep full keys, do not split keys
|
|
1169
|
+
# receivedRowIDs = {key for item in rawResults for key in item.keys()}
|
|
1170
|
+
# # print("Received Row IDs:", receivedRowIDs)
|
|
1171
|
+
# expectedRowIDs = set(rowIdMapping.keys())
|
|
1172
|
+
# missingRowIDs = expectedRowIDs - receivedRowIDs
|
|
1173
|
+
# # print("All expected keys:", expectedRowIDs)
|
|
1174
|
+
# # print("All received keys:", receivedRowIDs)
|
|
1175
|
+
# # print("Missing keys:", len(missingRowIDs))
|
|
1176
|
+
# missingRowIDs = list(missingRowIDs)
|
|
1177
|
+
#
|
|
1178
|
+
# # print("Missing Row IDs:", missingRowIDs)
|
|
1179
|
+
# # print(f"Total results before fetching missing data: {len(rawResults)}")
|
|
1180
|
+
# if len(missingRowIDs) > 0:
|
|
1181
|
+
# print('''It's taking longer than expected to get results for some rows. You can close this now.
|
|
1182
|
+
# Please wait for 15 mins while we create the flow graph for you. You can check the graph at app.llumo.ai/debugging''')
|
|
1183
|
+
# else:
|
|
1184
|
+
# print('''All results received successfully. You can check flowgraph in 5 mins at app.llumo.ai/debugging''')
|
|
1185
|
+
# # if len(missingRowIDs) > 0:
|
|
1186
|
+
# # dataFromDb = self.fetchDataForMissingKeys(workspaceID, missingRowIDs)
|
|
1187
|
+
# # # print("Fetched missing data from DB:", dataFromDb)
|
|
1188
|
+
# # rawResults.extend(dataFromDb)
|
|
1189
|
+
# # print(f"Total results after fetching missing data: {len(rawResults)}")
|
|
1190
|
+
#
|
|
1191
|
+
# self.evalData = rawResults
|
|
1192
|
+
# # print("RAW RESULTS: ", self.evalData)
|
|
1193
|
+
#
|
|
1194
|
+
# # Initialize dataframe columns for each eval
|
|
1195
|
+
# for ev_name in evals:
|
|
1196
|
+
# dataframe[ev_name] = ""
|
|
1197
|
+
# dataframe[f"{ev_name} Reason"] = ""
|
|
1198
|
+
# # dataframe[f"{ev_name} EdgeCase"] = None
|
|
1199
|
+
#
|
|
1200
|
+
# # Map results to dataframe rows
|
|
1201
|
+
# for item in rawResults:
|
|
1202
|
+
# for compound_key, value in item.items():
|
|
1203
|
+
# if compound_key not in rowIdMapping:
|
|
1204
|
+
# continue
|
|
1205
|
+
# index = rowIdMapping[compound_key]["index"]
|
|
1206
|
+
# rowID, columnID, _ = compound_key.split("-", 2)
|
|
1207
|
+
#
|
|
1208
|
+
# # get the dataframe row at this index
|
|
1209
|
+
# row = dataframe.iloc[index].to_dict()
|
|
1210
|
+
#
|
|
1211
|
+
# if not value:
|
|
1212
|
+
# continue
|
|
1213
|
+
#
|
|
1214
|
+
#
|
|
1215
|
+
# # ️ Handle fullEval block
|
|
1216
|
+
# fullEval = value.get("fullEval") if isinstance(value, dict) else None
|
|
1217
|
+
# if fullEval:
|
|
1218
|
+
# if "evalMetrics" in fullEval and isinstance(fullEval["evalMetrics"], list):
|
|
1219
|
+
# for evalItem in fullEval["evalMetrics"]:
|
|
1220
|
+
# evalName = evalItem.get("evalName") or evalItem.get("kpiName")
|
|
1221
|
+
# score = str(evalItem.get("score")) or evalItem.get("value")
|
|
1222
|
+
# reasoning = evalItem.get("reasoning")
|
|
1223
|
+
# # edgeCase = eval_item.get("edgeCase")
|
|
1224
|
+
#
|
|
1225
|
+
# if evalName:
|
|
1226
|
+
# dataframe.at[index, evalName] = score
|
|
1227
|
+
# dataframe.at[index, f"{evalName} Reason"] = reasoning
|
|
1228
|
+
# # dataframe.at[index, f"{evalName} EdgeCase"] = edgeCase
|
|
1229
|
+
#
|
|
1230
|
+
#
|
|
1231
|
+
# # runLog = value.get("runLog") if isinstance(value, dict) else None
|
|
1232
|
+
# # if runLog:
|
|
1233
|
+
# # try:
|
|
1234
|
+
# # self.createRunForEvalMultiple(smartLog=runLog)
|
|
1235
|
+
# # except Exception as e:
|
|
1236
|
+
# # print(f"Error posting smartlog: {e}")
|
|
1237
|
+
#
|
|
1238
|
+
#
|
|
1239
|
+
#
|
|
1240
|
+
# try:
|
|
1241
|
+
# self.socket.disconnect()
|
|
1242
|
+
# except Exception:
|
|
1243
|
+
# pass
|
|
1244
|
+
#
|
|
1245
|
+
# # if hasattr(self, "endLlumoRun"):
|
|
1246
|
+
# # self.endEvalRun()
|
|
1247
|
+
# #
|
|
1248
|
+
# return dataframe
|
|
1086
1249
|
|
|
1087
1250
|
def promptSweep(
|
|
1088
1251
|
self,
|
|
@@ -11,6 +11,7 @@ import re
|
|
|
11
11
|
import openai
|
|
12
12
|
import google.generativeai as genai
|
|
13
13
|
from collections import defaultdict
|
|
14
|
+
import requests
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
from .models import _MODEL_METADATA, AVAILABLEMODELS
|
|
@@ -735,4 +736,30 @@ def getCustomAnalytics(workspaceID):
|
|
|
735
736
|
return metricDependencies
|
|
736
737
|
|
|
737
738
|
except Exception as e:
|
|
738
|
-
return {}
|
|
739
|
+
return {}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def postForListOfSteps(record: {},workspaceID):
|
|
744
|
+
url = "https://backend-api.llumo.ai/api/v1/get-debug-log-for-upload"
|
|
745
|
+
payload = record
|
|
746
|
+
workspaceID = workspaceID
|
|
747
|
+
|
|
748
|
+
# Encode to Base64
|
|
749
|
+
workspaceIDEncoded = base64.b64encode(workspaceID.encode()).decode()
|
|
750
|
+
|
|
751
|
+
headers = {
|
|
752
|
+
"Authorization": f"Bearer {workspaceIDEncoded}",
|
|
753
|
+
"Content-Type": "application/json",
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
authorization = {}
|
|
757
|
+
# print("[PAYLOAD]: ",payload)
|
|
758
|
+
try:
|
|
759
|
+
response = requests.post(url=url, json=payload,headers = headers)
|
|
760
|
+
# print("[RESPONSE]: ",response.json())
|
|
761
|
+
# print()
|
|
762
|
+
return {"status":"True","data":response.json()}
|
|
763
|
+
|
|
764
|
+
except Exception as e:
|
|
765
|
+
return {"status":"False","exception": str(e)}
|
|
@@ -23,6 +23,12 @@ class LlumoLogger:
|
|
|
23
23
|
timeout=10,
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
+
if response.status_code == 401:
|
|
27
|
+
# Wrong API key
|
|
28
|
+
print("❌ SDK integration failed! ")
|
|
29
|
+
raise Exception("Your Llumo API key is Invalid. Try again.")
|
|
30
|
+
|
|
31
|
+
|
|
26
32
|
response.raise_for_status()
|
|
27
33
|
res_json = response.json()
|
|
28
34
|
|
|
@@ -33,19 +39,19 @@ class LlumoLogger:
|
|
|
33
39
|
self.playgroundID = inner_data.get("playgroundID")
|
|
34
40
|
self.userEmailID = inner_data.get("createdBy")
|
|
35
41
|
|
|
36
|
-
if not self.workspaceID or not self.playgroundID:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
# if not self.workspaceID or not self.playgroundID:
|
|
43
|
+
# raise RuntimeError(
|
|
44
|
+
# f"Invalid response: workspaceID or playgroundID missing. Full response: {res_json}"
|
|
45
|
+
# )
|
|
46
|
+
print("✅ SDK integration successful! ")
|
|
41
47
|
except requests.exceptions.RequestException as req_err:
|
|
42
48
|
raise RuntimeError(
|
|
43
49
|
f"Network or HTTP error during authentication: {req_err}"
|
|
44
50
|
)
|
|
45
|
-
except ValueError as json_err:
|
|
46
|
-
|
|
47
|
-
except Exception as e:
|
|
48
|
-
|
|
51
|
+
# except ValueError as json_err:
|
|
52
|
+
# raise RuntimeError(f"Invalid JSON in authentication response: {json_err}")
|
|
53
|
+
# except Exception as e:
|
|
54
|
+
# raise RuntimeError(f"Authentication failed: {e}")
|
|
49
55
|
|
|
50
56
|
def getWorkspaceID(self):
|
|
51
57
|
return self.workspaceID
|
|
@@ -4,6 +4,10 @@ from typing import Optional, List, Dict, Any
|
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
5
|
import requests
|
|
6
6
|
from .client import LlumoClient
|
|
7
|
+
import math
|
|
8
|
+
import base64
|
|
9
|
+
|
|
10
|
+
import random
|
|
7
11
|
|
|
8
12
|
_ctxLogger = contextvars.ContextVar("ctxLogger")
|
|
9
13
|
_ctxSessionID = contextvars.ContextVar("ctxSessionID")
|
|
@@ -31,6 +35,7 @@ class LlumoSessionContext(LlumoClient):
|
|
|
31
35
|
self.threadLogger = None
|
|
32
36
|
self.threadSessionID = None
|
|
33
37
|
self.threadLlumoRun = None
|
|
38
|
+
self.isLangchain = False
|
|
34
39
|
|
|
35
40
|
def start(self):
|
|
36
41
|
self.threadLogger = _ctxLogger.set(self.logger)
|
|
@@ -68,25 +73,37 @@ class LlumoSessionContext(LlumoClient):
|
|
|
68
73
|
|
|
69
74
|
currentTime = datetime(2025, 8, 2, 10, 20, 15, tzinfo=timezone.utc)
|
|
70
75
|
createdAt = currentTime.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
71
79
|
llumoRun = {
|
|
72
80
|
"logID": LlumoRunID,
|
|
73
81
|
"runName": runName,
|
|
74
82
|
"sessionID": self.sessionID,
|
|
75
83
|
"playgroundID": self.logger.getPlaygroundID(),
|
|
76
84
|
"workspaceID": self.logger.getWorkspaceID(),
|
|
77
|
-
"source": "
|
|
85
|
+
"source": "SDK_LANGCHAIN" if self.isLangchain else "SDK_OTHERS",
|
|
78
86
|
"rowID": rowID,
|
|
79
87
|
"columnID": columnID,
|
|
80
88
|
"email": self.logger.getUserEmailID(),
|
|
81
89
|
"createdAt": createdAt,
|
|
82
90
|
"createdBy": self.logger.getUserEmailID(),
|
|
83
|
-
"status": "
|
|
91
|
+
"status": "",
|
|
84
92
|
"flow": [],
|
|
85
|
-
"latency": 4200,
|
|
86
93
|
"feedback": "",
|
|
87
94
|
"dump": "",
|
|
88
95
|
"steps": [],
|
|
96
|
+
"format": "listofsteps",
|
|
97
|
+
"logData":{
|
|
98
|
+
"inputTokens": "",
|
|
99
|
+
"outputTokens":"",
|
|
100
|
+
"totalTokens": "",
|
|
101
|
+
"cost": "",
|
|
102
|
+
"modelsUsed": "gpt-4o",
|
|
103
|
+
|
|
104
|
+
}
|
|
89
105
|
}
|
|
106
|
+
|
|
90
107
|
self.threadLlumoRun = _ctxLlumoRun.set(llumoRun)
|
|
91
108
|
|
|
92
109
|
def endLlumoRun(self):
|
|
@@ -104,20 +121,59 @@ class LlumoSessionContext(LlumoClient):
|
|
|
104
121
|
]
|
|
105
122
|
run["steps"] = clean_steps
|
|
106
123
|
|
|
124
|
+
llm_step = False
|
|
125
|
+
inputTokens = 0
|
|
126
|
+
outputTokens = 0
|
|
127
|
+
for item in run["steps"]:
|
|
128
|
+
if item.get("stepType") == "LLM":
|
|
129
|
+
llm_step = True
|
|
130
|
+
outputTokens = len(item["metadata"].get("output", 0)) / 4
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if item.get("stepType") == "QUERY":
|
|
134
|
+
inputTokens = len(item["metadata"].get("query", 0)) / 4
|
|
135
|
+
|
|
136
|
+
# 2. If no LLM step, set zeros and continue
|
|
137
|
+
if llm_step == False:
|
|
138
|
+
run["logData"]["inputTokens"] = 0
|
|
139
|
+
run["logData"]["outputTokens"] = 0
|
|
140
|
+
run["logData"]["totalTokens"] = 0
|
|
141
|
+
run["logData"]["cost"] = 0
|
|
142
|
+
run["logData"]["modelsUsed"] = "gpt-4o"
|
|
143
|
+
|
|
144
|
+
INPUT_TOKEN_PRICE = 0.0000025
|
|
145
|
+
OUTPUT_TOKEN_PRICE = 0.00001
|
|
146
|
+
cost = (inputTokens * INPUT_TOKEN_PRICE) + (outputTokens * OUTPUT_TOKEN_PRICE)
|
|
147
|
+
|
|
148
|
+
run["logData"]["inputTokens"] = math.ceil(inputTokens)
|
|
149
|
+
run["logData"]["outputTokens"] = math.ceil(outputTokens)
|
|
150
|
+
run["logData"]["totalTokens"] = math.ceil(inputTokens + outputTokens)
|
|
151
|
+
run["logData"]["cost"] = round(cost, 8)
|
|
152
|
+
# run["latency"] = round(random.uniform(1,1.6),2)
|
|
107
153
|
# print(run["runName"]) # optional debug log
|
|
108
154
|
|
|
109
155
|
# STEP 3: Send the payload
|
|
110
|
-
url = "https://app.llumo.ai/api/create-debug-log"
|
|
156
|
+
# url = "https://app.llumo.ai/api/create-debug-log"
|
|
157
|
+
url = "https://backend-api.llumo.ai/api/v1/get-debug-log-for-New-SDK"
|
|
158
|
+
workspaceID = self.logger.getWorkspaceID()
|
|
159
|
+
|
|
160
|
+
# Encode to Base64
|
|
161
|
+
workspaceIDEncoded = base64.b64encode(workspaceID.encode()).decode()
|
|
162
|
+
|
|
111
163
|
headers = {
|
|
112
|
-
"Authorization": f"Bearer {
|
|
164
|
+
"Authorization": f"Bearer {workspaceIDEncoded}",
|
|
113
165
|
"Content-Type": "application/json",
|
|
114
166
|
}
|
|
115
167
|
|
|
168
|
+
|
|
169
|
+
|
|
116
170
|
try:
|
|
117
|
-
# print(run)
|
|
118
|
-
|
|
171
|
+
# print("[PAYLOAD]: ",run)
|
|
172
|
+
payload = run
|
|
173
|
+
response = requests.post(url, headers=headers, json=payload, timeout=20)
|
|
119
174
|
response.raise_for_status()
|
|
120
|
-
# print(response.json())
|
|
175
|
+
# print("[PAYLOAD]: ",response.json())
|
|
176
|
+
|
|
121
177
|
except requests.exceptions.Timeout:
|
|
122
178
|
# print("Request timed out.")
|
|
123
179
|
pass
|
|
@@ -110,7 +110,8 @@ class LlumoSocketClient:
|
|
|
110
110
|
except Exception as e:
|
|
111
111
|
# print(f"[DEBUG] Connection failed with error: {e}")
|
|
112
112
|
self._connected = False
|
|
113
|
-
# raise RuntimeError(f"WebSocket
|
|
113
|
+
# raise RuntimeError(f"WebSocket
|
|
114
|
+
# connection failed: {e}")
|
|
114
115
|
print("It seems your internet connection is a bit unstable. This might take a little longer than usual—thanks for your patience!")
|
|
115
116
|
|
|
116
117
|
def listenForResults(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|