edsl 0.1.48__py3-none-any.whl → 0.1.50__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 (239) 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 +75 -0
  14. edsl/buckets/model_buckets.py +1 -2
  15. edsl/buckets/token_bucket.py +11 -6
  16. edsl/buckets/token_bucket_api.py +1 -2
  17. edsl/buckets/token_bucket_client.py +9 -7
  18. edsl/caching/cache.py +7 -2
  19. edsl/caching/cache_entry.py +10 -9
  20. edsl/caching/exceptions.py +113 -7
  21. edsl/caching/remote_cache_sync.py +1 -2
  22. edsl/caching/sql_dict.py +17 -12
  23. edsl/cli.py +43 -0
  24. edsl/config/config_class.py +30 -6
  25. edsl/conversation/Conversation.py +3 -2
  26. edsl/conversation/exceptions.py +58 -0
  27. edsl/conversation/mug_negotiation.py +0 -2
  28. edsl/coop/__init__.py +20 -1
  29. edsl/coop/coop.py +129 -38
  30. edsl/coop/exceptions.py +188 -9
  31. edsl/coop/price_fetcher.py +3 -6
  32. edsl/coop/utils.py +4 -6
  33. edsl/dataset/__init__.py +5 -4
  34. edsl/dataset/dataset.py +53 -43
  35. edsl/dataset/dataset_operations_mixin.py +86 -72
  36. edsl/dataset/dataset_tree.py +9 -5
  37. edsl/dataset/display/table_display.py +0 -2
  38. edsl/dataset/display/table_renderers.py +0 -1
  39. edsl/dataset/exceptions.py +125 -0
  40. edsl/dataset/file_exports.py +18 -11
  41. edsl/dataset/r/ggplot.py +13 -6
  42. edsl/display/__init__.py +27 -0
  43. edsl/display/core.py +147 -0
  44. edsl/display/plugin.py +189 -0
  45. edsl/display/utils.py +52 -0
  46. edsl/inference_services/__init__.py +9 -1
  47. edsl/inference_services/available_model_cache_handler.py +1 -1
  48. edsl/inference_services/available_model_fetcher.py +4 -5
  49. edsl/inference_services/data_structures.py +9 -6
  50. edsl/inference_services/exceptions.py +132 -1
  51. edsl/inference_services/inference_service_abc.py +2 -2
  52. edsl/inference_services/inference_services_collection.py +2 -6
  53. edsl/inference_services/registry.py +4 -3
  54. edsl/inference_services/service_availability.py +2 -1
  55. edsl/inference_services/services/anthropic_service.py +4 -1
  56. edsl/inference_services/services/aws_bedrock.py +13 -12
  57. edsl/inference_services/services/azure_ai.py +12 -10
  58. edsl/inference_services/services/deep_infra_service.py +1 -4
  59. edsl/inference_services/services/deep_seek_service.py +1 -5
  60. edsl/inference_services/services/google_service.py +6 -2
  61. edsl/inference_services/services/groq_service.py +1 -1
  62. edsl/inference_services/services/mistral_ai_service.py +4 -2
  63. edsl/inference_services/services/ollama_service.py +1 -1
  64. edsl/inference_services/services/open_ai_service.py +7 -5
  65. edsl/inference_services/services/perplexity_service.py +6 -2
  66. edsl/inference_services/services/test_service.py +8 -7
  67. edsl/inference_services/services/together_ai_service.py +2 -3
  68. edsl/inference_services/services/xai_service.py +1 -1
  69. edsl/instructions/__init__.py +1 -1
  70. edsl/instructions/change_instruction.py +3 -2
  71. edsl/instructions/exceptions.py +61 -0
  72. edsl/instructions/instruction.py +5 -2
  73. edsl/instructions/instruction_collection.py +2 -1
  74. edsl/instructions/instruction_handler.py +4 -9
  75. edsl/interviews/ReportErrors.py +0 -3
  76. edsl/interviews/__init__.py +9 -2
  77. edsl/interviews/answering_function.py +11 -13
  78. edsl/interviews/exception_tracking.py +14 -7
  79. edsl/interviews/exceptions.py +79 -0
  80. edsl/interviews/interview.py +32 -29
  81. edsl/interviews/interview_status_dictionary.py +4 -2
  82. edsl/interviews/interview_status_log.py +2 -1
  83. edsl/interviews/interview_task_manager.py +3 -3
  84. edsl/interviews/request_token_estimator.py +3 -1
  85. edsl/interviews/statistics.py +2 -3
  86. edsl/invigilators/__init__.py +7 -1
  87. edsl/invigilators/exceptions.py +79 -0
  88. edsl/invigilators/invigilator_base.py +0 -1
  89. edsl/invigilators/invigilators.py +8 -12
  90. edsl/invigilators/prompt_constructor.py +1 -5
  91. edsl/invigilators/prompt_helpers.py +8 -4
  92. edsl/invigilators/question_instructions_prompt_builder.py +1 -1
  93. edsl/invigilators/question_option_processor.py +9 -5
  94. edsl/invigilators/question_template_replacements_builder.py +3 -2
  95. edsl/jobs/__init__.py +3 -3
  96. edsl/jobs/async_interview_runner.py +24 -22
  97. edsl/jobs/check_survey_scenario_compatibility.py +7 -6
  98. edsl/jobs/data_structures.py +7 -4
  99. edsl/jobs/exceptions.py +177 -8
  100. edsl/jobs/fetch_invigilator.py +1 -1
  101. edsl/jobs/jobs.py +72 -67
  102. edsl/jobs/jobs_checks.py +2 -3
  103. edsl/jobs/jobs_component_constructor.py +2 -2
  104. edsl/jobs/jobs_pricing_estimation.py +3 -2
  105. edsl/jobs/jobs_remote_inference_logger.py +5 -4
  106. edsl/jobs/jobs_runner_asyncio.py +1 -2
  107. edsl/jobs/jobs_runner_status.py +8 -9
  108. edsl/jobs/remote_inference.py +26 -23
  109. edsl/jobs/results_exceptions_handler.py +8 -5
  110. edsl/key_management/__init__.py +3 -1
  111. edsl/key_management/exceptions.py +62 -0
  112. edsl/key_management/key_lookup.py +1 -1
  113. edsl/key_management/key_lookup_builder.py +37 -14
  114. edsl/key_management/key_lookup_collection.py +2 -0
  115. edsl/language_models/__init__.py +1 -1
  116. edsl/language_models/exceptions.py +302 -14
  117. edsl/language_models/language_model.py +4 -7
  118. edsl/language_models/model.py +4 -4
  119. edsl/language_models/model_list.py +1 -1
  120. edsl/language_models/price_manager.py +1 -1
  121. edsl/language_models/raw_response_handler.py +14 -9
  122. edsl/language_models/registry.py +17 -21
  123. edsl/language_models/repair.py +0 -6
  124. edsl/language_models/unused/fake_openai_service.py +0 -1
  125. edsl/load_plugins.py +69 -0
  126. edsl/logger.py +146 -0
  127. edsl/notebooks/notebook.py +1 -1
  128. edsl/notebooks/notebook_to_latex.py +0 -1
  129. edsl/plugins/__init__.py +63 -0
  130. edsl/plugins/built_in/export_example.py +50 -0
  131. edsl/plugins/built_in/pig_latin.py +67 -0
  132. edsl/plugins/cli.py +372 -0
  133. edsl/plugins/cli_typer.py +283 -0
  134. edsl/plugins/exceptions.py +31 -0
  135. edsl/plugins/hookspec.py +51 -0
  136. edsl/plugins/plugin_host.py +128 -0
  137. edsl/plugins/plugin_manager.py +633 -0
  138. edsl/plugins/plugins_registry.py +168 -0
  139. edsl/prompts/__init__.py +2 -0
  140. edsl/prompts/exceptions.py +107 -5
  141. edsl/prompts/prompt.py +14 -6
  142. edsl/questions/HTMLQuestion.py +5 -11
  143. edsl/questions/Quick.py +0 -1
  144. edsl/questions/__init__.py +2 -0
  145. edsl/questions/answer_validator_mixin.py +318 -318
  146. edsl/questions/compose_questions.py +2 -2
  147. edsl/questions/descriptors.py +10 -49
  148. edsl/questions/exceptions.py +278 -22
  149. edsl/questions/loop_processor.py +7 -5
  150. edsl/questions/prompt_templates/question_list.jinja +3 -0
  151. edsl/questions/question_base.py +14 -16
  152. edsl/questions/question_base_gen_mixin.py +2 -2
  153. edsl/questions/question_base_prompts_mixin.py +9 -3
  154. edsl/questions/question_budget.py +9 -5
  155. edsl/questions/question_check_box.py +3 -5
  156. edsl/questions/question_dict.py +171 -194
  157. edsl/questions/question_extract.py +1 -1
  158. edsl/questions/question_free_text.py +4 -6
  159. edsl/questions/question_functional.py +4 -3
  160. edsl/questions/question_list.py +36 -9
  161. edsl/questions/question_matrix.py +95 -61
  162. edsl/questions/question_multiple_choice.py +6 -4
  163. edsl/questions/question_numerical.py +2 -4
  164. edsl/questions/question_registry.py +4 -2
  165. edsl/questions/register_questions_meta.py +0 -1
  166. edsl/questions/response_validator_abc.py +7 -13
  167. edsl/questions/templates/dict/answering_instructions.jinja +1 -0
  168. edsl/questions/templates/rank/question_presentation.jinja +1 -1
  169. edsl/results/__init__.py +1 -1
  170. edsl/results/exceptions.py +141 -7
  171. edsl/results/report.py +0 -1
  172. edsl/results/result.py +4 -5
  173. edsl/results/results.py +10 -51
  174. edsl/results/results_selector.py +8 -4
  175. edsl/scenarios/PdfExtractor.py +2 -2
  176. edsl/scenarios/construct_download_link.py +69 -35
  177. edsl/scenarios/directory_scanner.py +33 -14
  178. edsl/scenarios/document_chunker.py +1 -1
  179. edsl/scenarios/exceptions.py +238 -14
  180. edsl/scenarios/file_methods.py +1 -1
  181. edsl/scenarios/file_store.py +7 -3
  182. edsl/scenarios/handlers/__init__.py +17 -0
  183. edsl/scenarios/handlers/docx_file_store.py +0 -5
  184. edsl/scenarios/handlers/pdf_file_store.py +0 -1
  185. edsl/scenarios/handlers/pptx_file_store.py +0 -5
  186. edsl/scenarios/handlers/py_file_store.py +0 -1
  187. edsl/scenarios/handlers/sql_file_store.py +1 -4
  188. edsl/scenarios/handlers/sqlite_file_store.py +0 -1
  189. edsl/scenarios/handlers/txt_file_store.py +1 -1
  190. edsl/scenarios/scenario.py +0 -1
  191. edsl/scenarios/scenario_list.py +152 -18
  192. edsl/scenarios/scenario_list_pdf_tools.py +1 -0
  193. edsl/scenarios/scenario_selector.py +0 -1
  194. edsl/surveys/__init__.py +3 -4
  195. edsl/surveys/dag/__init__.py +4 -2
  196. edsl/surveys/descriptors.py +1 -1
  197. edsl/surveys/edit_survey.py +1 -0
  198. edsl/surveys/exceptions.py +165 -9
  199. edsl/surveys/memory/__init__.py +5 -3
  200. edsl/surveys/memory/memory_management.py +1 -0
  201. edsl/surveys/memory/memory_plan.py +6 -15
  202. edsl/surveys/rules/__init__.py +5 -3
  203. edsl/surveys/rules/rule.py +1 -2
  204. edsl/surveys/rules/rule_collection.py +1 -1
  205. edsl/surveys/survey.py +12 -24
  206. edsl/surveys/survey_export.py +6 -3
  207. edsl/surveys/survey_flow_visualization.py +10 -1
  208. edsl/tasks/__init__.py +2 -0
  209. edsl/tasks/question_task_creator.py +3 -3
  210. edsl/tasks/task_creators.py +1 -3
  211. edsl/tasks/task_history.py +5 -7
  212. edsl/tasks/task_status_log.py +1 -2
  213. edsl/tokens/__init__.py +3 -1
  214. edsl/tokens/token_usage.py +1 -1
  215. edsl/utilities/__init__.py +21 -1
  216. edsl/utilities/decorators.py +1 -2
  217. edsl/utilities/markdown_to_docx.py +2 -2
  218. edsl/utilities/markdown_to_pdf.py +1 -1
  219. edsl/utilities/repair_functions.py +0 -1
  220. edsl/utilities/restricted_python.py +0 -1
  221. edsl/utilities/template_loader.py +2 -3
  222. edsl/utilities/utilities.py +8 -29
  223. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/METADATA +32 -2
  224. edsl-0.1.50.dist-info/RECORD +363 -0
  225. edsl-0.1.50.dist-info/entry_points.txt +3 -0
  226. edsl/dataset/smart_objects.py +0 -96
  227. edsl/exceptions/BaseException.py +0 -21
  228. edsl/exceptions/__init__.py +0 -54
  229. edsl/exceptions/configuration.py +0 -16
  230. edsl/exceptions/general.py +0 -34
  231. edsl/study/ObjectEntry.py +0 -173
  232. edsl/study/ProofOfWork.py +0 -113
  233. edsl/study/SnapShot.py +0 -80
  234. edsl/study/Study.py +0 -520
  235. edsl/study/__init__.py +0 -6
  236. edsl/utilities/interface.py +0 -135
  237. edsl-0.1.48.dist-info/RECORD +0 -347
  238. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/LICENSE +0 -0
  239. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/WHEEL +0 -0
