edsl 0.1.49__py3-none-any.whl → 0.1.51__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.
Files changed (257) hide show
  1. edsl/__init__.py +124 -53
  2. edsl/__version__.py +1 -1
  3. edsl/agents/agent.py +21 -21
  4. edsl/agents/agent_list.py +2 -5
  5. edsl/agents/exceptions.py +119 -5
  6. edsl/base/__init__.py +10 -35
  7. edsl/base/base_class.py +71 -36
  8. edsl/base/base_exception.py +204 -0
  9. edsl/base/data_transfer_models.py +1 -1
  10. edsl/base/exceptions.py +94 -0
  11. edsl/buckets/__init__.py +15 -1
  12. edsl/buckets/bucket_collection.py +3 -4
  13. edsl/buckets/exceptions.py +107 -0
  14. edsl/buckets/model_buckets.py +1 -2
  15. edsl/buckets/token_bucket.py +11 -6
  16. edsl/buckets/token_bucket_api.py +27 -12
  17. edsl/buckets/token_bucket_client.py +9 -7
  18. edsl/caching/cache.py +12 -4
  19. edsl/caching/cache_entry.py +10 -9
  20. edsl/caching/exceptions.py +113 -7
  21. edsl/caching/remote_cache_sync.py +6 -7
  22. edsl/caching/sql_dict.py +20 -14
  23. edsl/cli.py +43 -0
  24. edsl/config/__init__.py +1 -1
  25. edsl/config/config_class.py +32 -6
  26. edsl/conversation/Conversation.py +8 -4
  27. edsl/conversation/car_buying.py +1 -3
  28. edsl/conversation/exceptions.py +58 -0
  29. edsl/conversation/mug_negotiation.py +2 -8
  30. edsl/coop/__init__.py +28 -6
  31. edsl/coop/coop.py +120 -29
  32. edsl/coop/coop_functions.py +1 -1
  33. edsl/coop/ep_key_handling.py +1 -1
  34. edsl/coop/exceptions.py +188 -9
  35. edsl/coop/price_fetcher.py +5 -8
  36. edsl/coop/utils.py +4 -6
  37. edsl/dataset/__init__.py +5 -4
  38. edsl/dataset/dataset.py +177 -86
  39. edsl/dataset/dataset_operations_mixin.py +98 -76
  40. edsl/dataset/dataset_tree.py +11 -7
  41. edsl/dataset/display/table_display.py +0 -2
  42. edsl/dataset/display/table_renderers.py +6 -4
  43. edsl/dataset/exceptions.py +125 -0
  44. edsl/dataset/file_exports.py +18 -11
  45. edsl/dataset/r/ggplot.py +13 -6
  46. edsl/display/__init__.py +27 -0
  47. edsl/display/core.py +147 -0
  48. edsl/display/plugin.py +189 -0
  49. edsl/display/utils.py +52 -0
  50. edsl/inference_services/__init__.py +9 -1
  51. edsl/inference_services/available_model_cache_handler.py +1 -1
  52. edsl/inference_services/available_model_fetcher.py +5 -6
  53. edsl/inference_services/data_structures.py +10 -7
  54. edsl/inference_services/exceptions.py +132 -1
  55. edsl/inference_services/inference_service_abc.py +2 -2
  56. edsl/inference_services/inference_services_collection.py +2 -6
  57. edsl/inference_services/registry.py +4 -3
  58. edsl/inference_services/service_availability.py +4 -3
  59. edsl/inference_services/services/anthropic_service.py +4 -1
  60. edsl/inference_services/services/aws_bedrock.py +13 -12
  61. edsl/inference_services/services/azure_ai.py +12 -10
  62. edsl/inference_services/services/deep_infra_service.py +1 -4
  63. edsl/inference_services/services/deep_seek_service.py +1 -5
  64. edsl/inference_services/services/google_service.py +7 -3
  65. edsl/inference_services/services/groq_service.py +1 -1
  66. edsl/inference_services/services/mistral_ai_service.py +4 -2
  67. edsl/inference_services/services/ollama_service.py +1 -1
  68. edsl/inference_services/services/open_ai_service.py +7 -5
  69. edsl/inference_services/services/perplexity_service.py +6 -2
  70. edsl/inference_services/services/test_service.py +8 -7
  71. edsl/inference_services/services/together_ai_service.py +2 -3
  72. edsl/inference_services/services/xai_service.py +1 -1
  73. edsl/instructions/__init__.py +1 -1
  74. edsl/instructions/change_instruction.py +7 -5
  75. edsl/instructions/exceptions.py +61 -0
  76. edsl/instructions/instruction.py +6 -2
  77. edsl/instructions/instruction_collection.py +6 -4
  78. edsl/instructions/instruction_handler.py +12 -15
  79. edsl/interviews/ReportErrors.py +0 -3
  80. edsl/interviews/__init__.py +9 -2
  81. edsl/interviews/answering_function.py +11 -13
  82. edsl/interviews/exception_tracking.py +15 -8
  83. edsl/interviews/exceptions.py +79 -0
  84. edsl/interviews/interview.py +33 -30
  85. edsl/interviews/interview_status_dictionary.py +4 -2
  86. edsl/interviews/interview_status_log.py +2 -1
  87. edsl/interviews/interview_task_manager.py +5 -5
  88. edsl/interviews/request_token_estimator.py +5 -2
  89. edsl/interviews/statistics.py +3 -4
  90. edsl/invigilators/__init__.py +7 -1
  91. edsl/invigilators/exceptions.py +79 -0
  92. edsl/invigilators/invigilator_base.py +0 -1
  93. edsl/invigilators/invigilators.py +9 -13
  94. edsl/invigilators/prompt_constructor.py +1 -5
  95. edsl/invigilators/prompt_helpers.py +8 -4
  96. edsl/invigilators/question_instructions_prompt_builder.py +1 -1
  97. edsl/invigilators/question_option_processor.py +9 -5
  98. edsl/invigilators/question_template_replacements_builder.py +3 -2
  99. edsl/jobs/__init__.py +42 -5
  100. edsl/jobs/async_interview_runner.py +25 -23
  101. edsl/jobs/check_survey_scenario_compatibility.py +11 -10
  102. edsl/jobs/data_structures.py +8 -5
  103. edsl/jobs/exceptions.py +177 -8
  104. edsl/jobs/fetch_invigilator.py +1 -1
  105. edsl/jobs/jobs.py +74 -69
  106. edsl/jobs/jobs_checks.py +6 -7
  107. edsl/jobs/jobs_component_constructor.py +4 -4
  108. edsl/jobs/jobs_pricing_estimation.py +4 -3
  109. edsl/jobs/jobs_remote_inference_logger.py +5 -4
  110. edsl/jobs/jobs_runner_asyncio.py +3 -4
  111. edsl/jobs/jobs_runner_status.py +8 -9
  112. edsl/jobs/remote_inference.py +27 -24
  113. edsl/jobs/results_exceptions_handler.py +10 -7
  114. edsl/key_management/__init__.py +3 -1
  115. edsl/key_management/exceptions.py +62 -0
  116. edsl/key_management/key_lookup.py +1 -1
  117. edsl/key_management/key_lookup_builder.py +37 -14
  118. edsl/key_management/key_lookup_collection.py +2 -0
  119. edsl/language_models/__init__.py +1 -1
  120. edsl/language_models/exceptions.py +302 -14
  121. edsl/language_models/language_model.py +9 -8
  122. edsl/language_models/model.py +4 -4
  123. edsl/language_models/model_list.py +1 -1
  124. edsl/language_models/price_manager.py +1 -1
  125. edsl/language_models/raw_response_handler.py +14 -9
  126. edsl/language_models/registry.py +17 -21
  127. edsl/language_models/repair.py +0 -6
  128. edsl/language_models/unused/fake_openai_service.py +0 -1
  129. edsl/load_plugins.py +69 -0
  130. edsl/logger.py +146 -0
  131. edsl/notebooks/__init__.py +24 -1
  132. edsl/notebooks/exceptions.py +82 -0
  133. edsl/notebooks/notebook.py +7 -3
  134. edsl/notebooks/notebook_to_latex.py +1 -2
  135. edsl/plugins/__init__.py +63 -0
  136. edsl/plugins/built_in/export_example.py +50 -0
  137. edsl/plugins/built_in/pig_latin.py +67 -0
  138. edsl/plugins/cli.py +372 -0
  139. edsl/plugins/cli_typer.py +283 -0
  140. edsl/plugins/exceptions.py +31 -0
  141. edsl/plugins/hookspec.py +51 -0
  142. edsl/plugins/plugin_host.py +128 -0
  143. edsl/plugins/plugin_manager.py +633 -0
  144. edsl/plugins/plugins_registry.py +168 -0
  145. edsl/prompts/__init__.py +24 -1
  146. edsl/prompts/exceptions.py +107 -5
  147. edsl/prompts/prompt.py +15 -7
  148. edsl/questions/HTMLQuestion.py +5 -11
  149. edsl/questions/Quick.py +0 -1
  150. edsl/questions/__init__.py +6 -4
  151. edsl/questions/answer_validator_mixin.py +318 -323
  152. edsl/questions/compose_questions.py +3 -3
  153. edsl/questions/descriptors.py +11 -50
  154. edsl/questions/exceptions.py +278 -22
  155. edsl/questions/loop_processor.py +7 -5
  156. edsl/questions/prompt_templates/question_list.jinja +3 -0
  157. edsl/questions/question_base.py +46 -19
  158. edsl/questions/question_base_gen_mixin.py +2 -2
  159. edsl/questions/question_base_prompts_mixin.py +13 -7
  160. edsl/questions/question_budget.py +503 -98
  161. edsl/questions/question_check_box.py +660 -160
  162. edsl/questions/question_dict.py +345 -194
  163. edsl/questions/question_extract.py +401 -61
  164. edsl/questions/question_free_text.py +80 -14
  165. edsl/questions/question_functional.py +119 -9
  166. edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
  167. edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
  168. edsl/questions/question_list.py +275 -28
  169. edsl/questions/question_matrix.py +643 -96
  170. edsl/questions/question_multiple_choice.py +219 -51
  171. edsl/questions/question_numerical.py +361 -32
  172. edsl/questions/question_rank.py +401 -124
  173. edsl/questions/question_registry.py +7 -5
  174. edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
  175. edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
  176. edsl/questions/register_questions_meta.py +2 -2
  177. edsl/questions/response_validator_abc.py +13 -15
  178. edsl/questions/response_validator_factory.py +10 -12
  179. edsl/questions/templates/dict/answering_instructions.jinja +1 -0
  180. edsl/questions/templates/rank/question_presentation.jinja +1 -1
  181. edsl/results/__init__.py +1 -1
  182. edsl/results/exceptions.py +141 -7
  183. edsl/results/report.py +1 -2
  184. edsl/results/result.py +11 -9
  185. edsl/results/results.py +480 -321
  186. edsl/results/results_selector.py +8 -4
  187. edsl/scenarios/PdfExtractor.py +2 -2
  188. edsl/scenarios/construct_download_link.py +69 -35
  189. edsl/scenarios/directory_scanner.py +33 -14
  190. edsl/scenarios/document_chunker.py +1 -1
  191. edsl/scenarios/exceptions.py +238 -14
  192. edsl/scenarios/file_methods.py +1 -1
  193. edsl/scenarios/file_store.py +7 -3
  194. edsl/scenarios/handlers/__init__.py +17 -0
  195. edsl/scenarios/handlers/docx_file_store.py +0 -5
  196. edsl/scenarios/handlers/pdf_file_store.py +0 -1
  197. edsl/scenarios/handlers/pptx_file_store.py +0 -5
  198. edsl/scenarios/handlers/py_file_store.py +0 -1
  199. edsl/scenarios/handlers/sql_file_store.py +1 -4
  200. edsl/scenarios/handlers/sqlite_file_store.py +0 -1
  201. edsl/scenarios/handlers/txt_file_store.py +1 -1
  202. edsl/scenarios/scenario.py +1 -3
  203. edsl/scenarios/scenario_list.py +179 -27
  204. edsl/scenarios/scenario_list_pdf_tools.py +1 -0
  205. edsl/scenarios/scenario_selector.py +0 -1
  206. edsl/surveys/__init__.py +3 -4
  207. edsl/surveys/dag/__init__.py +4 -2
  208. edsl/surveys/descriptors.py +1 -1
  209. edsl/surveys/edit_survey.py +1 -0
  210. edsl/surveys/exceptions.py +165 -9
  211. edsl/surveys/memory/__init__.py +5 -3
  212. edsl/surveys/memory/memory_management.py +1 -0
  213. edsl/surveys/memory/memory_plan.py +6 -15
  214. edsl/surveys/rules/__init__.py +5 -3
  215. edsl/surveys/rules/rule.py +1 -2
  216. edsl/surveys/rules/rule_collection.py +1 -1
  217. edsl/surveys/survey.py +12 -24
  218. edsl/surveys/survey_css.py +3 -3
  219. edsl/surveys/survey_export.py +6 -3
  220. edsl/surveys/survey_flow_visualization.py +10 -1
  221. edsl/surveys/survey_simulator.py +2 -1
  222. edsl/tasks/__init__.py +23 -1
  223. edsl/tasks/exceptions.py +72 -0
  224. edsl/tasks/question_task_creator.py +3 -3
  225. edsl/tasks/task_creators.py +1 -3
  226. edsl/tasks/task_history.py +8 -10
  227. edsl/tasks/task_status_log.py +1 -2
  228. edsl/tokens/__init__.py +29 -1
  229. edsl/tokens/exceptions.py +37 -0
  230. edsl/tokens/interview_token_usage.py +3 -2
  231. edsl/tokens/token_usage.py +4 -3
  232. edsl/utilities/__init__.py +21 -1
  233. edsl/utilities/decorators.py +1 -2
  234. edsl/utilities/markdown_to_docx.py +2 -2
  235. edsl/utilities/markdown_to_pdf.py +1 -1
  236. edsl/utilities/repair_functions.py +0 -1
  237. edsl/utilities/restricted_python.py +0 -1
  238. edsl/utilities/template_loader.py +2 -3
  239. edsl/utilities/utilities.py +8 -29
  240. {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/METADATA +32 -2
  241. edsl-0.1.51.dist-info/RECORD +365 -0
  242. edsl-0.1.51.dist-info/entry_points.txt +3 -0
  243. edsl/dataset/smart_objects.py +0 -96
  244. edsl/exceptions/BaseException.py +0 -21
  245. edsl/exceptions/__init__.py +0 -54
  246. edsl/exceptions/configuration.py +0 -16
  247. edsl/exceptions/general.py +0 -34
  248. edsl/questions/derived/__init__.py +0 -0
  249. edsl/study/ObjectEntry.py +0 -173
  250. edsl/study/ProofOfWork.py +0 -113
  251. edsl/study/SnapShot.py +0 -80
  252. edsl/study/Study.py +0 -520
  253. edsl/study/__init__.py +0 -6
  254. edsl/utilities/interface.py +0 -135
  255. edsl-0.1.49.dist-info/RECORD +0 -347
  256. {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/LICENSE +0 -0
  257. {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/WHEEL +0 -0
@@ -0,0 +1,79 @@
1
+ """
2
+ Exceptions specific to the invigilators module.
3
+
4
+ This module defines custom exception classes for all invigilator-related errors
5
+ in the EDSL framework, ensuring consistent error handling and user feedback.
6
+ """
7
+
8
+ from ..base import BaseException
9
+
10
+
11
+ class InvigilatorError(BaseException):
12
+ """
13
+ Base exception class for all invigilator-related errors.
14
+
15
+ This is the parent class for all exceptions related to invigilator creation,
16
+ execution, and management in the EDSL framework.
17
+
18
+ Examples:
19
+ ```python
20
+ # Usually not raised directly, but through subclasses
21
+ # For example, when attempting to use an unimplemented feature
22
+ invigilator.encode_image(image_path) # Would raise InvigilatorNotImplementedError
23
+ ```
24
+ """
25
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
26
+
27
+
28
+ class InvigilatorTypeError(InvigilatorError):
29
+ """
30
+ Exception raised when there's a type mismatch in invigilator operations.
31
+
32
+ This exception occurs when:
33
+ - Invalid input types are provided to invigilator methods
34
+ - Template variables have incompatible types
35
+ - Return value types don't match expected types
36
+
37
+ Examples:
38
+ ```python
39
+ # Providing an invalid type to an invigilator method
40
+ invigilator.process_options(123) # Raises InvigilatorTypeError if expected a dict
41
+ ```
42
+ """
43
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
44
+
45
+
46
+ class InvigilatorValueError(InvigilatorError):
47
+ """
48
+ Exception raised when invalid values are provided to invigilator operations.
49
+
50
+ This exception occurs when:
51
+ - Template variables are missing or invalid
52
+ - Option values don't match expected patterns
53
+ - Configuration values are outside allowed ranges
54
+
55
+ Examples:
56
+ ```python
57
+ # Template with invalid variables
58
+ invigilator.process_template("Hello {{ missing }}") # Raises InvigilatorValueError
59
+ ```
60
+ """
61
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
62
+
63
+
64
+ class InvigilatorNotImplementedError(InvigilatorError):
65
+ """
66
+ Exception raised when attempting to use an unimplemented feature.
67
+
68
+ This exception occurs when:
69
+ - Calling methods that are not implemented in a specific invigilator
70
+ - Using features only available in certain invigilator types
71
+ - Using planned functionality that is not yet available
72
+
73
+ Examples:
74
+ ```python
75
+ # Attempting to use image encoding when not implemented
76
+ invigilator.encode_image(image_path) # Raises InvigilatorNotImplementedError
77
+ ```
78
+ """
79
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
@@ -3,7 +3,6 @@ import asyncio
3
3
  from typing import Coroutine, Dict, Any, Optional, TYPE_CHECKING
4
4
 
5
5
  from ..utilities.decorators import jupyter_nb_handler
6
- from ..data_transfer_models import AgentResponseDict
7
6
 
8
7
  if TYPE_CHECKING:
9
8
  from ..prompts import Prompt
@@ -2,14 +2,12 @@
2
2
  from abc import ABC, abstractmethod
3
3
  import asyncio
4
4
  from typing import Coroutine, Dict, Any, Optional, TYPE_CHECKING
5
- from typing import Dict, Any, Optional, TYPE_CHECKING, Literal
5
+ from typing import Literal
6
6
 
7
7
  from ..utilities.decorators import sync_wrapper
8
8
  from ..questions.exceptions import QuestionAnswerValidationError
9
- from ..data_transfer_models import AgentResponseDict, EDSLResultObjectInput
9
+ from ..base.data_transfer_models import AgentResponseDict, EDSLResultObjectInput
10
10
  from ..utilities.decorators import jupyter_nb_handler
11
- from ..data_transfer_models import AgentResponseDict
12
- from ..data_transfer_models import EDSLResultObjectInput
13
11
 
14
12
  from .prompt_constructor import PromptConstructor
15
13
  from .prompt_helpers import PromptPlan
@@ -18,13 +16,10 @@ if TYPE_CHECKING:
18
16
  from ..prompts import Prompt
19
17
  from ..scenarios import Scenario
20
18
  from ..surveys import Survey
21
- from ..prompts import Prompt
22
19
  from ..caching import Cache
23
20
  from ..questions import QuestionBase
24
- from ..scenarios import Scenario
25
21
  from ..surveys.memory import MemoryPlan
26
22
  from ..language_models import LanguageModel
27
- from ..surveys import Survey
28
23
  from ..agents import Agent
29
24
  from ..key_management import KeyLookup
30
25
 
@@ -171,14 +166,14 @@ class InvigilatorBase(ABC):
171
166
  """
172
167
  data = {
173
168
  "answer": None,
174
- "generated_tokens": None,
169
+ "generated_tokens": getattr(self, "generated_tokens", None),
175
170
  "comment": failure_reason,
176
171
  "question_name": self.question.question_name,
177
172
  "prompts": self.get_prompts(),
178
- "cached_response": None,
179
- "raw_model_response": None,
180
- "cache_used": None,
181
- "cache_key": None,
173
+ "cached_response": getattr(self, "cached_response", None),
174
+ "raw_model_response": getattr(self, "raw_model_response", None),
175
+ "cache_used": getattr(self, "cache_used", None),
176
+ "cache_key": getattr(self, "cache_key", None),
182
177
  }
183
178
  return EDSLResultObjectInput(**data)
184
179
 
@@ -285,7 +280,8 @@ class InvigilatorAI(InvigilatorBase):
285
280
  }
286
281
  if "encoded_image" in prompts:
287
282
  params["encoded_image"] = prompts["encoded_image"]
288
- raise NotImplementedError("encoded_image not implemented")
283
+ from .exceptions import InvigilatorNotImplementedError
284
+ raise InvigilatorNotImplementedError("encoded_image not implemented")
289
285
 
290
286
  if "files_list" in prompts:
291
287
  params["files_list"] = prompts["files_list"]
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
- from typing import Dict, Any, Optional, Set, Union, TYPE_CHECKING, Literal
2
+ from typing import Dict, Any, Optional, TYPE_CHECKING
3
3
  from functools import cached_property
4
- import time
5
4
  import logging
6
5
 
7
6
  from ..prompts import Prompt
@@ -20,7 +19,6 @@ if TYPE_CHECKING:
20
19
  from ..agents import Agent
21
20
  from ..language_models import LanguageModel
22
21
  from ..surveys.memory import MemoryPlan
23
- from ..questions import QuestionBase
24
22
  from ..scenarios import Scenario
25
23
 
26
24
  logger = logging.getLogger(__name__)
@@ -438,8 +436,6 @@ class PromptConstructor:
438
436
 
439
437
  def get_prompts(self) -> Dict[str, Any]:
440
438
  """Get the prompts for the question."""
441
- start = time.time()
442
-
443
439
  # Build all the components
444
440
  agent_instructions = self.agent_instructions_prompt
445
441
  agent_persona = self.agent_persona_prompt
@@ -76,19 +76,22 @@ class PromptPlan:
76
76
  system_prompt_order = (system_prompt_order,)
77
77
 
78
78
  if not isinstance(user_prompt_order, tuple):
79
- raise TypeError(
79
+ from edsl.invigilators.exceptions import InvigilatorTypeError
80
+ raise InvigilatorTypeError(
80
81
  f"Expected a tuple, but got {type(user_prompt_order).__name__}"
81
82
  )
82
83
 
83
84
  if not isinstance(system_prompt_order, tuple):
84
- raise TypeError(
85
+ from edsl.invigilators.exceptions import InvigilatorTypeError
86
+ raise InvigilatorTypeError(
85
87
  f"Expected a tuple, but got {type(system_prompt_order).__name__}"
86
88
  )
87
89
 
88
90
  self.user_prompt_order = self._convert_to_enum(user_prompt_order)
89
91
  self.system_prompt_order = self._convert_to_enum(system_prompt_order)
90
92
  if not self._is_valid_plan():
91
- raise ValueError(
93
+ from edsl.invigilators.exceptions import InvigilatorValueError
94
+ raise InvigilatorValueError(
92
95
  "Invalid plan: must contain each value of PromptComponent exactly once."
93
96
  )
94
97
 
@@ -109,7 +112,8 @@ class PromptPlan:
109
112
  # check is valid components passed
110
113
  component_strings = set([pc.value for pc in PromptComponent])
111
114
  if not set(kwargs.keys()) == component_strings:
112
- raise ValueError(
115
+ from edsl.invigilators.exceptions import InvigilatorValueError
116
+ raise InvigilatorValueError(
113
117
  f"Invalid components passed: {set(kwargs.keys())} but expected {PromptComponent}"
114
118
  )
115
119
 
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Set, Any, Union, TYPE_CHECKING
1
+ from typing import Dict, Set, Any, Union, TYPE_CHECKING
2
2
  from warnings import warn
3
3
  import logging
4
4
  from ..prompts import Prompt
@@ -1,5 +1,7 @@
1
- from jinja2 import Environment, meta
2
- from typing import List, Optional, Union
1
+ from jinja2 import Environment
2
+ from typing import List, Union
3
+
4
+ import edsl.scenarios.scenario # noqa: F401
3
5
 
4
6
 
5
7
  def extract_template_variables(ast) -> List[Union[str, tuple]]:
@@ -61,7 +63,7 @@ class QuestionOptionProcessor:
61
63
 
62
64
  return cls(scenario, prior_answers_dict)
63
65
 
64
- def __init__(self, scenario: 'Scenario', prior_answers_dict: dict):
66
+ def __init__(self, scenario: 'edsl.scenarios.scenario.Scenario', prior_answers_dict: dict):
65
67
  # This handles cases where the question has {{ scenario.key }} - eventually
66
68
  # we might not allow 'naked' scenario keys w/o the scenario prefix
67
69
  #new_scenario = scenario.copy()
@@ -106,9 +108,11 @@ class QuestionOptionProcessor:
106
108
  undeclared_variables = extract_template_variables(parsed_content)
107
109
 
108
110
  if not undeclared_variables:
109
- raise ValueError("No variables found in template string")
111
+ from edsl.invigilators.exceptions import InvigilatorValueError
112
+ raise InvigilatorValueError("No variables found in template string")
110
113
  if len(undeclared_variables) > 1:
111
- raise ValueError("Multiple variables found in template string")
114
+ from edsl.invigilators.exceptions import InvigilatorValueError
115
+ raise InvigilatorValueError("Multiple variables found in template string")
112
116
 
113
117
  return undeclared_variables[0]
114
118
 
@@ -156,7 +156,8 @@ class QuestionTemplateReplacementsBuilder:
156
156
  if var == "scenario":
157
157
  # If we find a scenario variable, we need to check for nested references
158
158
  # Create a modified template with just {{ scenario.* }} expressions to isolate them
159
- scenario_template = "".join([
159
+ # Using a template format for reference, not actually used
160
+ _ = "".join([
160
161
  "{% for key, value in scenario.items() %}{{ key }}{% endfor %}"
161
162
  ])
162
163
  try:
@@ -168,7 +169,7 @@ class QuestionTemplateReplacementsBuilder:
168
169
  for key in scenario_refs:
169
170
  if key in scenario_file_keys:
170
171
  question_file_keys.append(key)
171
- except:
172
+ except Exception:
172
173
  # If there's any issue parsing, just continue with what we have
173
174
  pass
174
175
 
edsl/jobs/__init__.py CHANGED
@@ -1,7 +1,44 @@
1
- from .jobs import Jobs
2
- from .jobs import RunConfig, RunParameters, RunEnvironment
3
- from .remote_inference import JobsRemoteInferenceHandler
4
- from .jobs_runner_status import JobsRunnerStatusBase
1
+ """
2
+ The jobs module provides tools for running and managing EDSL jobs.
3
+
4
+ It includes classes for job configuration, execution, pricing estimation,
5
+ and management of concurrent language model API calls.
6
+ """
5
7
 
8
+ from .jobs import Jobs
9
+ from .jobs import RunConfig, RunParameters, RunEnvironment # noqa: F401
10
+ from .remote_inference import JobsRemoteInferenceHandler # noqa: F401
11
+ from .jobs_runner_status import JobsRunnerStatusBase # noqa: F401
12
+ from .exceptions import (
13
+ JobsErrors,
14
+ JobsRunError,
15
+ MissingRemoteInferenceError,
16
+ InterviewError,
17
+ InterviewErrorPriorTaskCanceled,
18
+ InterviewTimeoutError,
19
+ JobsValueError,
20
+ JobsCompatibilityError,
21
+ JobsImplementationError,
22
+ RemoteInferenceError,
23
+ JobsTypeError
24
+ )
6
25
 
7
- __all__ = ["Jobs"]
26
+ __all__ = [
27
+ "Jobs",
28
+ "JobsErrors",
29
+ "JobsRunError",
30
+ "MissingRemoteInferenceError",
31
+ "InterviewError",
32
+ "InterviewErrorPriorTaskCanceled",
33
+ "InterviewTimeoutError",
34
+ "JobsValueError",
35
+ "JobsCompatibilityError",
36
+ "JobsImplementationError",
37
+ "RemoteInferenceError",
38
+ "JobsTypeError",
39
+ "JobsRemoteInferenceHandler",
40
+ "JobsRunnerStatusBase",
41
+ "RunConfig",
42
+ "RunParameters",
43
+ "RunEnvironment"
44
+ ]
@@ -9,27 +9,28 @@ from collections.abc import AsyncGenerator
9
9
  from typing import List, Generator, Tuple, TYPE_CHECKING
10
10
  from dataclasses import dataclass
11
11
  import asyncio
12
- from edsl.data_transfer_models import EDSLResultObjectInput
12
+ from ..data_transfer_models import EDSLResultObjectInput
13
13
 
14
14
  from ..results import Result
15
15
  from ..interviews import Interview
16
16
  from ..config import Config
17
+ from .data_structures import RunConfig
18
+
17
19
  config = Config()
18
20
 
19
21
  if TYPE_CHECKING:
20
22
  from ..jobs import Jobs
21
23
 
22
- from .data_structures import RunConfig
23
-
24
24
  @dataclass
25
25
  class InterviewResult:
26
26
  """Container for the result of an interview along with metadata.
27
-
27
+
28
28
  Attributes:
29
29
  result: The Result object containing the interview answers
30
30
  interview: The Interview object used to conduct the interview
31
31
  order: The original position of this interview in the processing queue
32
32
  """
33
+
33
34
  result: Result
34
35
  interview: Interview
35
36
  order: int
@@ -38,10 +39,10 @@ class InterviewResult:
38
39
  class AsyncInterviewRunner:
39
40
  """
40
41
  Runs interviews asynchronously with controlled concurrency.
41
-
42
+
42
43
  This class manages the parallel execution of multiple interviews while
43
44
  respecting concurrency limits and handling errors appropriately.
44
-
45
+
45
46
  Examples:
46
47
  >>> from unittest.mock import MagicMock, AsyncMock
47
48
  >>> mock_jobs = MagicMock()
@@ -52,13 +53,13 @@ class AsyncInterviewRunner:
52
53
  >>> isinstance(runner._initialized, asyncio.Event)
53
54
  True
54
55
  """
55
-
56
+
56
57
  MAX_CONCURRENT = int(config.EDSL_MAX_CONCURRENT_TASKS)
57
58
 
58
59
  def __init__(self, jobs: "Jobs", run_config: RunConfig):
59
60
  """
60
61
  Initialize the AsyncInterviewRunner.
61
-
62
+
62
63
  Args:
63
64
  jobs: The Jobs object that generates interviews
64
65
  run_config: Configuration for running the interviews
@@ -70,13 +71,13 @@ class AsyncInterviewRunner:
70
71
  def _expand_interviews(self) -> Generator["Interview", None, None]:
71
72
  """
72
73
  Create multiple copies of each interview based on the run configuration.
73
-
74
+
74
75
  This method expands interviews for repeated runs and ensures each has
75
76
  the proper cache configuration.
76
-
77
+
77
78
  Yields:
78
79
  Interview objects ready to be conducted
79
-
80
+
80
81
  Examples:
81
82
  >>> from unittest.mock import MagicMock
82
83
  >>> mock_jobs = MagicMock()
@@ -105,16 +106,16 @@ class AsyncInterviewRunner:
105
106
  ) -> Tuple["Result", "Interview"]:
106
107
  """
107
108
  Asynchronously conduct a single interview.
108
-
109
+
109
110
  This method performs the interview and creates a Result object with
110
111
  the extracted answers and model responses.
111
-
112
+
112
113
  Args:
113
114
  interview: The interview to conduct
114
-
115
+
115
116
  Returns:
116
117
  Tuple containing the Result object and the Interview object
117
-
118
+
118
119
  Notes:
119
120
  'extracted_answers' contains the processed and validated answers
120
121
  from the interview, which may differ from the raw model output.
@@ -137,14 +138,14 @@ class AsyncInterviewRunner:
137
138
  ) -> AsyncGenerator[tuple[Result, Interview], None]:
138
139
  """
139
140
  Run all interviews asynchronously and yield results as they complete.
140
-
141
+
141
142
  This method processes interviews in chunks based on MAX_CONCURRENT,
142
143
  maintaining controlled concurrency while yielding results as soon as
143
144
  they become available.
144
-
145
+
145
146
  Yields:
146
147
  Tuples of (Result, Interview) as interviews complete
147
-
148
+
148
149
  Notes:
149
150
  - Uses structured concurrency patterns for proper resource management
150
151
  - Handles exceptions according to the run configuration
@@ -159,11 +160,11 @@ class AsyncInterviewRunner:
159
160
  try:
160
161
  result, interview = await self._conduct_interview(interview)
161
162
  self.run_config.environment.jobs_runner_status.add_completed_interview(
162
- result
163
+ interview
163
164
  )
164
165
  result.order = idx
165
166
  return InterviewResult(result, interview, idx)
166
- except Exception as e:
167
+ except Exception:
167
168
  if self.run_config.parameters.stop_on_exception:
168
169
  raise
169
170
  return None
@@ -187,7 +188,7 @@ class AsyncInterviewRunner:
187
188
  for result in (r for r in results if r is not None):
188
189
  yield result.result, result.interview
189
190
 
190
- except Exception as e:
191
+ except Exception:
191
192
  if self.run_config.parameters.stop_on_exception:
192
193
  raise
193
194
  continue
@@ -200,5 +201,6 @@ class AsyncInterviewRunner:
200
201
 
201
202
 
202
203
  if __name__ == "__main__":
203
- import doctest
204
- doctest.testmod()
204
+ import doctest
205
+
206
+ doctest.testmod()
@@ -1,11 +1,12 @@
1
1
  import warnings
2
2
  from typing import TYPE_CHECKING
3
- from edsl.scenarios import ScenarioList
4
- from edsl.surveys import Survey
3
+ from ..scenarios import ScenarioList
4
+ from ..surveys import Survey
5
+ from .exceptions import JobsCompatibilityError
5
6
 
6
7
  if TYPE_CHECKING:
7
- from edsl.surveys.Survey import Survey
8
- from edsl.scenarios.ScenarioList import ScenarioList
8
+ from ..surveys.survey import Survey
9
+ from ..scenarios.scenario_list import ScenarioList
9
10
 
10
11
 
11
12
  class CheckSurveyScenarioCompatibility:
@@ -34,19 +35,19 @@ class CheckSurveyScenarioCompatibility:
34
35
  >>> s = Scenario({'plop': "A", 'poo': "B"})
35
36
  >>> j = Jobs(survey = Survey(questions=[q])).by(s)
36
37
  >>> cs = CheckSurveyScenarioCompatibility(j.survey, j.scenarios)
37
- >>> cs.check(strict = True)
38
+ >>> cs.check(strict = True) # doctest: +ELLIPSIS
38
39
  Traceback (most recent call last):
39
40
  ...
40
- ValueError: The following parameters are in the scenarios but not in the survey: {'plop'}
41
+ edsl.jobs.exceptions.JobsCompatibilityError: The following parameters are in the scenarios but not in the survey: {'plop'}...
41
42
 
42
43
  >>> q = QuestionFreeText(question_text = "Hello", question_name = "ugly_question")
43
44
  >>> s = Scenario({'ugly_question': "B"})
44
45
  >>> from edsl.scenarios import ScenarioList
45
46
  >>> cs = CheckSurveyScenarioCompatibility(Survey(questions=[q]), ScenarioList([s]))
46
- >>> cs.check()
47
+ >>> cs.check() # doctest: +ELLIPSIS
47
48
  Traceback (most recent call last):
48
49
  ...
49
- ValueError: The following names are in both the survey question_names and the scenario keys: {'ugly_question'}. This will create issues.
50
+ edsl.jobs.exceptions.JobsCompatibilityError: The following names are in both the survey question_names and the scenario keys: {'ugly_question'}. This will create issues...
50
51
  """
51
52
  survey_parameters: set = self.survey.parameters
52
53
  scenario_parameters: set = self.scenarios.parameters
@@ -59,7 +60,7 @@ class CheckSurveyScenarioCompatibility:
59
60
  ):
60
61
  msg0 = f"The following names are in both the survey question_names and the scenario keys: {intersection}. This will create issues."
61
62
 
62
- raise ValueError(msg0)
63
+ raise JobsCompatibilityError(msg0)
63
64
 
64
65
  if in_survey_but_not_in_scenarios := survey_parameters - scenario_parameters:
65
66
  msg1 = f"The following parameters are in the survey but not in the scenarios: {in_survey_but_not_in_scenarios}"
@@ -69,7 +70,7 @@ class CheckSurveyScenarioCompatibility:
69
70
  if msg1 or msg2:
70
71
  message = "\n".join(filter(None, [msg1, msg2]))
71
72
  if strict:
72
- raise ValueError(message)
73
+ raise JobsCompatibilityError(message)
73
74
  else:
74
75
  if warn:
75
76
  warnings.warn(message)
@@ -1,5 +1,7 @@
1
- from typing import Optional, Literal
1
+ from typing import Optional, Literal, TYPE_CHECKING
2
2
  from dataclasses import dataclass, asdict
3
+ from collections import UserDict
4
+ from ..data_transfer_models import EDSLResultObjectInput
3
5
 
4
6
  # from edsl.data_transfer_models import VisibilityType
5
7
  from ..caching import Cache
@@ -9,6 +11,10 @@ from ..base import Base
9
11
 
10
12
  from .jobs_runner_status import JobsRunnerStatus
11
13
 
14
+ if TYPE_CHECKING:
15
+ from ..questions.question_base import QuestionBase
16
+ from ..surveys import Survey
17
+
12
18
  VisibilityType = Literal["private", "public", "unlisted"]
13
19
 
14
20
  @dataclass
@@ -80,7 +86,7 @@ class RunParameters(Base):
80
86
  def to_dict(self, add_edsl_version=False) -> dict:
81
87
  d = asdict(self)
82
88
  if add_edsl_version:
83
- from edsl import __version__
89
+ from .. import __version__
84
90
 
85
91
  d["edsl_version"] = __version__
86
92
  d["edsl_class_name"] = "RunConfig"
@@ -157,9 +163,6 @@ class RunConfig:
157
163
  Additional data structures for working with job results and answers.
158
164
  """
159
165
 
160
- from collections import UserDict
161
- from edsl.data_transfer_models import EDSLResultObjectInput
162
-
163
166
 
164
167
  class Answers(UserDict):
165
168
  """