edsl 0.1.49__py3-none-any.whl → 0.1.51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +107 -0
- edsl/buckets/model_buckets.py +1 -2
- edsl/buckets/token_bucket.py +11 -6
- edsl/buckets/token_bucket_api.py +27 -12
- edsl/buckets/token_bucket_client.py +9 -7
- edsl/caching/cache.py +12 -4
- edsl/caching/cache_entry.py +10 -9
- edsl/caching/exceptions.py +113 -7
- edsl/caching/remote_cache_sync.py +6 -7
- edsl/caching/sql_dict.py +20 -14
- edsl/cli.py +43 -0
- edsl/config/__init__.py +1 -1
- edsl/config/config_class.py +32 -6
- edsl/conversation/Conversation.py +8 -4
- edsl/conversation/car_buying.py +1 -3
- edsl/conversation/exceptions.py +58 -0
- edsl/conversation/mug_negotiation.py +2 -8
- edsl/coop/__init__.py +28 -6
- edsl/coop/coop.py +120 -29
- edsl/coop/coop_functions.py +1 -1
- edsl/coop/ep_key_handling.py +1 -1
- edsl/coop/exceptions.py +188 -9
- edsl/coop/price_fetcher.py +5 -8
- edsl/coop/utils.py +4 -6
- edsl/dataset/__init__.py +5 -4
- edsl/dataset/dataset.py +177 -86
- edsl/dataset/dataset_operations_mixin.py +98 -76
- edsl/dataset/dataset_tree.py +11 -7
- edsl/dataset/display/table_display.py +0 -2
- edsl/dataset/display/table_renderers.py +6 -4
- 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 +5 -6
- edsl/inference_services/data_structures.py +10 -7
- 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 +4 -3
- 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 +7 -3
- 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 +7 -5
- edsl/instructions/exceptions.py +61 -0
- edsl/instructions/instruction.py +6 -2
- edsl/instructions/instruction_collection.py +6 -4
- edsl/instructions/instruction_handler.py +12 -15
- edsl/interviews/ReportErrors.py +0 -3
- edsl/interviews/__init__.py +9 -2
- edsl/interviews/answering_function.py +11 -13
- edsl/interviews/exception_tracking.py +15 -8
- edsl/interviews/exceptions.py +79 -0
- edsl/interviews/interview.py +33 -30
- edsl/interviews/interview_status_dictionary.py +4 -2
- edsl/interviews/interview_status_log.py +2 -1
- edsl/interviews/interview_task_manager.py +5 -5
- edsl/interviews/request_token_estimator.py +5 -2
- edsl/interviews/statistics.py +3 -4
- edsl/invigilators/__init__.py +7 -1
- edsl/invigilators/exceptions.py +79 -0
- edsl/invigilators/invigilator_base.py +0 -1
- edsl/invigilators/invigilators.py +9 -13
- 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 +42 -5
- edsl/jobs/async_interview_runner.py +25 -23
- edsl/jobs/check_survey_scenario_compatibility.py +11 -10
- edsl/jobs/data_structures.py +8 -5
- edsl/jobs/exceptions.py +177 -8
- edsl/jobs/fetch_invigilator.py +1 -1
- edsl/jobs/jobs.py +74 -69
- edsl/jobs/jobs_checks.py +6 -7
- edsl/jobs/jobs_component_constructor.py +4 -4
- edsl/jobs/jobs_pricing_estimation.py +4 -3
- edsl/jobs/jobs_remote_inference_logger.py +5 -4
- edsl/jobs/jobs_runner_asyncio.py +3 -4
- edsl/jobs/jobs_runner_status.py +8 -9
- edsl/jobs/remote_inference.py +27 -24
- edsl/jobs/results_exceptions_handler.py +10 -7
- 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 +9 -8
- 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/__init__.py +24 -1
- edsl/notebooks/exceptions.py +82 -0
- edsl/notebooks/notebook.py +7 -3
- edsl/notebooks/notebook_to_latex.py +1 -2
- 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 +24 -1
- edsl/prompts/exceptions.py +107 -5
- edsl/prompts/prompt.py +15 -7
- edsl/questions/HTMLQuestion.py +5 -11
- edsl/questions/Quick.py +0 -1
- edsl/questions/__init__.py +6 -4
- edsl/questions/answer_validator_mixin.py +318 -323
- edsl/questions/compose_questions.py +3 -3
- edsl/questions/descriptors.py +11 -50
- 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 +46 -19
- edsl/questions/question_base_gen_mixin.py +2 -2
- edsl/questions/question_base_prompts_mixin.py +13 -7
- edsl/questions/question_budget.py +503 -98
- edsl/questions/question_check_box.py +660 -160
- edsl/questions/question_dict.py +345 -194
- edsl/questions/question_extract.py +401 -61
- edsl/questions/question_free_text.py +80 -14
- edsl/questions/question_functional.py +119 -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 +275 -28
- edsl/questions/question_matrix.py +643 -96
- edsl/questions/question_multiple_choice.py +219 -51
- edsl/questions/question_numerical.py +361 -32
- edsl/questions/question_rank.py +401 -124
- edsl/questions/question_registry.py +7 -5
- 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 -2
- edsl/questions/response_validator_abc.py +13 -15
- edsl/questions/response_validator_factory.py +10 -12
- 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 +1 -2
- edsl/results/result.py +11 -9
- edsl/results/results.py +480 -321
- 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 +1 -3
- edsl/scenarios/scenario_list.py +179 -27
- 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_css.py +3 -3
- edsl/surveys/survey_export.py +6 -3
- edsl/surveys/survey_flow_visualization.py +10 -1
- edsl/surveys/survey_simulator.py +2 -1
- edsl/tasks/__init__.py +23 -1
- edsl/tasks/exceptions.py +72 -0
- edsl/tasks/question_task_creator.py +3 -3
- edsl/tasks/task_creators.py +1 -3
- edsl/tasks/task_history.py +8 -10
- edsl/tasks/task_status_log.py +1 -2
- edsl/tokens/__init__.py +29 -1
- edsl/tokens/exceptions.py +37 -0
- edsl/tokens/interview_token_usage.py +3 -2
- edsl/tokens/token_usage.py +4 -3
- 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.49.dist-info → edsl-0.1.51.dist-info}/METADATA +32 -2
- edsl-0.1.51.dist-info/RECORD +365 -0
- edsl-0.1.51.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/questions/derived/__init__.py +0 -0
- 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.49.dist-info/RECORD +0 -347
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/LICENSE +0 -0
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/WHEEL +0 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
"""
|
2
|
+
Exceptions specific to the invigilators module.
|
3
|
+
|
4
|
+
This module defines custom exception classes for all invigilator-related errors
|
5
|
+
in the EDSL framework, ensuring consistent error handling and user feedback.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from ..base import BaseException
|
9
|
+
|
10
|
+
|
11
|
+
class InvigilatorError(BaseException):
|
12
|
+
"""
|
13
|
+
Base exception class for all invigilator-related errors.
|
14
|
+
|
15
|
+
This is the parent class for all exceptions related to invigilator creation,
|
16
|
+
execution, and management in the EDSL framework.
|
17
|
+
|
18
|
+
Examples:
|
19
|
+
```python
|
20
|
+
# Usually not raised directly, but through subclasses
|
21
|
+
# For example, when attempting to use an unimplemented feature
|
22
|
+
invigilator.encode_image(image_path) # Would raise InvigilatorNotImplementedError
|
23
|
+
```
|
24
|
+
"""
|
25
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
|
26
|
+
|
27
|
+
|
28
|
+
class InvigilatorTypeError(InvigilatorError):
|
29
|
+
"""
|
30
|
+
Exception raised when there's a type mismatch in invigilator operations.
|
31
|
+
|
32
|
+
This exception occurs when:
|
33
|
+
- Invalid input types are provided to invigilator methods
|
34
|
+
- Template variables have incompatible types
|
35
|
+
- Return value types don't match expected types
|
36
|
+
|
37
|
+
Examples:
|
38
|
+
```python
|
39
|
+
# Providing an invalid type to an invigilator method
|
40
|
+
invigilator.process_options(123) # Raises InvigilatorTypeError if expected a dict
|
41
|
+
```
|
42
|
+
"""
|
43
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
|
44
|
+
|
45
|
+
|
46
|
+
class InvigilatorValueError(InvigilatorError):
|
47
|
+
"""
|
48
|
+
Exception raised when invalid values are provided to invigilator operations.
|
49
|
+
|
50
|
+
This exception occurs when:
|
51
|
+
- Template variables are missing or invalid
|
52
|
+
- Option values don't match expected patterns
|
53
|
+
- Configuration values are outside allowed ranges
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
```python
|
57
|
+
# Template with invalid variables
|
58
|
+
invigilator.process_template("Hello {{ missing }}") # Raises InvigilatorValueError
|
59
|
+
```
|
60
|
+
"""
|
61
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
|
62
|
+
|
63
|
+
|
64
|
+
class InvigilatorNotImplementedError(InvigilatorError):
|
65
|
+
"""
|
66
|
+
Exception raised when attempting to use an unimplemented feature.
|
67
|
+
|
68
|
+
This exception occurs when:
|
69
|
+
- Calling methods that are not implemented in a specific invigilator
|
70
|
+
- Using features only available in certain invigilator types
|
71
|
+
- Using planned functionality that is not yet available
|
72
|
+
|
73
|
+
Examples:
|
74
|
+
```python
|
75
|
+
# Attempting to use image encoding when not implemented
|
76
|
+
invigilator.encode_image(image_path) # Raises InvigilatorNotImplementedError
|
77
|
+
```
|
78
|
+
"""
|
79
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#invigilators"
|
@@ -2,14 +2,12 @@
|
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
import asyncio
|
4
4
|
from typing import Coroutine, Dict, Any, Optional, TYPE_CHECKING
|
5
|
-
from typing import
|
5
|
+
from typing import Literal
|
6
6
|
|
7
7
|
from ..utilities.decorators import sync_wrapper
|
8
8
|
from ..questions.exceptions import QuestionAnswerValidationError
|
9
|
-
from ..data_transfer_models import AgentResponseDict, EDSLResultObjectInput
|
9
|
+
from ..base.data_transfer_models import AgentResponseDict, EDSLResultObjectInput
|
10
10
|
from ..utilities.decorators import jupyter_nb_handler
|
11
|
-
from ..data_transfer_models import AgentResponseDict
|
12
|
-
from ..data_transfer_models import EDSLResultObjectInput
|
13
11
|
|
14
12
|
from .prompt_constructor import PromptConstructor
|
15
13
|
from .prompt_helpers import PromptPlan
|
@@ -18,13 +16,10 @@ if TYPE_CHECKING:
|
|
18
16
|
from ..prompts import Prompt
|
19
17
|
from ..scenarios import Scenario
|
20
18
|
from ..surveys import Survey
|
21
|
-
from ..prompts import Prompt
|
22
19
|
from ..caching import Cache
|
23
20
|
from ..questions import QuestionBase
|
24
|
-
from ..scenarios import Scenario
|
25
21
|
from ..surveys.memory import MemoryPlan
|
26
22
|
from ..language_models import LanguageModel
|
27
|
-
from ..surveys import Survey
|
28
23
|
from ..agents import Agent
|
29
24
|
from ..key_management import KeyLookup
|
30
25
|
|
@@ -171,14 +166,14 @@ class InvigilatorBase(ABC):
|
|
171
166
|
"""
|
172
167
|
data = {
|
173
168
|
"answer": None,
|
174
|
-
"generated_tokens": None,
|
169
|
+
"generated_tokens": getattr(self, "generated_tokens", None),
|
175
170
|
"comment": failure_reason,
|
176
171
|
"question_name": self.question.question_name,
|
177
172
|
"prompts": self.get_prompts(),
|
178
|
-
"cached_response": None,
|
179
|
-
"raw_model_response": None,
|
180
|
-
"cache_used": None,
|
181
|
-
"cache_key": None,
|
173
|
+
"cached_response": getattr(self, "cached_response", None),
|
174
|
+
"raw_model_response": getattr(self, "raw_model_response", None),
|
175
|
+
"cache_used": getattr(self, "cache_used", None),
|
176
|
+
"cache_key": getattr(self, "cache_key", None),
|
182
177
|
}
|
183
178
|
return EDSLResultObjectInput(**data)
|
184
179
|
|
@@ -285,7 +280,8 @@ class InvigilatorAI(InvigilatorBase):
|
|
285
280
|
}
|
286
281
|
if "encoded_image" in prompts:
|
287
282
|
params["encoded_image"] = prompts["encoded_image"]
|
288
|
-
|
283
|
+
from .exceptions import InvigilatorNotImplementedError
|
284
|
+
raise InvigilatorNotImplementedError("encoded_image not implemented")
|
289
285
|
|
290
286
|
if "files_list" in prompts:
|
291
287
|
params["files_list"] = prompts["files_list"]
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import Dict, Any, Optional,
|
2
|
+
from typing import Dict, Any, Optional, TYPE_CHECKING
|
3
3
|
from functools import cached_property
|
4
|
-
import time
|
5
4
|
import logging
|
6
5
|
|
7
6
|
from ..prompts import Prompt
|
@@ -20,7 +19,6 @@ if TYPE_CHECKING:
|
|
20
19
|
from ..agents import Agent
|
21
20
|
from ..language_models import LanguageModel
|
22
21
|
from ..surveys.memory import MemoryPlan
|
23
|
-
from ..questions import QuestionBase
|
24
22
|
from ..scenarios import Scenario
|
25
23
|
|
26
24
|
logger = logging.getLogger(__name__)
|
@@ -438,8 +436,6 @@ class PromptConstructor:
|
|
438
436
|
|
439
437
|
def get_prompts(self) -> Dict[str, Any]:
|
440
438
|
"""Get the prompts for the question."""
|
441
|
-
start = time.time()
|
442
|
-
|
443
439
|
# Build all the components
|
444
440
|
agent_instructions = self.agent_instructions_prompt
|
445
441
|
agent_persona = self.agent_persona_prompt
|
@@ -76,19 +76,22 @@ class PromptPlan:
|
|
76
76
|
system_prompt_order = (system_prompt_order,)
|
77
77
|
|
78
78
|
if not isinstance(user_prompt_order, tuple):
|
79
|
-
|
79
|
+
from edsl.invigilators.exceptions import InvigilatorTypeError
|
80
|
+
raise InvigilatorTypeError(
|
80
81
|
f"Expected a tuple, but got {type(user_prompt_order).__name__}"
|
81
82
|
)
|
82
83
|
|
83
84
|
if not isinstance(system_prompt_order, tuple):
|
84
|
-
|
85
|
+
from edsl.invigilators.exceptions import InvigilatorTypeError
|
86
|
+
raise InvigilatorTypeError(
|
85
87
|
f"Expected a tuple, but got {type(system_prompt_order).__name__}"
|
86
88
|
)
|
87
89
|
|
88
90
|
self.user_prompt_order = self._convert_to_enum(user_prompt_order)
|
89
91
|
self.system_prompt_order = self._convert_to_enum(system_prompt_order)
|
90
92
|
if not self._is_valid_plan():
|
91
|
-
|
93
|
+
from edsl.invigilators.exceptions import InvigilatorValueError
|
94
|
+
raise InvigilatorValueError(
|
92
95
|
"Invalid plan: must contain each value of PromptComponent exactly once."
|
93
96
|
)
|
94
97
|
|
@@ -109,7 +112,8 @@ class PromptPlan:
|
|
109
112
|
# check is valid components passed
|
110
113
|
component_strings = set([pc.value for pc in PromptComponent])
|
111
114
|
if not set(kwargs.keys()) == component_strings:
|
112
|
-
|
115
|
+
from edsl.invigilators.exceptions import InvigilatorValueError
|
116
|
+
raise InvigilatorValueError(
|
113
117
|
f"Invalid components passed: {set(kwargs.keys())} but expected {PromptComponent}"
|
114
118
|
)
|
115
119
|
|
@@ -1,5 +1,7 @@
|
|
1
|
-
from jinja2 import Environment
|
2
|
-
from typing import List,
|
1
|
+
from jinja2 import Environment
|
2
|
+
from typing import List, Union
|
3
|
+
|
4
|
+
import edsl.scenarios.scenario # noqa: F401
|
3
5
|
|
4
6
|
|
5
7
|
def extract_template_variables(ast) -> List[Union[str, tuple]]:
|
@@ -61,7 +63,7 @@ class QuestionOptionProcessor:
|
|
61
63
|
|
62
64
|
return cls(scenario, prior_answers_dict)
|
63
65
|
|
64
|
-
def __init__(self, scenario: 'Scenario', prior_answers_dict: dict):
|
66
|
+
def __init__(self, scenario: 'edsl.scenarios.scenario.Scenario', prior_answers_dict: dict):
|
65
67
|
# This handles cases where the question has {{ scenario.key }} - eventually
|
66
68
|
# we might not allow 'naked' scenario keys w/o the scenario prefix
|
67
69
|
#new_scenario = scenario.copy()
|
@@ -106,9 +108,11 @@ class QuestionOptionProcessor:
|
|
106
108
|
undeclared_variables = extract_template_variables(parsed_content)
|
107
109
|
|
108
110
|
if not undeclared_variables:
|
109
|
-
|
111
|
+
from edsl.invigilators.exceptions import InvigilatorValueError
|
112
|
+
raise InvigilatorValueError("No variables found in template string")
|
110
113
|
if len(undeclared_variables) > 1:
|
111
|
-
|
114
|
+
from edsl.invigilators.exceptions import InvigilatorValueError
|
115
|
+
raise InvigilatorValueError("Multiple variables found in template string")
|
112
116
|
|
113
117
|
return undeclared_variables[0]
|
114
118
|
|
@@ -156,7 +156,8 @@ class QuestionTemplateReplacementsBuilder:
|
|
156
156
|
if var == "scenario":
|
157
157
|
# If we find a scenario variable, we need to check for nested references
|
158
158
|
# Create a modified template with just {{ scenario.* }} expressions to isolate them
|
159
|
-
|
159
|
+
# Using a template format for reference, not actually used
|
160
|
+
_ = "".join([
|
160
161
|
"{% for key, value in scenario.items() %}{{ key }}{% endfor %}"
|
161
162
|
])
|
162
163
|
try:
|
@@ -168,7 +169,7 @@ class QuestionTemplateReplacementsBuilder:
|
|
168
169
|
for key in scenario_refs:
|
169
170
|
if key in scenario_file_keys:
|
170
171
|
question_file_keys.append(key)
|
171
|
-
except:
|
172
|
+
except Exception:
|
172
173
|
# If there's any issue parsing, just continue with what we have
|
173
174
|
pass
|
174
175
|
|
edsl/jobs/__init__.py
CHANGED
@@ -1,7 +1,44 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
"""
|
2
|
+
The jobs module provides tools for running and managing EDSL jobs.
|
3
|
+
|
4
|
+
It includes classes for job configuration, execution, pricing estimation,
|
5
|
+
and management of concurrent language model API calls.
|
6
|
+
"""
|
5
7
|
|
8
|
+
from .jobs import Jobs
|
9
|
+
from .jobs import RunConfig, RunParameters, RunEnvironment # noqa: F401
|
10
|
+
from .remote_inference import JobsRemoteInferenceHandler # noqa: F401
|
11
|
+
from .jobs_runner_status import JobsRunnerStatusBase # noqa: F401
|
12
|
+
from .exceptions import (
|
13
|
+
JobsErrors,
|
14
|
+
JobsRunError,
|
15
|
+
MissingRemoteInferenceError,
|
16
|
+
InterviewError,
|
17
|
+
InterviewErrorPriorTaskCanceled,
|
18
|
+
InterviewTimeoutError,
|
19
|
+
JobsValueError,
|
20
|
+
JobsCompatibilityError,
|
21
|
+
JobsImplementationError,
|
22
|
+
RemoteInferenceError,
|
23
|
+
JobsTypeError
|
24
|
+
)
|
6
25
|
|
7
|
-
__all__ = [
|
26
|
+
__all__ = [
|
27
|
+
"Jobs",
|
28
|
+
"JobsErrors",
|
29
|
+
"JobsRunError",
|
30
|
+
"MissingRemoteInferenceError",
|
31
|
+
"InterviewError",
|
32
|
+
"InterviewErrorPriorTaskCanceled",
|
33
|
+
"InterviewTimeoutError",
|
34
|
+
"JobsValueError",
|
35
|
+
"JobsCompatibilityError",
|
36
|
+
"JobsImplementationError",
|
37
|
+
"RemoteInferenceError",
|
38
|
+
"JobsTypeError",
|
39
|
+
"JobsRemoteInferenceHandler",
|
40
|
+
"JobsRunnerStatusBase",
|
41
|
+
"RunConfig",
|
42
|
+
"RunParameters",
|
43
|
+
"RunEnvironment"
|
44
|
+
]
|
@@ -9,27 +9,28 @@ from collections.abc import AsyncGenerator
|
|
9
9
|
from typing import List, Generator, Tuple, TYPE_CHECKING
|
10
10
|
from dataclasses import dataclass
|
11
11
|
import asyncio
|
12
|
-
from
|
12
|
+
from ..data_transfer_models import EDSLResultObjectInput
|
13
13
|
|
14
14
|
from ..results import Result
|
15
15
|
from ..interviews import Interview
|
16
16
|
from ..config import Config
|
17
|
+
from .data_structures import RunConfig
|
18
|
+
|
17
19
|
config = Config()
|
18
20
|
|
19
21
|
if TYPE_CHECKING:
|
20
22
|
from ..jobs import Jobs
|
21
23
|
|
22
|
-
from .data_structures import RunConfig
|
23
|
-
|
24
24
|
@dataclass
|
25
25
|
class InterviewResult:
|
26
26
|
"""Container for the result of an interview along with metadata.
|
27
|
-
|
27
|
+
|
28
28
|
Attributes:
|
29
29
|
result: The Result object containing the interview answers
|
30
30
|
interview: The Interview object used to conduct the interview
|
31
31
|
order: The original position of this interview in the processing queue
|
32
32
|
"""
|
33
|
+
|
33
34
|
result: Result
|
34
35
|
interview: Interview
|
35
36
|
order: int
|
@@ -38,10 +39,10 @@ class InterviewResult:
|
|
38
39
|
class AsyncInterviewRunner:
|
39
40
|
"""
|
40
41
|
Runs interviews asynchronously with controlled concurrency.
|
41
|
-
|
42
|
+
|
42
43
|
This class manages the parallel execution of multiple interviews while
|
43
44
|
respecting concurrency limits and handling errors appropriately.
|
44
|
-
|
45
|
+
|
45
46
|
Examples:
|
46
47
|
>>> from unittest.mock import MagicMock, AsyncMock
|
47
48
|
>>> mock_jobs = MagicMock()
|
@@ -52,13 +53,13 @@ class AsyncInterviewRunner:
|
|
52
53
|
>>> isinstance(runner._initialized, asyncio.Event)
|
53
54
|
True
|
54
55
|
"""
|
55
|
-
|
56
|
+
|
56
57
|
MAX_CONCURRENT = int(config.EDSL_MAX_CONCURRENT_TASKS)
|
57
58
|
|
58
59
|
def __init__(self, jobs: "Jobs", run_config: RunConfig):
|
59
60
|
"""
|
60
61
|
Initialize the AsyncInterviewRunner.
|
61
|
-
|
62
|
+
|
62
63
|
Args:
|
63
64
|
jobs: The Jobs object that generates interviews
|
64
65
|
run_config: Configuration for running the interviews
|
@@ -70,13 +71,13 @@ class AsyncInterviewRunner:
|
|
70
71
|
def _expand_interviews(self) -> Generator["Interview", None, None]:
|
71
72
|
"""
|
72
73
|
Create multiple copies of each interview based on the run configuration.
|
73
|
-
|
74
|
+
|
74
75
|
This method expands interviews for repeated runs and ensures each has
|
75
76
|
the proper cache configuration.
|
76
|
-
|
77
|
+
|
77
78
|
Yields:
|
78
79
|
Interview objects ready to be conducted
|
79
|
-
|
80
|
+
|
80
81
|
Examples:
|
81
82
|
>>> from unittest.mock import MagicMock
|
82
83
|
>>> mock_jobs = MagicMock()
|
@@ -105,16 +106,16 @@ class AsyncInterviewRunner:
|
|
105
106
|
) -> Tuple["Result", "Interview"]:
|
106
107
|
"""
|
107
108
|
Asynchronously conduct a single interview.
|
108
|
-
|
109
|
+
|
109
110
|
This method performs the interview and creates a Result object with
|
110
111
|
the extracted answers and model responses.
|
111
|
-
|
112
|
+
|
112
113
|
Args:
|
113
114
|
interview: The interview to conduct
|
114
|
-
|
115
|
+
|
115
116
|
Returns:
|
116
117
|
Tuple containing the Result object and the Interview object
|
117
|
-
|
118
|
+
|
118
119
|
Notes:
|
119
120
|
'extracted_answers' contains the processed and validated answers
|
120
121
|
from the interview, which may differ from the raw model output.
|
@@ -137,14 +138,14 @@ class AsyncInterviewRunner:
|
|
137
138
|
) -> AsyncGenerator[tuple[Result, Interview], None]:
|
138
139
|
"""
|
139
140
|
Run all interviews asynchronously and yield results as they complete.
|
140
|
-
|
141
|
+
|
141
142
|
This method processes interviews in chunks based on MAX_CONCURRENT,
|
142
143
|
maintaining controlled concurrency while yielding results as soon as
|
143
144
|
they become available.
|
144
|
-
|
145
|
+
|
145
146
|
Yields:
|
146
147
|
Tuples of (Result, Interview) as interviews complete
|
147
|
-
|
148
|
+
|
148
149
|
Notes:
|
149
150
|
- Uses structured concurrency patterns for proper resource management
|
150
151
|
- Handles exceptions according to the run configuration
|
@@ -159,11 +160,11 @@ class AsyncInterviewRunner:
|
|
159
160
|
try:
|
160
161
|
result, interview = await self._conduct_interview(interview)
|
161
162
|
self.run_config.environment.jobs_runner_status.add_completed_interview(
|
162
|
-
|
163
|
+
interview
|
163
164
|
)
|
164
165
|
result.order = idx
|
165
166
|
return InterviewResult(result, interview, idx)
|
166
|
-
except Exception
|
167
|
+
except Exception:
|
167
168
|
if self.run_config.parameters.stop_on_exception:
|
168
169
|
raise
|
169
170
|
return None
|
@@ -187,7 +188,7 @@ class AsyncInterviewRunner:
|
|
187
188
|
for result in (r for r in results if r is not None):
|
188
189
|
yield result.result, result.interview
|
189
190
|
|
190
|
-
except Exception
|
191
|
+
except Exception:
|
191
192
|
if self.run_config.parameters.stop_on_exception:
|
192
193
|
raise
|
193
194
|
continue
|
@@ -200,5 +201,6 @@ class AsyncInterviewRunner:
|
|
200
201
|
|
201
202
|
|
202
203
|
if __name__ == "__main__":
|
203
|
-
import doctest
|
204
|
-
|
204
|
+
import doctest
|
205
|
+
|
206
|
+
doctest.testmod()
|
@@ -1,11 +1,12 @@
|
|
1
1
|
import warnings
|
2
2
|
from typing import TYPE_CHECKING
|
3
|
-
from
|
4
|
-
from
|
3
|
+
from ..scenarios import ScenarioList
|
4
|
+
from ..surveys import Survey
|
5
|
+
from .exceptions import JobsCompatibilityError
|
5
6
|
|
6
7
|
if TYPE_CHECKING:
|
7
|
-
from
|
8
|
-
from
|
8
|
+
from ..surveys.survey import Survey
|
9
|
+
from ..scenarios.scenario_list import ScenarioList
|
9
10
|
|
10
11
|
|
11
12
|
class CheckSurveyScenarioCompatibility:
|
@@ -34,19 +35,19 @@ class CheckSurveyScenarioCompatibility:
|
|
34
35
|
>>> s = Scenario({'plop': "A", 'poo': "B"})
|
35
36
|
>>> j = Jobs(survey = Survey(questions=[q])).by(s)
|
36
37
|
>>> cs = CheckSurveyScenarioCompatibility(j.survey, j.scenarios)
|
37
|
-
>>> cs.check(strict = True)
|
38
|
+
>>> cs.check(strict = True) # doctest: +ELLIPSIS
|
38
39
|
Traceback (most recent call last):
|
39
40
|
...
|
40
|
-
|
41
|
+
edsl.jobs.exceptions.JobsCompatibilityError: The following parameters are in the scenarios but not in the survey: {'plop'}...
|
41
42
|
|
42
43
|
>>> q = QuestionFreeText(question_text = "Hello", question_name = "ugly_question")
|
43
44
|
>>> s = Scenario({'ugly_question': "B"})
|
44
45
|
>>> from edsl.scenarios import ScenarioList
|
45
46
|
>>> cs = CheckSurveyScenarioCompatibility(Survey(questions=[q]), ScenarioList([s]))
|
46
|
-
>>> cs.check()
|
47
|
+
>>> cs.check() # doctest: +ELLIPSIS
|
47
48
|
Traceback (most recent call last):
|
48
49
|
...
|
49
|
-
|
50
|
+
edsl.jobs.exceptions.JobsCompatibilityError: The following names are in both the survey question_names and the scenario keys: {'ugly_question'}. This will create issues...
|
50
51
|
"""
|
51
52
|
survey_parameters: set = self.survey.parameters
|
52
53
|
scenario_parameters: set = self.scenarios.parameters
|
@@ -59,7 +60,7 @@ class CheckSurveyScenarioCompatibility:
|
|
59
60
|
):
|
60
61
|
msg0 = f"The following names are in both the survey question_names and the scenario keys: {intersection}. This will create issues."
|
61
62
|
|
62
|
-
raise
|
63
|
+
raise JobsCompatibilityError(msg0)
|
63
64
|
|
64
65
|
if in_survey_but_not_in_scenarios := survey_parameters - scenario_parameters:
|
65
66
|
msg1 = f"The following parameters are in the survey but not in the scenarios: {in_survey_but_not_in_scenarios}"
|
@@ -69,7 +70,7 @@ class CheckSurveyScenarioCompatibility:
|
|
69
70
|
if msg1 or msg2:
|
70
71
|
message = "\n".join(filter(None, [msg1, msg2]))
|
71
72
|
if strict:
|
72
|
-
raise
|
73
|
+
raise JobsCompatibilityError(message)
|
73
74
|
else:
|
74
75
|
if warn:
|
75
76
|
warnings.warn(message)
|
edsl/jobs/data_structures.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
from typing import Optional, Literal
|
1
|
+
from typing import Optional, Literal, TYPE_CHECKING
|
2
2
|
from dataclasses import dataclass, asdict
|
3
|
+
from collections import UserDict
|
4
|
+
from ..data_transfer_models import EDSLResultObjectInput
|
3
5
|
|
4
6
|
# from edsl.data_transfer_models import VisibilityType
|
5
7
|
from ..caching import Cache
|
@@ -9,6 +11,10 @@ from ..base import Base
|
|
9
11
|
|
10
12
|
from .jobs_runner_status import JobsRunnerStatus
|
11
13
|
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from ..questions.question_base import QuestionBase
|
16
|
+
from ..surveys import Survey
|
17
|
+
|
12
18
|
VisibilityType = Literal["private", "public", "unlisted"]
|
13
19
|
|
14
20
|
@dataclass
|
@@ -80,7 +86,7 @@ class RunParameters(Base):
|
|
80
86
|
def to_dict(self, add_edsl_version=False) -> dict:
|
81
87
|
d = asdict(self)
|
82
88
|
if add_edsl_version:
|
83
|
-
from
|
89
|
+
from .. import __version__
|
84
90
|
|
85
91
|
d["edsl_version"] = __version__
|
86
92
|
d["edsl_class_name"] = "RunConfig"
|
@@ -157,9 +163,6 @@ class RunConfig:
|
|
157
163
|
Additional data structures for working with job results and answers.
|
158
164
|
"""
|
159
165
|
|
160
|
-
from collections import UserDict
|
161
|
-
from edsl.data_transfer_models import EDSLResultObjectInput
|
162
|
-
|
163
166
|
|
164
167
|
class Answers(UserDict):
|
165
168
|
"""
|