edsl 0.1.50__py3-none-any.whl → 0.1.52__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.
- edsl/__init__.py +45 -34
- edsl/__version__.py +1 -1
- edsl/base/base_exception.py +2 -2
- edsl/buckets/bucket_collection.py +1 -1
- edsl/buckets/exceptions.py +32 -0
- edsl/buckets/token_bucket_api.py +26 -10
- edsl/caching/cache.py +5 -2
- edsl/caching/remote_cache_sync.py +5 -5
- edsl/caching/sql_dict.py +12 -11
- edsl/config/__init__.py +1 -1
- edsl/config/config_class.py +4 -2
- edsl/conversation/Conversation.py +9 -5
- edsl/conversation/car_buying.py +1 -3
- edsl/conversation/mug_negotiation.py +2 -6
- edsl/coop/__init__.py +11 -8
- edsl/coop/coop.py +15 -13
- edsl/coop/coop_functions.py +1 -1
- edsl/coop/ep_key_handling.py +1 -1
- edsl/coop/price_fetcher.py +2 -2
- edsl/coop/utils.py +2 -2
- edsl/dataset/dataset.py +144 -63
- edsl/dataset/dataset_operations_mixin.py +14 -6
- edsl/dataset/dataset_tree.py +3 -3
- edsl/dataset/display/table_renderers.py +6 -3
- edsl/dataset/file_exports.py +4 -4
- edsl/dataset/r/ggplot.py +3 -3
- edsl/inference_services/available_model_fetcher.py +2 -2
- edsl/inference_services/data_structures.py +5 -5
- edsl/inference_services/inference_service_abc.py +1 -1
- edsl/inference_services/inference_services_collection.py +1 -1
- edsl/inference_services/service_availability.py +3 -3
- edsl/inference_services/services/azure_ai.py +3 -3
- edsl/inference_services/services/google_service.py +1 -1
- edsl/inference_services/services/test_service.py +1 -1
- edsl/instructions/change_instruction.py +5 -4
- edsl/instructions/instruction.py +1 -0
- edsl/instructions/instruction_collection.py +5 -4
- edsl/instructions/instruction_handler.py +10 -8
- edsl/interviews/answering_function.py +20 -21
- edsl/interviews/exception_tracking.py +3 -2
- edsl/interviews/interview.py +1 -1
- edsl/interviews/interview_status_dictionary.py +1 -1
- edsl/interviews/interview_task_manager.py +7 -4
- edsl/interviews/request_token_estimator.py +3 -2
- edsl/interviews/statistics.py +2 -2
- edsl/invigilators/invigilators.py +34 -6
- edsl/jobs/__init__.py +39 -2
- edsl/jobs/async_interview_runner.py +1 -1
- edsl/jobs/check_survey_scenario_compatibility.py +5 -5
- edsl/jobs/data_structures.py +2 -2
- edsl/jobs/html_table_job_logger.py +494 -257
- edsl/jobs/jobs.py +2 -2
- edsl/jobs/jobs_checks.py +5 -5
- edsl/jobs/jobs_component_constructor.py +2 -2
- edsl/jobs/jobs_pricing_estimation.py +1 -1
- edsl/jobs/jobs_runner_asyncio.py +2 -2
- edsl/jobs/jobs_status_enums.py +1 -0
- edsl/jobs/remote_inference.py +47 -13
- edsl/jobs/results_exceptions_handler.py +2 -2
- edsl/language_models/language_model.py +151 -145
- edsl/notebooks/__init__.py +24 -1
- edsl/notebooks/exceptions.py +82 -0
- edsl/notebooks/notebook.py +7 -3
- edsl/notebooks/notebook_to_latex.py +1 -1
- edsl/prompts/__init__.py +23 -2
- edsl/prompts/prompt.py +1 -1
- edsl/questions/__init__.py +4 -4
- edsl/questions/answer_validator_mixin.py +0 -5
- edsl/questions/compose_questions.py +2 -2
- edsl/questions/descriptors.py +1 -1
- edsl/questions/question_base.py +32 -3
- edsl/questions/question_base_prompts_mixin.py +4 -4
- edsl/questions/question_budget.py +503 -102
- edsl/questions/question_check_box.py +658 -156
- edsl/questions/question_dict.py +176 -2
- edsl/questions/question_extract.py +401 -61
- edsl/questions/question_free_text.py +77 -9
- edsl/questions/question_functional.py +118 -9
- edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
- edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
- edsl/questions/question_list.py +246 -26
- edsl/questions/question_matrix.py +586 -73
- edsl/questions/question_multiple_choice.py +213 -47
- edsl/questions/question_numerical.py +360 -29
- edsl/questions/question_rank.py +401 -124
- edsl/questions/question_registry.py +3 -3
- edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
- edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
- edsl/questions/register_questions_meta.py +2 -1
- edsl/questions/response_validator_abc.py +6 -2
- edsl/questions/response_validator_factory.py +10 -12
- edsl/results/report.py +1 -1
- edsl/results/result.py +7 -4
- edsl/results/results.py +500 -271
- edsl/results/results_selector.py +2 -2
- edsl/scenarios/construct_download_link.py +3 -3
- edsl/scenarios/scenario.py +1 -2
- edsl/scenarios/scenario_list.py +41 -23
- edsl/surveys/survey_css.py +3 -3
- edsl/surveys/survey_simulator.py +2 -1
- edsl/tasks/__init__.py +22 -2
- edsl/tasks/exceptions.py +72 -0
- edsl/tasks/task_history.py +48 -11
- edsl/templates/error_reporting/base.html +37 -4
- edsl/templates/error_reporting/exceptions_table.html +105 -33
- edsl/templates/error_reporting/interview_details.html +130 -126
- edsl/templates/error_reporting/overview.html +21 -25
- edsl/templates/error_reporting/report.css +215 -46
- edsl/templates/error_reporting/report.js +122 -20
- edsl/tokens/__init__.py +27 -1
- edsl/tokens/exceptions.py +37 -0
- edsl/tokens/interview_token_usage.py +3 -2
- edsl/tokens/token_usage.py +4 -3
- {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/METADATA +1 -1
- {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/RECORD +118 -116
- edsl/questions/derived/__init__.py +0 -0
- {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/LICENSE +0 -0
- {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/WHEEL +0 -0
- {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/entry_points.txt +0 -0
edsl/results/results_selector.py
CHANGED
@@ -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
|
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
|
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:
|
108
|
-
custom_filenames: Optional[
|
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.
|
edsl/scenarios/scenario.py
CHANGED
@@ -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__()
|
edsl/scenarios/scenario_list.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
901
|
+
"""Create a ScenarioList of Scenario objects from files in a directory.
|
900
902
|
|
901
|
-
This method scans a directory and creates a
|
902
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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]
|
edsl/surveys/survey_css.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from typing import Optional
|
2
|
-
from
|
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
|
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
|
236
|
+
from .. import __version__
|
237
237
|
|
238
238
|
d["edsl_version"] = __version__
|
239
239
|
d["edsl_class_name"] = self.__class__.__name__
|
edsl/surveys/survey_simulator.py
CHANGED
@@ -58,7 +58,8 @@ class Simulator:
|
|
58
58
|
|
59
59
|
if num_passes > 100:
|
60
60
|
print("Too many passes.")
|
61
|
-
|
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__ = [
|
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
|
+
)
|
edsl/tasks/exceptions.py
ADDED
@@ -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
|
edsl/tasks/task_history.py
CHANGED
@@ -146,7 +146,7 @@ class TaskHistory(RepresentationMixin):
|
|
146
146
|
"include_traceback": self.include_traceback,
|
147
147
|
}
|
148
148
|
if add_edsl_version:
|
149
|
-
from
|
149
|
+
from .. import __version__
|
150
150
|
|
151
151
|
d["edsl_version"] = __version__
|
152
152
|
d["edsl_class_name"] = "TaskHistory"
|
@@ -302,22 +302,59 @@ class TaskHistory(RepresentationMixin):
|
|
302
302
|
js = env.joinpath("report.js").read_text()
|
303
303
|
return js
|
304
304
|
|
305
|
+
# @property
|
306
|
+
# def exceptions_table(self) -> dict:
|
307
|
+
# """Return a dictionary of exceptions organized by type, service, model, and question name."""
|
308
|
+
# exceptions_table = {}
|
309
|
+
# for interview in self.total_interviews:
|
310
|
+
# for question_name, exceptions in interview.exceptions.items():
|
311
|
+
# for exception in exceptions:
|
312
|
+
# key = (
|
313
|
+
# exception.exception.__class__.__name__, # Exception type
|
314
|
+
# interview.model._inference_service_, # Service
|
315
|
+
# interview.model.model, # Model
|
316
|
+
# question_name, # Question name
|
317
|
+
# )
|
318
|
+
# if key not in exceptions_table:
|
319
|
+
# exceptions_table[key] = 0
|
320
|
+
# exceptions_table[key] += 1
|
321
|
+
# return exceptions_table
|
322
|
+
|
305
323
|
@property
|
306
324
|
def exceptions_table(self) -> dict:
|
307
|
-
"""Return a dictionary of exceptions organized by type, service, model, and question name."""
|
325
|
+
"""Return a dictionary of unique exceptions organized by type, service, model, and question name."""
|
308
326
|
exceptions_table = {}
|
327
|
+
seen_exceptions = set()
|
328
|
+
|
309
329
|
for interview in self.total_interviews:
|
310
330
|
for question_name, exceptions in interview.exceptions.items():
|
311
331
|
for exception in exceptions:
|
312
|
-
|
332
|
+
# Create a unique identifier for this exception based on its content
|
333
|
+
exception_key = (
|
313
334
|
exception.exception.__class__.__name__, # Exception type
|
314
|
-
interview.model._inference_service_,
|
315
|
-
interview.model.model,
|
316
|
-
question_name,
|
335
|
+
interview.model._inference_service_, # Service
|
336
|
+
interview.model.model, # Model
|
337
|
+
question_name, # Question name
|
338
|
+
exception.name, # Exception name
|
339
|
+
str(exception.traceback)[:100] if exception.traceback else "", # Truncated traceback
|
317
340
|
)
|
318
|
-
|
319
|
-
|
320
|
-
|
341
|
+
|
342
|
+
# Only count if we haven't seen this exact exception before
|
343
|
+
if exception_key not in seen_exceptions:
|
344
|
+
seen_exceptions.add(exception_key)
|
345
|
+
|
346
|
+
# Add to the summary table
|
347
|
+
table_key = (
|
348
|
+
exception.exception.__class__.__name__, # Exception type
|
349
|
+
interview.model._inference_service_, # Service
|
350
|
+
interview.model.model, # Model
|
351
|
+
question_name, # Question name
|
352
|
+
)
|
353
|
+
|
354
|
+
if table_key not in exceptions_table:
|
355
|
+
exceptions_table[table_key] = 0
|
356
|
+
exceptions_table[table_key] += 1
|
357
|
+
|
321
358
|
return exceptions_table
|
322
359
|
|
323
360
|
@property
|
@@ -406,7 +443,7 @@ class TaskHistory(RepresentationMixin):
|
|
406
443
|
models_used = set([i.model.model for index, i in self._interviews.items()])
|
407
444
|
|
408
445
|
from jinja2 import Environment
|
409
|
-
from
|
446
|
+
from ..utilities import TemplateLoader
|
410
447
|
|
411
448
|
env = Environment(loader=TemplateLoader("edsl", "templates/error_reporting"))
|
412
449
|
|
@@ -465,7 +502,7 @@ class TaskHistory(RepresentationMixin):
|
|
465
502
|
"""
|
466
503
|
from IPython.display import display, HTML
|
467
504
|
import os
|
468
|
-
from
|
505
|
+
from ..utilities.utilities import is_notebook
|
469
506
|
|
470
507
|
output = self.generate_html_report(css)
|
471
508
|
|
@@ -5,6 +5,39 @@
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
6
|
<title>Exceptions Report</title>
|
7
7
|
<style>
|
8
|
+
/* Global styles */
|
9
|
+
:root {
|
10
|
+
--primary-color: #3f51b5;
|
11
|
+
--secondary-color: #5c6bc0;
|
12
|
+
--success-color: #4caf50;
|
13
|
+
--error-color: #f44336;
|
14
|
+
--warning-color: #ff9800;
|
15
|
+
--text-color: #333;
|
16
|
+
--light-bg: #f5f7fa;
|
17
|
+
--border-color: #e0e0e0;
|
18
|
+
--header-bg: #f9f9f9;
|
19
|
+
--card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
20
|
+
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
21
|
+
}
|
22
|
+
|
23
|
+
* {
|
24
|
+
box-sizing: border-box;
|
25
|
+
}
|
26
|
+
|
27
|
+
body {
|
28
|
+
font-family: var(--font-family);
|
29
|
+
background-color: var(--light-bg);
|
30
|
+
color: var(--text-color);
|
31
|
+
line-height: 1.6;
|
32
|
+
margin: 0;
|
33
|
+
padding: 20px;
|
34
|
+
}
|
35
|
+
|
36
|
+
.container {
|
37
|
+
max-width: 1200px;
|
38
|
+
margin: 0 auto;
|
39
|
+
}
|
40
|
+
|
8
41
|
{{ css }}
|
9
42
|
</style>
|
10
43
|
|
@@ -14,9 +47,9 @@
|
|
14
47
|
|
15
48
|
</head>
|
16
49
|
<body>
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
50
|
+
<div class="container">
|
51
|
+
{% include 'exceptions_table.html' %}
|
52
|
+
{% include 'interviews.html' %}
|
53
|
+
</div>
|
21
54
|
</body>
|
22
55
|
</html>
|
@@ -1,34 +1,106 @@
|
|
1
|
+
<div class="summary-section">
|
2
|
+
<div class="table-container">
|
3
|
+
<h2>Exceptions Report</h2>
|
4
|
+
<table class="exceptions-table">
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th>Exception Type</th>
|
8
|
+
<th>Service</th>
|
9
|
+
<th>Model</th>
|
10
|
+
<th>Question Name</th>
|
11
|
+
<th class="count-column">Count</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
{% for (exception_type, service, model, question_name), count in exceptions_table.items() %}
|
16
|
+
<tr>
|
17
|
+
<td>{{ exception_type }}</td>
|
18
|
+
<td>{{ service }}</td>
|
19
|
+
<td>{{ model }}</td>
|
20
|
+
<td>{{ question_name }}</td>
|
21
|
+
<td class="count-cell">{{ count }}</td>
|
22
|
+
</tr>
|
23
|
+
{% endfor %}
|
24
|
+
</tbody>
|
25
|
+
</table>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<p class="note">
|
29
|
+
Note: Each unique exception is counted only once. You may encounter repeated exceptions where retries were attempted.
|
30
|
+
</p>
|
31
|
+
</div>
|
32
|
+
|
1
33
|
<style>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
/* Summary section styles */
|
35
|
+
.summary-section {
|
36
|
+
background-color: white;
|
37
|
+
border-radius: 8px;
|
38
|
+
margin-bottom: 24px;
|
39
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
40
|
+
overflow: hidden;
|
41
|
+
border: 1px solid #e0e0e0;
|
42
|
+
padding: 0 0 16px 0;
|
43
|
+
}
|
44
|
+
|
45
|
+
.section-header {
|
46
|
+
background-color: #f9f9f9;
|
47
|
+
padding: 12px 16px;
|
48
|
+
border-bottom: 1px solid #e0e0e0;
|
49
|
+
}
|
50
|
+
|
51
|
+
.section-header h2 {
|
52
|
+
margin: 0;
|
53
|
+
font-size: 18px;
|
54
|
+
font-weight: 500;
|
55
|
+
color: #3f51b5;
|
56
|
+
}
|
57
|
+
|
58
|
+
.table-container {
|
59
|
+
padding: 16px;
|
60
|
+
overflow-x: auto;
|
61
|
+
}
|
62
|
+
|
63
|
+
/* Table styles */
|
64
|
+
.exceptions-table {
|
65
|
+
width: 100%;
|
66
|
+
border-collapse: collapse;
|
67
|
+
margin-bottom: 16px;
|
68
|
+
}
|
69
|
+
|
70
|
+
.exceptions-table th {
|
71
|
+
background-color: #f5f5f5;
|
72
|
+
color: #333;
|
73
|
+
font-weight: 500;
|
74
|
+
text-align: left;
|
75
|
+
padding: 12px;
|
76
|
+
border-bottom: 2px solid #e0e0e0;
|
77
|
+
}
|
78
|
+
|
79
|
+
.exceptions-table td {
|
80
|
+
padding: 10px 12px;
|
81
|
+
border-bottom: 1px solid #e0e0e0;
|
82
|
+
color: #333;
|
83
|
+
}
|
84
|
+
|
85
|
+
.exceptions-table tr:hover {
|
86
|
+
background-color: #f9f9f9;
|
87
|
+
}
|
88
|
+
|
89
|
+
.count-column {
|
90
|
+
width: 80px;
|
91
|
+
text-align: center;
|
92
|
+
}
|
93
|
+
|
94
|
+
.count-cell {
|
95
|
+
text-align: center;
|
96
|
+
font-weight: 500;
|
97
|
+
}
|
98
|
+
|
99
|
+
/* Note styles */
|
100
|
+
.note {
|
101
|
+
font-size: 14px;
|
102
|
+
color: #666;
|
103
|
+
margin: 0 16px;
|
104
|
+
line-height: 1.5;
|
105
|
+
}
|
106
|
+
</style>
|