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.
- edsl/__init__.py +124 -53
- edsl/__version__.py +1 -1
- edsl/agents/agent.py +21 -21
- edsl/agents/agent_list.py +2 -5
- edsl/agents/exceptions.py +119 -5
- edsl/base/__init__.py +10 -35
- edsl/base/base_class.py +71 -36
- edsl/base/base_exception.py +204 -0
- edsl/base/data_transfer_models.py +1 -1
- edsl/base/exceptions.py +94 -0
- edsl/buckets/__init__.py +15 -1
- edsl/buckets/bucket_collection.py +3 -4
- edsl/buckets/exceptions.py +75 -0
- edsl/buckets/model_buckets.py +1 -2
- edsl/buckets/token_bucket.py +11 -6
- edsl/buckets/token_bucket_api.py +1 -2
- edsl/buckets/token_bucket_client.py +9 -7
- edsl/caching/cache.py +7 -2
- edsl/caching/cache_entry.py +10 -9
- edsl/caching/exceptions.py +113 -7
- edsl/caching/remote_cache_sync.py +1 -2
- edsl/caching/sql_dict.py +17 -12
- edsl/cli.py +43 -0
- edsl/config/config_class.py +30 -6
- edsl/conversation/Conversation.py +3 -2
- edsl/conversation/exceptions.py +58 -0
- edsl/conversation/mug_negotiation.py +0 -2
- edsl/coop/__init__.py +20 -1
- edsl/coop/coop.py +129 -38
- edsl/coop/exceptions.py +188 -9
- edsl/coop/price_fetcher.py +3 -6
- edsl/coop/utils.py +4 -6
- edsl/dataset/__init__.py +5 -4
- edsl/dataset/dataset.py +53 -43
- edsl/dataset/dataset_operations_mixin.py +86 -72
- edsl/dataset/dataset_tree.py +9 -5
- edsl/dataset/display/table_display.py +0 -2
- edsl/dataset/display/table_renderers.py +0 -1
- edsl/dataset/exceptions.py +125 -0
- edsl/dataset/file_exports.py +18 -11
- edsl/dataset/r/ggplot.py +13 -6
- edsl/display/__init__.py +27 -0
- edsl/display/core.py +147 -0
- edsl/display/plugin.py +189 -0
- edsl/display/utils.py +52 -0
- edsl/inference_services/__init__.py +9 -1
- edsl/inference_services/available_model_cache_handler.py +1 -1
- edsl/inference_services/available_model_fetcher.py +4 -5
- edsl/inference_services/data_structures.py +9 -6
- edsl/inference_services/exceptions.py +132 -1
- edsl/inference_services/inference_service_abc.py +2 -2
- edsl/inference_services/inference_services_collection.py +2 -6
- edsl/inference_services/registry.py +4 -3
- edsl/inference_services/service_availability.py +2 -1
- edsl/inference_services/services/anthropic_service.py +4 -1
- edsl/inference_services/services/aws_bedrock.py +13 -12
- edsl/inference_services/services/azure_ai.py +12 -10
- edsl/inference_services/services/deep_infra_service.py +1 -4
- edsl/inference_services/services/deep_seek_service.py +1 -5
- edsl/inference_services/services/google_service.py +6 -2
- edsl/inference_services/services/groq_service.py +1 -1
- edsl/inference_services/services/mistral_ai_service.py +4 -2
- edsl/inference_services/services/ollama_service.py +1 -1
- edsl/inference_services/services/open_ai_service.py +7 -5
- edsl/inference_services/services/perplexity_service.py +6 -2
- edsl/inference_services/services/test_service.py +8 -7
- edsl/inference_services/services/together_ai_service.py +2 -3
- edsl/inference_services/services/xai_service.py +1 -1
- edsl/instructions/__init__.py +1 -1
- edsl/instructions/change_instruction.py +3 -2
- edsl/instructions/exceptions.py +61 -0
- edsl/instructions/instruction.py +5 -2
- edsl/instructions/instruction_collection.py +2 -1
- edsl/instructions/instruction_handler.py +4 -9
- edsl/interviews/ReportErrors.py +0 -3
- edsl/interviews/__init__.py +9 -2
- edsl/interviews/answering_function.py +11 -13
- edsl/interviews/exception_tracking.py +14 -7
- edsl/interviews/exceptions.py +79 -0
- edsl/interviews/interview.py +32 -29
- edsl/interviews/interview_status_dictionary.py +4 -2
- edsl/interviews/interview_status_log.py +2 -1
- edsl/interviews/interview_task_manager.py +3 -3
- edsl/interviews/request_token_estimator.py +3 -1
- edsl/interviews/statistics.py +2 -3
- edsl/invigilators/__init__.py +7 -1
- edsl/invigilators/exceptions.py +79 -0
- edsl/invigilators/invigilator_base.py +0 -1
- edsl/invigilators/invigilators.py +8 -12
- edsl/invigilators/prompt_constructor.py +1 -5
- edsl/invigilators/prompt_helpers.py +8 -4
- edsl/invigilators/question_instructions_prompt_builder.py +1 -1
- edsl/invigilators/question_option_processor.py +9 -5
- edsl/invigilators/question_template_replacements_builder.py +3 -2
- edsl/jobs/__init__.py +3 -3
- edsl/jobs/async_interview_runner.py +24 -22
- edsl/jobs/check_survey_scenario_compatibility.py +7 -6
- edsl/jobs/data_structures.py +7 -4
- edsl/jobs/exceptions.py +177 -8
- edsl/jobs/fetch_invigilator.py +1 -1
- edsl/jobs/jobs.py +72 -67
- edsl/jobs/jobs_checks.py +2 -3
- edsl/jobs/jobs_component_constructor.py +2 -2
- edsl/jobs/jobs_pricing_estimation.py +3 -2
- edsl/jobs/jobs_remote_inference_logger.py +5 -4
- edsl/jobs/jobs_runner_asyncio.py +1 -2
- edsl/jobs/jobs_runner_status.py +8 -9
- edsl/jobs/remote_inference.py +26 -23
- edsl/jobs/results_exceptions_handler.py +8 -5
- edsl/key_management/__init__.py +3 -1
- edsl/key_management/exceptions.py +62 -0
- edsl/key_management/key_lookup.py +1 -1
- edsl/key_management/key_lookup_builder.py +37 -14
- edsl/key_management/key_lookup_collection.py +2 -0
- edsl/language_models/__init__.py +1 -1
- edsl/language_models/exceptions.py +302 -14
- edsl/language_models/language_model.py +4 -7
- edsl/language_models/model.py +4 -4
- edsl/language_models/model_list.py +1 -1
- edsl/language_models/price_manager.py +1 -1
- edsl/language_models/raw_response_handler.py +14 -9
- edsl/language_models/registry.py +17 -21
- edsl/language_models/repair.py +0 -6
- edsl/language_models/unused/fake_openai_service.py +0 -1
- edsl/load_plugins.py +69 -0
- edsl/logger.py +146 -0
- edsl/notebooks/notebook.py +1 -1
- edsl/notebooks/notebook_to_latex.py +0 -1
- edsl/plugins/__init__.py +63 -0
- edsl/plugins/built_in/export_example.py +50 -0
- edsl/plugins/built_in/pig_latin.py +67 -0
- edsl/plugins/cli.py +372 -0
- edsl/plugins/cli_typer.py +283 -0
- edsl/plugins/exceptions.py +31 -0
- edsl/plugins/hookspec.py +51 -0
- edsl/plugins/plugin_host.py +128 -0
- edsl/plugins/plugin_manager.py +633 -0
- edsl/plugins/plugins_registry.py +168 -0
- edsl/prompts/__init__.py +2 -0
- edsl/prompts/exceptions.py +107 -5
- edsl/prompts/prompt.py +14 -6
- edsl/questions/HTMLQuestion.py +5 -11
- edsl/questions/Quick.py +0 -1
- edsl/questions/__init__.py +2 -0
- edsl/questions/answer_validator_mixin.py +318 -318
- edsl/questions/compose_questions.py +2 -2
- edsl/questions/descriptors.py +10 -49
- edsl/questions/exceptions.py +278 -22
- edsl/questions/loop_processor.py +7 -5
- edsl/questions/prompt_templates/question_list.jinja +3 -0
- edsl/questions/question_base.py +14 -16
- edsl/questions/question_base_gen_mixin.py +2 -2
- edsl/questions/question_base_prompts_mixin.py +9 -3
- edsl/questions/question_budget.py +9 -5
- edsl/questions/question_check_box.py +3 -5
- edsl/questions/question_dict.py +171 -194
- edsl/questions/question_extract.py +1 -1
- edsl/questions/question_free_text.py +4 -6
- edsl/questions/question_functional.py +4 -3
- edsl/questions/question_list.py +36 -9
- edsl/questions/question_matrix.py +95 -61
- edsl/questions/question_multiple_choice.py +6 -4
- edsl/questions/question_numerical.py +2 -4
- edsl/questions/question_registry.py +4 -2
- edsl/questions/register_questions_meta.py +0 -1
- edsl/questions/response_validator_abc.py +7 -13
- edsl/questions/templates/dict/answering_instructions.jinja +1 -0
- edsl/questions/templates/rank/question_presentation.jinja +1 -1
- edsl/results/__init__.py +1 -1
- edsl/results/exceptions.py +141 -7
- edsl/results/report.py +0 -1
- edsl/results/result.py +4 -5
- edsl/results/results.py +10 -51
- edsl/results/results_selector.py +8 -4
- edsl/scenarios/PdfExtractor.py +2 -2
- edsl/scenarios/construct_download_link.py +69 -35
- edsl/scenarios/directory_scanner.py +33 -14
- edsl/scenarios/document_chunker.py +1 -1
- edsl/scenarios/exceptions.py +238 -14
- edsl/scenarios/file_methods.py +1 -1
- edsl/scenarios/file_store.py +7 -3
- edsl/scenarios/handlers/__init__.py +17 -0
- edsl/scenarios/handlers/docx_file_store.py +0 -5
- edsl/scenarios/handlers/pdf_file_store.py +0 -1
- edsl/scenarios/handlers/pptx_file_store.py +0 -5
- edsl/scenarios/handlers/py_file_store.py +0 -1
- edsl/scenarios/handlers/sql_file_store.py +1 -4
- edsl/scenarios/handlers/sqlite_file_store.py +0 -1
- edsl/scenarios/handlers/txt_file_store.py +1 -1
- edsl/scenarios/scenario.py +0 -1
- edsl/scenarios/scenario_list.py +152 -18
- edsl/scenarios/scenario_list_pdf_tools.py +1 -0
- edsl/scenarios/scenario_selector.py +0 -1
- edsl/surveys/__init__.py +3 -4
- edsl/surveys/dag/__init__.py +4 -2
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/edit_survey.py +1 -0
- edsl/surveys/exceptions.py +165 -9
- edsl/surveys/memory/__init__.py +5 -3
- edsl/surveys/memory/memory_management.py +1 -0
- edsl/surveys/memory/memory_plan.py +6 -15
- edsl/surveys/rules/__init__.py +5 -3
- edsl/surveys/rules/rule.py +1 -2
- edsl/surveys/rules/rule_collection.py +1 -1
- edsl/surveys/survey.py +12 -24
- edsl/surveys/survey_export.py +6 -3
- edsl/surveys/survey_flow_visualization.py +10 -1
- edsl/tasks/__init__.py +2 -0
- edsl/tasks/question_task_creator.py +3 -3
- edsl/tasks/task_creators.py +1 -3
- edsl/tasks/task_history.py +5 -7
- edsl/tasks/task_status_log.py +1 -2
- edsl/tokens/__init__.py +3 -1
- edsl/tokens/token_usage.py +1 -1
- edsl/utilities/__init__.py +21 -1
- edsl/utilities/decorators.py +1 -2
- edsl/utilities/markdown_to_docx.py +2 -2
- edsl/utilities/markdown_to_pdf.py +1 -1
- edsl/utilities/repair_functions.py +0 -1
- edsl/utilities/restricted_python.py +0 -1
- edsl/utilities/template_loader.py +2 -3
- edsl/utilities/utilities.py +8 -29
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/METADATA +32 -2
- edsl-0.1.50.dist-info/RECORD +363 -0
- edsl-0.1.50.dist-info/entry_points.txt +3 -0
- edsl/dataset/smart_objects.py +0 -96
- edsl/exceptions/BaseException.py +0 -21
- edsl/exceptions/__init__.py +0 -54
- edsl/exceptions/configuration.py +0 -16
- edsl/exceptions/general.py +0 -34
- edsl/study/ObjectEntry.py +0 -173
- edsl/study/ProofOfWork.py +0 -113
- edsl/study/SnapShot.py +0 -80
- edsl/study/Study.py +0 -520
- edsl/study/__init__.py +0 -6
- edsl/utilities/interface.py +0 -135
- edsl-0.1.48.dist-info/RECORD +0 -347
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/LICENSE +0 -0
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/WHEEL +0 -0
edsl/scenarios/scenario_list.py
CHANGED
@@ -30,6 +30,8 @@ from typing import (
|
|
30
30
|
import warnings
|
31
31
|
import csv
|
32
32
|
import random
|
33
|
+
import os
|
34
|
+
import glob
|
33
35
|
from io import StringIO
|
34
36
|
import inspect
|
35
37
|
from collections import UserList, defaultdict
|
@@ -57,8 +59,9 @@ from ..dataset import ScenarioListOperationsMixin
|
|
57
59
|
|
58
60
|
from .exceptions import ScenarioError
|
59
61
|
from .scenario import Scenario
|
60
|
-
from .directory_scanner import DirectoryScanner
|
61
62
|
from .scenario_list_pdf_tools import PdfTools
|
63
|
+
from .directory_scanner import DirectoryScanner
|
64
|
+
from .file_store import FileStore
|
62
65
|
|
63
66
|
|
64
67
|
if TYPE_CHECKING:
|
@@ -887,6 +890,119 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
887
890
|
sl = self.duplicate()
|
888
891
|
return ScenarioList([scenario.keep(fields) for scenario in sl])
|
889
892
|
|
893
|
+
@classmethod
|
894
|
+
def from_directory(
|
895
|
+
cls,
|
896
|
+
path: Optional[str] = None,
|
897
|
+
recursive: bool = False,
|
898
|
+
) -> "ScenarioList":
|
899
|
+
"""Create a ScenarioList of FileStore objects from files in a directory.
|
900
|
+
|
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
|
+
the current working directory is used.
|
904
|
+
|
905
|
+
Args:
|
906
|
+
path: The directory path to scan, optionally including a wildcard pattern.
|
907
|
+
If None, uses the current working directory.
|
908
|
+
Examples:
|
909
|
+
- "/path/to/directory" - scans all files in the directory
|
910
|
+
- "/path/to/directory/*.py" - scans only Python files in the directory
|
911
|
+
- "*.txt" - scans only text files in the current working directory
|
912
|
+
recursive: Whether to scan subdirectories recursively. Defaults to False.
|
913
|
+
|
914
|
+
Returns:
|
915
|
+
A ScenarioList containing FileStore objects for all matching files.
|
916
|
+
|
917
|
+
Raises:
|
918
|
+
FileNotFoundError: If the specified directory does not exist.
|
919
|
+
|
920
|
+
Examples:
|
921
|
+
# Get all files in the current directory
|
922
|
+
sl = ScenarioList.from_directory()
|
923
|
+
|
924
|
+
# Get all Python files in a specific directory
|
925
|
+
sl = ScenarioList.from_directory('*.py')
|
926
|
+
|
927
|
+
# Get all image files in the current directory
|
928
|
+
sl = ScenarioList.from_directory('*.png')
|
929
|
+
|
930
|
+
# Get all files recursively including subdirectories
|
931
|
+
sl = ScenarioList.from_directory(recursive=True)
|
932
|
+
"""
|
933
|
+
# Handle default case - use current directory
|
934
|
+
if path is None:
|
935
|
+
directory_path = os.getcwd()
|
936
|
+
pattern = None
|
937
|
+
else:
|
938
|
+
# Special handling for "**" pattern which indicates recursive scanning
|
939
|
+
has_recursive_pattern = '**' in path if path else False
|
940
|
+
|
941
|
+
# Check if path contains any wildcard
|
942
|
+
if path and ('*' in path):
|
943
|
+
# Handle "**/*.ext" pattern - find the directory part before the **
|
944
|
+
if has_recursive_pattern:
|
945
|
+
# Extract the base directory by finding the part before **
|
946
|
+
parts = path.split('**')
|
947
|
+
if parts and parts[0]:
|
948
|
+
# Remove trailing slash if any
|
949
|
+
directory_path = parts[0].rstrip('/')
|
950
|
+
if not directory_path:
|
951
|
+
directory_path = os.getcwd()
|
952
|
+
# Get the pattern after **
|
953
|
+
pattern = parts[1] if len(parts) > 1 else None
|
954
|
+
if pattern and pattern.startswith('/'):
|
955
|
+
pattern = pattern[1:] # Remove leading slash
|
956
|
+
else:
|
957
|
+
directory_path = os.getcwd()
|
958
|
+
pattern = None
|
959
|
+
# Handle case where path is just a pattern (e.g., "*.py")
|
960
|
+
elif os.path.dirname(path) == '':
|
961
|
+
directory_path = os.getcwd()
|
962
|
+
pattern = os.path.basename(path)
|
963
|
+
else:
|
964
|
+
# Split into directory and pattern
|
965
|
+
directory_path = os.path.dirname(path)
|
966
|
+
if not directory_path:
|
967
|
+
directory_path = os.getcwd()
|
968
|
+
pattern = os.path.basename(path)
|
969
|
+
else:
|
970
|
+
# Path is a directory with no pattern
|
971
|
+
directory_path = path
|
972
|
+
pattern = None
|
973
|
+
|
974
|
+
# Ensure directory exists
|
975
|
+
if not os.path.isdir(directory_path):
|
976
|
+
raise FileNotFoundError(f"Directory not found: {directory_path}")
|
977
|
+
|
978
|
+
# Create a DirectoryScanner for the directory
|
979
|
+
scanner = DirectoryScanner(directory_path)
|
980
|
+
|
981
|
+
# Configure wildcard pattern filtering
|
982
|
+
suffix_allow_list = None
|
983
|
+
example_suffix = None
|
984
|
+
|
985
|
+
if pattern:
|
986
|
+
if pattern.startswith('*.'):
|
987
|
+
# Simple extension filter (e.g., "*.py")
|
988
|
+
suffix_allow_list = [pattern[2:]]
|
989
|
+
elif '*' in pattern:
|
990
|
+
# Other wildcard patterns
|
991
|
+
example_suffix = pattern
|
992
|
+
else:
|
993
|
+
# Handle simple non-wildcard pattern (exact match)
|
994
|
+
example_suffix = pattern
|
995
|
+
|
996
|
+
# Use scanner to find files and create FileStore objects
|
997
|
+
file_stores = scanner.scan(
|
998
|
+
factory=lambda path: FileStore(path),
|
999
|
+
recursive=recursive,
|
1000
|
+
suffix_allow_list=suffix_allow_list,
|
1001
|
+
example_suffix=example_suffix
|
1002
|
+
)
|
1003
|
+
|
1004
|
+
return cls(file_stores)
|
1005
|
+
|
890
1006
|
@classmethod
|
891
1007
|
def from_list(
|
892
1008
|
cls, name: str, values: list, func: Optional[Callable] = None
|
@@ -903,7 +1019,9 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
903
1019
|
ScenarioList([Scenario({'name': 'Alice'}), Scenario({'name': 'Bob'})])
|
904
1020
|
"""
|
905
1021
|
if not func:
|
906
|
-
|
1022
|
+
def identity(x):
|
1023
|
+
return x
|
1024
|
+
func = identity
|
907
1025
|
return cls([Scenario({name: func(value)}) for value in values])
|
908
1026
|
|
909
1027
|
def table(
|
@@ -914,7 +1032,6 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
914
1032
|
) -> str:
|
915
1033
|
"""Return the ScenarioList as a table."""
|
916
1034
|
|
917
|
-
from tabulate import tabulate_formats
|
918
1035
|
|
919
1036
|
if tablefmt is not None and tablefmt not in tabulate_formats:
|
920
1037
|
raise ValueError(
|
@@ -951,13 +1068,10 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
951
1068
|
|
952
1069
|
Example:
|
953
1070
|
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
Traceback (most recent call last):
|
959
|
-
...
|
960
|
-
AssertionError
|
1071
|
+
# Example:
|
1072
|
+
# s = ScenarioList([Scenario({'a': 1, 'b': 2}), Scenario({'a': 3, 'b': 4})])
|
1073
|
+
# s.reorder_keys(['b', 'a']) # Returns a new ScenarioList with reordered keys
|
1074
|
+
# Attempting s.reorder_keys(['a', 'b', 'c']) would fail as 'c' is not a valid key
|
961
1075
|
"""
|
962
1076
|
assert set(new_order) == set(self.parameters)
|
963
1077
|
|
@@ -1020,7 +1134,7 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
1020
1134
|
return ScenarioList(new_scenarios)
|
1021
1135
|
|
1022
1136
|
@classmethod
|
1023
|
-
def from_list_of_tuples(self, *names: str, values: List[
|
1137
|
+
def from_list_of_tuples(self, *names: str, values: List[tuple]) -> ScenarioList:
|
1024
1138
|
sl = ScenarioList.from_list(names[0], [value[0] for value in values])
|
1025
1139
|
for index, name in enumerate(names[1:]):
|
1026
1140
|
sl = sl.add_list(name, [value[index + 1] for value in values])
|
@@ -1210,7 +1324,6 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
1210
1324
|
"""
|
1211
1325
|
import tempfile
|
1212
1326
|
import requests
|
1213
|
-
from docx import Document
|
1214
1327
|
|
1215
1328
|
if "/edit" in url:
|
1216
1329
|
doc_id = url.split("/d/")[1].split("/edit")[0]
|
@@ -1468,7 +1581,6 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
1468
1581
|
ScenarioList: An instance of the ScenarioList class.
|
1469
1582
|
|
1470
1583
|
"""
|
1471
|
-
import pandas as pd
|
1472
1584
|
import tempfile
|
1473
1585
|
import requests
|
1474
1586
|
|
@@ -1542,14 +1654,37 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
1542
1654
|
response.raise_for_status()
|
1543
1655
|
file_obj = StringIO(response.text)
|
1544
1656
|
else:
|
1545
|
-
|
1657
|
+
# Try different encodings if the default fails
|
1658
|
+
encodings_to_try = ["utf-8", "latin-1", "cp1252", "ISO-8859-1"]
|
1659
|
+
last_exception = None
|
1660
|
+
file_obj = None
|
1661
|
+
|
1662
|
+
for encoding in encodings_to_try:
|
1663
|
+
try:
|
1664
|
+
file_obj = open(source, "r", encoding=encoding)
|
1665
|
+
# Test reading a bit to verify encoding
|
1666
|
+
file_obj.readline()
|
1667
|
+
file_obj.seek(0) # Reset file position
|
1668
|
+
break
|
1669
|
+
except UnicodeDecodeError as e:
|
1670
|
+
last_exception = e
|
1671
|
+
if file_obj:
|
1672
|
+
file_obj.close()
|
1673
|
+
file_obj = None
|
1674
|
+
|
1675
|
+
if file_obj is None:
|
1676
|
+
raise ValueError(f"Could not decode file {source} with any of the attempted encodings. Original error: {last_exception}")
|
1546
1677
|
|
1547
1678
|
reader = csv.reader(file_obj, delimiter=delimiter)
|
1548
|
-
|
1549
|
-
|
1679
|
+
try:
|
1680
|
+
header = next(reader)
|
1681
|
+
observations = [Scenario(dict(zip(header, row))) for row in reader]
|
1682
|
+
except StopIteration:
|
1683
|
+
raise ValueError(f"File {source} appears to be empty or has an invalid format")
|
1550
1684
|
|
1551
1685
|
finally:
|
1552
|
-
file_obj
|
1686
|
+
if file_obj:
|
1687
|
+
file_obj.close()
|
1553
1688
|
|
1554
1689
|
return cls(observations)
|
1555
1690
|
|
@@ -1614,7 +1749,6 @@ class ScenarioList(Base, UserList, ScenarioListOperationsMixin):
|
|
1614
1749
|
"""
|
1615
1750
|
from ..surveys import Survey
|
1616
1751
|
from ..questions import QuestionBase
|
1617
|
-
from ..jobs import Jobs
|
1618
1752
|
|
1619
1753
|
if isinstance(survey, QuestionBase):
|
1620
1754
|
return Survey([survey]).by(self)
|
edsl/surveys/__init__.py
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
from .survey import Survey
|
2
|
-
from .survey_flow_visualization import SurveyFlowVisualization
|
3
|
-
|
4
|
-
from .
|
5
|
-
from .base import EndOfSurvey, RulePriority
|
2
|
+
from .survey_flow_visualization import SurveyFlowVisualization # noqa: F401
|
3
|
+
from .rules import Rule, RuleCollection # noqa: F401
|
4
|
+
from .base import EndOfSurvey, RulePriority # noqa: F401
|
6
5
|
|
7
6
|
__all__ = ["Survey"]
|
8
7
|
## , "SurveyFlowVisualization", "Rule", "RuleCollection", "EndOfSurvey", "RulePriority"]
|
edsl/surveys/dag/__init__.py
CHANGED
edsl/surveys/descriptors.py
CHANGED
edsl/surveys/edit_survey.py
CHANGED
@@ -5,6 +5,7 @@ from typing import Union, Optional, TYPE_CHECKING
|
|
5
5
|
if TYPE_CHECKING:
|
6
6
|
from ..questions import QuestionBase
|
7
7
|
from .survey import Survey
|
8
|
+
from ..instructions import Instruction, ChangeInstruction
|
8
9
|
|
9
10
|
from .exceptions import SurveyError, SurveyCreationError
|
10
11
|
from .rules.rule import Rule
|
edsl/surveys/exceptions.py
CHANGED
@@ -1,36 +1,192 @@
|
|
1
1
|
from ..base import BaseException
|
2
2
|
|
3
3
|
class SurveyError(BaseException):
|
4
|
-
|
4
|
+
"""
|
5
|
+
Base exception class for all survey-related errors.
|
6
|
+
|
7
|
+
This exception is the parent class for all exceptions related to Survey operations,
|
8
|
+
including creation, validation, and navigation. It provides a common type
|
9
|
+
for catching any survey-specific error.
|
10
|
+
|
11
|
+
This exception is raised directly when:
|
12
|
+
- Question names don't meet validation requirements
|
13
|
+
- Survey operations encounter general errors not covered by more specific exceptions
|
14
|
+
"""
|
15
|
+
doc_page = "surveys"
|
5
16
|
|
6
17
|
|
7
18
|
class SurveyCreationError(SurveyError):
|
8
|
-
|
19
|
+
"""
|
20
|
+
Exception raised when there's an error creating or modifying a survey.
|
21
|
+
|
22
|
+
This exception occurs when:
|
23
|
+
- Adding skip rules to EndOfSurvey (which isn't allowed)
|
24
|
+
- Combining surveys with non-default rules
|
25
|
+
- Adding questions with duplicate names
|
26
|
+
- Creating invalid question groups
|
27
|
+
- Validating group names and boundaries
|
28
|
+
|
29
|
+
To fix this error:
|
30
|
+
1. Ensure all question names in a survey are unique
|
31
|
+
2. Don't add skip rules to EndOfSurvey
|
32
|
+
3. Check that question groups are properly defined
|
33
|
+
4. When combining surveys, ensure they're compatible
|
34
|
+
|
35
|
+
Examples:
|
36
|
+
```python
|
37
|
+
survey.add(question) # Raises SurveyCreationError if question's name already exists
|
38
|
+
survey.add_rules_to_question(EndOfSurvey) # Raises SurveyCreationError
|
39
|
+
```
|
40
|
+
"""
|
41
|
+
doc_anchor = "creating-surveys"
|
9
42
|
|
10
43
|
|
11
44
|
class SurveyHasNoRulesError(SurveyError):
|
12
|
-
|
45
|
+
"""
|
46
|
+
Exception raised when rules are required but not found for a question.
|
47
|
+
|
48
|
+
This exception occurs when:
|
49
|
+
- The survey's next_question method is called but no rules exist for the current question
|
50
|
+
- Navigation can't proceed because there's no defined path forward
|
51
|
+
|
52
|
+
To fix this error:
|
53
|
+
1. Add appropriate rules to all questions in the survey
|
54
|
+
2. Ensure the survey has a complete navigation path from start to finish
|
55
|
+
3. Use default rules where appropriate (add_default_rules method)
|
56
|
+
|
57
|
+
Examples:
|
58
|
+
```python
|
59
|
+
survey.next_question(0) # Raises SurveyHasNoRulesError if question 0 has no rules
|
60
|
+
```
|
61
|
+
"""
|
62
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/surveys.html#rules"
|
13
63
|
|
14
64
|
|
15
65
|
class SurveyRuleSendsYouBackwardsError(SurveyError):
|
16
|
-
|
66
|
+
"""
|
67
|
+
Exception raised when a rule would navigate backward in the survey flow.
|
68
|
+
|
69
|
+
This exception occurs during rule initialization to prevent rules that
|
70
|
+
would navigate backward in the survey flow, which is not allowed.
|
71
|
+
|
72
|
+
Backward navigation creates potential loops and is generally considered
|
73
|
+
poor survey design. EDSL enforces forward-only navigation.
|
74
|
+
|
75
|
+
To fix this error:
|
76
|
+
1. Redesign your survey to avoid backward navigation
|
77
|
+
2. Use forward-only rules with proper branching logic
|
78
|
+
3. Consider using memory to carry forward information if needed
|
79
|
+
|
80
|
+
Examples:
|
81
|
+
```python
|
82
|
+
survey.add_rule(question_index=2, rule=Rule(lambda x: True, 1)) # Raises SurveyRuleSendsYouBackwardsError
|
83
|
+
```
|
84
|
+
"""
|
85
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/surveys.html#rules"
|
17
86
|
|
18
87
|
|
19
88
|
class SurveyRuleSkipLogicSyntaxError(SurveyError):
|
20
|
-
|
89
|
+
"""
|
90
|
+
Exception raised when a rule's expression has invalid syntax.
|
91
|
+
|
92
|
+
This exception occurs when:
|
93
|
+
- The expression in a rule is not valid Python syntax
|
94
|
+
- The expression can't be parsed or compiled
|
95
|
+
|
96
|
+
To fix this error:
|
97
|
+
1. Check the syntax of your rule expression
|
98
|
+
2. Ensure all variables in the expression are properly referenced
|
99
|
+
3. Test the expression in isolation to verify it's valid Python
|
100
|
+
|
101
|
+
Examples:
|
102
|
+
```python
|
103
|
+
Rule(lambda x: x[question] ==, 1) # Raises SurveyRuleSkipLogicSyntaxError (invalid syntax)
|
104
|
+
```
|
105
|
+
"""
|
106
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/surveys.html#rules"
|
21
107
|
|
22
108
|
|
23
109
|
class SurveyRuleReferenceInRuleToUnknownQuestionError(SurveyError):
|
24
|
-
|
110
|
+
"""
|
111
|
+
Exception raised when a rule references an unknown question.
|
112
|
+
|
113
|
+
This exception is designed to catch cases where a rule's condition
|
114
|
+
references a question that doesn't exist in the survey.
|
115
|
+
|
116
|
+
To fix this error:
|
117
|
+
1. Ensure all questions referenced in rule conditions exist in the survey
|
118
|
+
2. Check for typos in question names or indices
|
119
|
+
|
120
|
+
Note: This exception is defined but not actively used in the codebase.
|
121
|
+
It raises Exception("not used") to indicate this state.
|
122
|
+
"""
|
123
|
+
def __init__(self, message="Rule references an unknown question", **kwargs):
|
124
|
+
super().__init__(message, **kwargs)
|
25
125
|
|
26
126
|
|
27
127
|
class SurveyRuleRefersToFutureStateError(SurveyError):
|
28
|
-
|
128
|
+
"""
|
129
|
+
Exception raised when a rule references questions that come later in the survey.
|
130
|
+
|
131
|
+
This exception occurs when:
|
132
|
+
- A rule condition refers to questions that haven't been presented yet
|
133
|
+
- Rule evaluation would require information not yet collected
|
134
|
+
|
135
|
+
To fix this error:
|
136
|
+
1. Redesign your rules to only reference current or previous questions
|
137
|
+
2. Ensure rule conditions only depend on information already collected
|
138
|
+
3. Restructure your survey if you need different branching logic
|
139
|
+
|
140
|
+
Examples:
|
141
|
+
```python
|
142
|
+
# If question 3 hasn't been asked yet:
|
143
|
+
Rule(lambda x: x['q3_answer'] == 'Yes', next_question=4) # Raises SurveyRuleRefersToFutureStateError
|
144
|
+
```
|
145
|
+
"""
|
146
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/surveys.html#rules"
|
29
147
|
|
30
148
|
|
31
149
|
class SurveyRuleCollectionHasNoRulesAtNodeError(SurveyError):
|
32
|
-
|
150
|
+
"""
|
151
|
+
Exception raised when no rules are found for a specific question during navigation.
|
152
|
+
|
153
|
+
This exception occurs when:
|
154
|
+
- The RuleCollection's next_question method can't find applicable rules
|
155
|
+
- A survey is trying to determine the next question but has no rule for the current state
|
156
|
+
|
157
|
+
To fix this error:
|
158
|
+
1. Add rules for all questions in your survey
|
159
|
+
2. Ensure rules are properly added to the RuleCollection
|
160
|
+
3. Add default rules where appropriate
|
161
|
+
|
162
|
+
Examples:
|
163
|
+
```python
|
164
|
+
# If rule_collection has no rules for question 2:
|
165
|
+
rule_collection.next_question(2, {}) # Raises SurveyRuleCollectionHasNoRulesAtNodeError
|
166
|
+
```
|
167
|
+
"""
|
168
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/surveys.html#rule-collections"
|
33
169
|
|
34
170
|
|
35
171
|
class SurveyRuleCannotEvaluateError(SurveyError):
|
36
|
-
|
172
|
+
"""
|
173
|
+
Exception raised when a rule expression cannot be evaluated.
|
174
|
+
|
175
|
+
This exception occurs when:
|
176
|
+
- The rule's expression fails to evaluate with the provided data
|
177
|
+
- Required variables are missing in the evaluation context
|
178
|
+
- The expression contains errors that only appear at runtime
|
179
|
+
|
180
|
+
To fix this error:
|
181
|
+
1. Check that your rule expression is valid
|
182
|
+
2. Ensure all referenced variables are available in the context
|
183
|
+
3. Add error handling in complex expressions
|
184
|
+
4. Test rules with sample data before using in production
|
185
|
+
|
186
|
+
Examples:
|
187
|
+
```python
|
188
|
+
# If 'q1_answer' is not in the data dictionary:
|
189
|
+
Rule(lambda x: x['q1_answer'] == 'Yes', 2).evaluate({}) # Raises SurveyRuleCannotEvaluateError
|
190
|
+
```
|
191
|
+
"""
|
192
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/surveys.html#rules"
|
edsl/surveys/memory/__init__.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
-
from .memory_plan import MemoryPlan
|
2
|
-
from .memory_management import MemoryManagement
|
3
|
-
from .memory import Memory
|
1
|
+
from .memory_plan import MemoryPlan # noqa: F401
|
2
|
+
from .memory_management import MemoryManagement # noqa: F401
|
3
|
+
from .memory import Memory # noqa: F401
|
4
|
+
|
5
|
+
__all__ = []
|
@@ -8,6 +8,7 @@ if TYPE_CHECKING:
|
|
8
8
|
from ...surveys import Survey
|
9
9
|
from ...prompts import Prompt
|
10
10
|
from ..dag import DAG
|
11
|
+
from ...questions.question_base import QuestionBase
|
11
12
|
|
12
13
|
class MemoryPlan(UserDict):
|
13
14
|
"""A survey has a memory plan that specifies what the agent should remember when answering a question.
|
@@ -103,23 +104,13 @@ class MemoryPlan(UserDict):
|
|
103
104
|
:param focal_question: The current question being answered.
|
104
105
|
:param prior_question: The question that was answered before the focal question that should be remembered.
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
Traceback (most recent call last):
|
109
|
-
...
|
110
|
-
ValueError: q1 must come before q0.
|
107
|
+
# Prior question must come before focal question
|
108
|
+
# Example: adding "q0", "q1" would raise ValueError as q1 comes after q0
|
111
109
|
|
112
|
-
|
113
|
-
|
114
|
-
Traceback (most recent call last):
|
115
|
-
...
|
116
|
-
ValueError: crap is not in the survey. Current names are ['q0', 'q1', 'q2']
|
110
|
+
# Question names must exist in the survey
|
111
|
+
# Example: using a non-existent question name "crap" would raise ValueError
|
117
112
|
|
118
|
-
|
119
|
-
>>> mp.add_single_memory("crap", "q0")
|
120
|
-
Traceback (most recent call last):
|
121
|
-
...
|
122
|
-
ValueError: crap is not in the survey. Current names are ['q0', 'q1', 'q2']
|
113
|
+
# Similarly, focal question must also exist in the survey
|
123
114
|
"""
|
124
115
|
self._check_valid_question_name(focal_question)
|
125
116
|
self._check_valid_question_name(prior_question)
|
edsl/surveys/rules/__init__.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
-
from .rule import Rule
|
2
|
-
from .rule_manager import RuleManager
|
3
|
-
from .rule_collection import RuleCollection
|
1
|
+
from .rule import Rule # noqa: F401
|
2
|
+
from .rule_manager import RuleManager # noqa: F401
|
3
|
+
from .rule_collection import RuleCollection # noqa: F401
|
4
|
+
|
5
|
+
__all__ = []
|
edsl/surveys/rules/rule.py
CHANGED
@@ -19,7 +19,7 @@ with a low (-1) priority.
|
|
19
19
|
|
20
20
|
import ast
|
21
21
|
import random
|
22
|
-
from typing import Any, Union
|
22
|
+
from typing import Any, Union
|
23
23
|
from collections import defaultdict
|
24
24
|
|
25
25
|
|
@@ -27,7 +27,6 @@ from collections import defaultdict
|
|
27
27
|
from simpleeval import EvalWithCompoundTypes
|
28
28
|
|
29
29
|
from ..exceptions import SurveyError
|
30
|
-
|
31
30
|
from ..exceptions import (
|
32
31
|
SurveyRuleCannotEvaluateError,
|
33
32
|
SurveyRuleRefersToFutureStateError,
|