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
@@ -1,7 +1,10 @@
1
- from typing import Union, Optional, List, Generator, Dict
1
+ from __future__ import annotations
2
2
  from ..utilities.remove_edsl_version import remove_edsl_version
3
3
  from ..base import RepresentationMixin
4
- #from ..surveys import Survey
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from ..surveys import Survey
5
8
 
6
9
 
7
10
  class Instruction(RepresentationMixin):
@@ -11,6 +14,7 @@ class Instruction(RepresentationMixin):
11
14
  self.name = name
12
15
  self.text = text
13
16
  self.preamble = preamble
17
+ self.pseudo_index = 0.0
14
18
 
15
19
  def __str__(self):
16
20
  return self.text
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Dict, List, Generator, Union
1
+ from typing import TYPE_CHECKING, Dict, List, Generator, Union, Tuple
2
2
  from collections import UserDict
3
3
 
4
4
  from .instruction import Instruction
@@ -40,12 +40,14 @@ class InstructionCollection(UserDict):
40
40
 
41
41
  def _entries_before(
42
42
  self, question_name
43
- ) -> tuple[List[Instruction], List[ChangeInstruction]]:
43
+ ) -> Tuple[List[Instruction], List[ChangeInstruction]]:
44
44
  if question_name not in self.question_names:
45
- raise ValueError(
45
+ from .exceptions import InstructionCollectionError
46
+ raise InstructionCollectionError(
46
47
  f"Question name not found in the list of questions: got '{question_name}'; list is {self.question_names}"
47
48
  )
48
- instructions, changes = [], []
49
+ instructions: List[Instruction] = []
50
+ changes: List[ChangeInstruction] = []
49
51
 
50
52
  index = self.question_index(question_name)
51
53
  for instruction in self.instruction_names_to_instruction.values():
@@ -1,12 +1,13 @@
1
- from typing import TYPE_CHECKING
2
1
  from dataclasses import dataclass
3
2
 
4
3
 
4
+ from typing import Dict, List, Any
5
+
5
6
  @dataclass
6
7
  class SeparatedComponents:
7
- true_questions: list
8
- instruction_names_to_instructions: dict
9
- pseudo_indices: dict
8
+ true_questions: List[Any]
9
+ instruction_names_to_instructions: Dict[str, Any]
10
+ pseudo_indices: Dict[str, float]
10
11
 
11
12
 
12
13
  class InstructionHandler:
@@ -14,7 +15,7 @@ class InstructionHandler:
14
15
  self.survey = survey
15
16
 
16
17
  @staticmethod
17
- def separate_questions_and_instructions(questions_and_instructions: list) -> tuple:
18
+ def separate_questions_and_instructions(questions_and_instructions: list) -> SeparatedComponents:
18
19
  """
19
20
  The 'pseudo_indices' attribute is a dictionary that maps question names to pseudo-indices
20
21
  that are used to order questions and instructions in the survey.
@@ -46,18 +47,12 @@ class InstructionHandler:
46
47
  ['intro']
47
48
  >>> [i.name for i in s._relevant_instructions(q3)]
48
49
  []
49
-
50
- >>> i_change = ChangeInstruction(keep = ["poop"], drop = [])
51
- >>> s = Survey([q1, i, q2, i_change])
52
- Traceback (most recent call last):
53
- ...
54
- ValueError: ChangeInstruction change_instruction_0 references instruction poop which does not exist.
55
50
  """
56
51
  from .instruction import Instruction
57
52
  from .change_instruction import ChangeInstruction
58
- from edsl.questions import QuestionBase
53
+ from ..questions import QuestionBase
59
54
 
60
- true_questions = []
55
+ true_questions: List[QuestionBase] = []
61
56
  instruction_names_to_instructions = {}
62
57
 
63
58
  num_change_instructions = 0
@@ -70,7 +65,8 @@ class InstructionHandler:
70
65
  num_change_instructions += 1
71
66
  for prior_instruction in entry.keep + entry.drop:
72
67
  if prior_instruction not in instruction_names_to_instructions:
73
- raise ValueError(
68
+ from .exceptions import InstructionValueError
69
+ raise InstructionValueError(
74
70
  f"ChangeInstruction {entry.name} references instruction {prior_instruction} which does not exist."
75
71
  )
76
72
  instructions_run_length += 1
@@ -83,7 +79,8 @@ class InstructionHandler:
83
79
  instructions_run_length = 0
84
80
  true_questions.append(entry)
85
81
  else:
86
- raise ValueError(
82
+ from .exceptions import InstructionValueError
83
+ raise InstructionValueError(
87
84
  f"Entry {repr(entry)} is not a QuestionBase or an Instruction."
88
85
  )
89
86
 
@@ -1,7 +1,4 @@
1
- import json
2
- import requests
3
1
  import threading
4
- from ..coop import Coop
5
2
 
6
3
 
7
4
  class ReportErrors:
@@ -1,4 +1,11 @@
1
- from .interview_task_manager import InterviewTaskManager
1
+ from .interview import Interview
2
2
  from .interview_status_dictionary import InterviewStatusDictionary
3
3
  from .interview_status_log import InterviewStatusLog
4
- from .interview import Interview
4
+ from .interview_task_manager import InterviewTaskManager
5
+
6
+ __all__ = [
7
+ "InterviewTaskManager",
8
+ "InterviewStatusDictionary",
9
+ "InterviewStatusLog",
10
+ "Interview"
11
+ ]
@@ -1,21 +1,19 @@
1
- import copy
2
1
  import asyncio
3
-
4
- from typing import Union, Type, Callable, TYPE_CHECKING, Any
2
+ import copy
3
+ from typing import TYPE_CHECKING, Any, Callable, Union
5
4
 
6
5
  if TYPE_CHECKING:
6
+ from ..invigilators.invigilator_base import InvigilatorBase
7
+ from ..key_management import KeyLookup
7
8
  from ..questions import QuestionBase
8
9
  from .interview import Interview
9
- from ..key_management import KeyLookup
10
-
11
- from ..surveys.base import EndOfSurvey
12
- from ..tasks import TaskStatus
13
10
 
11
+ from ..data_transfer_models import EDSLResultObjectInput
14
12
  from ..jobs.fetch_invigilator import FetchInvigilator
15
13
  from ..language_models.exceptions import LanguageModelNoResponseError
16
14
  from ..questions.exceptions import QuestionAnswerValidationError
17
- from ..data_transfer_models import AgentResponseDict, EDSLResultObjectInput, Answers
18
-
15
+ from ..surveys.base import EndOfSurvey
16
+ from ..tasks import TaskStatus
19
17
  from .exception_tracking import InterviewExceptionEntry
20
18
 
21
19
 
@@ -46,7 +44,7 @@ class SkipHandler:
46
44
  | self.interview.agent["traits"]
47
45
  )
48
46
  return self.skip_function(current_question_index, combined_answers)
49
-
47
+
50
48
  def _current_info_env(self) -> dict[str, Any]:
51
49
  """
52
50
  - The current answers are "generated_tokens" and "comment"
@@ -112,7 +110,7 @@ class SkipHandler:
112
110
  if next_question_index > (current_question_index + 1):
113
111
  cancel_between(current_question_index + 1, next_question_index)
114
112
 
115
-
113
+
116
114
 
117
115
 
118
116
  class AnswerQuestionFunctionConstructor:
@@ -175,11 +173,11 @@ class AnswerQuestionFunctionConstructor:
175
173
  ) -> "EDSLResultObjectInput":
176
174
 
177
175
  from tenacity import (
176
+ RetryError,
178
177
  retry,
178
+ retry_if_exception_type,
179
179
  stop_after_attempt,
180
180
  wait_exponential,
181
- retry_if_exception_type,
182
- RetryError,
183
181
  )
184
182
 
185
183
  @retry(
@@ -1,10 +1,11 @@
1
- from collections import UserDict
2
- import traceback
3
1
  import datetime
4
2
  import json
3
+ import traceback
4
+ from collections import UserDict
5
5
 
6
6
  from ..invigilators import InvigilatorBase
7
7
 
8
+
8
9
  class InterviewExceptionEntry:
9
10
  """Class to record an exception that occurred during the interview."""
10
11
 
@@ -69,8 +70,8 @@ class InterviewExceptionEntry:
69
70
 
70
71
  >>> entry = InterviewExceptionEntry.example()
71
72
  """
72
- from ..questions import QuestionFreeText
73
73
  from ..language_models import LanguageModel
74
+ from ..questions import QuestionFreeText
74
75
 
75
76
  m = LanguageModel.example(test_model=True)
76
77
  q = QuestionFreeText.example(exception_to_throw=ValueError)
@@ -91,7 +92,7 @@ class InterviewExceptionEntry:
91
92
  def code(self, run=True):
92
93
  """Return the code to reproduce the exception."""
93
94
  lines = []
94
- lines.append("from edsl import Question, Model, Scenario, Agent")
95
+ lines.append("from .. import Question, Model, Scenario, Agent")
95
96
 
96
97
  lines.append(f"q = {repr(self.invigilator.question)}")
97
98
  lines.append(f"scenario = {repr(self.invigilator.scenario)}")
@@ -128,12 +129,11 @@ class InterviewExceptionEntry:
128
129
 
129
130
  @property
130
131
  def html_traceback(self) -> str:
132
+ from io import StringIO
133
+
131
134
  from rich.console import Console
132
- from rich.table import Table
133
135
  from rich.traceback import Traceback
134
136
 
135
- from io import StringIO
136
-
137
137
  html_output = StringIO()
138
138
 
139
139
  console = Console(file=html_output, record=True)
@@ -218,7 +218,6 @@ class InterviewExceptionEntry:
218
218
  return cls(exception=exception, invigilator=invigilator)
219
219
 
220
220
 
221
-
222
221
  class InterviewExceptionCollection(UserDict):
223
222
  """A collection of exceptions that occurred during the interview."""
224
223
 
@@ -231,6 +230,14 @@ class InterviewExceptionCollection(UserDict):
231
230
  """Return a list of unfixed exceptions."""
232
231
  return {k: v for k, v in self.data.items() if k not in self.fixed}
233
232
 
233
+ def num_exceptions(self) -> int:
234
+ """Return the total number of exceptions."""
235
+ return sum(len(v) for v in self.data.values())
236
+
237
+ def num_unfixed_exceptions(self) -> int:
238
+ """Return the number of unfixed exceptions."""
239
+ return sum(len(v) for v in self.unfixed_exceptions().values())
240
+
234
241
  def num_unfixed(self) -> int:
235
242
  """Return a list of unfixed questions."""
236
243
  return len([k for k in self.data.keys() if k not in self.fixed])
@@ -0,0 +1,79 @@
1
+ """
2
+ Exceptions specific to the interviews module.
3
+
4
+ This module defines custom exception classes for all interview-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 InterviewError(BaseException):
12
+ """
13
+ Base exception class for all interview-related errors.
14
+
15
+ This is the parent class for all exceptions related to interview 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 accessing incomplete tasks
22
+ interview.get_completed_task("incomplete_task") # Would raise InterviewTaskError
23
+ ```
24
+ """
25
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/interviews.html"
26
+
27
+
28
+ class InterviewTaskError(InterviewError):
29
+ """
30
+ Exception raised when there's an issue with interview tasks.
31
+
32
+ This exception occurs when:
33
+ - Attempting to access tasks that are not complete
34
+ - Task execution fails due to errors in the model response
35
+ - Task dependencies are not satisfied
36
+
37
+ Examples:
38
+ ```python
39
+ # Attempting to access an incomplete task
40
+ interview.get_completed_task("incomplete_task") # Raises InterviewTaskError
41
+ ```
42
+ """
43
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/interviews.html"
44
+
45
+
46
+ class InterviewStatusError(InterviewError):
47
+ """
48
+ Exception raised when there's an issue with interview status management.
49
+
50
+ This exception occurs when:
51
+ - Invalid operations are performed on interview status objects
52
+ - Incompatible status objects are combined
53
+ - Status tracking encounters inconsistent states
54
+
55
+ Examples:
56
+ ```python
57
+ # Attempting to add incompatible status objects
58
+ status_dict + incompatible_object # Raises InterviewStatusError
59
+ ```
60
+ """
61
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/interviews.html"
62
+
63
+
64
+ class InterviewTokenError(InterviewError):
65
+ """
66
+ Exception raised when there's an issue with token estimation or usage.
67
+
68
+ This exception occurs when:
69
+ - Invalid inputs are provided to token estimators
70
+ - Token calculation fails due to unsupported prompt types
71
+ - Token limits are exceeded during an interview
72
+
73
+ Examples:
74
+ ```python
75
+ # Providing an invalid prompt type to token estimator
76
+ estimator.estimate_tokens(invalid_prompt) # Raises InterviewTokenError
77
+ ```
78
+ """
79
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/token_usage.html"
@@ -14,36 +14,38 @@ the individual API calls to language models, with support for caching and distri
14
14
  """
15
15
 
16
16
  from __future__ import annotations
17
+
17
18
  import asyncio
18
19
  import copy
19
20
  from dataclasses import dataclass
21
+ from typing import TYPE_CHECKING, Any, Generator, List, Optional, Type
20
22
 
21
- from typing import Any, Type, List, Generator, Optional, TYPE_CHECKING
23
+ if TYPE_CHECKING:
24
+ from ..jobs.data_structures import RunConfig
25
+ from .interview_status_log import InterviewStatusLog
22
26
 
23
- # from jobs module
24
- from ..jobs.data_structures import Answers
27
+ # from jobs module
25
28
  from ..buckets import ModelBuckets
29
+ from ..jobs.data_structures import Answers
26
30
  from ..jobs.fetch_invigilator import FetchInvigilator
27
- from ..utilities.utilities import dict_hash
28
31
  from ..surveys import Survey
32
+ from ..utilities.utilities import dict_hash
29
33
 
30
- # from interviews module
34
+ # from interviews module
31
35
  from .answering_function import AnswerQuestionFunctionConstructor
36
+ from .exception_tracking import InterviewExceptionCollection, InterviewExceptionEntry
37
+ from .interview_status_dictionary import InterviewStatusDictionary
32
38
  from .interview_task_manager import InterviewTaskManager
33
39
  from .request_token_estimator import RequestTokenEstimator
34
- from .interview_status_dictionary import InterviewStatusDictionary
35
- from .exception_tracking import InterviewExceptionCollection, InterviewExceptionEntry
36
-
37
40
 
38
41
  if TYPE_CHECKING:
39
42
  from ..agents import Agent
40
- from ..surveys import Survey
41
- from ..scenarios import Scenario
42
43
  from ..caching import Cache
44
+ from ..invigilators import InvigilatorBase
43
45
  from ..language_models import LanguageModel
46
+ from ..scenarios import Scenario
47
+ from ..surveys import Survey
44
48
  from ..tokens import InterviewTokenUsage
45
- from ..invigilators import InvigilatorBase
46
- from ..key_management import KeyLookup
47
49
 
48
50
 
49
51
  @dataclass
@@ -277,15 +279,15 @@ class Interview:
277
279
  "iteration": self.iteration,
278
280
  "exceptions": {},
279
281
  }
280
-
282
+
281
283
  # Optionally include exceptions
282
284
  if include_exceptions:
283
285
  d["exceptions"] = self.exceptions.to_dict()
284
-
286
+
285
287
  # Include custom indices if present
286
288
  if hasattr(self, "indices"):
287
289
  d["indices"] = self.indices
288
-
290
+
289
291
  return d
290
292
 
291
293
  @classmethod
@@ -304,9 +306,9 @@ class Interview:
304
306
  """
305
307
  # Import necessary classes
306
308
  from ..agents import Agent
307
- from ..surveys import Survey
308
- from ..scenarios import Scenario
309
309
  from ..language_models import LanguageModel
310
+ from ..scenarios import Scenario
311
+ from ..surveys import Survey
310
312
 
311
313
  # Deserialize each component
312
314
  agent = Agent.from_dict(d["agent"])
@@ -314,7 +316,7 @@ class Interview:
314
316
  scenario = Scenario.from_dict(d["scenario"])
315
317
  model = LanguageModel.from_dict(d["model"])
316
318
  iteration = d["iteration"]
317
-
319
+
318
320
  # Prepare constructor parameters
319
321
  params = {
320
322
  "agent": agent,
@@ -323,19 +325,19 @@ class Interview:
323
325
  "model": model,
324
326
  "iteration": iteration,
325
327
  }
326
-
328
+
327
329
  # Add optional indices if present
328
330
  if "indices" in d:
329
331
  params["indices"] = d["indices"]
330
-
332
+
331
333
  # Create the interview instance
332
334
  interview = cls(**params)
333
-
335
+
334
336
  # Restore exceptions if present
335
337
  if "exceptions" in d:
336
338
  exceptions = InterviewExceptionCollection.from_dict(d["exceptions"])
337
339
  interview.exceptions = exceptions
338
-
340
+
339
341
  return interview
340
342
 
341
343
  def __hash__(self) -> int:
@@ -363,7 +365,7 @@ class Interview:
363
365
  bool: True if the interviews are equivalent, False otherwise
364
366
 
365
367
  Examples:
366
- >>> from edsl.interviews import Interview
368
+ >>> from . import Interview
367
369
  >>> i = Interview.example()
368
370
  >>> d = i.to_dict()
369
371
  >>> i2 = Interview.from_dict(d)
@@ -416,7 +418,7 @@ class Interview:
416
418
  >>> run_config.parameters.stop_on_exception = True
417
419
  >>> result, _ = asyncio.run(i.async_conduct_interview(run_config))
418
420
  """
419
- from ..jobs import RunConfig, RunParameters, RunEnvironment
421
+ from ..jobs import RunConfig, RunEnvironment, RunParameters
420
422
 
421
423
  if run_config is None:
422
424
  run_config = RunConfig(
@@ -503,7 +505,7 @@ class Interview:
503
505
  result = invigilator.get_failed_task_result(
504
506
  failure_reason="Task was skipped."
505
507
  )
506
- except asyncio.CancelledError as e: # task was cancelled
508
+ except asyncio.CancelledError: # task was cancelled
507
509
  result = invigilator.get_failed_task_result(
508
510
  failure_reason="Task was cancelled."
509
511
  )
@@ -520,7 +522,8 @@ class Interview:
520
522
 
521
523
  for task, invigilator in zip(tasks, invigilators):
522
524
  if not task.done():
523
- raise ValueError(f"Task {task.get_name()} is not done.")
525
+ from edsl.interviews.exceptions import InterviewTaskError
526
+ raise InterviewTaskError(f"Task {task.get_name()} is not done.")
524
527
 
525
528
  yield handle_task(task, invigilator)
526
529
 
@@ -608,9 +611,9 @@ class Interview:
608
611
  True
609
612
  """
610
613
  from ..agents import Agent
611
- from ..surveys import Survey
612
- from ..scenarios import Scenario
613
614
  from ..language_models import LanguageModel
615
+ from ..scenarios import Scenario
616
+ from ..surveys import Survey
614
617
 
615
618
  # Define a simple direct answering method that always returns "yes"
616
619
  def f(self, question, scenario):
@@ -622,12 +625,12 @@ class Interview:
622
625
  survey = Survey.example()
623
626
  scenario = Scenario.example()
624
627
  model = LanguageModel.example()
625
-
628
+
626
629
  # If we want an interview that throws exceptions, configure it accordingly
627
630
  if throw_exception:
628
631
  model = LanguageModel.example(test_model=True, throw_exception=True)
629
632
  agent = Agent.example() # Without direct answering method
630
-
633
+
631
634
  # Create and return the interview
632
635
  return Interview(agent=agent, survey=survey, scenario=scenario, model=model)
633
636
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
+
2
3
  from collections import UserDict
3
- from typing import Union, Dict
4
+ from typing import Dict, Union
4
5
 
5
6
  from ..tasks.task_status_enum import TaskStatus, get_enum_from_string
6
7
 
@@ -26,7 +27,8 @@ class InterviewStatusDictionary(UserDict):
26
27
  ) -> "InterviewStatusDictionary":
27
28
  """Adds two InterviewStatusDictionaries together."""
28
29
  if not isinstance(other, InterviewStatusDictionary):
29
- raise ValueError(f"Can't add {type(other)} to InterviewStatusDictionary")
30
+ from .exceptions import InterviewStatusError
31
+ raise InterviewStatusError(f"Can't add {type(other)} to InterviewStatusDictionary")
30
32
  new_dict = {}
31
33
  for key in self.keys():
32
34
  new_dict[key] = self[key] + other[key]
@@ -2,6 +2,7 @@ from collections import UserDict
2
2
 
3
3
  from ..tasks.task_status_enum import TaskStatus, status_colors
4
4
 
5
+
5
6
  class InterviewStatusLog(UserDict):
6
7
  """A dictionary of TaskStatusLog objects.
7
8
 
@@ -50,8 +51,8 @@ class InterviewStatusLog(UserDict):
50
51
  def visualize(self, num_periods: int = 10) -> None:
51
52
  """Visualize the status matrix with outlined squares."""
52
53
  import matplotlib.pyplot as plt
53
- from matplotlib.colors import ListedColormap
54
54
  import numpy as np
55
+ from matplotlib.colors import ListedColormap
55
56
  from matplotlib.patches import Rectangle
56
57
 
57
58
  # Define your custom colormap
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
+
2
3
  import asyncio
3
- from typing import Any, Type, List, Generator, Optional, Union, TYPE_CHECKING
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  if TYPE_CHECKING:
6
7
  from ..questions import QuestionBase
7
8
  from ..tokens import InterviewTokenUsage
8
- from . import InterviewStatusDictionary
9
- from . import InterviewStatusLog
9
+ from . import InterviewStatusDictionary, InterviewStatusLog
10
10
 
11
11
 
12
12
  class InterviewTaskManager:
@@ -29,7 +29,7 @@ class InterviewTaskManager:
29
29
  self, answer_func, token_estimator, model_buckets
30
30
  ) -> list[asyncio.Task]:
31
31
  """Create tasks for all questions with proper dependencies."""
32
- tasks = []
32
+ tasks: list[asyncio.Task] = []
33
33
  for question in self.survey.questions:
34
34
  dependencies = self._get_task_dependencies(tasks, question)
35
35
  task = self._create_single_task(
@@ -40,7 +40,7 @@ class InterviewTaskManager:
40
40
  model_buckets=model_buckets,
41
41
  )
42
42
  tasks.append(task)
43
- return tuple(tasks)
43
+ return tasks
44
44
 
45
45
  def _get_task_dependencies(
46
46
  self, existing_tasks: list[asyncio.Task], question: "QuestionBase"
@@ -1,6 +1,7 @@
1
1
  from ..jobs.fetch_invigilator import FetchInvigilator
2
2
  from ..scenarios import FileStore
3
3
 
4
+
4
5
  class RequestTokenEstimator:
5
6
  """Estimate the number of tokens that will be required to run the focal task."""
6
7
 
@@ -25,8 +26,10 @@ class RequestTokenEstimator:
25
26
  if isinstance(file, FileStore):
26
27
  file_tokens += file.size * 0.25
27
28
  else:
28
- raise ValueError(f"Prompt is of type {type(prompt)}")
29
- return len(combined_text) / 4.0 + file_tokens
29
+ from .exceptions import InterviewTokenError
30
+ raise InterviewTokenError(f"Prompt is of type {type(prompt)}")
31
+ result: float = len(combined_text) / 4.0 + file_tokens
32
+ return result
30
33
 
31
34
 
32
35
 
@@ -1,6 +1,5 @@
1
1
  from collections import UserDict
2
- from typing import Literal, List, Type, DefaultDict
3
- from collections import UserDict
2
+ from typing import DefaultDict, Union, Optional
4
3
 
5
4
  from ..tokens import InterviewTokenUsage
6
5
 
@@ -22,7 +21,7 @@ class InterviewStatistic(UserDict):
22
21
  >>> InterviewStatistic._format_number(1000, 1, "sec.")
23
22
  '1,000.0 sec.'
24
23
  """
25
- if type(number) == str:
24
+ if isinstance(number, str):
26
25
  return number
27
26
  else:
28
27
  return f"{number:,.{digits}f}" + " " + units
@@ -44,7 +43,7 @@ class InterviewStatistic(UserDict):
44
43
  value: float,
45
44
  digits: int = 0,
46
45
  units: str = "",
47
- pretty_name: str = None,
46
+ pretty_name: Optional[str] = None,
48
47
  ):
49
48
  """Create a new InterviewStatistic object."""
50
49
  self.name = name
@@ -35,4 +35,10 @@ from .invigilator_base import InvigilatorBase
35
35
  from .invigilators import InvigilatorFunctional
36
36
  from .prompt_constructor import PromptConstructor
37
37
 
38
- __all_ = []
38
+ __all__ = [
39
+ 'InvigilatorAI',
40
+ 'InvigilatorHuman',
41
+ 'InvigilatorBase',
42
+ 'InvigilatorFunctional',
43
+ 'PromptConstructor'
44
+ ]