levelapp 0.1.0__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.

Potentially problematic release.


This version of levelapp might be problematic. Click here for more details.

Files changed (46) hide show
  1. levelapp/__init__.py +0 -0
  2. levelapp/aspects/__init__.py +8 -0
  3. levelapp/aspects/loader.py +253 -0
  4. levelapp/aspects/logger.py +59 -0
  5. levelapp/aspects/monitor.py +614 -0
  6. levelapp/aspects/sanitizer.py +168 -0
  7. levelapp/clients/__init__.py +119 -0
  8. levelapp/clients/anthropic.py +112 -0
  9. levelapp/clients/ionos.py +116 -0
  10. levelapp/clients/mistral.py +106 -0
  11. levelapp/clients/openai.py +102 -0
  12. levelapp/comparator/__init__.py +5 -0
  13. levelapp/comparator/comparator.py +232 -0
  14. levelapp/comparator/extractor.py +108 -0
  15. levelapp/comparator/schemas.py +61 -0
  16. levelapp/comparator/scorer.py +271 -0
  17. levelapp/comparator/utils.py +136 -0
  18. levelapp/config/__init__.py +5 -0
  19. levelapp/config/endpoint.py +190 -0
  20. levelapp/config/prompts.py +35 -0
  21. levelapp/core/__init__.py +0 -0
  22. levelapp/core/base.py +386 -0
  23. levelapp/core/session.py +214 -0
  24. levelapp/evaluator/__init__.py +3 -0
  25. levelapp/evaluator/evaluator.py +265 -0
  26. levelapp/metrics/__init__.py +67 -0
  27. levelapp/metrics/embedding.py +2 -0
  28. levelapp/metrics/exact.py +182 -0
  29. levelapp/metrics/fuzzy.py +80 -0
  30. levelapp/metrics/token.py +103 -0
  31. levelapp/plugins/__init__.py +0 -0
  32. levelapp/repository/__init__.py +3 -0
  33. levelapp/repository/firestore.py +282 -0
  34. levelapp/simulator/__init__.py +3 -0
  35. levelapp/simulator/schemas.py +89 -0
  36. levelapp/simulator/simulator.py +441 -0
  37. levelapp/simulator/utils.py +201 -0
  38. levelapp/workflow/__init__.py +5 -0
  39. levelapp/workflow/base.py +113 -0
  40. levelapp/workflow/factory.py +51 -0
  41. levelapp/workflow/registration.py +6 -0
  42. levelapp/workflow/schemas.py +121 -0
  43. levelapp-0.1.0.dist-info/METADATA +254 -0
  44. levelapp-0.1.0.dist-info/RECORD +46 -0
  45. levelapp-0.1.0.dist-info/WHEEL +4 -0
  46. levelapp-0.1.0.dist-info/licenses/LICENSE +0 -0