@@ -4,6 +4,7 @@ import time
4
4
  from threading import RLock
5
5
 
6
6
  from ..jobs.decorators import synchronized_class
7
+ from .exceptions import TokenLimitError
7
8
 
8
9
 
9
10
  @synchronized_class
@@ -350,6 +351,7 @@ class TokenBucket:
350
351
  - Usage statistics and token levels are logged for tracking purposes
351
352
 
352
353
  Example:
354
+ >>> from edsl.buckets.token_bucket import TokenBucket
353
355
  >>> bucket = TokenBucket(bucket_name="api", bucket_type="test", capacity=100, refill_rate=10)
354
356
  >>> bucket.tokens = 100
355
357
  >>> import asyncio
@@ -358,29 +360,32 @@ class TokenBucket:
358
360
  70
359
361
 
360
362
  >>> # Example with capacity cheating
363
+ >>> from edsl.buckets.token_bucket import TokenBucket
361
364
  >>> bucket = TokenBucket(bucket_name="api", bucket_type="test", capacity=10, refill_rate=1)
362
365
  >>> asyncio.run(bucket.get_tokens(15, cheat_bucket_capacity=True))
363
366
  >>> bucket.capacity > 15 # Capacity should have been increased
364
367
  True
365
368
 
366
- >>> # Example raising ValueError
369
+ >>> # Example raising TokenLimitError
370
+ >>> from edsl.buckets.token_bucket import TokenBucket
371
+ >>> from edsl.buckets.exceptions import TokenLimitError
367
372
  >>> bucket = TokenBucket(bucket_name="api", bucket_type="test", capacity=10, refill_rate=1)
