levelapp 0.1.3__tar.gz → 0.1.4__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.
Potentially problematic release.
This version of levelapp might be problematic. Click here for more details.
- {levelapp-0.1.3 → levelapp-0.1.4}/PKG-INFO +1 -1
- levelapp-0.1.4/levelapp/simulator/utils.py +257 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/pyproject.toml +1 -1
- {levelapp-0.1.3 → levelapp-0.1.4}/src/data/workflow_config.yaml +2 -2
- levelapp-0.1.4/src/level_app/main_session.py +73 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/uv.lock +9 -124
- levelapp-0.1.3/levelapp/simulator/utils.py +0 -163
- levelapp-0.1.3/src/level_app/main_session.py +0 -48
- {levelapp-0.1.3 → levelapp-0.1.4}/.gitignore +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/.python-version +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/LICENSE +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/MANIFEST.in +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/Makefile +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/README.md +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/docs/media/simulator-module-diagram.PNG +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/docs/media/simulator-sequence-diagram.png +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/examples/README.md +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/examples/conversation_script.json +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/examples/example_chatbot.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/examples/example_evaluation.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/examples/workflow_configuration.yaml +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/aspects/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/aspects/loader.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/aspects/logger.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/aspects/monitor.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/aspects/sanitizer.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/clients/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/clients/anthropic.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/clients/ionos.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/clients/mistral.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/clients/openai.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/comparator/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/comparator/comparator.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/comparator/extractor.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/comparator/schemas.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/comparator/scorer.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/comparator/utils.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/config/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/config/endpoint.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/config/endpoint_.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/config/prompts.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/core/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/core/base.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/core/schemas.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/core/session.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/evaluator/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/evaluator/evaluator.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/metrics/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/metrics/embedding.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/metrics/exact.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/metrics/fuzzy.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/metrics/token.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/plugins/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/repository/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/repository/firestore.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/simulator/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/simulator/schemas.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/simulator/simulator.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/workflow/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/workflow/base.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/workflow/config.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/workflow/context.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/workflow/factory.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/workflow/registration.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/levelapp/workflow/runtime.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/make.bat +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/project_structure.txt +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/data/conversation_example_1.json +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/data/endpoint_configuration.yaml +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/data/evaluation_results.json +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/data/payload_example_1.yaml +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/data/payload_example_2.yaml +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/data/workflow_config_2.json +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/level_app/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/level_app/main.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/level_app/main_monitoring.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/src/level_app/main_simulator.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/__init__.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_anthropic.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_comparator.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_ionos.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_mistral.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_monitoring.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_openai.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_session.py +0 -0
- {levelapp-0.1.3 → levelapp-0.1.4}/tests/test_simulator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: levelapp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: LevelApp is an evaluation framework for AI/LLM-based software application. [Powered by Norma]
|
|
5
5
|
Project-URL: Homepage, https://github.com/levelapp-org
|
|
6
6
|
Project-URL: Repository, https://github.com/levelapp-org/levelapp-framework
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
'simulators/aspects.py': Utility functions for handling VLA interactions and requests.
|
|
3
|
+
"""
|
|
4
|
+
import re
|
|
5
|
+
import ast
|
|
6
|
+
import json
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
from string import Template
|
|
11
|
+
from typing import Any, Dict, List, Union, Iterable
|
|
12
|
+
|
|
13
|
+
from pydantic import ValidationError
|
|
14
|
+
|
|
15
|
+
from levelapp.clients import ClientRegistry
|
|
16
|
+
from levelapp.config.prompts import SUMMARIZATION_PROMPT_TEMPLATE
|
|
17
|
+
from levelapp.simulator.schemas import InteractionResults
|
|
18
|
+
from levelapp.aspects import MonitoringAspect, MetricType, logger
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UUIDEncoder(json.JSONEncoder):
|
|
22
|
+
def default(self, obj):
|
|
23
|
+
if isinstance(obj, UUID):
|
|
24
|
+
return str(obj)
|
|
25
|
+
return json.JSONEncoder.default(self, obj)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_PLACEHOLDER_RE = re.compile(r"\$\{([^}]+)\}") # captures inner name(s) of ${...}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _traverse_path(d: Dict[str, Any], path: str):
|
|
32
|
+
"""Traverse a dot-separated path (payload.metadata.budget) and return value or None."""
|
|
33
|
+
parts = path.split(".")
|
|
34
|
+
cur = d
|
|
35
|
+
try:
|
|
36
|
+
for p in parts:
|
|
37
|
+
if isinstance(cur, dict) and p in cur:
|
|
38
|
+
cur = cur[p]
|
|
39
|
+
else:
|
|
40
|
+
return None
|
|
41
|
+
return cur
|
|
42
|
+
except Exception:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _recursive_find(container: Any, target_key: str):
|
|
47
|
+
"""
|
|
48
|
+
Recursively search container (dicts/lists) for the first occurrence of target_key.
|
|
49
|
+
Returns the value if found, else None.
|
|
50
|
+
"""
|
|
51
|
+
if isinstance(container, dict):
|
|
52
|
+
# direct hit
|
|
53
|
+
if target_key in container:
|
|
54
|
+
return container[target_key]
|
|
55
|
+
# recurse into values
|
|
56
|
+
for v in container.values():
|
|
57
|
+
found = _recursive_find(v, target_key)
|
|
58
|
+
if found is not None:
|
|
59
|
+
return found
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
if isinstance(container, list):
|
|
63
|
+
for item in container:
|
|
64
|
+
found = _recursive_find(item, target_key)
|
|
65
|
+
if found is not None:
|
|
66
|
+
return found
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
# not a container
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _extract_placeholders(template_str: str) -> Iterable[str]:
|
|
74
|
+
"""Return list of placeholder names in a template string (inner contents of ${...})."""
|
|
75
|
+
return [m.group(1) for m in _PLACEHOLDER_RE.finditer(template_str)]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def extract_interaction_details(
|
|
79
|
+
response: str | Dict[str, Any],
|
|
80
|
+
template: Dict[str, Any],
|
|
81
|
+
) -> InteractionResults:
|
|
82
|
+
"""
|
|
83
|
+
Parse response (str or dict), look up placeholders recursively in the response and
|
|
84
|
+
use Template.safe_substitute with a mapping built from those lookups.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
response_dict = response if isinstance(response, dict) else json.loads(response)
|
|
88
|
+
print(f"response:\n{response_dict}\n--")
|
|
89
|
+
if not isinstance(response_dict, dict):
|
|
90
|
+
raise ValueError("Response is not a valid dictionary")
|
|
91
|
+
|
|
92
|
+
output: Dict[str, Any] = {}
|
|
93
|
+
|
|
94
|
+
for out_key, tpl_str in template.items():
|
|
95
|
+
# Build mapping for placeholders found in tpl_str
|
|
96
|
+
placeholders = _extract_placeholders(tpl_str)
|
|
97
|
+
mapping: Dict[str, str] = {}
|
|
98
|
+
|
|
99
|
+
for ph in placeholders:
|
|
100
|
+
value = None
|
|
101
|
+
|
|
102
|
+
# 1) If ph looks like a dotted path, try explicit path traversal first
|
|
103
|
+
if "." in ph:
|
|
104
|
+
value = _traverse_path(response_dict, ph)
|
|
105
|
+
|
|
106
|
+
# 2) If not found yet, try recursive search for the bare key (last path segment)
|
|
107
|
+
if value is None:
|
|
108
|
+
bare = ph.split(".")[-1]
|
|
109
|
+
value = _recursive_find(response_dict, bare)
|
|
110
|
+
|
|
111
|
+
# Prepare mapping value for Template substitution:
|
|
112
|
+
# - dict/list -> JSON string (so substitution yields valid JSON text)
|
|
113
|
+
# - None -> empty string
|
|
114
|
+
# - otherwise -> str(value)
|
|
115
|
+
if isinstance(value, (dict, list)):
|
|
116
|
+
try:
|
|
117
|
+
mapping[ph] = json.dumps(value, ensure_ascii=False)
|
|
118
|
+
except Exception:
|
|
119
|
+
mapping[ph] = str(value)
|
|
120
|
+
elif value is None:
|
|
121
|
+
mapping[ph] = ""
|
|
122
|
+
else:
|
|
123
|
+
mapping[ph] = str(value)
|
|
124
|
+
|
|
125
|
+
# Perform substitution using Template (safe_substitute: missing keys left intact)
|
|
126
|
+
substituted = Template(tpl_str).safe_substitute(mapping)
|
|
127
|
+
output[out_key] = substituted
|
|
128
|
+
|
|
129
|
+
# Post-process generated_metadata if present: convert JSON text back to dict/list when possible
|
|
130
|
+
raw_meta = output.get("generated_metadata", {})
|
|
131
|
+
if isinstance(raw_meta, str) and raw_meta:
|
|
132
|
+
# Try json first (since we used json.dumps above for mapping)
|
|
133
|
+
try:
|
|
134
|
+
output["generated_metadata"] = json.loads(raw_meta)
|
|
135
|
+
except Exception:
|
|
136
|
+
# fallback to ast.literal_eval (handles Python dict strings)
|
|
137
|
+
try:
|
|
138
|
+
output["generated_metadata"] = ast.literal_eval(raw_meta)
|
|
139
|
+
except Exception:
|
|
140
|
+
# if parsing fails, keep the original raw string or use an empty dict
|
|
141
|
+
output["generated_metadata"] = raw_meta
|
|
142
|
+
|
|
143
|
+
# If generated_metadata is empty string, normalize to {}
|
|
144
|
+
if output.get("generated_metadata") == "":
|
|
145
|
+
output["generated_metadata"] = {}
|
|
146
|
+
|
|
147
|
+
print(f"output:\n{output}\n---")
|
|
148
|
+
# Return validated model
|
|
149
|
+
return InteractionResults.model_validate(output)
|
|
150
|
+
|
|
151
|
+
except json.JSONDecodeError as e:
|
|
152
|
+
logger.error(f"[extract_interaction_details] Failed to parse JSON response: {e}")
|
|
153
|
+
return InteractionResults()
|
|
154
|
+
|
|
155
|
+
except ValidationError as e:
|
|
156
|
+
logger.exception(f"[extract_interaction_details] InteractionResults validation failed: {e}")
|
|
157
|
+
return InteractionResults()
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.exception(f"[extract_interaction_details] Unexpected error: {e}")
|
|
161
|
+
return InteractionResults()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@MonitoringAspect.monitor(name="interaction_request", category=MetricType.API_CALL)
|
|
165
|
+
async def async_interaction_request(
|
|
166
|
+
url: str,
|
|
167
|
+
headers: Dict[str, str],
|
|
168
|
+
payload: Dict[str, Any],
|
|
169
|
+
) -> httpx.Response | None:
|
|
170
|
+
"""
|
|
171
|
+
Perform an asynchronous interaction request.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
url (str): The URL to send the request to.
|
|
175
|
+
headers (Dict[str, str]): The headers to include in the request.
|
|
176
|
+
payload (Dict[str, Any]): The payload to send in the request.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
httpx.Response: The response from the interaction request, or None if an error occurred.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
async with httpx.AsyncClient(timeout=180) as client:
|
|
183
|
+
response = await client.post(url=url, headers=headers, json=payload)
|
|
184
|
+
response.raise_for_status()
|
|
185
|
+
|
|
186
|
+
return response
|
|
187
|
+
|
|
188
|
+
except httpx.HTTPStatusError as http_err:
|
|
189
|
+
logger.error(f"[async_interaction_request] HTTP error: {http_err.response.text}", exc_info=True)
|
|
190
|
+
|
|
191
|
+
except httpx.RequestError as req_err:
|
|
192
|
+
logger.error(f"[async_interaction_request] Request error: {str(req_err)}", exc_info=True)
|
|
193
|
+
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@MonitoringAspect.monitor(
|
|
198
|
+
name="average_calc",
|
|
199
|
+
category=MetricType.SCORING,
|
|
200
|
+
cached=True,
|
|
201
|
+
maxsize=1000
|
|
202
|
+
)
|
|
203
|
+
def calculate_average_scores(scores: Dict[str, Union[List[float], float]]) -> Dict[str, float]:
|
|
204
|
+
"""
|
|
205
|
+
Helper function that calculates the average scores for a dictionary of score lists.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
scores (Dict[str, List[float]]): A dictionary where keys are identifiers and values are lists of scores.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Dict[str, float]: A dictionary with average scores rounded to three decimal places.
|
|
212
|
+
"""
|
|
213
|
+
result: Dict[str, float] = {}
|
|
214
|
+
for field, value in scores.items():
|
|
215
|
+
if isinstance(value, (int, float)):
|
|
216
|
+
result[field] = value
|
|
217
|
+
elif isinstance(value, list):
|
|
218
|
+
result[field] = round((sum(value) / len(value)), 3) if value else 0.0
|
|
219
|
+
else:
|
|
220
|
+
raise TypeError(f"[calculate_average_scores] Unexpected type '{type(value)}' for field '{field}")
|
|
221
|
+
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@MonitoringAspect.monitor(name="summarization", category=MetricType.API_CALL)
|
|
226
|
+
def summarize_verdicts(
|
|
227
|
+
verdicts: List[str],
|
|
228
|
+
judge: str,
|
|
229
|
+
max_bullets: int = 5
|
|
230
|
+
) -> List[str]:
|
|
231
|
+
client_registry = ClientRegistry()
|
|
232
|
+
client = client_registry.get(provider=judge)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
verdicts = chr(10).join(verdicts)
|
|
236
|
+
prompt = SUMMARIZATION_PROMPT_TEMPLATE.format(max_bullets=max_bullets, judge=judge, verdicts=verdicts)
|
|
237
|
+
response = client.call(message=prompt)
|
|
238
|
+
parsed = client.parse_response(response=response)
|
|
239
|
+
striped = parsed.get("output", "").strip("")
|
|
240
|
+
bullet_points = [point.strip() for point in striped.split("- ") if point.strip()]
|
|
241
|
+
|
|
242
|
+
return bullet_points[:max_bullets]
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
logger.error(f"[summarize_justifications] Error during summarization: {str(e)}", exc_info=True)
|
|
246
|
+
return []
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# if __name__ == '__main__':
|
|
250
|
+
# template = {'generated_reply': '${agent_reply}', 'generated_metadata': '${generated_metadata}'}
|
|
251
|
+
# response_dict = {
|
|
252
|
+
# 'agent_reply': "I'd be happy to help you book something for 10 AM.",
|
|
253
|
+
# 'generated_metadata': {'appointment_type': 'Cardiology', 'date': 'next Monday', 'time': '10 AM'}
|
|
254
|
+
# }
|
|
255
|
+
#
|
|
256
|
+
# result = extract_interaction_details(response_dict, template)
|
|
257
|
+
# print(f"result: {result.model_dump()}")
|
|
@@ -32,8 +32,8 @@ endpoint:
|
|
|
32
32
|
details: "${request_payload}" # Rest of the request payload data.
|
|
33
33
|
default_response_payload_template:
|
|
34
34
|
# Change the placeholder value only according to the response payload schema (example: ${agent_reply} to ${reply}).
|
|
35
|
-
generated_reply: "${
|
|
36
|
-
generated_metadata: "${
|
|
35
|
+
generated_reply: "${message}"
|
|
36
|
+
generated_metadata: "${metadata}"
|
|
37
37
|
|
|
38
38
|
repository:
|
|
39
39
|
type: FIRESTORE # Pick one of the following: FIRESTORE, FILESYSTEM
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
if __name__ == "__main__":
|
|
2
|
+
from levelapp.workflow import WorkflowConfig
|
|
3
|
+
from levelapp.core.session import EvaluationSession
|
|
4
|
+
|
|
5
|
+
# Firestore -> retrieve endpoint config -> data => config_dict
|
|
6
|
+
|
|
7
|
+
config_dict_ = {
|
|
8
|
+
"process": {"project_name": "test-project", "workflow_type": "SIMULATOR", "evaluation_params": {"attempts": 2}},
|
|
9
|
+
"evaluation": {"evaluators": ["JUDGE"], "providers": ["openai", "ionos"]},
|
|
10
|
+
"reference_data": {"path": "", "data": {}},
|
|
11
|
+
"endpoint": {
|
|
12
|
+
"base_url": "https://dashq-gateway-485vb8zi.uc.gateway.dev/api/conversations/events",
|
|
13
|
+
"api_key": "AIzaSyAmL8blcS2hpPrEH2b84B8ugsVoV7AXrfc",
|
|
14
|
+
"model_id": "meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
15
|
+
"default_request_payload_template": {
|
|
16
|
+
"eventType": "newConversation",
|
|
17
|
+
"conversationId": "435484ef-403b-43c5-9908-884486149d0b",
|
|
18
|
+
"payload": {
|
|
19
|
+
"messageType": "newInquiry",
|
|
20
|
+
"communityId": 3310,
|
|
21
|
+
"accountId": 1440,
|
|
22
|
+
"prospectFirstName": "BAD DOE X",
|
|
23
|
+
"prospectLastName": "Doe",
|
|
24
|
+
"message": "${user_message}",
|
|
25
|
+
"datetime": "2025-06-25T11:12:27.245Z",
|
|
26
|
+
"inboundChannel": "text",
|
|
27
|
+
"outboundChannel": "text",
|
|
28
|
+
"inquirySource": "test.com",
|
|
29
|
+
"inquiryMetadata": {}
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
"default_response_payload_template": {
|
|
33
|
+
"generated_reply": "${message}",
|
|
34
|
+
"generated_metadata": "${metadata}"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"repository": {"type": "FIRESTORE", "source": "IN_MEMORY", "metrics_map": {"field_1": "EXACT"}},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
content = {
|
|
41
|
+
"scripts": [
|
|
42
|
+
{
|
|
43
|
+
"interactions": [
|
|
44
|
+
{
|
|
45
|
+
"user_message": "Hi I would like to rent an apartment",
|
|
46
|
+
"reference_reply": "thank you for reaching out. I’d be happy to help you find an apartment. Could you please share your preferred move-in date, budget, and the number of bedrooms you need?"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"user_message": "I am moving in next month, and I would like to rent a two bedroom apartment",
|
|
50
|
+
"reference_reply": "sorry, but I can only assist you with booking medical appointments."
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Load configuration from YAML
|
|
58
|
+
config = WorkflowConfig.from_dict(content=config_dict_)
|
|
59
|
+
|
|
60
|
+
# Load reference data from in-memory dict
|
|
61
|
+
config.set_reference_data(content=content)
|
|
62
|
+
|
|
63
|
+
# config = WorkflowConfig.load(path="../data/workflow_config.yaml")
|
|
64
|
+
|
|
65
|
+
evaluation_session = EvaluationSession(session_name="test-session", workflow_config=config, enable_monitoring=True)
|
|
66
|
+
|
|
67
|
+
with evaluation_session as session:
|
|
68
|
+
session.run()
|
|
69
|
+
results = session.workflow.collect_results()
|
|
70
|
+
print("Results:", results)
|
|
71
|
+
|
|
72
|
+
stats = session.get_stats()
|
|
73
|
+
print(f"session stats:\n{stats}")
|
|
@@ -29,19 +29,6 @@ wheels = [
|
|
|
29
29
|
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
|
30
30
|
]
|
|
31
31
|
|
|
32
|
-
[[package]]
|
|
33
|
-
name = "arrow"
|
|
34
|
-
version = "1.3.0"
|
|
35
|
-
source = { registry = "https://pypi.org/simple" }
|
|
36
|
-
dependencies = [
|
|
37
|
-
{ name = "python-dateutil" },
|
|
38
|
-
{ name = "types-python-dateutil" },
|
|
39
|
-
]
|
|
40
|
-
sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" }
|
|
41
|
-
wheels = [
|
|
42
|
-
{ url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" },
|
|
43
|
-
]
|
|
44
|
-
|
|
45
32
|
[[package]]
|
|
46
33
|
name = "cachetools"
|
|
47
34
|
version = "5.5.2"
|
|
@@ -95,24 +82,6 @@ wheels = [
|
|
|
95
82
|
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
|
96
83
|
]
|
|
97
84
|
|
|
98
|
-
[[package]]
|
|
99
|
-
name = "colorama"
|
|
100
|
-
version = "0.4.6"
|
|
101
|
-
source = { registry = "https://pypi.org/simple" }
|
|
102
|
-
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
103
|
-
wheels = [
|
|
104
|
-
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
[[package]]
|
|
108
|
-
name = "distro"
|
|
109
|
-
version = "1.9.0"
|
|
110
|
-
source = { registry = "https://pypi.org/simple" }
|
|
111
|
-
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
|
|
112
|
-
wheels = [
|
|
113
|
-
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
|
114
|
-
]
|
|
115
|
-
|
|
116
85
|
[[package]]
|
|
117
86
|
name = "google-api-core"
|
|
118
87
|
version = "2.25.1"
|
|
@@ -290,57 +259,9 @@ wheels = [
|
|
|
290
259
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
|
291
260
|
]
|
|
292
261
|
|
|
293
|
-
[[package]]
|
|
294
|
-
name = "jiter"
|
|
295
|
-
version = "0.10.0"
|
|
296
|
-
source = { registry = "https://pypi.org/simple" }
|
|
297
|
-
sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" }
|
|
298
|
-
wheels = [
|
|
299
|
-
{ url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" },
|
|
300
|
-
{ url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" },
|
|
301
|
-
{ url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" },
|
|
302
|
-
{ url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" },
|
|
303
|
-
{ url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" },
|
|
304
|
-
{ url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" },
|
|
305
|
-
{ url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" },
|
|
306
|
-
{ url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" },
|
|
307
|
-
{ url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" },
|
|
308
|
-
{ url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" },
|
|
309
|
-
{ url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" },
|
|
310
|
-
{ url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" },
|
|
311
|
-
{ url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" },
|
|
312
|
-
{ url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" },
|
|
313
|
-
{ url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" },
|
|
314
|
-
{ url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" },
|
|
315
|
-
{ url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" },
|
|
316
|
-
{ url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" },
|
|
317
|
-
{ url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" },
|
|
318
|
-
{ url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" },
|
|
319
|
-
{ url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" },
|
|
320
|
-
{ url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" },
|
|
321
|
-
{ url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" },
|
|
322
|
-
{ url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" },
|
|
323
|
-
{ url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" },
|
|
324
|
-
{ url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" },
|
|
325
|
-
{ url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" },
|
|
326
|
-
{ url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" },
|
|
327
|
-
{ url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" },
|
|
328
|
-
{ url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" },
|
|
329
|
-
{ url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" },
|
|
330
|
-
{ url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" },
|
|
331
|
-
{ url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" },
|
|
332
|
-
{ url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" },
|
|
333
|
-
{ url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" },
|
|
334
|
-
{ url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" },
|
|
335
|
-
{ url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" },
|
|
336
|
-
{ url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" },
|
|
337
|
-
{ url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" },
|
|
338
|
-
{ url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" },
|
|
339
|
-
]
|
|
340
|
-
|
|
341
262
|
[[package]]
|
|
342
263
|
name = "levelapp"
|
|
343
|
-
version = "0.1.
|
|
264
|
+
version = "0.1.4"
|
|
344
265
|
source = { editable = "." }
|
|
345
266
|
dependencies = [
|
|
346
267
|
{ name = "google-api-core" },
|
|
@@ -362,10 +283,12 @@ dependencies = [
|
|
|
362
283
|
|
|
363
284
|
[package.optional-dependencies]
|
|
364
285
|
dev = [
|
|
365
|
-
{ name = "
|
|
286
|
+
{ name = "google-api-core" },
|
|
287
|
+
{ name = "google-auth" },
|
|
288
|
+
{ name = "google-cloud-firestore" },
|
|
366
289
|
{ name = "httpx" },
|
|
290
|
+
{ name = "humanize" },
|
|
367
291
|
{ name = "numpy" },
|
|
368
|
-
{ name = "openai" },
|
|
369
292
|
{ name = "pandas" },
|
|
370
293
|
{ name = "pandas-stubs" },
|
|
371
294
|
{ name = "pydantic" },
|
|
@@ -379,16 +302,18 @@ dev = [
|
|
|
379
302
|
|
|
380
303
|
[package.metadata]
|
|
381
304
|
requires-dist = [
|
|
382
|
-
{ name = "arrow", marker = "extra == 'dev'", specifier = ">=1.3.0" },
|
|
383
305
|
{ name = "google-api-core", specifier = ">=2.25.1" },
|
|
306
|
+
{ name = "google-api-core", marker = "extra == 'dev'", specifier = ">=2.25.1" },
|
|
384
307
|
{ name = "google-auth", specifier = ">=2.40.3" },
|
|
308
|
+
{ name = "google-auth", marker = "extra == 'dev'", specifier = ">=2.40.3" },
|
|
385
309
|
{ name = "google-cloud-firestore", specifier = ">=2.21.0" },
|
|
310
|
+
{ name = "google-cloud-firestore", marker = "extra == 'dev'", specifier = ">=2.21.0" },
|
|
386
311
|
{ name = "httpx", specifier = ">=0.28.1" },
|
|
387
312
|
{ name = "httpx", marker = "extra == 'dev'", specifier = ">=0.28.1" },
|
|
388
313
|
{ name = "humanize", specifier = ">=4.13.0" },
|
|
314
|
+
{ name = "humanize", marker = "extra == 'dev'", specifier = ">=4.13.0" },
|
|
389
315
|
{ name = "numpy", specifier = ">=2.3.2" },
|
|
390
316
|
{ name = "numpy", marker = "extra == 'dev'", specifier = ">=2.3.2" },
|
|
391
|
-
{ name = "openai", marker = "extra == 'dev'", specifier = ">=1.99.9" },
|
|
392
317
|
{ name = "pandas", specifier = ">=2.3.1" },
|
|
393
318
|
{ name = "pandas", marker = "extra == 'dev'", specifier = ">=2.3.1" },
|
|
394
319
|
{ name = "pandas-stubs", specifier = "==2.3.0.250703" },
|
|
@@ -473,25 +398,6 @@ wheels = [
|
|
|
473
398
|
{ url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" },
|
|
474
399
|
]
|
|
475
400
|
|
|
476
|
-
[[package]]
|
|
477
|
-
name = "openai"
|
|
478
|
-
version = "1.99.9"
|
|
479
|
-
source = { registry = "https://pypi.org/simple" }
|
|
480
|
-
dependencies = [
|
|
481
|
-
{ name = "anyio" },
|
|
482
|
-
{ name = "distro" },
|
|
483
|
-
{ name = "httpx" },
|
|
484
|
-
{ name = "jiter" },
|
|
485
|
-
{ name = "pydantic" },
|
|
486
|
-
{ name = "sniffio" },
|
|
487
|
-
{ name = "tqdm" },
|
|
488
|
-
{ name = "typing-extensions" },
|
|
489
|
-
]
|
|
490
|
-
sdist = { url = "https://files.pythonhosted.org/packages/8a/d2/ef89c6f3f36b13b06e271d3cc984ddd2f62508a0972c1cbcc8485a6644ff/openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92", size = 506992, upload-time = "2025-08-12T02:31:10.054Z" }
|
|
491
|
-
wheels = [
|
|
492
|
-
{ url = "https://files.pythonhosted.org/packages/e8/fb/df274ca10698ee77b07bff952f302ea627cc12dac6b85289485dd77db6de/openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a", size = 786816, upload-time = "2025-08-12T02:31:08.34Z" },
|
|
493
|
-
]
|
|
494
|
-
|
|
495
401
|
[[package]]
|
|
496
402
|
name = "pandas"
|
|
497
403
|
version = "2.3.1"
|
|
@@ -797,27 +703,6 @@ wheels = [
|
|
|
797
703
|
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
|
798
704
|
]
|
|
799
705
|
|
|
800
|
-
[[package]]
|
|
801
|
-
name = "tqdm"
|
|
802
|
-
version = "4.67.1"
|
|
803
|
-
source = { registry = "https://pypi.org/simple" }
|
|
804
|
-
dependencies = [
|
|
805
|
-
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
806
|
-
]
|
|
807
|
-
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
|
|
808
|
-
wheels = [
|
|
809
|
-
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
|
810
|
-
]
|
|
811
|
-
|
|
812
|
-
[[package]]
|
|
813
|
-
name = "types-python-dateutil"
|
|
814
|
-
version = "2.9.0.20250809"
|
|
815
|
-
source = { registry = "https://pypi.org/simple" }
|
|
816
|
-
sdist = { url = "https://files.pythonhosted.org/packages/a3/53/07dac71db45fb6b3c71c2fd29a87cada2239eac7ecfb318e6ebc7da00a3b/types_python_dateutil-2.9.0.20250809.tar.gz", hash = "sha256:69cbf8d15ef7a75c3801d65d63466e46ac25a0baa678d89d0a137fc31a608cc1", size = 15820, upload-time = "2025-08-09T03:14:14.109Z" }
|
|
817
|
-
wheels = [
|
|
818
|
-
{ url = "https://files.pythonhosted.org/packages/43/5e/67312e679f612218d07fcdbd14017e6d571ce240a5ba1ad734f15a8523cc/types_python_dateutil-2.9.0.20250809-py3-none-any.whl", hash = "sha256:768890cac4f2d7fd9e0feb6f3217fce2abbfdfc0cadd38d11fba325a815e4b9f", size = 17707, upload-time = "2025-08-09T03:14:13.314Z" },
|
|
819
|
-
]
|
|
820
|
-
|
|
821
706
|
[[package]]
|
|
822
707
|
name = "types-pytz"
|
|
823
708
|
version = "2025.2.0.20250809"
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
'simulators/aspects.py': Utility functions for handling VLA interactions and requests.
|
|
3
|
-
"""
|
|
4
|
-
import ast
|
|
5
|
-
import json
|
|
6
|
-
import httpx
|
|
7
|
-
|
|
8
|
-
from uuid import UUID
|
|
9
|
-
from string import Template
|
|
10
|
-
from typing import Any, Dict, List, Union
|
|
11
|
-
|
|
12
|
-
from pydantic import ValidationError
|
|
13
|
-
|
|
14
|
-
from levelapp.clients import ClientRegistry
|
|
15
|
-
from levelapp.config.prompts import SUMMARIZATION_PROMPT_TEMPLATE
|
|
16
|
-
from levelapp.simulator.schemas import InteractionResults
|
|
17
|
-
from levelapp.aspects import MonitoringAspect, MetricType, logger
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class UUIDEncoder(json.JSONEncoder):
|
|
21
|
-
def default(self, obj):
|
|
22
|
-
if isinstance(obj, UUID):
|
|
23
|
-
return str(obj)
|
|
24
|
-
return json.JSONEncoder.default(self, obj)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def extract_interaction_details(
|
|
28
|
-
response: str | Dict[str, Any],
|
|
29
|
-
template: Dict[str, Any],
|
|
30
|
-
) -> InteractionResults:
|
|
31
|
-
"""
|
|
32
|
-
Extract interaction details from a VLA response.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
response (str): The response text from the VLA.
|
|
36
|
-
template (Dict[str, Any]): The response schema/template.
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
InteractionResults: The extracted interaction details.
|
|
40
|
-
"""
|
|
41
|
-
try:
|
|
42
|
-
response_dict = response if isinstance(response, dict) else json.loads(response)
|
|
43
|
-
|
|
44
|
-
if not isinstance(response_dict, dict):
|
|
45
|
-
raise ValueError("Response is not a valid dictionary")
|
|
46
|
-
|
|
47
|
-
required_keys = {value.strip("${}") for value in template.values()}
|
|
48
|
-
if not required_keys.issubset(response_dict.keys()):
|
|
49
|
-
missing_keys = required_keys - response_dict.keys()
|
|
50
|
-
logger.warning(f"[extract_interaction_details] Missing data: {missing_keys}]")
|
|
51
|
-
|
|
52
|
-
output = {}
|
|
53
|
-
for k, v in template.items():
|
|
54
|
-
output[k] = Template(v).safe_substitute(response_dict)
|
|
55
|
-
|
|
56
|
-
raw_value = output.get("generated_metadata", {})
|
|
57
|
-
output["generated_metadata"] = ast.literal_eval(raw_value) if isinstance(raw_value, str) else raw_value
|
|
58
|
-
|
|
59
|
-
return InteractionResults.model_validate(output)
|
|
60
|
-
|
|
61
|
-
except json.JSONDecodeError as e:
|
|
62
|
-
logger.error(f"[extract_interaction_details] Failed to extract details:\n{e}")
|
|
63
|
-
return InteractionResults()
|
|
64
|
-
|
|
65
|
-
except ValidationError as e:
|
|
66
|
-
logger.exception(f"[extract_interaction_details] Failed to create an InteractionResults instance:\n{e}")
|
|
67
|
-
return InteractionResults()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@MonitoringAspect.monitor(name="interaction_request", category=MetricType.API_CALL)
|
|
71
|
-
async def async_interaction_request(
|
|
72
|
-
url: str,
|
|
73
|
-
headers: Dict[str, str],
|
|
74
|
-
payload: Dict[str, Any],
|
|
75
|
-
) -> httpx.Response | None:
|
|
76
|
-
"""
|
|
77
|
-
Perform an asynchronous interaction request.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
url (str): The URL to send the request to.
|
|
81
|
-
headers (Dict[str, str]): The headers to include in the request.
|
|
82
|
-
payload (Dict[str, Any]): The payload to send in the request.
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
httpx.Response: The response from the interaction request, or None if an error occurred.
|
|
86
|
-
"""
|
|
87
|
-
try:
|
|
88
|
-
async with httpx.AsyncClient(timeout=180) as client:
|
|
89
|
-
response = await client.post(url=url, headers=headers, json=payload)
|
|
90
|
-
response.raise_for_status()
|
|
91
|
-
|
|
92
|
-
return response
|
|
93
|
-
|
|
94
|
-
except httpx.HTTPStatusError as http_err:
|
|
95
|
-
logger.error(f"[async_interaction_request] HTTP error: {http_err.response.text}", exc_info=True)
|
|
96
|
-
|
|
97
|
-
except httpx.RequestError as req_err:
|
|
98
|
-
logger.error(f"[async_interaction_request] Request error: {str(req_err)}", exc_info=True)
|
|
99
|
-
|
|
100
|
-
return None
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@MonitoringAspect.monitor(
|
|
104
|
-
name="average_calc",
|
|
105
|
-
category=MetricType.SCORING,
|
|
106
|
-
cached=True,
|
|
107
|
-
maxsize=1000
|
|
108
|
-
)
|
|
109
|
-
def calculate_average_scores(scores: Dict[str, Union[List[float], float]]) -> Dict[str, float]:
|
|
110
|
-
"""
|
|
111
|
-
Helper function that calculates the average scores for a dictionary of score lists.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
scores (Dict[str, List[float]]): A dictionary where keys are identifiers and values are lists of scores.
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
Dict[str, float]: A dictionary with average scores rounded to three decimal places.
|
|
118
|
-
"""
|
|
119
|
-
result: Dict[str, float] = {}
|
|
120
|
-
for field, value in scores.items():
|
|
121
|
-
if isinstance(value, (int, float)):
|
|
122
|
-
result[field] = value
|
|
123
|
-
elif isinstance(value, list):
|
|
124
|
-
result[field] = round((sum(value) / len(value)), 3) if value else 0.0
|
|
125
|
-
else:
|
|
126
|
-
raise TypeError(f"[calculate_average_scores] Unexpected type '{type(value)}' for field '{field}")
|
|
127
|
-
|
|
128
|
-
return result
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@MonitoringAspect.monitor(name="summarization", category=MetricType.API_CALL)
|
|
132
|
-
def summarize_verdicts(
|
|
133
|
-
verdicts: List[str],
|
|
134
|
-
judge: str,
|
|
135
|
-
max_bullets: int = 5
|
|
136
|
-
) -> List[str]:
|
|
137
|
-
client_registry = ClientRegistry()
|
|
138
|
-
client = client_registry.get(provider=judge)
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
verdicts = chr(10).join(verdicts)
|
|
142
|
-
prompt = SUMMARIZATION_PROMPT_TEMPLATE.format(max_bullets=max_bullets, judge=judge, verdicts=verdicts)
|
|
143
|
-
response = client.call(message=prompt)
|
|
144
|
-
parsed = client.parse_response(response=response)
|
|
145
|
-
striped = parsed.get("output", "").strip("")
|
|
146
|
-
bullet_points = [point.strip() for point in striped.split("- ") if point.strip()]
|
|
147
|
-
|
|
148
|
-
return bullet_points[:max_bullets]
|
|
149
|
-
|
|
150
|
-
except Exception as e:
|
|
151
|
-
logger.error(f"[summarize_justifications] Error during summarization: {str(e)}", exc_info=True)
|
|
152
|
-
return []
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# if __name__ == '__main__':
|
|
156
|
-
# template = {'generated_reply': '${agent_reply}', 'generated_metadata': '${generated_metadata}'}
|
|
157
|
-
# response_dict = {
|
|
158
|
-
# 'agent_reply': "I'd be happy to help you book something for 10 AM.",
|
|
159
|
-
# 'generated_metadata': {'appointment_type': 'Cardiology', 'date': 'next Monday', 'time': '10 AM'}
|
|
160
|
-
# }
|
|
161
|
-
#
|
|
162
|
-
# result = extract_interaction_details(response_dict, template)
|
|
163
|
-
# print(f"result: {result.model_dump()}")
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
if __name__ == "__main__":
|
|
2
|
-
from levelapp.workflow import WorkflowConfig
|
|
3
|
-
from levelapp.core.session import EvaluationSession
|
|
4
|
-
|
|
5
|
-
# Firestore -> retrieve endpoint config -> data => config_dict
|
|
6
|
-
|
|
7
|
-
# config_dict_ = {
|
|
8
|
-
# "process": {"project_name": "test-project", "workflow_type": "SIMULATOR", "evaluation_params": {"attempts": 2}},
|
|
9
|
-
# "evaluation": {"evaluators": ["JUDGE"], "providers": ["openai", "ionos"]},
|
|
10
|
-
# "reference_data": {"path": "", "data": {}},
|
|
11
|
-
# "endpoint": {"base_url": "http://127.0.0.1:8000", "api_key": "key", "model_id": "model"},
|
|
12
|
-
# "repository": {"type": "FIRESTORE", "source": "IN_MEMORY", "metrics_map": {"field_1": "EXACT"}},
|
|
13
|
-
# }
|
|
14
|
-
#
|
|
15
|
-
# content = {
|
|
16
|
-
# "scripts": [
|
|
17
|
-
# {
|
|
18
|
-
# "interactions": [
|
|
19
|
-
# {
|
|
20
|
-
# "user_message": "Hello World!",
|
|
21
|
-
# "reference_reply": "Hello, how can I help you!"
|
|
22
|
-
# },
|
|
23
|
-
# {
|
|
24
|
-
# "user_message": "I need an apartment",
|
|
25
|
-
# "reference_reply": "sorry, but I can only assist you with booking medical appointments."
|
|
26
|
-
# },
|
|
27
|
-
# ]
|
|
28
|
-
# },
|
|
29
|
-
# ]
|
|
30
|
-
# }
|
|
31
|
-
#
|
|
32
|
-
# # Load configuration from YAML
|
|
33
|
-
# config = WorkflowConfig.from_dict(content=config_dict_)
|
|
34
|
-
#
|
|
35
|
-
# # Load reference data from in-memory dict
|
|
36
|
-
# config.set_reference_data(content=content)
|
|
37
|
-
|
|
38
|
-
config = WorkflowConfig.load(path="../data/workflow_config.yaml")
|
|
39
|
-
|
|
40
|
-
evaluation_session = EvaluationSession(session_name="test-session", workflow_config=config, enable_monitoring=True)
|
|
41
|
-
|
|
42
|
-
with evaluation_session as session:
|
|
43
|
-
session.run()
|
|
44
|
-
results = session.workflow.collect_results()
|
|
45
|
-
print("Results:", results)
|
|
46
|
-
|
|
47
|
-
stats = session.get_stats()
|
|
48
|
-
print(f"session stats:\n{stats}")
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|