edsl 0.1.50__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 (109) hide show
  1. edsl/__version__.py +1 -1
  2. edsl/base/base_exception.py +2 -2
  3. edsl/buckets/bucket_collection.py +1 -1
  4. edsl/buckets/exceptions.py +32 -0
  5. edsl/buckets/token_bucket_api.py +26 -10
  6. edsl/caching/cache.py +5 -2
  7. edsl/caching/remote_cache_sync.py +5 -5
  8. edsl/caching/sql_dict.py +12 -11
  9. edsl/config/__init__.py +1 -1
  10. edsl/config/config_class.py +4 -2
  11. edsl/conversation/Conversation.py +7 -4
  12. edsl/conversation/car_buying.py +1 -3
  13. edsl/conversation/mug_negotiation.py +2 -6
  14. edsl/coop/__init__.py +11 -8
  15. edsl/coop/coop.py +13 -13
  16. edsl/coop/coop_functions.py +1 -1
  17. edsl/coop/ep_key_handling.py +1 -1
  18. edsl/coop/price_fetcher.py +2 -2
  19. edsl/coop/utils.py +2 -2
  20. edsl/dataset/dataset.py +144 -63
  21. edsl/dataset/dataset_operations_mixin.py +14 -6
  22. edsl/dataset/dataset_tree.py +3 -3
  23. edsl/dataset/display/table_renderers.py +6 -3
  24. edsl/dataset/file_exports.py +4 -4
  25. edsl/dataset/r/ggplot.py +3 -3
  26. edsl/inference_services/available_model_fetcher.py +2 -2
  27. edsl/inference_services/data_structures.py +5 -5
  28. edsl/inference_services/inference_service_abc.py +1 -1
  29. edsl/inference_services/inference_services_collection.py +1 -1
  30. edsl/inference_services/service_availability.py +3 -3
  31. edsl/inference_services/services/azure_ai.py +3 -3
  32. edsl/inference_services/services/google_service.py +1 -1
  33. edsl/inference_services/services/test_service.py +1 -1
  34. edsl/instructions/change_instruction.py +5 -4
  35. edsl/instructions/instruction.py +1 -0
  36. edsl/instructions/instruction_collection.py +5 -4
  37. edsl/instructions/instruction_handler.py +10 -8
  38. edsl/interviews/exception_tracking.py +1 -1
  39. edsl/interviews/interview.py +1 -1
  40. edsl/interviews/interview_status_dictionary.py +1 -1
  41. edsl/interviews/interview_task_manager.py +2 -2
  42. edsl/interviews/request_token_estimator.py +3 -2
  43. edsl/interviews/statistics.py +2 -2
  44. edsl/invigilators/invigilators.py +2 -2
  45. edsl/jobs/__init__.py +39 -2
  46. edsl/jobs/async_interview_runner.py +1 -1
  47. edsl/jobs/check_survey_scenario_compatibility.py +5 -5
  48. edsl/jobs/data_structures.py +2 -2
  49. edsl/jobs/jobs.py +2 -2
  50. edsl/jobs/jobs_checks.py +5 -5
  51. edsl/jobs/jobs_component_constructor.py +2 -2
  52. edsl/jobs/jobs_pricing_estimation.py +1 -1
  53. edsl/jobs/jobs_runner_asyncio.py +2 -2
  54. edsl/jobs/remote_inference.py +1 -1
  55. edsl/jobs/results_exceptions_handler.py +2 -2
  56. edsl/language_models/language_model.py +5 -1
  57. edsl/notebooks/__init__.py +24 -1
  58. edsl/notebooks/exceptions.py +82 -0
  59. edsl/notebooks/notebook.py +7 -3
  60. edsl/notebooks/notebook_to_latex.py +1 -1
  61. edsl/prompts/__init__.py +23 -2
  62. edsl/prompts/prompt.py +1 -1
  63. edsl/questions/__init__.py +4 -4
  64. edsl/questions/answer_validator_mixin.py +0 -5
  65. edsl/questions/compose_questions.py +2 -2
  66. edsl/questions/descriptors.py +1 -1
  67. edsl/questions/question_base.py +32 -3
  68. edsl/questions/question_base_prompts_mixin.py +4 -4
  69. edsl/questions/question_budget.py +503 -102
  70. edsl/questions/question_check_box.py +658 -156
  71. edsl/questions/question_dict.py +176 -2
  72. edsl/questions/question_extract.py +401 -61
  73. edsl/questions/question_free_text.py +77 -9
  74. edsl/questions/question_functional.py +118 -9
  75. edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
  76. edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
  77. edsl/questions/question_list.py +246 -26
  78. edsl/questions/question_matrix.py +586 -73
  79. edsl/questions/question_multiple_choice.py +213 -47
  80. edsl/questions/question_numerical.py +360 -29
  81. edsl/questions/question_rank.py +401 -124
  82. edsl/questions/question_registry.py +3 -3
  83. edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
  84. edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
  85. edsl/questions/register_questions_meta.py +2 -1
  86. edsl/questions/response_validator_abc.py +6 -2
  87. edsl/questions/response_validator_factory.py +10 -12
  88. edsl/results/report.py +1 -1
  89. edsl/results/result.py +7 -4
  90. edsl/results/results.py +471 -271
  91. edsl/results/results_selector.py +2 -2
  92. edsl/scenarios/construct_download_link.py +3 -3
  93. edsl/scenarios/scenario.py +1 -2
  94. edsl/scenarios/scenario_list.py +41 -23
  95. edsl/surveys/survey_css.py +3 -3
  96. edsl/surveys/survey_simulator.py +2 -1
  97. edsl/tasks/__init__.py +22 -2
  98. edsl/tasks/exceptions.py +72 -0
  99. edsl/tasks/task_history.py +3 -3
  100. edsl/tokens/__init__.py +27 -1
  101. edsl/tokens/exceptions.py +37 -0
  102. edsl/tokens/interview_token_usage.py +3 -2
  103. edsl/tokens/token_usage.py +4 -3
  104. {edsl-0.1.50.dist-info → edsl-0.1.51.dist-info}/METADATA +1 -1
  105. {edsl-0.1.50.dist-info → edsl-0.1.51.dist-info}/RECORD +108 -106
  106. edsl/questions/derived/__init__.py +0 -0
  107. {edsl-0.1.50.dist-info → edsl-0.1.51.dist-info}/LICENSE +0 -0
  108. {edsl-0.1.50.dist-info → edsl-0.1.51.dist-info}/WHEEL +0 -0
  109. {edsl-0.1.50.dist-info → edsl-0.1.51.dist-info}/entry_points.txt +0 -0