368
373
  >>> try:
369
374
  ... asyncio.run(bucket.get_tokens(15, cheat_bucket_capacity=False))
370
- ... except ValueError as e:
371
- ... print("ValueError raised")
372
- ValueError raised
375
+ ... except TokenLimitError as e:
376
+ ... print("TokenLimitError raised")
377
+ TokenLimitError raised
373
378
  """
374
379
  self.num_requests += amount
375
380
  if amount >= self.capacity:
376
381
  if not cheat_bucket_capacity:
377
382
  msg = f"Requested amount exceeds bucket capacity. Bucket capacity: {self.capacity}, requested amount: {amount}. As the bucket never overflows, the requested amount will never be available."
378
- raise ValueError(msg)
383
+ raise TokenLimitError(msg)
379
384
  else:
380
385
  self.capacity = amount * 1.10
381
386
  self._old_capacity = self.capacity
382
387
 
383
- start_time = time.monotonic()
388
+ # Loop until we have enough tokens
384
389
  while True:
385
390
  self.refill() # Refill based on elapsed time
386
391
  if self.tokens >= amount:
@@ -1,8 +1,7 @@
1
1
  from fastapi import FastAPI, HTTPException
2
2
  from pydantic import BaseModel
3
3
  from typing import Union, Dict
4
- from typing import Union, List, Any, Optional
5
- from threading import RLock
4
+ from typing import Optional
6
5
 
7
6
  from .token_bucket import TokenBucket # Original implementation
8
7
 
@@ -7,13 +7,15 @@ operations to a remote server, enabling distributed rate limiting across
7
7
  multiple processes or machines.
8
8
  """