File without changes
@@ -0,0 +1,3 @@
1
+ from .firestore import FirestoreRepository
2
+
3
+ __all__ = ['FirestoreRepository']
@@ -0,0 +1,282 @@
1
+ """levelapp/repository/firestore.py"""
2
+ import google.auth
3
+
4
+ from typing import List, Dict, Any, Type
5
+ from pydantic import ValidationError
6
+
7
+ from google.cloud import firestore_v1
8
+ from google.cloud.firestore_v1 import DocumentSnapshot
9
+ from google.api_core.exceptions import ClientError, ServerError, NotFound, InvalidArgument, DeadlineExceeded
10
+ from google.auth.exceptions import DefaultCredentialsError
11
+
12
+ from levelapp.core.base import BaseRepository, Model
13
+ from levelapp.aspects import logger
14
+
15
+
16
+ class FirestoreRepository(BaseRepository):
17
+ """
18
+ Firestore implementation of BaseRepository.
19
+ (Uses hierarchical path: {user_id}/{collection_id}/{document_id}
20
+ """
21
+
22
+ def __init__(self, project_id: str | Any = None, database_name: str | Any = '(default)'):
23
+ self.project_id = project_id
24
+ self.database_name = database_name
25
+ self.client: firestore_v1.Client | None = None
26
+
27
+ def connect(self) -> None:
28
+ """
29
+ Connects to Firestore, prioritizing the project ID passed to the constructor.
30
+ """
31
+ try:
32
+ credentials, default_project_id = google.auth.default()
33
+
34
+ if not credentials:
35
+ raise ValueError(
36
+ "Failed to obtain credentials. "
37
+ "Please set GOOGLE_APPLICATION_CREDENTIALS "
38
+ "or run 'gcloud auth application-default login'."
39
+ )
40
+
41
+ project_id = self.project_id if self.project_id else default_project_id
42
+
43
+ self.client = firestore_v1.Client(
44
+ project=project_id,
45
+ credentials=credentials,
46
+ database=self.database_name
47
+ )
48
+
49
+ if not self.client:
50
+ raise ValueError("Failed to initialize Firestore client")
51
+
52
+ logger.info(
53
+ f"Successfully connected to Firestore. "
54
+ f"Project: '{self.client.project}', "
55
+ f"Scope: '{self.client.SCOPE}'"
56
+ )
57
+
58
+ except (ClientError, ServerError, DefaultCredentialsError, ValueError) as e:
59
+ logger.error(f"Failed to initialize Firestore client:\n{e}")
60
+
61
+ def close(self) -> None:
62
+ if self.client:
63
+ self.client.close()
64
+
65
+ def retrieve_document(
66
+ self,
67
+ collection_id: str,
68
+ section_id: str,
69
+ sub_collection_id: str,
70
+ document_id: str,
71
+ model_type: Type[Model]
72
+ ) -> Model | None:
73
+ """
74
+ Retrieves a document from Firestore.
75
+
76
+ Args:
77
+ collection_id (str): User reference.
78
+ section_id (str): Section reference.
79
+ sub_collection_id (str): Collection reference.
80
+ document_id (str): Document reference.
81
+ model_type (Type[Model]): Pydantic model for parsing.
82
+
83
+ Returns:
84
+ An instance of the provide Pydantic model.
85
+ """
86
+ if not self.client:
87
+ logger.error("Client connection lost")
88
+ return None
89
+
90
+ try:
91
+ doc_ref = (
92
+ self.client
93
+ .collection(collection_id)
94
+ .document(section_id)
95
+ .collection(sub_collection_id)
96
+ .document(document_id)
97
+ )
98
+ snapshot: DocumentSnapshot = doc_ref.get()
99
+
100
+ if not snapshot.exists:
101
+ logger.warning(f"Document '{document_id}' does not exist in Firestore")
102
+ return None
103
+
104
+ data = snapshot.to_dict()
105
+ return model_type.model_validate(data)
106
+
107
+ except NotFound as e:
108
+ logger.warning(f"Failed to retrieve Firestore document <ID:{document_id}>:\n{e}")
109
+ return None
110
+
111
+ except InvalidArgument as e:
112
+ logger.error(f"Invalid argument in document path <{sub_collection_id}/{sub_collection_id}/{document_id}>:\n{e}")
113
+ return None
114
+
115
+ except DeadlineExceeded as e:
116
+ logger.error(f"Request to retrieved document <ID:{document_id}> timout:\n{e}")
117
+ return None
118
+
119
+ except ValidationError as e:
120
+ logger.exception(f"Failed to parse the retrieved document <ID:{document_id}>:\n{e}")
121
+ return None
122
+
123
+ except Exception as e:
124
+ logger.exception(f"Failed to retrieve Firestore document <ID:{document_id}>:\n{e}")
125
+ return None
126
+
127
+ def store_document(
128
+ self,
129
+ collection_id: str,
130
+ section_id: str,
131
+ sub_collection_id: str,
132
+ document_id: str,
133
+ data: Model
134
+ ) -> None:
135
+ """
136
+ Stores a document in Firestore.
137
+
138
+ Args:
139
+ collection_id (str): Collection reference.
140
+ section_id (str): Section reference.
141
+ sub_collection_id (str): Sub-collection reference.
142
+ document_id (str): Document reference.
143
+ data (Model): An instance of the Pydantic model containing the data.
144
+ """
145
+ if not self.client:
146
+ logger.error("Client connection lost")
147
+
148
+ try:
149
+ doc_ref = (
150
+ self.client
151
+ .collection(collection_id)
152
+ .document(section_id)
153
+ .collection(sub_collection_id)
154
+ .document(document_id)
155
+ )
156
+ data = data.model_dump()
157
+ doc_ref.set(data)
158
+
159
+ except NotFound as e:
160
+ logger.warning(f"Failed to store Firestore document <ID:{document_id}>:\n{e}")
161
+ return None
162
+
163
+ except InvalidArgument as e:
164
+ logger.error(f"Invalid argument in document path <{sub_collection_id}/{sub_collection_id}/{document_id}>:\n{e}")
165
+ return None
166
+
167
+ except DeadlineExceeded as e:
168
+ logger.error(f"Request to retrieved document <ID:{document_id}> timout:\n{e}")
169
+ return None
170
+
171
+ except ValidationError as e:
172
+ logger.exception(f"Failed to parse the retrieved document <ID:{document_id}>:\n{e}")
173
+ return None
174
+
175
+ except Exception as e:
176
+ logger.exception(f"Failed to retrieve Firestore document <ID:{document_id}>:\n{e}")
177
+ return None
178
+
179
+ def query_collection(
180
+ self,
181
+ collection_id: str,
182
+ section_id: str,
183
+ sub_collection_id: str,
184
+ filters: Dict[str, Any],
185
+ model_type: Type[Model]
186
+ ) -> List[Model]:
187
+ """
188
+ Queries a collection with specified filters.
189
+
190
+ Args:
191
+ collection_id (str): Collection reference.
192
+ section_id (str): Section reference.
193
+ sub_collection_id (str): Sub-collection reference.
194
+ filters (Dict[str, Any]): A dictionary of key-value pairs to filter the query.
195
+ model_type (Type [Model]): The class to deserialize the documents into.
196
+
197
+ Returns:
198
+ A list of deserialized models that match the query.
199
+ """
200
+ if not self.client:
201
+ logger.error("Client connection lost")
202
+ return []
203
+
204
+ try:
205
+ collection_ref = self.client.collection('users', collection_id, sub_collection_id)
206
+ query = collection_ref
207
+
208
+ for key, value in filters.items():
209
+ query = query.where(key, "==", value)
210
+
211
+ results = []
212
+ for doc in query.stream():
213
+ if doc.exists and doc.to_dict():
214
+ results.append(model_type.model_validate(doc.to_dict()))
215
+
216
+ return results
217
+
218
+ except NotFound as e:
219
+ logger.warning(f"Collection for user '{collection_id}' not found:\n{e}")
220
+ return []
221
+
222
+ except InvalidArgument as e:
223
+ logger.error(f"Invalid query argument for user '{collection_id}':\n{e}")
224
+ return []
225
+
226
+ except DeadlineExceeded as e:
227
+ logger.error(f"Query for user '{collection_id}' timed out:\n{e}")
228
+ return []
229
+
230
+ except ValidationError as e:
231
+ logger.exception(f"Failed to parse a document from query results:\n{e}")
232
+ return []
233
+
234
+ except Exception as e:
235
+ logger.exception(f"An unexpected error occurred during collection query:\n{e}")
236
+ return []
237
+
238
+ def delete_document(
239
+ self,
240
+ collection_id: str,
241
+ section_id: str,
242
+ sub_collection_id: str,
243
+ document_id: str
244
+ ) -> bool:
245
+ """
246
+ Deletes a document from Firestore.
247
+
248
+ Fields:
249
+ collection_id (str): Collection reference.
250
+ section_id (str): Section reference.
251
+ sub_collection_id (str): Sub-collection reference.
252
+ document_id (str): Document reference.
253
+
254
+ Returns:
255
+ True if the document was deleted successfully, False otherwise.
256
+ """
257
+ if not self.client:
258
+ logger.error("Client connection lost")
259
+ return False
260
+
261
+ try:
262
+ doc_ref = self.client.collection(
263
+ collection_id,
264
+ section_id,
265
+ sub_collection_id
266
+ ).document(document_id)
267
+ doc_ref.delete()
268
+ logger.info(f"Document '{document_id}' deleted successfully.")
269
+ return True
270
+
271
+ except NotFound as e:
272
+ logger.warning(f"Failed to delete document. Document '{document_id}' not found:\n{e}")
273
+ return False
274
+ except InvalidArgument as e:
275
+ logger.error(f"Invalid argument in document path <{collection_id}/{sub_collection_id}/{document_id}>:\n{e}")
276
+ return False
277
+ except DeadlineExceeded as e:
278
+ logger.error(f"Request to delete document <ID:{document_id}> timed out:\n{e}")
279
+ return False
280
+ except Exception as e:
281
+ logger.exception(f"Failed to delete Firestore document <ID:{document_id}>:\n{e}")
282
+ return False
@@ -0,0 +1,3 @@
1
+ from .simulator import ConversationSimulator
2
+
3
+ __all__ = ['ConversationSimulator']
@@ -0,0 +1,89 @@
1
+ """
2
+ levelapp/simulator/schemas.py
3
+
4
+ Defines Pydantic models for simulator-related data structures,
5
+ including test configurations, batch metadata, and evaluation results.
6
+ """
7
+ from enum import Enum
8
+ from uuid import UUID, uuid4
9
+ from datetime import datetime
10
+
11
+ from typing import Optional, Dict, Any, List
12
+ from pydantic import BaseModel, Field, computed_field
13
+
14
+ from levelapp.evaluator.evaluator import JudgeEvaluationResults
15
+
16
+
17
+ class InteractionLevel(str, Enum):
18
+ """Enum representing the type of interaction."""
19
+ INITIAL = "initial"
20
+ INTERMEDIATE = "intermediate"
21
+ FINAL = "final"
22
+
23
+
24
+ class Interaction(BaseModel):
25
+ """Represents a single interaction within a conversation."""
26
+ id: UUID = Field(default_factory=uuid4, description="Interaction identifier")
27
+ user_message: str = Field(..., description="The user's query message")
28
+ generated_reply: str = Field(..., description="The agent's reply message")
29
+ reference_reply: str = Field(..., description="The preset reference message")
30
+ interaction_type: InteractionLevel = Field(..., description="Type of interaction")
31
+ reference_metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Expected metadata")
32
+ generated_metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Extracted metadata")
33
+ guardrail_flag: bool = Field(default=False, description="Flag for guardrail signaling")
34
+ request_payload: Dict[str, Any] = Field(default_factory=dict, description="Additional request payload")
35
+
36
+
37
+ class ConversationScript(BaseModel):
38
+ """Represents a basic conversation with multiple interactions."""
39
+ id: UUID = Field(default_factory=uuid4, description="Conversation identifier")
40
+ interactions: List[Interaction] = Field(default_factory=list, description="List of interactions")
41
+ description: str = Field(..., description="A short description of the conversation")
42
+ details: Dict[str, str] = Field(default_factory=dict, description="Conversation details")
43
+
44
+
45
+ class ScriptsBatch(BaseModel):
46
+ id: UUID = Field(default_factory=uuid4, description="Batch identifier")
47
+ scripts: List[ConversationScript] = Field(default_factory=list, description="List of conversation scripts")
48
+
49
+
50
+ # ---- Interaction Details Models ----
51
+ class InteractionResults(BaseModel):
52
+ """Represents metadata extracted from a VLA interaction."""
53
+ generated_reply: str | None = "No response"
54
+ generated_metadata: Dict[str, Any] | None = {}
55
+ guardrail_flag: Any | None = False
56
+ interaction_type: str | None = ""
57
+
58
+
59
+ class InteractionEvaluationResults(BaseModel):
60
+ """Model representing the evaluation result of an interaction."""
61
+ judge_evaluations: Dict[str, JudgeEvaluationResults] = Field(default_factory=dict)
62
+ metadata_evaluation: Dict[str, float] = Field(default_factory=dict)
63
+ guardrail_flag: int = Field(default=0)
64
+
65
+
66
+ class SimulationResults(BaseModel):
67
+ # Initial data
68
+ project_id: str = Field(default_factory=uuid4, description="Project identifier")
69
+ user_id: str = Field(default_factory=uuid4, description="User identifier")
70
+ batch_id: str = Field(default_factory=uuid4, description="Batch identifier")
71
+ # Collected data
72
+ started_at: datetime = datetime.now()
73
+ finished_at: datetime
74
+ # Collected Results
75
+ evaluation_summary: Dict[str, Any] | None = Field(default_factory=dict, description="Evaluation result")
76
+ average_scores: Dict[str, Any] | None = Field(default_factory=dict, description="Average scores")
77
+
78
+ @computed_field
79
+ @property
80
+ def elapsed_time(self) -> float:
81
+ return (self.finished_at - self.started_at).total_seconds()
82
+
83
+
84
+ class TestResults(BaseModel):
85
+ api_host: str = Field(..., alias="apiHost")
86
+ ionos_model_name: str = Field(..., alias="ionosModelName")
87
+ test_name: str = Field(..., alias="testName")
88
+ test_type: str = Field(..., alias="testType")
89
+ batch_details: Optional[SimulationResults] = Field(..., alias="results")