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

@@ -9,7 +9,7 @@ from uuid import UUID, uuid4
9
9
  from datetime import datetime
10
10
 
11
11
  from typing import Dict, Any, List
12
- from pydantic import BaseModel, Field, computed_field, field_validator
12
+ from pydantic import BaseModel, Field, computed_field
13
13
 
14
14
  from levelapp.evaluator.evaluator import JudgeEvaluationResults
15
15
 
@@ -24,13 +24,12 @@ class InteractionLevel(str, Enum):
24
24
  class Interaction(BaseModel):
25
25
  """Represents a single interaction within a conversation."""
26
26
  id: UUID = Field(default_factory=uuid4, description="Interaction identifier")
27
+ user_message_path: str = Field(..., description="Path of the user message in the request payload")
27
28
  user_message: str = Field(..., description="The user's query message")
28
- # generated_reply: str = Field(..., description="The agent's reply message")
29
29
  reference_reply: str = Field(..., description="The preset reference message")
30
30
  interaction_type: InteractionLevel = Field(default=InteractionLevel.INITIAL, description="Type of interaction")
31
31
  reference_metadata: Dict[str, Any] = Field(default_factory=dict, description="Expected metadata")
32
- # generated_metadata: Dict[str, Any] = Field(default_factory=dict, description="Extracted metadata")
33
- guardrail_flag: bool = Field(default=False, description="Flag for guardrail signaling")
32
+ guardrail_flag: Any = Field(default=False, description="Flag for guardrail signaling")
34
33
  request_payload: Dict[str, Any] = Field(default_factory=dict, description="Additional request payload")
35
34
 
36
35
 
@@ -40,6 +39,7 @@ class ConversationScript(BaseModel):
40
39
  interactions: List[Interaction] = Field(default_factory=list, description="List of interactions")
41
40
  description: str = Field(default="no-description", description="A short description of the conversation")
42
41
  details: Dict[str, str] = Field(default_factory=dict, description="Conversation details")
42
+ variable_request_schema: bool = Field(default=False, description="The payload schema changes for each request")
43
43
 
44
44
 
45
45
  class ScriptsBatch(BaseModel):
@@ -1,7 +1,6 @@
1
1
  """
2
2
  'simulators/service.py': Service layer to manage conversation simulation and evaluation.
3
3
  """
4
- import json
5
4
  import time
6
5
  import asyncio
7
6
 
@@ -9,8 +8,12 @@ from datetime import datetime
9
8
  from collections import defaultdict
10
9
  from typing import Dict, Any, List
11
10
 