9
9
 
10
- from typing import Union, Optional, Dict, List, Any, Tuple
10
+ from typing import Union, Optional, Dict, Any
11
11
  import asyncio
12
12
  import time
13
13
  import aiohttp
14
14
  from matplotlib import pyplot as plt
15
15
  from matplotlib.figure import Figure
16
16
 
17
+ from .exceptions import BucketError, TokenBucketClientError
18
+
17
19
 
18
20
  class TokenBucketClient:
19
21
  """
@@ -125,7 +127,7 @@ class TokenBucketClient:
125
127
  json=payload,
126
128
  ) as response:
127
129
  if response.status != 200:
128
- raise ValueError(f"Unexpected error: {await response.text()}")
130
+ raise TokenBucketClientError(f"Unexpected error: {await response.text()}")
129
131
 
130
132
  # Process server response
131
133
  result = await response.json()
@@ -200,7 +202,7 @@ class TokenBucketClient:
200
202
  params={"amount": amount},
201
203
  ) as response:
202
204
  if response.status != 200:
203
- raise ValueError(f"Failed to add tokens: {await response.text()}")
205
+ raise TokenBucketClientError(f"Failed to add tokens: {await response.text()}")
204
206
 
205
207
  async def _set_turbo_mode(self, state: bool) -> None:
206
208
  """
@@ -220,7 +222,7 @@ class TokenBucketClient:
220
222
  f"{self.api_base_url}/bucket/{self.bucket_id}/turbo_mode/{str(state).lower()}"
221
223
  ) as response:
222
224
  if response.status != 200:
223
- raise ValueError(
225
+ raise TokenBucketClientError(
224
226
  f"Failed to set turbo mode: {await response.text()}"
225
227
  )
226
228
 
@@ -259,7 +261,7 @@ class TokenBucketClient:
259
261
  },
260
262
  ) as response:
261
263
  if response.status != 200:
262
- raise ValueError(f"Failed to get tokens: {await response.text()}")
264
+ raise TokenBucketClientError(f"Failed to get tokens: {await response.text()}")
263
265
 
264
266
  def get_throughput(self, time_window: Optional[float] = None) -> float:
265
267
  """
@@ -324,7 +326,7 @@ class TokenBucketClient:
324
326
  f"{self.api_base_url}/bucket/{self.bucket_id}/status"
325
327
  ) as response:
326
328
  if response.status != 200:
327
- raise ValueError(
329
+ raise TokenBucketClientError(
328
330
  f"Failed to get bucket status: {await response.text()}"
329
331
  )
330
332
  return await response.json()
@@ -408,7 +410,7 @@ class TokenBucketClient:
408
410
  # Calculate time needed to accumulate the required tokens
409
411
  return (requested_tokens - self.tokens) / self.refill_rate
410
412
  except Exception as e:
411
- raise ValueError(f"Error calculating wait time: {e}")
413
+ raise BucketError(f"Error calculating wait time: {e}")
412
414
 
413
415
  # Note: The commented out method below is a reminder for future implementation
414
416
  # def wait_time(self, num_tokens: Union[int, float]) -> float:
edsl/caching/cache.py CHANGED
@@ -32,10 +32,14 @@ import json
32
32
  import os
33
33
  import warnings
34
34
  from typing import Optional, Union, TYPE_CHECKING
35
- from ..base import Base
36
35
 
36
+ from ..base import Base
37
37
  from ..utilities import remove_edsl_version, dict_hash
38
38
  from .exceptions import CacheError
39
+ from .sql_dict import SQLiteDict
40
+
41
+ if TYPE_CHECKING:
42
+ from .cache_entry import CacheEntry
39
43
 
40
44
  class Cache(Base):
41
45
  """Cache for storing and retrieving language model responses.