@@ -106,7 +106,7 @@ class Selector:
106
106
  new_data = self._fetch_data(to_fetch)
107
107
  except ResultsColumnNotFoundError as e:
108
108
  # Check is_notebook with explicit import to ensure mock works
109
- from edsl.utilities import is_notebook as is_notebook_check
109
+ from ..utilities import is_notebook as is_notebook_check
110
110
  if is_notebook_check():
111
111
  print("Error:", e, file=sys.stderr)
112
112
  return None
@@ -114,7 +114,7 @@ class Selector:
114
114
  raise e
115
115
 
116
116
  # Import Dataset here to avoid circular import issues
117
- from edsl.dataset import Dataset
117
+ from ..dataset import Dataset
118
118
  return Dataset(new_data)
119
119
 
120
120
  def _normalize_columns(self, columns: Union[str, List[str]]) -> Tuple[str, ...]:
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING, Optional, List
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from ..display import HTML
@@ -104,8 +104,8 @@ class ConstructDownloadLink:
104
104
 
105
105
  def create_multiple_links(
106
106
  self,
107
- files: list[FileStore],
108
- custom_filenames: Optional[list[str | None]] = None,
107
+ files: List["FileStore"],
108
+ custom_filenames: Optional[List[Optional[str]]] = None,
109
109
  style: Optional[dict] = None,
110
110
  ) -> HTML:
111
111
  """Create multiple download links in a horizontal layout.
@@ -96,8 +96,7 @@ class Scenario(Base, UserDict):
96
96
  data = dict(data)
97
97
  except Exception as e:
98
98
  raise ScenarioError(
99
- f"You must pass in a dictionary to initialize a Scenario. You passed in {data}",
100
- "Exception message:" + str(e),
99
+ f"You must pass in a dictionary to initialize a Scenario. You passed in {data}" + "Exception message:" + str(e),
101
100
  )
102
101
 
103
102
  super().__init__()
@@ -31,7 +31,6 @@ import warnings
31
31
  import csv
32
32
  import random
33
33
  import os
34
- import glob
35
34
  from io import StringIO
36
35
  import inspect
37
36
  from collections import UserList, defaultdict
@@ -488,7 +487,8 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
488
487
  if isinstance(other, Scenario):
489
488
  other = ScenarioList([other])
490
489
  elif not isinstance(other, ScenarioList):
491
- raise TypeError(f"Cannot multiply ScenarioList with {type(other)}")
490
+ from .exceptions import TypeScenarioError
491
+ raise TypeScenarioError(f"Cannot multiply ScenarioList with {type(other)}")
492
492
 
493
493
  new_sl = []
494
494
  for s1, s2 in list(product(self, other)):
@@ -599,7 +599,8 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
599
599
  # Convert to a set (removes duplicates)
600
600
  new_scenario[field_name] = set(values)
601
601
  else:
602
- raise ValueError(f"Invalid output_type: {output_type}. Must be 'string', 'list', or 'set'.")
602
+ from .exceptions import ValueScenarioError
603
+ raise ValueScenarioError(f"Invalid output_type: {output_type}. Must be 'string', 'list', or 'set'.")
603
604
 
604
605
  new_scenarios.append(new_scenario)
605
606
 
@@ -895,11 +896,13 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
895
896
  cls,
896
897
  path: Optional[str] = None,
897
898
  recursive: bool = False,
899
+ key_name: str = "content",
898
900
  ) -> "ScenarioList":
899
- """Create a ScenarioList of FileStore objects from files in a directory.
901
+ """Create a ScenarioList of Scenario objects from files in a directory.
900
902
 
901
- This method scans a directory and creates a FileStore object for each file found,
902
- optionally filtering files based on a wildcard pattern. If no path is provided,
903
+ This method scans a directory and creates a Scenario object for each file found,
904
+ where each Scenario contains a FileStore object under the specified key.
905
+ Optionally filters files based on a wildcard pattern. If no path is provided,
903
906
  the current working directory is used.
904
907
 
905
908
  Args:
@@ -910,25 +913,27 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
910
913
  - "/path/to/directory/*.py" - scans only Python files in the directory
911
914
  - "*.txt" - scans only text files in the current working directory
912
915
  recursive: Whether to scan subdirectories recursively. Defaults to False.
916
+ key_name: The key to use for the FileStore object in each Scenario. Defaults to "content".
913
917
 
914
918
  Returns:
915
- A ScenarioList containing FileStore objects for all matching files.
919
+ A ScenarioList containing Scenario objects for all matching files, where each Scenario
920
+ has a FileStore object under the specified key.
916
921
 
917
922
  Raises:
918
923
  FileNotFoundError: If the specified directory does not exist.
919
924
 
920
925
  Examples:
921
- # Get all files in the current directory
926
+ # Get all files in the current directory with default key "content"
922
927
  sl = ScenarioList.from_directory()
923
928
 
924
- # Get all Python files in a specific directory
925
- sl = ScenarioList.from_directory('*.py')
929
+ # Get all Python files in a specific directory with custom key "python_file"
930
+ sl = ScenarioList.from_directory('*.py', key_name="python_file")
926
931
 
927
932
  # Get all image files in the current directory
928
- sl = ScenarioList.from_directory('*.png')
933
+ sl = ScenarioList.from_directory('*.png', key_name="image")
929
934
 
930
935
  # Get all files recursively including subdirectories
931
- sl = ScenarioList.from_directory(recursive=True)
936
+ sl = ScenarioList.from_directory(recursive=True, key_name="document")
932
937
  """