12
- from levelapp.core.base import BaseRepository, BaseProcess, BaseEvaluator
13
- from levelapp.config.endpoint import EndpointConfig
11
+
12
+ from levelapp.core.base import BaseProcess, BaseEvaluator
13
+ from levelapp.endpoint.client import EndpointConfig
14
+ from levelapp.endpoint.manager import EndpointConfigManager
15
+
16
+ from levelapp.core.schemas import EvaluatorType
14
17
  from levelapp.simulator.schemas import (
15
18
  InteractionEvaluationResults,
16
19
  ScriptsBatch,
@@ -18,13 +21,10 @@ from levelapp.simulator.schemas import (
18
21
  SimulationResults
19
22
  )
20
23
  from levelapp.simulator.utils import (
21
- extract_interaction_details,
22
- async_interaction_request,
23
24
  calculate_average_scores,
24
25
  summarize_verdicts,
25
26
  )
26
27
  from levelapp.aspects import logger
27
- from levelapp.core.schemas import EvaluatorType
28
28
 
29
29
 
30
30
  class ConversationSimulator(BaseProcess):
@@ -32,29 +32,26 @@ class ConversationSimulator(BaseProcess):
32
32
 
33
33
  def __init__(
34
34
  self,
35
- repository: BaseRepository | None = None,
35
+ endpoint_config: EndpointConfig | None = None,
36
36
  evaluators: Dict[EvaluatorType, BaseEvaluator] | None = None,
37
37
  providers: List[str] | None = None,
38
- endpoint_config: EndpointConfig | None = None,
38
+
39
39
  ):
40
40
  """
41
41
  Initialize the ConversationSimulator.
42
42
 
43
43
  Args:
44
- repository (BaseRepository): Service for saving simulation results.
44
+ endpoint_config (EndpointConfig): Endpoint configuration.
45
45
  evaluators (EvaluationService): Service for evaluating interactions.
46
46
  endpoint_config (EndpointConfig): Configuration object for VLA.
47
47
  """
48
48
  self._CLASS_NAME = self.__class__.__name__
49
49
 
50
- self.repository = repository
50
+ self.endpoint_config = endpoint_config
51
51
  self.evaluators = evaluators
52
52
  self.providers = providers
53
- self.endpoint_config = endpoint_config
54
53
 
55
- self._url: str | None = None
56
- self._credentials: str | None = None
57
- self._headers: Dict[str, Any] | None = None
54
+ self.endpoint_cm = EndpointConfigManager()
58
55
 
59
56
  self.test_batch: ScriptsBatch | None = None
60
57
  self.evaluation_verdicts: Dict[str, List[str]] = defaultdict(list)
@@ -62,36 +59,34 @@ class ConversationSimulator(BaseProcess):
62
59
 
63
60
  def setup(
64
61
  self,
65
- repository: BaseRepository,
62
+ endpoint_config: EndpointConfig,
66
63
  evaluators: Dict[EvaluatorType, BaseEvaluator],
67
64
  providers: List[str],
68
- endpoint_config: EndpointConfig,
69
65
  ) -> None:
70
66
  """
71
67
  Initialize the ConversationSimulator.
72
68
 
73
69
  Args:
74
- repository (BaseRepository): Repository object for storing simulation results.
70
+ endpoint_config (EndpointConfig): Configuration object for user endpoint API.
75
71
  evaluators (Dict[str, BaseEvaluator]): List of evaluator objects for evaluating interactions.
76
72
  providers (List[str]): List of LLM provider names.
77
- endpoint_config (EndpointConfig): Configuration object for VLA.
73
+
78
74
  """
79
75
  _LOG: str = f"[{self._CLASS_NAME}][{self.setup.__name__}]"
80
76
  logger.info(f"{_LOG} Setting up the Conversation Simulator..")
81
77
 
82
- self.repository = repository
78
+ if not self.endpoint_cm:
79
+ self.endpoint_cm = EndpointConfigManager()
80
+
81
+ self.endpoint_config = endpoint_config
82
+ self.endpoint_cm.set_endpoints(endpoints_config=[endpoint_config])
83
+
83
84
  self.evaluators = evaluators
84
85
  self.providers = providers
85
86
 
86
87
  if not self.providers:
87
88
  logger.warning(f"{_LOG} No LLM providers were provided. The Judge Evaluation process will not be executed.")
88
89
 
89
- self.endpoint_config = endpoint_config
90
-
91
- self._url = endpoint_config.full_url
92
- self._credentials = endpoint_config.api_key.get_secret_value()
93
- self._headers = endpoint_config.headers
94
-
95
90
  def get_evaluator(self, name: EvaluatorType) -> BaseEvaluator:
96
91
  """
97
92
  Retrieve an evaluator by name.
@@ -278,22 +273,37 @@ class ConversationSimulator(BaseProcess):
278
273
  start_time = time.time()
279
274
 
280
275
  results = []
276
+ contextual_mode: bool = script.variable_request_schema
277
+ logger.info(f"{_LOG} Contextual Mode ON: {contextual_mode}")
281
278
  interactions = script.interactions
282
279
 
283
280
  for interaction in interactions:
284
- user_message = interaction.user_message
285
- request_payload = interaction.request_payload
286
- self.endpoint_config.variables = {
287
- "user_message": user_message,
288
- "request_payload": request_payload
289
- }
290
-
291
- response = await async_interaction_request(
292
- url=self.endpoint_config.full_url,
293
- headers=self.endpoint_config.headers,
294
- payload=self.endpoint_config.request_payload,
281
+ if contextual_mode:
282
+ from levelapp.simulator.utils import set_by_path
283
+ request_payload = interaction.request_payload
284
+ user_message = interaction.user_message
285
+ set_by_path(
286
+ obj=request_payload,
287
+ path=interaction.user_message_path,
288
+ value=user_message,
289
+ )
290
+ logger.info(f"{_LOG} Request payload (Variable Request Schema):\n{request_payload}\n---")
291
+ else:
292
+ user_message = interaction.user_message
293
+ request_payload = interaction.request_payload
294
+ request_payload.update({"user_message": user_message})
295
+ logger.info(f"{_LOG} Request payload (Configured Request Schema):\n{request_payload}\n---")
296
+
297
+ mappings = self.endpoint_config.response_mapping
298
+
299
+ response = await self.endpoint_cm.send_request(
300
+ endpoint_config=self.endpoint_config,
301
+ context=request_payload,
302
+ contextual_mode=contextual_mode
295
303
  )
296
304
 
305
+ logger.info(f"{_LOG} Response:\n{response}\n---")
306
+
297
307
  reference_reply = interaction.reference_reply
298
308
  reference_metadata = interaction.reference_metadata
299
309
  reference_guardrail_flag: bool = interaction.guardrail_flag
@@ -312,14 +322,18 @@ class ConversationSimulator(BaseProcess):
312
322
  results.append(result)
313
323
  continue
314
324
 
315
- interaction_details = extract_interaction_details(
316
- response=response.text,
317
- template=self.endpoint_config.response_payload,
325
+ interaction_details = self.endpoint_cm.extract_response_data(
326
+ response=response,
327
+ mappings=mappings,
318
328
  )
319
329
 
320
- generated_reply = interaction_details.generated_reply
321
- generated_metadata = interaction_details.generated_metadata
322
- extracted_guardrail_flag: bool = interaction_details.guardrail_flag
330
+ logger.info(f"{_LOG} Interaction details:\n{interaction_details}\n---")
331
+
332
+ generated_reply = interaction_details.get("agent_reply", "")
333
+ generated_metadata = interaction_details.get("metadata", {})
334
+ extracted_guardrail_flag = interaction_details.get("guardrail_flag", False)
335
+
336
+ logger.info(f"{_LOG} Generated reply:\n{generated_reply}\n---")
323
337
 
324
338
  evaluation_results = await self.evaluate_interaction(
325
339
  user_input=user_message,
@@ -346,7 +360,7 @@ class ConversationSimulator(BaseProcess):
346
360
  "reference_reply": reference_reply,
347
361
  "generated_metadata": generated_metadata,
348
362
  "reference_metadata": reference_metadata,
349
- "guardrail_details": interaction_details.guardrail_flag,
363
+ "guardrail_details": extracted_guardrail_flag,
350
364
  "evaluation_results": evaluation_results.model_dump(),
351
365
  }
352
366
 
@@ -1,197 +1,74 @@
1
1
  """
2
2
  'simulators/aspects.py': Utility functions for handling VLA interactions and requests.
3
3
  """
4
- import re
5
- import ast
6
- import json
7
4
  import httpx
8
5
 
9
- from uuid import UUID
10
- from string import Template
11
- from typing import Any, Dict, List, Union, Iterable
6
+ from typing import Any, Dict, List, Union
12
7
 
13
- from pydantic import ValidationError
14
8
 
15
9
  from levelapp.clients import ClientRegistry
16
10
  from levelapp.config.prompts import SUMMARIZATION_PROMPT_TEMPLATE
17
- from levelapp.simulator.schemas import InteractionResults
18
11
  from levelapp.aspects import MonitoringAspect, MetricType, logger
19
12
 
20
13
 
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):
14
+ def set_by_path(obj: Dict, path: str, value: Any) -> None:
47
15
  """
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.
16
+ Sets a value in a nested dictionary using JSON path-like notation.
172
17
 
173
18
  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.
19
+ obj (dict): Dictionary to modify.
20
+ path (str): Path (e.g., "a.b[0].c") indicating where to set the value.
21
+ value (Any): Value to assign at the specified path.
177
22
 
178
23
  Returns:
179
- httpx.Response: The response from the interaction request, or None if an error occurred.
24
+ None
180
25
  """
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)
26
+ parts = path.split(".")
27
+ current = obj
28
+
29
+ for i, part in enumerate(parts):
30
+ is_last = i == len(parts) - 1
31
+
32
+ try:
33
+ # Handle list index access, e.g., key[0] or [1]
34
+ if '[' in part and ']' in part:
35
+ key, idx = part.split('[')
36
+ idx = int(idx.rstrip(']'))
37
+
38
+ # If we have a key before the list
39
+ if key:
40
+ if key not in current or not isinstance(current[key], list):
41
+ current[key] = []
42
+ while len(current[key]) <= idx:
43
+ current[key].append({})
44
+ target = current[key]
45
+ else:
46
+ if not isinstance(current, list):
47
+ print("[set_by_path][WARNING] Expected a list at this level.")
48
+ return
49
+ while len(current) <= idx:
50
+ current.append({})
51
+ target = current
52
+
53
+ if is_last:
54
+ target[idx] = value
55
+ else:
56
+ if not isinstance(target[idx], dict):
57
+ target[idx] = {}
58
+ current = target[idx]
190
59
 
191
- except httpx.RequestError as req_err:
192
- logger.error(f"[async_interaction_request] Request error: {str(req_err)}", exc_info=True)
60
+ else:
61
+ # Regular dictionary key
62
+ if is_last:
63
+ current[part] = value
64
+ else:
65
+ if part not in current or not isinstance(current[part], dict):
66
+ current[part] = {}
67
+ current = current[part]
193
68
 
194
- return None
69
+ except (KeyError, IndexError, TypeError, AttributeError) as e:
70
+ print(f"[set_by_path][ERROR] Error type <{e.__class__.__name__}> : {e.args[0]}")
71
+ return
195
72
 
196
73
 
197
74
  @MonitoringAspect.monitor(
levelapp/workflow/base.py CHANGED
@@ -7,6 +7,8 @@ from pathlib import Path
7
7
  from typing import Any, Dict
8
8
 
9
9
  from levelapp.core.base import BaseProcess
10
+ from levelapp.endpoint.client import EndpointConfig
11
+ from levelapp.endpoint.manager import EndpointConfigManager
10
12
  from levelapp.simulator.schemas import ScriptsBatch
11
13
  from levelapp.simulator.simulator import ConversationSimulator
12
14
  from levelapp.workflow.runtime import WorkflowContext
@@ -68,6 +70,18 @@ class BaseWorkflow(ABC):
68
70
  """
69
71
  return self._results
70
72
 
73
+ @abstractmethod
74
+ async def test_connection(self, context: Dict[str, Any]) -> Dict[str, Any]:
75
+ """
76
+ Abstract method for testing endpoint connection.
77
+
78
+ Args:
79
+ context (Dict[str, Any]): The context (request payload) to test connectivity with.
80
+
81
+ Returns:
82
+ The test connectivity result.
83
+ """
84
+
71
85
  @abstractmethod
72
86
  def _setup_process(self, context: WorkflowContext) -> BaseProcess:
73
87
  """
@@ -105,13 +119,30 @@ class SimulatorWorkflow(BaseWorkflow):
105
119
  """
106
120
  simulator = ConversationSimulator()
107
121
  simulator.setup(
108
- repository=context.repository,
122
+ endpoint_config=context.endpoint,
109
123
  evaluators=context.evaluators,
110
124
  providers=context.providers,
111
- endpoint_config=context.endpoint_config,
112
125
  )
126
+
113
127
  return simulator
114
128
 
129
+ async def test_connection(self, context: Dict[str, Any]) -> Dict[str, Any]:
130
+ """
131
+ Runs a connectivity test of the configured endpoint.
132
+
133
+ Args:
134
+ context (Dict[str, Any]): The request payload to send for testing.
135
+
136
+ Returns:
137
+ The test connectivity result.
138
+ """
139
+ endpoint_cm = EndpointConfigManager()
140
+ endpoint_cm.set_endpoints(endpoints_config=[self.context.endpoint])
141
+ tester = endpoint_cm.get_tester(endpoint_name=self.context.endpoint.name)
142
+ results = await tester.test(context=context)
143
+
144
+ return results
145
+
115
146
  def _load_input_data(self, context: WorkflowContext) -> Dict[str, Any]:
116
147
  """
117
148
  Concrete implementation for loading the reference data.
@@ -3,7 +3,7 @@ from typing import List, Dict, Any, Optional
3
3
  from pydantic import BaseModel, Field
4
4
 
5
5
  from levelapp.aspects import logger
6
- from levelapp.config.endpoint import EndpointConfig
6
+ from levelapp.endpoint.client import EndpointConfig
7
7
  from levelapp.core.schemas import WorkflowType, RepositoryType, EvaluatorType
8
8
 
9
9
 
@@ -39,9 +39,9 @@ class WorkflowConfig(BaseModel):
39
39
  Supports both file-based loading and in-memory dictionary creation.
40
40
  """
41
41
  process: ProcessConfig
42
+ endpoint: EndpointConfig
42
43
  evaluation: EvaluationConfig
43
44
  reference_data: ReferenceDataConfig
44
- endpoint: EndpointConfig
45
45
  repository: RepositoryConfig
46
46
 
47
47
  class Config:
@@ -90,3 +90,7 @@ class WorkflowConfig(BaseModel):
90
90
  self.reference_data.data = content
91
91
  logger.info(f"[{self.__class__.__name__}] Reference data loaded from provided content")
92
92
 
93
+
94
+ if __name__ == '__main__':
95
+ workflow_config = WorkflowConfig.load(path="../../src/data/workflow_config.yaml")
96
+ print(f"Workflow Configuration:\n{workflow_config.model_dump_json(indent=2)}")
@@ -1,6 +1,7 @@
1
1
  """levelapp/workflow/context.py: Builds runtime WorkflowContext from WorkflowConfig."""
2
2
  from typing import Dict, Callable
3
3
 
4
+ from levelapp.repository.filesystem import FileSystemRepository
4
5
  from levelapp.workflow.config import WorkflowConfig
5
6
  from levelapp.core.base import BaseRepository, BaseEvaluator
6
7
  from levelapp.workflow.runtime import WorkflowContext
@@ -19,6 +20,7 @@ class WorkflowContextBuilder:
19
20
  # Map repository type to constructor that accepts the WorkflowConfig
20
21
  self.repository_map: Dict[RepositoryType, Callable[[WorkflowConfig], BaseRepository]] = {
21
22
  RepositoryType.FIRESTORE: lambda cfg: FirestoreRepository(cfg),
23
+ RepositoryType.FILESYSTEM: lambda cfg: FileSystemRepository(cfg),
22
24
  }
23
25
 
24
26
  # Map evaluator type to constructor that accepts the WorkflowConfig
@@ -57,6 +59,6 @@ class WorkflowContextBuilder:
57
59
  repository=repository,
58
60
  evaluators=evaluators,
59
61
  providers=providers,
60
- endpoint_config=endpoint_config,
62
+ endpoint=endpoint_config,
61
63
  inputs=inputs,
62
64
  )
@@ -2,7 +2,7 @@
2
2
  from dataclasses import dataclass
3
3
  from typing import Dict, List, Any
4
4
 
5
- from levelapp.config import EndpointConfig
5
+ from levelapp.endpoint.client import EndpointConfig
6
6
  from levelapp.core.base import BaseRepository, BaseEvaluator
7
7
  from levelapp.workflow.config import WorkflowConfig
8
8
  from levelapp.core.schemas import EvaluatorType
@@ -12,8 +12,8 @@ from levelapp.core.schemas import EvaluatorType
12
12
  class WorkflowContext:
13
13
  """Immutable data holder for workflow execution context."""
14
14
  config: WorkflowConfig
15
+ endpoint: EndpointConfig
15
16
  repository: BaseRepository
16
17
  evaluators: Dict[EvaluatorType, BaseEvaluator]
17
18
  providers: List[str]
18
- endpoint_config: EndpointConfig
19
- inputs: Dict[str, Any]
19
+ inputs: Dict[str, Any]