@@ -418,9 +422,10 @@ class Cache(Base):
418
422
  """
419
423
  # if a file doesn't exist at jsonfile, throw an error
420
424
  from .sql_dict import SQLiteDict
425
+ from .exceptions import CacheFileNotFoundError
421
426
 
422
427
  if not os.path.exists(jsonlfile):
423
- raise FileNotFoundError(f"File {jsonlfile} not found")
428
+ raise CacheFileNotFoundError(f"File {jsonlfile} not found")
424
429
 
425
430
  if db_path is None:
426
431
  data = {}
@@ -11,10 +11,11 @@ from __future__ import annotations
11
11
  import json
12
12
  import datetime
13
13
  import hashlib
14
- from typing import Optional, Dict, List, Any, Union
14
+ from typing import Optional, Dict, List, Any
15
15
  from uuid import uuid4
16
16
 
17
17
  from ..base import RepresentationMixin
18
+ from .exceptions import CacheError
18
19
 
19
20
 
20
21
  class CacheEntry(RepresentationMixin):
@@ -81,22 +82,22 @@ class CacheEntry(RepresentationMixin):
81
82
  TypeError: If any attribute has an incorrect type
82
83
  """
83
84
  if not isinstance(self.model, str):
84
- raise TypeError("`model` should be a string.")
85
+ raise CacheError("`model` should be a string.")
85
86
  if not isinstance(self.parameters, dict):
86
- raise TypeError("`parameters` should be a dictionary.")
87
+ raise CacheError("`parameters` should be a dictionary.")
87
88
  if not isinstance(self.system_prompt, str):
88
- raise TypeError("`system_prompt` should be a string.")
89
+ raise CacheError("`system_prompt` should be a string.")
89
90
  if not isinstance(self.user_prompt, str):
90
- raise TypeError("`user_prompt` should be a string")
91
+ raise CacheError("`user_prompt` should be a string")
91
92
  if not isinstance(self.output, str):
92
- raise TypeError("`output` should be a string")
93
+ raise CacheError("`output` should be a string")
93
94
  if not isinstance(self.iteration, int):
94
- raise TypeError("`iteration` should be an integer")
95
+ raise CacheError("`iteration` should be an integer")
95
96
  # Note: timestamp is stored as int for compatibility, but could be float in future
96
97
  if not isinstance(self.timestamp, int):
97
- raise TypeError(f"`timestamp` should be an integer")
98
+ raise CacheError("`timestamp` should be an integer")
98
99
  if self.service is not None and not isinstance(self.service, str):
99
- raise TypeError("`service` should be either a string or None")
100
+ raise CacheError("`service` should be either a string or None")
100
101
 
101
102
  @classmethod