933
938
  # Handle default case - use current directory
934
939
  if path is None:
@@ -973,7 +978,8 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
973
978
 
974
979
  # Ensure directory exists
975
980
  if not os.path.isdir(directory_path):
976
- raise FileNotFoundError(f"Directory not found: {directory_path}")
981
+ from .exceptions import FileNotFoundScenarioError
982
+ raise FileNotFoundScenarioError(f"Directory not found: {directory_path}")
977
983
 
978
984
  # Create a DirectoryScanner for the directory
979
985
  scanner = DirectoryScanner(directory_path)
@@ -1001,7 +1007,10 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
1001
1007
  example_suffix=example_suffix
1002
1008
  )
1003
1009
 
1004
- return cls(file_stores)
1010
+ # Convert FileStore objects to Scenario objects with the specified key
1011
+ scenarios = [Scenario({key_name: file_store}) for file_store in file_stores]
1012
+
1013
+ return cls(scenarios)
1005
1014
 
1006
1015
  @classmethod
1007
1016
  def from_list(
@@ -1262,7 +1271,8 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
1262
1271
  import sqlite3
1263
1272
 
1264
1273
  if table is None and sql_query is None:
1265
- raise ValueError("Either table or sql_query must be provided")
1274
+ from .exceptions import ValueScenarioError
1275
+ raise ValueScenarioError("Either table or sql_query must be provided")
1266
1276
 
1267
1277
  try:
1268
1278
  with sqlite3.connect(filepath) as conn:
@@ -1328,7 +1338,8 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
1328
1338
  if "/edit" in url:
1329
1339
  doc_id = url.split("/d/")[1].split("/edit")[0]
1330
1340
  else:
1331
- raise ValueError("Invalid Google Doc URL format.")
1341
+ from .exceptions import ValueScenarioError
1342
+ raise ValueScenarioError("Invalid Google Doc URL format.")
1332
1343
 
1333
1344
  export_url = f"https://docs.google.com/document/d/{doc_id}/export?format=docx"
1334
1345
 
@@ -1532,7 +1543,8 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
1532
1543
  print("The Excel file contains multiple sheets:")
1533
1544
  for name in all_sheets.keys():
1534
1545
  print(f"- {name}")
1535
- raise ValueError("Please provide a sheet name to load data from.")
1546
+ from .exceptions import ValueScenarioError
1547
+ raise ValueScenarioError("Please provide a sheet name to load data from.")
1536
1548
  else:
1537
1549
  # If there is only one sheet, use it
1538
1550
  sheet_name = list(all_sheets.keys())[0]
@@ -1587,7 +1599,8 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
1587
1599
  if "/edit" in url:
1588
1600
  sheet_id = url.split("/d/")[1].split("/edit")[0]
1589
1601
  else:
1590
- raise ValueError("Invalid Google Sheet URL format.")
1602
+ from .exceptions import ValueScenarioError
1603
+ raise ValueScenarioError("Invalid Google Sheet URL format.")
1591
1604
 
1592
1605
  export_url = (
1593
1606
  f"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=xlsx"
@@ -1673,14 +1686,16 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
1673
1686
  file_obj = None
1674
1687
 
1675
1688
  if file_obj is None:
1676
- raise ValueError(f"Could not decode file {source} with any of the attempted encodings. Original error: {last_exception}")
1689
+ from .exceptions import ValueScenarioError
1690
+ raise ValueScenarioError(f"Could not decode file {source} with any of the attempted encodings. Original error: {last_exception}")
1677
1691
 
1678
1692
  reader = csv.reader(file_obj, delimiter=delimiter)
1679
1693
  try:
1680
1694
  header = next(reader)
1681
1695
  observations = [Scenario(dict(zip(header, row))) for row in reader]
1682
1696
  except StopIteration:
1683
- raise ValueError(f"File {source} appears to be empty or has an invalid format")
1697
+ from .exceptions import ValueScenarioError
1698
+ raise ValueScenarioError(f"File {source} appears to be empty or has an invalid format")
1684
1699
 
1685
1700
  finally:
1686
1701
  if file_obj:
@@ -1996,13 +2011,16 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
1996
2011
  import string
1997
2012
 
1998
2013
  if num_options < 2:
1999
- raise ValueError("num_options must be at least 2")
2014
+ from .exceptions import ValueScenarioError
2015
+ raise ValueScenarioError("num_options must be at least 2")
2000
2016
 
2001
2017
  if num_options > len(self):
2002
- raise ValueError(f"num_options ({num_options}) cannot exceed the number of scenarios ({len(self)})")
2018
+ from .exceptions import ValueScenarioError
2019
+ raise ValueScenarioError(f"num_options ({num_options}) cannot exceed the number of scenarios ({len(self)})")
2003
2020
 
2004
2021
  if use_alphabet and num_options > 26:
2005
- raise ValueError("When using alphabet labels, num_options cannot exceed 26 (the number of letters in the English alphabet)")
2022
+ from .exceptions import ValueScenarioError
2023
+ raise ValueScenarioError("When using alphabet labels, num_options cannot exceed 26 (the number of letters in the English alphabet)")
2006
2024
 
2007
2025
  # Convert each scenario to a dictionary
2008
2026
  scenario_dicts = [scenario.to_dict(add_edsl_version=False) for scenario in self]
@@ -1,5 +1,5 @@
1
1
  from typing import Optional
2
- from edsl.utilities.remove_edsl_version import remove_edsl_version
2
+ from ..utilities.remove_edsl_version import remove_edsl_version
3
3
 
4
4
 
5
5
  class CSSRuleMeta(type):
@@ -73,7 +73,7 @@ class CSSRule(metaclass=CSSRuleMeta):
73
73
  d = {"selector": self.selector, "properties": self.properties}
74
74
 
75
75
  if add_esl_version:
76
- from edsl import __version__
76
+ from .. import __version__
77
77
 
78
78
  d["edsl_version"] = __version__
79
79
  d["edsl_class_name"] = self.__class__.__name__
@@ -233,7 +233,7 @@ class SurveyCSS:
233
233
  """
234
234
  d = {"rules": [rule.to_dict() for rule in self.rules.values()]}
235
235
  if add_edsl_version:
236
- from edsl import __version__
236
+ from .. import __version__
237
237
 
238
238
  d["edsl_version"] = __version__
239
239
  d["edsl_class_name"] = self.__class__.__name__
@@ -58,7 +58,8 @@ class Simulator:
58
58
 
59
59
  if num_passes > 100:
60
60
  print("Too many passes.")
61
- raise Exception("Too many passes.")
61
+ from .exceptions import SurveyError
62
+ raise SurveyError("Too many passes.")
62
63
  return self.survey.answers
63
64
 
64
65
  def create_agent(self) -> "Agent":
edsl/tasks/__init__.py CHANGED
@@ -26,9 +26,29 @@ For most users, this module works behind the scenes, but understanding it can
26
26
  be helpful when debugging or optimizing complex EDSL workflows.
27
27
  """
28
28
 
29
- __all__ = ['TaskHistory', 'QuestionTaskCreator', 'TaskCreators', 'TaskStatus', 'TaskStatusDescriptor']
29
+ __all__ = [
30
+ 'TaskHistory',
31
+ 'QuestionTaskCreator',
32
+ 'TaskCreators',
33
+ 'TaskStatus',
34
+ 'TaskStatusDescriptor',
35
+ 'TaskError',
36
+ 'TaskStatusError',
37
+ 'TaskExecutionError',
38
+ 'TaskDependencyError',
39
+ 'TaskResourceError',
40
+ 'TaskHistoryError'
41
+ ]
30
42
 
31
43
  from .task_history import TaskHistory
32
44
  from .question_task_creator import QuestionTaskCreator
33
45
  from .task_creators import TaskCreators
34
- from .task_status_enum import TaskStatus, TaskStatusDescriptor
46
+ from .task_status_enum import TaskStatus, TaskStatusDescriptor
47
+ from .exceptions import (
48
+ TaskError,
49
+ TaskStatusError,
50
+ TaskExecutionError,
51
+ TaskDependencyError,
52
+ TaskResourceError,
53
+ TaskHistoryError
54
+ )
@@ -0,0 +1,72 @@
1
+ """
2
+ This module defines the exception hierarchy for the tasks module.
3
+
4
+ All exceptions related to task creation, execution, and management are defined here.
5
+ These exceptions provide detailed error information for debugging and error reporting.
6
+ """
7
+
8
+ from ..base import BaseException
9
+
10
+
11
+ class TaskError(BaseException):
12
+ """
13
+ Base exception for all tasks-related errors.
14
+
15
+ This is the parent class for all exceptions raised within the tasks module.
16
+ It inherits from BaseException to ensure proper error tracking and reporting.
17
+ """
18
+ pass
19
+
20
+
21
+ class TaskStatusError(TaskError):
22
+ """
23
+ Raised when a task encounters an invalid status transition.
24
+
25
+ This exception is raised when a task attempts to transition to an invalid state
26
+ based on its current state, such as trying to set a completed task to running.
27
+
28
+ Attributes:
29
+ current_status: The current status of the task
30
+ attempted_status: The status that could not be set
31
+ """
32
+ pass
33
+
34
+
35
+ class TaskExecutionError(TaskError):
36
+ """
37
+ Raised when a task encounters an error during execution.
38
+
39
+ This is a general exception for errors that occur while a task is running,
40
+ not specific to dependency resolution or resource allocation.
41
+ """
42
+ pass
43
+
44
+
45
+ class TaskDependencyError(TaskError):
46
+ """
47
+ Raised when there is an issue with task dependencies.
48
+
49
+ This exception is raised for dependency-related issues, such as circular
50
+ dependencies or errors in dependent tasks.
51
+ """
52
+ pass
53
+
54
+
55
+ class TaskResourceError(TaskError):
56
+ """
57
+ Raised when a task cannot acquire necessary resources.
58
+
59
+ This exception is used when a task cannot obtain required resources
60
+ such as tokens or request capacity, beyond normal waiting situations.
61
+ """
62
+ pass
63
+
64
+
65
+ class TaskHistoryError(TaskError):
66
+ """
67
+ Raised for errors related to task history operations.
68
+
69
+ This exception covers issues with recording, accessing, or analyzing
70
+ task execution history and logs.
71
+ """
72
+ pass
@@ -146,7 +146,7 @@ class TaskHistory(RepresentationMixin):
146
146
  "include_traceback": self.include_traceback,
147
147
  }
148
148
  if add_edsl_version:
149
- from edsl import __version__
149
+ from .. import __version__
150
150
 
151
151
  d["edsl_version"] = __version__
152
152
  d["edsl_class_name"] = "TaskHistory"
@@ -406,7 +406,7 @@ class TaskHistory(RepresentationMixin):
406
406
  models_used = set([i.model.model for index, i in self._interviews.items()])
407
407
 
408
408
  from jinja2 import Environment
409
- from edsl.utilities import TemplateLoader
409
+ from ..utilities import TemplateLoader
410
410
 
411
411
  env = Environment(loader=TemplateLoader("edsl", "templates/error_reporting"))
412
412
 
@@ -465,7 +465,7 @@ class TaskHistory(RepresentationMixin):
465
465
  """
466
466
  from IPython.display import display, HTML
467
467
  import os
468
- from edsl.utilities.utilities import is_notebook
468
+ from ..utilities.utilities import is_notebook
469
469
 
470
470
  output = self.generate_html_report(css)
471
471
 
edsl/tokens/__init__.py CHANGED
@@ -1,4 +1,30 @@
1
+ """
2
+ The tokens module provides functionality for tracking and analyzing token usage in EDSL.
3
+
4
+ This module implements classes for tracking and reporting token usage across various
5
+ components of EDSL, particularly for language model calls. It supports aggregation,
6
+ cost calculation, and reporting of token usage metrics.
7
+
8
+ Key components:
9
+ 1. TokenUsage - Tracks prompt and completion tokens for a single operation
10
+ 2. InterviewTokenUsage - Aggregates token usage across an entire interview
11
+ 3. Exception classes for handling token-related errors
12
+
13
+ The token tracking system helps with:
14
+ - Cost estimation and billing
15
+ - Resource utilization analysis
16
+ - Cache effectiveness measurement
17
+ - API quota management
18
+ """
19
+
1
20
  from .token_usage import TokenUsage
2
21
  from .interview_token_usage import InterviewTokenUsage
22
+ from .exceptions import TokenError, TokenUsageError, TokenCostError
3
23
 
4
- __all__ = ["TokenUsage", "InterviewTokenUsage"]
24
+ __all__ = [
25
+ "TokenUsage",
26
+ "InterviewTokenUsage",
27
+ "TokenError",
28
+ "TokenUsageError",
29
+ "TokenCostError"
30
+ ]
@@ -0,0 +1,37 @@
1
+ """
2
+ This module defines the exception hierarchy for the tokens module.
3
+
4
+ All exceptions related to token usage tracking and cost calculations are defined here.
5
+ """
6
+
7
+ from ..base import BaseException
8
+
9
+
10
+ class TokenError(BaseException):
11
+ """
12
+ Base exception for all token-related errors.
13
+
14
+ This is the parent class for all exceptions raised within the tokens module.
15
+ It inherits from BaseException to ensure proper error tracking and reporting.
16
+ """
17
+ pass
18
+
19
+
20
+ class TokenUsageError(TokenError):
21
+ """
22
+ Raised when there is an error in token usage operations.
23
+
24
+ This exception is raised for issues related to token usage tracking,
25
+ such as invalid token counts or incompatible token usage types.
26
+ """
27
+ pass
28
+
29
+
30
+ class TokenCostError(TokenError):
31
+ """
32
+ Raised when there is an error in token cost calculations.
33
+
34
+ This exception is used for issues with cost calculations, such as
35
+ missing or invalid pricing information.
36
+ """
37
+ pass
@@ -1,7 +1,8 @@
1
1
  from typing import Optional
2
2
 
3
3
  from .token_usage import TokenUsage
4
- from edsl.enums import TokenPricing
4
+ from ..enums import TokenPricing
5
+ from .exceptions import TokenUsageError
5
6
 
6
7
  class InterviewTokenUsage:
7
8
  """A class to represent the token usage of an interview."""
@@ -24,7 +25,7 @@ class InterviewTokenUsage:
24
25
  >>> usage3 = usage1 + usage2
25
26
  """
26
27
  if not isinstance(other, InterviewTokenUsage):
27
- raise ValueError(f"Can't add {type(other)} to InterviewTokenSummary")
28
+ raise TokenUsageError(f"Can't add {type(other)} to InterviewTokenSummary")
28
29
  return InterviewTokenUsage(
29
30
  new_token_usage=self.new_token_usage + other.new_token_usage,
30
31
  cached_token_usage=self.cached_token_usage + other.cached_token_usage,
@@ -1,4 +1,5 @@
1
- from edsl.enums import TokenPricing
1
+ from ..enums import TokenPricing
2
+ from .exceptions import TokenUsageError, TokenCostError
2
3
 
3
4
 
4
5
  class TokenUsage:
@@ -15,9 +16,9 @@ class TokenUsage:
15
16
 
16
17
  def __add__(self, other):
17
18
  if not isinstance(other, TokenUsage):
18
- raise ValueError(f"Can't add {type(other)} to InterviewTokenUsage")
19
+ raise TokenUsageError(f"Can't add {type(other)} to InterviewTokenUsage")
19
20
  if self.from_cache != other.from_cache:
20
- raise ValueError("Can't add token usages from different sources")
21
+ raise TokenUsageError("Can't add token usages from different sources")
21
22
  return TokenUsage(
22
23
  from_cache=self.from_cache,
23
24
  prompt_tokens=self.prompt_tokens + other.prompt_tokens,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edsl
3
- Version: 0.1.50
3
+ Version: 0.1.51
4
4
  Summary: Create and analyze LLM-based surveys
5
5
  Home-page: https://www.expectedparrot.com/
6
6
  License: MIT