levelapp 0.1.3__py3-none-any.whl → 0.1.4__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.

@@ -1,13 +1,14 @@
1
1
  """
2
2
  'simulators/aspects.py': Utility functions for handling VLA interactions and requests.
3
3
  """
4
+ import re
4
5
  import ast
5
6
  import json
6
7
  import httpx
7
8
 
8
9
  from uuid import UUID
9
10
  from string import Template
10
- from typing import Any, Dict, List, Union
11
+ from typing import Any, Dict, List, Union, Iterable
11
12
 
12
13
  from pydantic import ValidationError
13
14
 
@@ -24,46 +25,139 @@ class UUIDEncoder(json.JSONEncoder):
24
25
  return json.JSONEncoder.default(self, obj)
25
26
 
26
27
 
27
- def extract_interaction_details(
28
- response: str | Dict[str, Any],
29
- template: Dict[str, Any],
30
- ) -> InteractionResults:
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.
31
50
  """
32
- Extract interaction details from a VLA response.
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
33
71
 
34
- Args:
35
- response (str): The response text from the VLA.
36
- template (Dict[str, Any]): The response schema/template.
37
72
 
38
- Returns:
39
- InteractionResults: The extracted interaction details.
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.
40
85
  """
41
86
  try:
42
87
  response_dict = response if isinstance(response, dict) else json.loads(response)
43
-
88
+ print(f"response:\n{response_dict}\n--")
44
89
  if not isinstance(response_dict, dict):
45
90
  raise ValueError("Response is not a valid dictionary")
46
91
 
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
-
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
59
149
  return InteractionResults.model_validate(output)
60
150
 
61
151
  except json.JSONDecodeError as e:
62
- logger.error(f"[extract_interaction_details] Failed to extract details:\n{e}")
152
+ logger.error(f"[extract_interaction_details] Failed to parse JSON response: {e}")
63
153
  return InteractionResults()
64
154
 
65
155
  except ValidationError as e:
66
- logger.exception(f"[extract_interaction_details] Failed to create an InteractionResults instance:\n{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}")
67
161
  return InteractionResults()
68
162
 
69
163
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: levelapp
3
- Version: 0.1.3
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
@@ -36,7 +36,7 @@ levelapp/repository/firestore.py,sha256=K9JgxsNCelAKtzTDv19c1dHRlitMeRzo7H3caTlK
36
36
  levelapp/simulator/__init__.py,sha256=8Dz8g7rbpBZX3WoknVmMVoWm_VT72ZL9BABOF1xFpqs,83
37
37
  levelapp/simulator/schemas.py,sha256=YGprtuRZ4m33WBD35xj1Ib5EbMTdDCOp-wCykf-Iz-4,3700
38
38
  levelapp/simulator/simulator.py,sha256=yreado12XMVEJ4N4cYj5m_bYVKU3BsOv5oQycB7wCFw,19889
39
- levelapp/simulator/utils.py,sha256=d1O4Q4Yl1lAAJWLJDiwNjwt0hD9bGlCan4a2G21E7yw,5930
39
+ levelapp/simulator/utils.py,sha256=xZXdF24rrOm5RCp5ELk0wQxxGd70CahDT79cIC-XmlE,9589
40
40
  levelapp/workflow/__init__.py,sha256=27b2obG7ObhR43yd2uH-R0koRB7-DG8Emnvrq8EjsTA,193
41
41
  levelapp/workflow/base.py,sha256=1A_xKSBOmVjfMbRBcNhDK6G17SEjqRIm-XjMw45IPC4,5596
42
42
  levelapp/workflow/config.py,sha256=MlHt1PsXD09aukB93fvKTew0D8WD4_jdnO93Nn6b2U0,2923
@@ -44,7 +44,7 @@ levelapp/workflow/context.py,sha256=gjAZXHEdlsXqWY6DbXOfKXNbxQbahRPSnNzyWDqryPU,
44
44
  levelapp/workflow/factory.py,sha256=z1ttJmI59sU9HgOvPo3ixUJ_oPv838XgehfuOorlTt8,1634
45
45
  levelapp/workflow/registration.py,sha256=VHUHjLHXad5kjcKukaEOIf7hBZ09bT3HAzVmIT08aLo,359
46
46
  levelapp/workflow/runtime.py,sha256=cFyXNWXSuURKbrMDHdkTcjeItM9wHP-5DPljntwYL5g,686
47
- levelapp-0.1.3.dist-info/METADATA,sha256=wYFEgC4bRV2nfbxv8PHLwejyhtrfsbjOCIckQvPdPeA,12572
48
- levelapp-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
- levelapp-0.1.3.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- levelapp-0.1.3.dist-info/RECORD,,
47
+ levelapp-0.1.4.dist-info/METADATA,sha256=zCmgM_evZ9Y0xcAX-so7foD_auO0I9PSzLGw0pL2HUY,12572
48
+ levelapp-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
+ levelapp-0.1.4.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
+ levelapp-0.1.4.dist-info/RECORD,,