102
103
  def gen_key(
@@ -1,5 +1,5 @@
1
1
  """
2
- Custom exceptions for the data module in the EDSL framework.
2
+ Custom exceptions for the caching module in the EDSL framework.
3
3
 
4
4
  This module defines exceptions that are raised during cache operations,
5
5
  such as when entries cannot be stored, retrieved, or synchronized.
@@ -13,12 +13,118 @@ class CacheError(BaseException):
13
13
  Exception raised for errors related to cache operations.
14
14
 
15
15
  This exception is raised when cache operations fail, such as when:
16
- - A cache key cannot be generated
17
- - A cache entry cannot be stored or retrieved
18
- - A cache synchronization operation fails
19
- - Cache migration encounters an error
16
+ - A cache key cannot be generated due to invalid or non-serializable inputs
17
+ - A cache entry cannot be stored or retrieved due to database access issues
18
+ - A cache synchronization operation fails when syncing with remote cache
19
+ - Cache migration encounters an error when upgrading between versions
20
+
21
+ To resolve this error:
22
+ 1. Check that your inputs to cached functions are serializable
23
+ 2. Ensure you have proper file system permissions for the SQLite cache file
24
+ 3. For remote cache issues, verify your network connection and API credentials
25
+ 4. For migration issues, you may need to clear the cache by calling cache.clear()
26
+
27
+ Examples:
28
+ ```python
29
+ # Cache key generation failure
30
+ @cache
31
+ def my_function(non_serializable_object): # Raises CacheError
32
+ return result
33
+
34
+ # Storage failure
35
+ cache.set("key", "value") # May raise CacheError if DB is locked
36
+ ```
20
37
 
21
38
  Attributes:
22
- message (str): Explanation of the error
39
+ message (str): Detailed explanation of the error
40
+ """
41
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
42
+
43
+
44
+ class CacheFileNotFoundError(CacheError):
45
+ """
46
+ Exception raised when a cache file cannot be found.
47
+
48
+ This exception is raised when attempting to load or access a cache file
49
+ that does not exist on the filesystem.
50
+
51
+ To resolve this error:
52
+ 1. Check that the file path is correct
53
+ 2. Verify that the file exists and has proper permissions
54
+ 3. If you're loading a serialized cache, make sure it was previously saved
55
+
56
+ Examples:
57
+ ```python
58
+ # Attempting to load a non-existent cache file
59
+ cache.load_from_file("non_existent_file.json") # Raises CacheFileNotFoundError
60
+ ```
61
+ """
62
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
63
+
64
+
65
+ class CacheValueError(CacheError):
66
+ """
67
+ Exception raised when an invalid value is provided to a cache operation.
68
+
69
+ This exception is raised when:
70
+ - An incorrect type is provided for a cache entry
71
+ - A value cannot be serialized for storage
72
+ - A validation check fails during cache operations
73
+
74
+ To resolve this error:
75
+ 1. Ensure the value has the expected type and structure
76
+ 2. Verify that values being stored are serializable
77
+ 3. Check for any special requirements in the cache implementation
78
+
79
+ Examples:
80
+ ```python
81
+ # Attempting to store an invalid value type
82
+ cache_dict["key"] = non_cache_entry_object # Raises CacheValueError
83
+ ```
84
+ """
85
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
86
+
87
+
88
+ class CacheKeyError(CacheError):
89
+ """
90
+ Exception raised when a key cannot be found in the cache.
91
+
92
+ This exception is raised when attempting to access a cache entry that
93
+ does not exist in the cache.
94
+
95
+ To resolve this error:
96
+ 1. Check that the key exists before accessing it
97
+ 2. Use cache.get() with a default value to handle missing keys gracefully
98
+ 3. Consider initializing the key-value pair before accessing
99
+
100
+ Examples:
101
+ ```python
102
+ # Attempting to access a non-existent key
103
+ value = cache["non_existent_key"] # Raises CacheKeyError
104
+
105
+ # Safer alternative
106
+ value = cache.get("non_existent_key", default=None) # Returns None instead of raising
107
+ ```
108
+ """
109
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
110
+
111
+
112
+ class CacheNotImplementedError(CacheError):
113
+ """
114
+ Exception raised when a cache method is not implemented.
115
+
116
+ This exception is raised when attempting to use a method that is
117
+ defined in the interface but not implemented in the concrete class.
118
+
119
+ To resolve this error:
120
+ 1. Check if the operation is supported by the specific cache implementation
121
+ 2. Consider using an alternative approach that is supported
122
+ 3. If you're implementing a custom cache, implement the required method
123
+
124
+ Examples:
125
+ ```python
126
+ # Attempting to use an unimplemented method
127
+ cache.some_unimplemented_method() # Raises CacheNotImplementedError
128
+ ```
23
129
  """
24
- relevant_doc = "https://docs.expectedparrot.com/en/latest/data.html#cache"
130
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
@@ -1,4 +1,4 @@
1
- from typing import List, Dict, Any, Optional, TYPE_CHECKING, Callable
1
+ from typing import List, TYPE_CHECKING, Callable
2
2
  from dataclasses import dataclass
3
3
  from contextlib import AbstractContextManager
4
4
  from collections import UserList
@@ -8,7 +8,6 @@ if TYPE_CHECKING:
8
8
  from edsl.coop.coop import Coop
9
9
  from .cache_entry import CacheEntry
10
10
 
11
- from logging import Logger
12
11
 
13
12
 
14
13
  class CacheKeyList(UserList):
edsl/caching/sql_dict.py CHANGED
@@ -9,11 +9,11 @@ making it a drop-in replacement for regular dictionaries but with database persi
9
9
 
10
10
  from __future__ import annotations
11
11
  import json
12
- from typing import Any, Generator, Optional, Union, Dict, List, Tuple, TypeVar
12
+ from typing import Any, Generator, Optional, Union, Dict, TypeVar
13
13
 
14
14
  from ..config import CONFIG
15
15
  from .cache_entry import CacheEntry
16
- from .orm import Base, Data
16
+ from .orm import Data
17
17
 
18
18
  T = TypeVar('T')
19
19
 
@@ -76,13 +76,14 @@ class SQLiteDict:
76
76
  if not self.db_path.startswith("sqlite:///"):
77
77
  self.db_path = f"sqlite:///{self.db_path}"
78
78
  try:
79
- from edsl.caching.orm import Base, Data
79
+ from edsl.caching.orm import Base
80
80
 
81
81
  self.engine = create_engine(self.db_path, echo=False, future=True)
82
82
  Base.metadata.create_all(self.engine)
83
83
  self.Session = sessionmaker(bind=self.engine)
84
84
  except SQLAlchemyError as e:
85
- raise Exception(
85
+ from edsl.caching.exceptions import CacheError
86
+ raise CacheError(
86
87
  f"""Database initialization error: {e}. The attempted DB path was {db_path}"""
87
88
  ) from e
88
89
 
@@ -99,7 +100,6 @@ class SQLiteDict:
99
100
  Path to a temporary file location
100
101
  """
101
102
  import tempfile
102
- import os
103
103
 
104
104
  _, temp_db_path = tempfile.mkstemp(suffix=".db")
105
105
  return temp_db_path
@@ -123,9 +123,10 @@ class SQLiteDict:
123
123
  >>> d["foo"] = CacheEntry.example()
124
124
  """
125
125
  if not isinstance(value, CacheEntry):
126
- raise ValueError(f"Value must be a CacheEntry object (got {type(value)}).")
126
+ from edsl.caching.exceptions import CacheValueError
127
+ raise CacheValueError(f"Value must be a CacheEntry object (got {type(value)}).")
127
128
  with self.Session() as db:
128
- from edsl.caching.orm import Base, Data
129
+ from edsl.caching.orm import Data
129
130
 
130
131
  db.merge(Data(key=key, value=json.dumps(value.to_dict())))
131
132
  db.commit()
@@ -154,11 +155,12 @@ class SQLiteDict:
154
155
  True
155
156
  """
156
157
  with self.Session() as db:
157
- from edsl.caching.orm import Base, Data
158
+ from edsl.caching.orm import Data
158
159
 
159
160
  value = db.query(Data).filter_by(key=key).first()
160
161
  if not value:
161
- raise KeyError(f"Key '{key}' not found.")
162
+ from edsl.caching.exceptions import CacheKeyError
163
+ raise CacheKeyError(f"Key '{key}' not found.")
162
164
  return CacheEntry.from_dict(json.loads(value.value))
163
165
 
164
166
  def get(self, key: str, default: Optional[Any] = None) -> Union[CacheEntry, Any]:
@@ -181,9 +183,10 @@ class SQLiteDict:
181
183
  >>> d.get("foo", "bar")
182
184
  'bar'
183
185
  """
186
+ from edsl.caching.exceptions import CacheKeyError
184
187
  try:
185
188
  return self[key]
186
- except KeyError:
189
+ except (KeyError, CacheKeyError):
187
190
  return default
188
191
 
189
192
  def __bool__(self) -> bool:
@@ -233,7 +236,8 @@ class SQLiteDict:
233
236
  the database from being locked for too long.
234
237
  """
235
238
  if not (isinstance(new_d, dict) or isinstance(new_d, SQLiteDict)):
236
- raise ValueError(
239
+ from edsl.caching.exceptions import CacheValueError
240
+ raise CacheValueError(
237
241
  f"new_d must be a dict or SQLiteDict object (got {type(new_d)})"
238
242
  )
239
243
  current_batch = 0
@@ -301,7 +305,8 @@ class SQLiteDict:
301
305
  db.delete(instance)
302
306
  db.commit()
303
307
  else:
304
- raise KeyError(f"Key '{key}' not found.")
308
+ from edsl.caching.exceptions import CacheKeyError
309
+ raise CacheKeyError(f"Key '{key}' not found.")
305
310
 
306
311
  def __contains__(self, key: str) -> bool:
307
312
  """
edsl/cli.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ EDSL Command Line Interface.
3
+
4
+ This module provides the main entry point for the EDSL command-line tool.
5
+ """
6
+
7
+ import sys
8
+ import typer
9
+ from typing import Optional
10
+ from rich.console import Console
11
+ from importlib import metadata
12
+
13
+ # Create the main Typer app
14
+ app = typer.Typer(help="Expected Parrot EDSL Command Line Interface")
15
+ console = Console()
16
+
17
+ # Import the plugins app
18
+ from .plugins.cli_typer import app as plugins_app
19
+
20
+ # Add the plugins subcommand
21
+ app.add_typer(plugins_app, name="plugins")
22
+
23
+ @app.callback()
24
+ def callback():
25
+ """
26
+ Expected Parrot EDSL Command Line Interface.
27
+
28
+ A toolkit for creating, managing, and running surveys with language models.
29
+ """
30
+ pass
31
+
32
+ @app.command()
33
+ def version():
34
+ """Show the EDSL version."""
35
+ try:
36
+ version = metadata.version("edsl")
37
+ console.print(f"[bold cyan]EDSL version:[/bold cyan] {version}")
38
+ except metadata.PackageNotFoundError:
39
+ console.print("[yellow]EDSL package not installed or version not available.[/yellow]")
40
+
41
+ def main():
42
+ """Main entry point for the EDSL CLI."""
43
+ app()
@@ -3,10 +3,16 @@
3
3
  import os
4
4
  import platformdirs
5
5
  from dotenv import load_dotenv, find_dotenv
6
- from edsl.exceptions.configuration import (
7
- InvalidEnvironmentVariableError,
8
- MissingEnvironmentVariableError,
9
- )
6
+ from edsl.base import BaseException
7
+ from edsl import logger
8
+
9
+ class InvalidEnvironmentVariableError(BaseException):
10
+ """Raised when an environment variable is invalid."""
11
+ pass
12
+
13
+ class MissingEnvironmentVariableError(BaseException):
14
+ """Raised when an expected environment variable is missing."""
15
+ pass
10
16
 
11
17
  cache_dir = platformdirs.user_cache_dir("edsl")
12
18
  os.makedirs(cache_dir, exist_ok=True)
@@ -50,6 +56,10 @@ CONFIG_MAP = {
50
56
  "default": "True",
51
57
  "info": "This config var determines whether to fetch prices for tokens used in remote inference",
52
58
  },
59
+ "EDSL_LOG_LEVEL": {
60
+ "default": "ERROR",
61
+ "info": "This config var determines the logging level for the EDSL package (DEBUG, INFO, WARNING, ERROR, CRITICAL).",
62
+ },
53
63
  "EDSL_MAX_ATTEMPTS": {
54
64
  "default": "5",
55
65
  "info": "This config var determines the maximum number of times to retry a failed API call.",
@@ -86,9 +96,11 @@ class Config:
86
96
 
87
97
  def __init__(self):
88
98
  """Initialize the Config class."""
99
+ logger.debug("Initializing Config class")
89
100
  self._set_run_mode()
90
101
  self._load_dotenv()
91
102
  self._set_env_vars()
103
+ logger.info(f"Config initialized with run mode: {self.EDSL_RUN_MODE}")
92
104
 
93
105
  def show_path_to_dot_env(self):
94
106
  print(find_dotenv(usecwd=True))
@@ -101,7 +113,12 @@ class Config:
101
113
  default = CONFIG_MAP.get("EDSL_RUN_MODE").get("default")
102
114
  if run_mode is None:
103
115
  run_mode = default
116
+ logger.debug(f"EDSL_RUN_MODE not set, using default: {default}")
117
+ else:
118
+ logger.debug(f"EDSL_RUN_MODE set to: {run_mode}")
119
+
104
120
  if run_mode not in EDSL_RUN_MODES:
121
+ logger.error(f"Invalid EDSL_RUN_MODE: {run_mode}")
105
122
  raise InvalidEnvironmentVariableError(
106
123
  f"Value `{run_mode}` is not allowed for EDSL_RUN_MODE."
107
124
  )
@@ -149,12 +166,19 @@ class Config:
149
166
  """
150
167
  Returns the value of an environment variable.
151
168
  """
169
+ logger.debug(f"Getting config value for: {env_var}")
170
+
152
171
  if env_var not in CONFIG_MAP:
172
+ logger.error(f"Invalid environment variable requested: {env_var}")
153
173
  raise InvalidEnvironmentVariableError(f"{env_var} is not a valid env var. ")
154
174
  elif env_var not in self.__dict__:
155
175
  info = CONFIG_MAP[env_var].get("info")
176
+ logger.error(f"Missing environment variable: {env_var}")
156
177
  raise MissingEnvironmentVariableError(f"{env_var} is not set. {info}")
157
- return self.__dict__.get(env_var)
178
+
179
+ value = self.__dict__.get(env_var)
180
+ logger.debug(f"Config value for {env_var}: {value}")
181
+ return value
158
182
 
159
183
  def __iter__(self):
160
184
  """Iterate over the environment variables."""
@@ -174,4 +198,4 @@ class Config:
174
198
 
175
199
  # Note: Python modules are singletons. As such, once this module is imported
176
200
  # the same instance of it is reused across the application.
177
- CONFIG = Config()
201
+ CONFIG = Config()
@@ -2,7 +2,7 @@ from collections import UserList
2
2
  import asyncio
3
3
  import inspect
4
4
  from typing import Optional, Callable
5
- from .. import Agent, QuestionFreeText, Results, AgentList, ScenarioList, Scenario
5
+ from .. import QuestionFreeText, Results, AgentList, ScenarioList, Scenario
6
6
  from ..questions import QuestionBase
7
7
  from ..results.Result import Result
8
8
  from jinja2 import Template
@@ -120,7 +120,8 @@ What do you say next?"""
120
120
  per_round_message_template
121
121
  and "{{ round_message }}" not in next_statement_question.question_text
122
122
  ):
123
- raise ValueError(
123
+ from edsl.conversation.exceptions import ConversationValueError
124
+ raise ConversationValueError(
124
125
  "If you pass in a per_round_message_template, you must include {{ round_message }} in the question_text."
125
126
  )
126
127
 
@@ -0,0 +1,58 @@
1
+ """
2
+ Exceptions for the conversation module.
3
+
4
+ This module defines custom exceptions for the conversation module,
5
+ including errors for invalid participant configurations, agent interaction
6
+ failures, and conversation state errors.
7
+ """
8
+
9
+ from ..base import BaseException
10
+
11
+
12
+ class ConversationError(BaseException):
13
+ """
14
+ Base exception class for all conversation-related errors.
15
+
16
+ This is the parent class for all exceptions related to conversation
17
+ operations, including agent communication, turn management, and
18
+ participant configuration.
19
+ """
20
+ relevant_doc = "https://docs.expectedparrot.com/"
21
+
22
+
23
+ class ConversationValueError(ConversationError):
24
+ """
25
+ Exception raised when an invalid value is provided to a conversation.
26
+
27
+ This exception occurs when attempting to create or modify a conversation
28
+ with invalid values, such as:
29
+ - Invalid participant configurations
30
+ - Inappropriate agent parameters
31
+ - Incompatible conversation settings
32
+
33
+ Examples:
34
+ ```python
35
+ # Attempting to add an invalid participant to a conversation
36
+ conversation.add_participant(None) # Raises ConversationValueError
37
+ ```
38
+ """
39
+ relevant_doc = "https://docs.expectedparrot.com/"
40
+
41
+
42
+ class ConversationStateError(ConversationError):
43
+ """
44
+ Exception raised when the conversation is in an invalid state.
45
+
46
+ This exception occurs when attempting to perform an operation that
47
+ is incompatible with the current state of the conversation, such as:
48
+ - Ending a conversation that hasn't started
49
+ - Starting a conversation that's already in progress
50
+ - Accessing a participant that doesn't exist
51
+
52
+ Examples:
53
+ ```python
54
+ # Attempting to get the next speaker when the conversation is empty
55
+ empty_conversation.next_speaker() # Raises ConversationStateError
56
+ ```
57
+ """
58
+ relevant_doc = "https://docs.expectedparrot.com/"
@@ -44,8 +44,6 @@ results.select("conversation_index", "index", "agent_name", "dialogue").print(
44
44
  )
45
45
 
46
46
  from edsl import (
47
- QuestionFreeText,
48
- QuestionMultipleChoice,
49
47
  QuestionYesNo,
50
48
  QuestionNumerical,
51
49
  )