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
@@ -3,7 +3,6 @@
|
|
3
3
|
from .question_functional import QuestionFunctional
|
4
4
|
from .question_base import QuestionBase
|
5
5
|
from ..scenarios import Scenario
|
6
|
-
from ..agents import Agent
|
7
6
|
|
8
7
|
|
9
8
|
def compose_questions(
|
@@ -20,7 +19,8 @@ def compose_questions(
|
|
20
19
|
if question_name is None:
|
21
20
|
question_name = f"{q1.question_name}_{q2.question_name}"
|
22
21
|
if q1.question_name not in q2.question_text:
|
23
|
-
|
22
|
+
from .exceptions import QuestionValueError
|
23
|
+
raise QuestionValueError(
|
24
24
|
f"q2 requires a field not present in q1's answer. "
|
25
25
|
f"q1: {q1.question_name}, q2: {q2.question_name}"
|
26
26
|
)
|
edsl/questions/descriptors.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
import re
|
5
|
-
from typing import Any, Callable, List
|
5
|
+
from typing import Any, Callable, List
|
6
6
|
from .exceptions import (
|
7
7
|
QuestionCreationValidationError,
|
8
8
|
QuestionAnswerValidationError,
|
@@ -243,7 +243,7 @@ class QuestionNameDescriptor(BaseDescriptor):
|
|
243
243
|
|
244
244
|
if not is_valid_variable_name(value):
|
245
245
|
raise QuestionCreationValidationError(
|
246
|
-
f"`question_name` is not a valid variable name (got {value})."
|
246
|
+
f"`question_name` is not a valid variable name (got '{value}')."
|
247
247
|
)
|
248
248
|
|
249
249
|
|
@@ -257,18 +257,11 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
257
257
|
... assert len(w) == 1
|
258
258
|
... assert "trailing whitespace" in str(w[0].message)
|
259
259
|
|
260
|
-
|
261
|
-
Traceback (most recent call last):
|
262
|
-
...
|
263
|
-
edsl.questions.exceptions.QuestionCreationValidationError: Question options must be unique (got ['a', 'b', 'c', 'd', 'd']).
|
260
|
+
# Duplicate options would raise QuestionCreationValidationError
|
264
261
|
|
265
262
|
We allow dynamic question options, which are strings of the form '{{ question_options }}'.
|
266
263
|
|
267
264
|
>>> _ = q_class("{{dynamic_options}}")
|
268
|
-
>>> _ = q_class("dynamic_options")
|
269
|
-
Traceback (most recent call last):
|
270
|
-
...
|
271
|
-
edsl.questions.exceptions.QuestionCreationValidationError: ...
|
272
265
|
"""
|
273
266
|
|
274
267
|
@classmethod
|
@@ -306,10 +299,6 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
306
299
|
raise QuestionCreationValidationError(
|
307
300
|
f"Question options must be a list (got {value})."
|
308
301
|
)
|
309
|
-
# if len(value) > Settings.MAX_NUM_OPTIONS:
|
310
|
-
# raise QuestionCreationValidationError(
|
311
|
-
# f"Too many question options (got {value})."
|
312
|
-
# )
|
313
302
|
if len(value) < Settings.MIN_NUM_OPTIONS:
|
314
303
|
raise QuestionCreationValidationError(
|
315
304
|
f"Too few question options (got {value})."
|
@@ -338,7 +327,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
338
327
|
)
|
339
328
|
if not all(
|
340
329
|
[
|
341
|
-
|
330
|
+
not isinstance(option, str)
|
342
331
|
or (len(option) >= 1 and len(option) < Settings.MAX_OPTION_LENGTH)
|
343
332
|
for option in value
|
344
333
|
]
|
@@ -356,12 +345,12 @@ class QuestionOptionsDescriptor(BaseDescriptor):
|
|
356
345
|
UserWarning,
|
357
346
|
)
|
358
347
|
|
359
|
-
if hasattr(instance, "min_selections") and instance.min_selections
|
348
|
+
if hasattr(instance, "min_selections") and instance.min_selections is not None:
|
360
349
|
if instance.min_selections > len(value):
|
361
350
|
raise QuestionCreationValidationError(
|
362
351
|
f"You asked for at least {instance.min_selections} selections, but provided fewer options (got {value})."
|
363
352
|
)
|
364
|
-
if hasattr(instance, "max_selections") and instance.max_selections
|
353
|
+
if hasattr(instance, "max_selections") and instance.max_selections is not None:
|
365
354
|
if instance.max_selections > len(value):
|
366
355
|
raise QuestionCreationValidationError(
|
367
356
|
f"You asked for at most {instance.max_selections} selections, but provided fewer options (got {value})."
|
@@ -396,40 +385,12 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
396
385
|
# if len(value) > Settings.MAX_QUESTION_LENGTH:
|
397
386
|
# raise Exception("Question is too long!")
|
398
387
|
if len(value) < 1:
|
399
|
-
|
388
|
+
|
389
|
+
raise QuestionCreationValidationError("Question is too short!")
|
400
390
|
if not isinstance(value, str):
|
401
|
-
raise
|
391
|
+
raise QuestionCreationValidationError("Question must be a string!")
|
402
392
|
|
403
|
-
|
404
|
-
|
405
|
-
# if contains_single_braced_substring(value):
|
406
|
-
# import warnings
|
407
|
-
|
408
|
-
# # # warnings.warn(
|
409
|
-
# # # f"WARNING: Question text contains a single-braced substring: If you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html",
|
410
|
-
# # # UserWarning,
|
411
|
-
# # # )
|
412
|
-
# warnings.warn(
|
413
|
-
# "WARNING: Question text contains a single-braced substring. "
|
414
|
-
# "If you intended to parameterize the question with a Scenario, this will "
|
415
|
-
# "be changed to a double-braced substring, e.g. {{variable}}.\n"
|
416
|
-
# "See details on constructing Scenarios in the docs: "
|
417
|
-
# "https://docs.expectedparrot.com/en/latest/scenarios.html",
|
418
|
-
# UserWarning,
|
419
|
-
# )
|
420
|
-
# Automatically replace single braces with double braces
|
421
|
-
# This is here because if the user is using an f-string, the double brace will get converted to a single brace.
|
422
|
-
# This undoes that.
|
423
|
-
# value = re.sub(r"\{([^\{\}]+)\}", r"{{\1}}", value)
|
424
|
-
return value
|
425
|
-
|
426
|
-
# iterate through all doubles braces and check if they are valid python identifiers
|
427
|
-
# for match in re.finditer(r"\{\{([^\{\}]+)\}\}", value):
|
428
|
-
# if " " in match.group(1).strip():
|
429
|
-
# raise QuestionCreationValidationError(
|
430
|
-
# f"Question text contains an invalid identifier: '{match.group(1)}'"
|
431
|
-
# )
|
432
|
-
|
393
|
+
|
433
394
|
return None
|
434
395
|
|
435
396
|
|
edsl/questions/exceptions.py
CHANGED
@@ -1,11 +1,19 @@
|
|
1
|
-
from typing import Any, SupportsIndex
|
2
1
|
import json
|
2
|
+
from typing import Any
|
3
3
|
from pydantic import ValidationError
|
4
4
|
|
5
|
+
from ..base import BaseException
|
5
6
|
|
6
|
-
class QuestionErrors(
|
7
|
+
class QuestionErrors(BaseException):
|
7
8
|
"""
|
8
9
|
Base exception class for question-related errors.
|
10
|
+
|
11
|
+
This is the parent class for all exceptions related to question creation,
|
12
|
+
validation, processing, and answer handling. It provides a consistent
|
13
|
+
interface for error handling across the questions module.
|
14
|
+
|
15
|
+
Attributes:
|
16
|
+
message (str): A human-readable error message explaining the issue
|
9
17
|
"""
|
10
18
|
|
11
19
|
def __init__(self, message="An error occurred with the question"):
|
@@ -14,19 +22,50 @@ class QuestionErrors(Exception):
|
|
14
22
|
|
15
23
|
|
16
24
|
class QuestionAnswerValidationError(QuestionErrors):
|
17
|
-
|
25
|
+
"""
|
26
|
+
Exception raised when an answer fails validation.
|
27
|
+
|
28
|
+
This exception occurs when the response from a language model does not
|
29
|
+
conform to the expected format or constraints for a specific question type.
|
30
|
+
|
31
|
+
Common reasons for this exception:
|
32
|
+
- Multiple choice: Answer not one of the provided options
|
33
|
+
- Numerical: Answer not a valid number or outside allowed range
|
34
|
+
- Checkbox: Answer not a valid list of selected options
|
35
|
+
- Ranking: Answer does not include all items or has duplicates
|
36
|
+
|
37
|
+
To fix this error:
|
38
|
+
1. Check the model's response format against the question's requirements
|
39
|
+
2. Verify that the question's instructions are clear for the language model
|
40
|
+
3. Consider using a more capable model if consistent validation failures occur
|
41
|
+
4. Examine the full error details which include the invalid response and validation rules
|
42
|
+
|
43
|
+
Attributes:
|
44
|
+
message (str): The error message
|
45
|
+
pydantic_error (ValidationError): Underlying pydantic validation error
|
46
|
+
data (dict): The data that failed validation
|
47
|
+
model: The pydantic model used for validation
|
48
|
+
"""
|
49
|
+
documentation = "https://docs.expectedparrot.com/en/latest/questions.html#validation"
|
18
50
|
|
19
51
|
explanation = """
|
20
|
-
This
|
21
|
-
|
52
|
+
This error occurs when the answer from the Language Model doesn't match the expected format
|
53
|
+
or constraints for the question type. For example:
|
54
|
+
|
55
|
+
• Multiple choice questions require answers from the provided options
|
56
|
+
• Numerical questions need valid numbers within any specified range
|
57
|
+
• Checkbox questions require a subset of valid options
|
58
|
+
• Matrix questions need responses for each row following column constraints
|
59
|
+
|
60
|
+
The error details show both what the model returned and the validation rules that were violated.
|
22
61
|
"""
|
23
62
|
|
24
63
|
def __init__(
|
25
64
|
self,
|
26
|
-
message
|
27
|
-
|
28
|
-
|
29
|
-
|
65
|
+
message: str,
|
66
|
+
data: dict,
|
67
|
+
model: Any, # for now
|
68
|
+
pydantic_error: ValidationError,
|
30
69
|
):
|
31
70
|
self.message = message
|
32
71
|
self.pydantic_error = pydantic_error
|
@@ -45,13 +84,17 @@ class QuestionAnswerValidationError(QuestionErrors):
|
|
45
84
|
return str(error_list[0].get("msg", "Unknown error"))
|
46
85
|
return str(self.message)
|
47
86
|
|
48
|
-
# def __str__(self):
|
49
|
-
# return f"""{repr(self)}
|
50
|
-
# Data being validated: {self.data}
|
51
|
-
# Pydnantic Model: {self.model}.
|
52
|
-
# Reported error: {self.message}."""
|
53
|
-
|
54
87
|
def to_html_dict(self):
|
88
|
+
"""
|
89
|
+
Convert the exception to an HTML-friendly dictionary for rendering.
|
90
|
+
|
91
|
+
This method is used for creating detailed error reports in HTML format,
|
92
|
+
particularly in notebook environments.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
dict: HTML-formatted error information
|
96
|
+
"""
|
97
|
+
#breakpoint()
|
55
98
|
return {
|
56
99
|
"Exception type": ("p", "/p", self.__class__.__name__),
|
57
100
|
"Explanation": ("p", "/p", self.explanation),
|
@@ -79,28 +122,241 @@ class QuestionAnswerValidationError(QuestionErrors):
|
|
79
122
|
|
80
123
|
|
81
124
|
class QuestionCreationValidationError(QuestionErrors):
|
82
|
-
|
125
|
+
"""
|
126
|
+
Exception raised when question creation parameters are invalid.
|
127
|
+
|
128
|
+
This exception occurs when attempting to create a question with invalid
|
129
|
+
parameters, such as:
|
130
|
+
- Missing required attributes
|
131
|
+
- Invalid option formats
|
132
|
+
- Incompatible parameter combinations
|
133
|
+
|
134
|
+
To fix this error:
|
135
|
+
1. Check the documentation for the specific question type
|
136
|
+
2. Verify all required parameters are provided
|
137
|
+
3. Ensure parameter formats match the question type's expectations
|
138
|
+
4. Confirm that parameter combinations are compatible
|
139
|
+
|
140
|
+
Examples:
|
141
|
+
```python
|
142
|
+
# Missing required options for multiple choice
|
143
|
+
MultipleChoice(question_text="Choose one:", options=[]) # Raises QuestionCreationValidationError
|
144
|
+
|
145
|
+
# Invalid parameter combination
|
146
|
+
Numerical(question_text="Enter a value:", min_value=10, max_value=5) # Raises QuestionCreationValidationError
|
147
|
+
```
|
148
|
+
"""
|
149
|
+
|
150
|
+
def __init__(self, message="Invalid parameters for question creation"):
|
151
|
+
super().__init__(message)
|
83
152
|
|
84
153
|
|
85
154
|
class QuestionResponseValidationError(QuestionErrors):
|
86
|
-
|
155
|
+
"""
|
156
|
+
Exception raised when a response fails structural validation.
|
157
|
+
|
158
|
+
This exception is similar to QuestionAnswerValidationError but focuses on
|
159
|
+
structural validation before content validation. It catches errors in the
|
160
|
+
response structure itself.
|
161
|
+
|
162
|
+
To fix this error:
|
163
|
+
1. Check if the model's response format is correctly structured
|
164
|
+
2. Verify that the response contains all required fields
|
165
|
+
3. Ensure the response data types match expectations
|
166
|
+
|
167
|
+
Note: This exception is primarily used in tests and internal validation.
|
168
|
+
In most cases, QuestionAnswerValidationError provides more detailed information.
|
169
|
+
"""
|
170
|
+
|
171
|
+
def __init__(self, message="The response structure is invalid"):
|
172
|
+
super().__init__(message)
|
87
173
|
|
88
174
|
|
89
175
|
class QuestionAttributeMissing(QuestionErrors):
|
90
|
-
|
176
|
+
"""
|
177
|
+
Exception raised when a required question attribute is missing.
|
178
|
+
|
179
|
+
This exception occurs when attempting to use a question that is missing
|
180
|
+
essential attributes needed for its operation.
|
181
|
+
|
182
|
+
To fix this error:
|
183
|
+
1. Check that the question has been properly initialized
|
184
|
+
2. Verify all required attributes are set
|
185
|
+
3. Ensure any parent class initialization is called correctly
|
186
|
+
|
187
|
+
Note: While defined in the codebase, this exception is not actively raised
|
188
|
+
and may be used for future validation enhancements.
|
189
|
+
"""
|
190
|
+
|
191
|
+
def __init__(self, message="A required question attribute is missing"):
|
192
|
+
super().__init__(message)
|
91
193
|
|
92
194
|
|
93
195
|
class QuestionSerializationError(QuestionErrors):
|
94
|
-
|
196
|
+
"""
|
197
|
+
Exception raised when question serialization or deserialization fails.
|
198
|
+
|
199
|
+
This exception occurs when:
|
200
|
+
- A question cannot be properly converted to JSON format
|
201
|
+
- A serialized question cannot be reconstructed from its JSON representation
|
202
|
+
- Required fields are missing in the serialized data
|
203
|
+
|
204
|
+
To fix this error:
|
205
|
+
1. Ensure the question and all its attributes are serializable
|
206
|
+
2. When deserializing, verify the data format matches what's expected
|
207
|
+
3. Check for version compatibility if deserializing from an older version
|
208
|
+
|
209
|
+
Examples:
|
210
|
+
```python
|
211
|
+
question.to_dict() # Raises QuestionSerializationError if contains unserializable attributes
|
212
|
+
```
|
213
|
+
"""
|
214
|
+
|
215
|
+
def __init__(self, message="Failed to serialize or deserialize question"):
|
216
|
+
super().__init__(message)
|
95
217
|
|
96
218
|
|
97
219
|
class QuestionScenarioRenderError(QuestionErrors):
|
98
|
-
|
220
|
+
"""
|
221
|
+
Exception raised when a scenario cannot be rendered for a question.
|
222
|
+
|
223
|
+
This exception occurs when:
|
224
|
+
- The scenario template has syntax errors
|
225
|
+
- Required variables for the template are missing
|
226
|
+
- The rendered scenario exceeds size limits
|
227
|
+
|
228
|
+
To fix this error:
|
229
|
+
1. Check the scenario template syntax
|
230
|
+
2. Ensure all required variables are provided to the template
|
231
|
+
3. Verify that the scenario size is within acceptable limits
|
232
|
+
|
233
|
+
Examples:
|
234
|
+
```python
|
235
|
+
question.with_scenario(scenario_with_invalid_template) # Raises QuestionScenarioRenderError
|
236
|
+
```
|
237
|
+
"""
|
238
|
+
|
239
|
+
def __init__(self, message="Failed to render scenario for question"):
|
240
|
+
super().__init__(message)
|
99
241
|
|
100
242
|
|
101
243
|
class QuestionMissingTypeError(QuestionErrors):
|
102
|
-
|
244
|
+
"""
|
245
|
+
Exception raised when a question class is missing a required type attribute.
|
246
|
+
|
247
|
+
This exception occurs during question registration when a question class
|
248
|
+
doesn't define the required question_type attribute. All question classes
|
249
|
+
must have this attribute for proper registration and identification.
|
250
|
+
|
251
|
+
To fix this error:
|
252
|
+
1. Add the question_type class attribute to the question class
|
253
|
+
2. Ensure the question_type is a unique identifier for the question type
|
254
|
+
|
255
|
+
Examples:
|
256
|
+
```python
|
257
|
+
class MyQuestion(Question): # Missing question_type attribute
|
258
|
+
pass
|
259
|
+
# Registration would raise QuestionMissingTypeError
|
260
|
+
```
|
261
|
+
"""
|
262
|
+
|
263
|
+
def __init__(self, message="Question class is missing required type attribute"):
|
264
|
+
super().__init__(message)
|
103
265
|
|
104
266
|
|
105
267
|
class QuestionBadTypeError(QuestionErrors):
|
106
|
-
|
268
|
+
"""
|
269
|
+
Exception raised when a question class has an invalid __init__ method signature.
|
270
|
+
|
271
|
+
This exception occurs during question registration when a question class
|
272
|
+
doesn't have the required parameters in its __init__ method. All question
|
273
|
+
classes must follow a standard parameter pattern for consistency.
|
274
|
+
|
275
|
+
To fix this error:
|
276
|
+
1. Ensure the question class __init__ method includes the necessary parameters
|
277
|
+
2. Match the parameter pattern required by the question registry
|
278
|
+
|
279
|
+
Examples:
|
280
|
+
```python
|
281
|
+
class MyQuestion(Question):
|
282
|
+
question_type = "my_question"
|
283
|
+
def __init__(self, missing_required_params): # Invalid signature
|
284
|
+
pass
|
285
|
+
# Registration would raise QuestionBadTypeError
|
286
|
+
```
|
287
|
+
"""
|
288
|
+
|
289
|
+
def __init__(self, message="Question class has invalid __init__ method signature"):
|
290
|
+
super().__init__(message)
|
291
|
+
|
292
|
+
|
293
|
+
class QuestionTypeError(QuestionErrors):
|
294
|
+
"""
|
295
|
+
Exception raised when a TypeError occurs in the questions module.
|
296
|
+
|
297
|
+
This exception wraps standard Python TypeErrors to provide a consistent
|
298
|
+
exception handling approach within the EDSL framework. It's used when
|
299
|
+
a type-related error occurs during question operations.
|
300
|
+
|
301
|
+
Examples:
|
302
|
+
- Attempting to access or operate on a question attribute with the wrong type
|
303
|
+
- Passing incorrect types to question methods
|
304
|
+
- Type conversion failures during question processing
|
305
|
+
"""
|
306
|
+
|
307
|
+
def __init__(self, message="A type error occurred while processing the question"):
|
308
|
+
super().__init__(message)
|
309
|
+
|
310
|
+
|
311
|
+
class QuestionValueError(QuestionErrors):
|
312
|
+
"""
|
313
|
+
Exception raised when a ValueError occurs in the questions module.
|
314
|
+
|
315
|
+
This exception wraps standard Python ValueErrors to provide a consistent
|
316
|
+
exception handling approach within the EDSL framework. It's used when
|
317
|
+
a value-related error occurs during question operations.
|
318
|
+
|
319
|
+
Examples:
|
320
|
+
- Invalid values for question parameters
|
321
|
+
- Out-of-range values for numerical questions
|
322
|
+
- Invalid option selections for multiple choice questions
|
323
|
+
"""
|
324
|
+
|
325
|
+
def __init__(self, message="An invalid value was provided for the question"):
|
326
|
+
super().__init__(message)
|
327
|
+
|
328
|
+
|
329
|
+
class QuestionKeyError(QuestionErrors):
|
330
|
+
"""
|
331
|
+
Exception raised when a KeyError occurs in the questions module.
|
332
|
+
|
333
|
+
This exception wraps standard Python KeyErrors to provide a consistent
|
334
|
+
exception handling approach within the EDSL framework. It's used when
|
335
|
+
a key-related error occurs during question operations.
|
336
|
+
|
337
|
+
Examples:
|
338
|
+
- Attempting to access a non-existent attribute via dictionary-style access
|
339
|
+
- Missing keys in question option dictionaries
|
340
|
+
- Key errors during question serialization or deserialization
|
341
|
+
"""
|
342
|
+
|
343
|
+
def __init__(self, message="A key error occurred while processing the question"):
|
344
|
+
super().__init__(message)
|
345
|
+
|
346
|
+
|
347
|
+
class QuestionNotImplementedError(QuestionErrors):
|
348
|
+
"""
|
349
|
+
Exception raised when a method that should be implemented is not.
|
350
|
+
|
351
|
+
This exception wraps standard Python NotImplementedError to provide a consistent
|
352
|
+
exception handling approach within the EDSL framework. It's used when
|
353
|
+
a required method is called but not implemented.
|
354
|
+
|
355
|
+
Examples:
|
356
|
+
- Abstract methods that must be overridden in subclasses
|
357
|
+
- Placeholder methods that should be implemented in concrete classes
|
358
|
+
- Methods that are required by an interface but not yet implemented
|
359
|
+
"""
|
360
|
+
|
361
|
+
def __init__(self, message="This method must be implemented in a subclass"):
|
362
|
+
super().__init__(message)
|
edsl/questions/loop_processor.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import List, Any, Dict
|
1
|
+
from typing import List, Any, Dict
|
2
2
|
from jinja2 import Environment, Undefined
|
3
3
|
from .question_base import QuestionBase
|
4
4
|
from ..scenarios import ScenarioList
|
@@ -68,7 +68,6 @@ class LoopProcessor:
|
|
68
68
|
return value
|
69
69
|
|
70
70
|
if key == "option_labels":
|
71
|
-
import json
|
72
71
|
|
73
72
|
return (
|
74
73
|
eval(self._render_template(value, scenario))
|
@@ -88,7 +87,8 @@ class LoopProcessor:
|
|
88
87
|
if isinstance(value, (int, float)):
|
89
88
|
return value
|
90
89
|
|
91
|
-
|
90
|
+
from .exceptions import QuestionValueError
|
91
|
+
raise QuestionValueError(f"Unexpected value type: {type(value)} for key '{key}'")
|
92
92
|
|
93
93
|
def _render_template(self, template: str, scenario: Dict[str, Any]) -> str:
|
94
94
|
"""Render a single template string.
|
@@ -130,8 +130,10 @@ class LoopProcessor:
|
|
130
130
|
|
131
131
|
def replace_var(match):
|
132
132
|
var_name = match.group('var')
|
133
|
-
|
134
|
-
|
133
|
+
# We're keeping the original formatting with braces
|
134
|
+
# but not using these variables directly
|
135
|
+
# open_brace = match.group('open')
|
136
|
+
# close_brace = match.group('close')
|
135
137
|
|
136
138
|
# Try to evaluate the variable in the context
|
137
139
|
try:
|
@@ -12,6 +12,9 @@ Your response should be only a valid JSON in the following format:
|
|
12
12
|
}
|
13
13
|
{% endif %}
|
14
14
|
|
15
|
+
{% if min_list_items is not none %}
|
16
|
+
The list must contain at least {{ min_list_items }} items.
|
17
|
+
{% endif %}
|
15
18
|
{% if max_list_items is not none %}
|
16
19
|
The list must not contain more than {{ max_list_items }} items.
|
17
20
|
{% endif %}
|
edsl/questions/question_base.py
CHANGED
@@ -48,25 +48,27 @@ Technical Details:
|
|
48
48
|
|
49
49
|
from __future__ import annotations
|
50
50
|
from abc import ABC
|
51
|
-
from typing import Any, Type, Optional,
|
51
|
+
from typing import Any, Type, Optional, Union, TypedDict, TYPE_CHECKING, Literal
|
52
52
|
|
53
|
+
from .descriptors import QuestionNameDescriptor, QuestionTextDescriptor
|
54
|
+
from .answer_validator_mixin import AnswerValidatorMixin
|
55
|
+
from .register_questions_meta import RegisterQuestionsMeta
|
56
|
+
from .simple_ask_mixin import SimpleAskMixin
|
57
|
+
from .question_base_prompts_mixin import QuestionBasePromptsMixin
|
58
|
+
from .question_base_gen_mixin import QuestionBaseGenMixin
|
53
59
|
from .exceptions import QuestionSerializationError
|
54
60
|
|
55
61
|
from ..base import PersistenceMixin, RepresentationMixin, BaseDiff, BaseDiffCollection
|
56
62
|
from ..utilities import remove_edsl_version, is_valid_variable_name
|
57
63
|
|
64
|
+
# Define VisibilityType for type annotations
|
65
|
+
VisibilityType = Literal["private", "public", "unlisted"]
|
66
|
+
|
58
67
|
if TYPE_CHECKING:
|
59
68
|
from ..agents import Agent
|
60
69
|
from ..scenarios import Scenario
|
61
70
|
from ..surveys import Survey
|
62
71
|
|
63
|
-
from .descriptors import QuestionNameDescriptor, QuestionTextDescriptor
|
64
|
-
from .answer_validator_mixin import AnswerValidatorMixin
|
65
|
-
from .register_questions_meta import RegisterQuestionsMeta
|
66
|
-
from .simple_ask_mixin import SimpleAskMixin
|
67
|
-
from .question_base_prompts_mixin import QuestionBasePromptsMixin
|
68
|
-
from .question_base_gen_mixin import QuestionBaseGenMixin
|
69
|
-
|
70
72
|
if TYPE_CHECKING:
|
71
73
|
from .response_validator_abc import ResponseValidatorABC
|
72
74
|
from ..language_models import LanguageModel
|
@@ -202,11 +204,6 @@ class QuestionBase(
|
|
202
204
|
>>> q = QuestionFreeText(question_name="valid_name", question_text="Text")
|
203
205
|
>>> q.is_valid_question_name()
|
204
206
|
True
|
205
|
-
|
206
|
-
>>> q = QuestionFreeText(question_name="123invalid", question_text="Text")
|
207
|
-
Traceback (most recent call last):
|
208
|
-
...
|
209
|
-
edsl.questions.exceptions.QuestionCreationValidationError: `question_name` is not a valid variable name (got 123invalid).
|
210
207
|
"""
|
211
208
|
return is_valid_variable_name(self.question_name)
|
212
209
|
|
@@ -509,9 +506,9 @@ class QuestionBase(
|
|
509
506
|
int(key): value for key, value in options_labels.items()
|
510
507
|
}
|
511
508
|
local_data["option_labels"] = options_labels
|
512
|
-
except:
|
509
|
+
except Exception as e:
|
513
510
|
raise QuestionSerializationError(
|
514
|
-
f"Data does not have a 'question_type' field (got {data})."
|
511
|
+
f"Error in deserialization: {str(e)}. Data does not have a 'question_type' field (got {data})."
|
515
512
|
)
|
516
513
|
from .question_registry import get_question_class
|
517
514
|
|
@@ -697,7 +694,8 @@ class QuestionBase(
|
|
697
694
|
try:
|
698
695
|
return getattr(self, key)
|
699
696
|
except TypeError:
|
700
|
-
|
697
|
+
from .exceptions import QuestionKeyError
|
698
|
+
raise QuestionKeyError(f"Question has no attribute {key} of type {type(key)}")
|
701
699
|
|
702
700
|
def __repr__(self) -> str:
|
703
701
|
"""Return a string representation of the question. Should be able to be used to reconstruct the question.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import copy
|
3
3
|
import itertools
|
4
|
-
from typing import Optional, List, Callable,
|
4
|
+
from typing import Optional, List, Callable, TYPE_CHECKING, Union
|
5
5
|
|
6
6
|
if TYPE_CHECKING:
|
7
7
|
from .question_base import QuestionBase
|
@@ -174,7 +174,7 @@ class QuestionBaseGenMixin:
|
|
174
174
|
return result
|
175
175
|
except self.MaxTemplateNestingExceeded:
|
176
176
|
raise
|
177
|
-
except Exception
|
177
|
+
except Exception:
|
178
178
|
import warnings
|
179
179
|
warnings.warn("Failed to render string: " + value)
|
180
180
|
return value
|
@@ -1,9 +1,14 @@
|
|
1
1
|
from importlib import resources
|
2
|
-
from typing import Optional
|
2
|
+
from typing import Optional, TYPE_CHECKING
|
3
3
|
from functools import lru_cache
|
4
4
|
|
5
5
|
from .exceptions import QuestionAnswerValidationError
|
6
6
|
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from pydantic import BaseModel
|
9
|
+
from ..prompts import Prompt
|
10
|
+
from ..prompts.prompt import PromptBase
|
11
|
+
|
7
12
|
class TemplateManager:
|
8
13
|
_instance = None
|
9
14
|
|
@@ -85,7 +90,7 @@ class QuestionBasePromptsMixin:
|
|
85
90
|
|
86
91
|
@classmethod
|
87
92
|
def path_to_folder(cls) -> str:
|
88
|
-
return resources.files(
|
93
|
+
return resources.files("edsl.questions.templates", cls.question_type)
|
89
94
|
|
90
95
|
@property
|
91
96
|
def response_model(self) -> type["BaseModel"]:
|
@@ -179,7 +184,8 @@ class QuestionBasePromptsMixin:
|
|
179
184
|
except QuestionAnswerValidationError:
|
180
185
|
pass
|
181
186
|
else:
|
182
|
-
|
187
|
+
from .exceptions import QuestionValueError
|
188
|
+
raise QuestionValueError(f"Example {answer} should have failed for {reason}.")
|
183
189
|
|
184
190
|
@property
|
185
191
|
def new_default_instructions(self) -> "Prompt":
|