edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.dev2__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/Base.py +169 -116
- edsl/__init__.py +14 -6
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +358 -146
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +88 -36
- edsl/agents/InvigilatorBase.py +59 -70
- edsl/agents/PromptConstructor.py +117 -219
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionOptionProcessor.py +172 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +104 -42
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +21 -14
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +33 -12
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +20 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +0 -3
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +209 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +2 -11
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +105 -80
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +1 -4
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -8
- edsl/inference_services/data_structures.py +62 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +40 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +48 -0
- edsl/jobs/Jobs.py +102 -243
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +5 -3
- edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +77 -380
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +14 -15
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +137 -234
- edsl/language_models/ModelList.py +11 -13
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +0 -1
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/registry.py +49 -59
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/AnswerValidatorMixin.py +47 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/LoopProcessor.py +149 -0
- edsl/questions/QuestionBase.py +37 -192
- edsl/questions/QuestionBaseGenMixin.py +52 -48
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionCheckBox.py +1 -1
- edsl/questions/QuestionExtract.py +1 -1
- edsl/questions/QuestionFreeText.py +1 -2
- edsl/questions/QuestionList.py +3 -5
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +66 -22
- edsl/questions/QuestionNumerical.py +1 -3
- edsl/questions/QuestionRank.py +6 -16
- edsl/questions/ResponseValidatorABC.py +37 -11
- edsl/questions/ResponseValidatorFactory.py +28 -0
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +1 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/question_registry.py +1 -1
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +224 -302
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +192 -206
- edsl/results/Results.py +120 -113
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/Selector.py +23 -13
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DirectoryScanner.py +96 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +118 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioJoin.py +10 -6
- edsl/scenarios/ScenarioList.py +383 -240
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/ScenarioSelector.py +156 -0
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +199 -771
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
- edsl-0.1.39.dev2.dist-info/RECORD +352 -0
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev1.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
import re
|
4
4
|
from typing import Any, Type, Union
|
5
|
-
from edsl.exceptions import (
|
5
|
+
from edsl.exceptions.questions import (
|
6
6
|
QuestionAnswerValidationError,
|
7
7
|
)
|
8
8
|
|
@@ -213,7 +213,12 @@ class AnswerValidatorMixin:
|
|
213
213
|
- is not less than `min_value`
|
214
214
|
- is not greater than `max_value`
|
215
215
|
"""
|
216
|
-
|
216
|
+
try:
|
217
|
+
value = float(answer.get("answer"))
|
218
|
+
except ValueError:
|
219
|
+
raise QuestionAnswerValidationError(
|
220
|
+
f"Answer must real number or convertible to a real number (got {answer.get('answer')})."
|
221
|
+
)
|
217
222
|
if self.min_value is not None and value < self.min_value:
|
218
223
|
raise QuestionAnswerValidationError(
|
219
224
|
f"Value {value} is less than {self.min_value}"
|
@@ -279,6 +284,46 @@ class AnswerValidatorMixin:
|
|
279
284
|
f"Rank answer {value}, but exactly {self.num_selections} selections required."
|
280
285
|
)
|
281
286
|
|
287
|
+
def _validate_answer_matrix(self, answer: dict[str, Any]) -> None:
|
288
|
+
"""Validate QuestionMatrix-specific answer.
|
289
|
+
|
290
|
+
Check that answer["answer"]:
|
291
|
+
- is a dictionary
|
292
|
+
- has all required question_items as keys
|
293
|
+
- has values that are valid options from question_options
|
294
|
+
- has the correct number of responses (one per item)
|
295
|
+
"""
|
296
|
+
value = answer.get("answer")
|
297
|
+
|
298
|
+
# Check that answer is a dictionary
|
299
|
+
if not isinstance(value, dict):
|
300
|
+
raise QuestionAnswerValidationError(
|
301
|
+
f"Matrix answer must be a dictionary mapping items to responses (got {value})"
|
302
|
+
)
|
303
|
+
|
304
|
+
# Check that all required items are present
|
305
|
+
required_items = set(self.question_items)
|
306
|
+
provided_items = set(value.keys())
|
307
|
+
|
308
|
+
if missing_items := (required_items - provided_items):
|
309
|
+
raise QuestionAnswerValidationError(
|
310
|
+
f"Missing responses for items: {missing_items}"
|
311
|
+
)
|
312
|
+
|
313
|
+
if extra_items := (provided_items - required_items):
|
314
|
+
raise QuestionAnswerValidationError(
|
315
|
+
f"Unexpected responses for items: {extra_items}"
|
316
|
+
)
|
317
|
+
|
318
|
+
# Check that all responses are valid options
|
319
|
+
valid_options = set(self.question_options)
|
320
|
+
for item, response in value.items():
|
321
|
+
if response not in valid_options:
|
322
|
+
raise QuestionAnswerValidationError(
|
323
|
+
f"Invalid response '{response}' for item '{item}'. "
|
324
|
+
f"Must be one of: {valid_options}"
|
325
|
+
)
|
326
|
+
|
282
327
|
|
283
328
|
if __name__ == "__main__":
|
284
329
|
pass
|
@@ -0,0 +1,77 @@
|
|
1
|
+
from pydantic import ValidationError
|
2
|
+
from typing import Union
|
3
|
+
|
4
|
+
|
5
|
+
class ExceptionExplainer:
|
6
|
+
"""
|
7
|
+
A class that converts validation errors into human-readable explanations,
|
8
|
+
specifically for Language Model responses.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(self, error: Union[ValidationError, Exception], model_response: str):
|
12
|
+
"""
|
13
|
+
Initialize the explainer with the error and model response.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
error: The validation error that occurred
|
17
|
+
model_response: The raw response from the Language Model
|
18
|
+
"""
|
19
|
+
self.error = error
|
20
|
+
self.model_response = model_response
|
21
|
+
|
22
|
+
def explain(self) -> str:
|
23
|
+
"""
|
24
|
+
Generate a human-readable explanation of why the model's response failed validation.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
A user-friendly explanation of why the model's response was invalid
|
28
|
+
"""
|
29
|
+
self.error = self.error.pydantic_error
|
30
|
+
return self._explain_validation_error()
|
31
|
+
|
32
|
+
# Fallback for unknown errors
|
33
|
+
return self._create_generic_explanation()
|
34
|
+
|
35
|
+
def _explain_validation_error(self) -> str:
|
36
|
+
"""Handle Pydantic ValidationError specifically."""
|
37
|
+
error_dict = self.error.errors()
|
38
|
+
explanations = []
|
39
|
+
|
40
|
+
context = f'The AI model returned "{self.model_response}", but this was invalid for the question you asked and the constraints you provided.\n'
|
41
|
+
explanations.append(context)
|
42
|
+
explanations.append("Reason(s) invalidated:")
|
43
|
+
for e in error_dict:
|
44
|
+
msg = e.get("msg", "Unknown error")
|
45
|
+
explanations.append(f"- {msg}")
|
46
|
+
|
47
|
+
main_message = "\n".join(explanations)
|
48
|
+
return f"{main_message}\n\n{self._get_suggestion()}"
|
49
|
+
|
50
|
+
def _create_generic_explanation(self) -> str:
|
51
|
+
"""Create a generic explanation for non-ValidationError exceptions."""
|
52
|
+
return (
|
53
|
+
f'The AI model returned "{self.model_response}", but this response was invalid. '
|
54
|
+
f"Error: {str(self.error)}"
|
55
|
+
)
|
56
|
+
|
57
|
+
def _get_suggestion(self) -> str:
|
58
|
+
"""Get a suggestion for handling the error."""
|
59
|
+
return (
|
60
|
+
"EDSL Advice:\n"
|
61
|
+
"- Look at the Model comments - often the model will provide a hint about what went wrong.\n"
|
62
|
+
"- If the model's response doesn't make sense, try rephrasing your question.\n"
|
63
|
+
"- Try using 'use_code' parameter of a MultipleChoice.\n"
|
64
|
+
"- A QuestionFreeText will almost always validate.\n"
|
65
|
+
"- Try setting the 'permissive' = True parameter in the Question constructor."
|
66
|
+
)
|
67
|
+
|
68
|
+
|
69
|
+
# Example usage:
|
70
|
+
if __name__ == "__main__":
|
71
|
+
try:
|
72
|
+
# Your validation code here
|
73
|
+
raise ValidationError.parse_obj({"answer": "120"})
|
74
|
+
except ValidationError as e:
|
75
|
+
explainer = ExceptionExplainer(e, "120")
|
76
|
+
explanation = explainer.explain()
|
77
|
+
print(explanation)
|
@@ -0,0 +1,103 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
from edsl.prompts.Prompt import Prompt
|
3
|
+
|
4
|
+
|
5
|
+
class HTMLQuestion:
|
6
|
+
def __init__(self, question):
|
7
|
+
self.question = question
|
8
|
+
|
9
|
+
def html(
|
10
|
+
self,
|
11
|
+
scenario: Optional[dict] = None,
|
12
|
+
agent: Optional[dict] = {},
|
13
|
+
answers: Optional[dict] = None,
|
14
|
+
include_question_name: bool = False,
|
15
|
+
height: Optional[int] = None,
|
16
|
+
width: Optional[int] = None,
|
17
|
+
iframe=False,
|
18
|
+
):
|
19
|
+
"""Return the question in HTML format."""
|
20
|
+
from jinja2 import Template
|
21
|
+
|
22
|
+
if scenario is None:
|
23
|
+
scenario = {}
|
24
|
+
|
25
|
+
prior_answers_dict = {}
|
26
|
+
|
27
|
+
if isinstance(answers, dict):
|
28
|
+
for key, value in answers.items():
|
29
|
+
if not key.endswith("_comment") and not key.endswith(
|
30
|
+
"_generated_tokens"
|
31
|
+
):
|
32
|
+
prior_answers_dict[key] = {"answer": value}
|
33
|
+
|
34
|
+
base_template = """
|
35
|
+
<div id="{{ question_name }}" class="survey_question" data-type="{{ question_type }}">
|
36
|
+
{% if include_question_name %}
|
37
|
+
<p>question_name: {{ question_name }}</p>
|
38
|
+
{% endif %}
|
39
|
+
<p class="question_text">{{ question_text }}</p>
|
40
|
+
{{ question_content }}
|
41
|
+
</div>
|
42
|
+
"""
|
43
|
+
if not hasattr(self.question, "question_type"):
|
44
|
+
self.question.question_type = "unknown"
|
45
|
+
|
46
|
+
if hasattr(self.question, "question_html_content"):
|
47
|
+
question_content = self.question.question_html_content
|
48
|
+
else:
|
49
|
+
question_content = Template("")
|
50
|
+
|
51
|
+
base_template = Template(base_template)
|
52
|
+
|
53
|
+
context = {
|
54
|
+
"scenario": scenario,
|
55
|
+
"agent": agent,
|
56
|
+
} | prior_answers_dict
|
57
|
+
|
58
|
+
# Render the question text
|
59
|
+
try:
|
60
|
+
question_text = Template(self.question.question_text).render(context)
|
61
|
+
except Exception as e:
|
62
|
+
print(
|
63
|
+
f"Error rendering question: question_text = {self.question.question_text}, error = {e}"
|
64
|
+
)
|
65
|
+
question_text = self.question.question_text
|
66
|
+
|
67
|
+
try:
|
68
|
+
question_content = Template(question_content).render(context)
|
69
|
+
except Exception as e:
|
70
|
+
print(
|
71
|
+
f"Error rendering question: question_content = {question_content}, error = {e}"
|
72
|
+
)
|
73
|
+
question_content = question_content
|
74
|
+
|
75
|
+
try:
|
76
|
+
params = {
|
77
|
+
"question_name": self.question.question_name,
|
78
|
+
"question_text": question_text,
|
79
|
+
"question_type": self.question.question_type,
|
80
|
+
"question_content": question_content,
|
81
|
+
"include_question_name": include_question_name,
|
82
|
+
}
|
83
|
+
except Exception as e:
|
84
|
+
raise ValueError(
|
85
|
+
f"Error rendering question: params = {params}, error = {e}"
|
86
|
+
)
|
87
|
+
rendered_html = base_template.render(**params)
|
88
|
+
|
89
|
+
if iframe:
|
90
|
+
import html
|
91
|
+
from IPython.display import display, HTML
|
92
|
+
|
93
|
+
height = height or 200
|
94
|
+
width = width or 600
|
95
|
+
escaped_output = html.escape(rendered_html)
|
96
|
+
# escaped_output = rendered_html
|
97
|
+
iframe = f""""
|
98
|
+
<iframe srcdoc="{ escaped_output }" style="width: {width}px; height: {height}px;"></iframe>
|
99
|
+
"""
|
100
|
+
display(HTML(iframe))
|
101
|
+
return None
|
102
|
+
|
103
|
+
return rendered_html
|
@@ -0,0 +1,149 @@
|
|
1
|
+
from typing import List, Any, Dict, Union
|
2
|
+
from jinja2 import Environment
|
3
|
+
from edsl.questions.QuestionBase import QuestionBase
|
4
|
+
from edsl import ScenarioList
|
5
|
+
|
6
|
+
|
7
|
+
class LoopProcessor:
|
8
|
+
def __init__(self, question: QuestionBase):
|
9
|
+
self.question = question
|
10
|
+
self.env = Environment()
|
11
|
+
|
12
|
+
def process_templates(self, scenario_list: ScenarioList) -> List[QuestionBase]:
|
13
|
+
"""Process templates for each scenario and return list of modified questions.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
scenario_list: List of scenarios to process templates against
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
List of QuestionBase objects with rendered templates
|
20
|
+
"""
|
21
|
+
questions = []
|
22
|
+
starting_name = self.question.question_name
|
23
|
+
|
24
|
+
for index, scenario in enumerate(scenario_list):
|
25
|
+
question_data = self.question.to_dict().copy()
|
26
|
+
processed_data = self._process_data(question_data, scenario)
|
27
|
+
|
28
|
+
if processed_data["question_name"] == starting_name:
|
29
|
+
processed_data["question_name"] += f"_{index}"
|
30
|
+
|
31
|
+
questions.append(QuestionBase.from_dict(processed_data))
|
32
|
+
|
33
|
+
return questions
|
34
|
+
|
35
|
+
def _process_data(
|
36
|
+
self, data: Dict[str, Any], scenario: Dict[str, Any]
|
37
|
+
) -> Dict[str, Any]:
|
38
|
+
"""Process all data fields according to their type.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
data: Dictionary of question data
|
42
|
+
scenario: Current scenario to render templates against
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
Processed dictionary with rendered templates
|
46
|
+
"""
|
47
|
+
processed = {}
|
48
|
+
|
49
|
+
for key, value in [(k, v) for k, v in data.items() if v is not None]:
|
50
|
+
processed[key] = self._process_value(key, value, scenario)
|
51
|
+
|
52
|
+
return processed
|
53
|
+
|
54
|
+
def _process_value(self, key: str, value: Any, scenario: Dict[str, Any]) -> Any:
|
55
|
+
"""Process a single value according to its type.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
key: Dictionary key
|
59
|
+
value: Value to process
|
60
|
+
scenario: Current scenario
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
Processed value
|
64
|
+
"""
|
65
|
+
if key == "question_options" and isinstance(value, str):
|
66
|
+
return value
|
67
|
+
|
68
|
+
if key == "option_labels":
|
69
|
+
import json
|
70
|
+
|
71
|
+
return (
|
72
|
+
eval(self._render_template(value, scenario))
|
73
|
+
if isinstance(value, str)
|
74
|
+
else value
|
75
|
+
)
|
76
|
+
|
77
|
+
if isinstance(value, str):
|
78
|
+
return self._render_template(value, scenario)
|
79
|
+
|
80
|
+
if isinstance(value, list):
|
81
|
+
return self._process_list(value, scenario)
|
82
|
+
|
83
|
+
if isinstance(value, dict):
|
84
|
+
return self._process_dict(value, scenario)
|
85
|
+
|
86
|
+
if isinstance(value, (int, float)):
|
87
|
+
return value
|
88
|
+
|
89
|
+
raise ValueError(f"Unexpected value type: {type(value)} for key '{key}'")
|
90
|
+
|
91
|
+
def _render_template(self, template: str, scenario: Dict[str, Any]) -> str:
|
92
|
+
"""Render a single template string.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
template: Template string to render
|
96
|
+
scenario: Current scenario
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
Rendered template string
|
100
|
+
"""
|
101
|
+
return self.env.from_string(template).render(scenario)
|
102
|
+
|
103
|
+
def _process_list(self, items: List[Any], scenario: Dict[str, Any]) -> List[Any]:
|
104
|
+
"""Process all items in a list.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
items: List of items to process
|
108
|
+
scenario: Current scenario
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
List of processed items
|
112
|
+
"""
|
113
|
+
return [
|
114
|
+
self._render_template(item, scenario) if isinstance(item, str) else item
|
115
|
+
for item in items
|
116
|
+
]
|
117
|
+
|
118
|
+
def _process_dict(
|
119
|
+
self, data: Dict[str, Any], scenario: Dict[str, Any]
|
120
|
+
) -> Dict[str, Any]:
|
121
|
+
"""Process all keys and values in a dictionary.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
data: Dictionary to process
|
125
|
+
scenario: Current scenario
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Dictionary with processed keys and values
|
129
|
+
"""
|
130
|
+
return {
|
131
|
+
(self._render_template(k, scenario) if isinstance(k, str) else k): (
|
132
|
+
self._render_template(v, scenario) if isinstance(v, str) else v
|
133
|
+
)
|
134
|
+
for k, v in data.items()
|
135
|
+
}
|
136
|
+
|
137
|
+
|
138
|
+
# Usage example:
|
139
|
+
"""
|
140
|
+
from edsl import QuestionFreeText, ScenarioList
|
141
|
+
|
142
|
+
question = QuestionFreeText(
|
143
|
+
question_text="What are your thoughts on: {{subject}}?",
|
144
|
+
question_name="base_{{subject}}"
|
145
|
+
)
|
146
|
+
processor = TemplateProcessor(question)
|
147
|
+
scenarios = ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])
|
148
|
+
processed_questions = processor.process_templates(scenarios)
|
149
|
+
"""
|