edsl 0.1.39__py3-none-any.whl → 0.1.39.dev1__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.
Files changed (212) hide show
  1. edsl/Base.py +116 -197
  2. edsl/__init__.py +7 -15
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +147 -351
  5. edsl/agents/AgentList.py +73 -211
  6. edsl/agents/Invigilator.py +50 -101
  7. edsl/agents/InvigilatorBase.py +70 -62
  8. edsl/agents/PromptConstructor.py +225 -143
  9. edsl/agents/__init__.py +1 -0
  10. edsl/agents/prompt_helpers.py +3 -3
  11. edsl/auto/AutoStudy.py +5 -18
  12. edsl/auto/StageBase.py +40 -53
  13. edsl/auto/StageQuestions.py +1 -2
  14. edsl/auto/utilities.py +6 -0
  15. edsl/config.py +2 -22
  16. edsl/conversation/car_buying.py +1 -2
  17. edsl/coop/PriceFetcher.py +1 -1
  18. edsl/coop/coop.py +47 -125
  19. edsl/coop/utils.py +14 -14
  20. edsl/data/Cache.py +27 -45
  21. edsl/data/CacheEntry.py +15 -12
  22. edsl/data/CacheHandler.py +12 -31
  23. edsl/data/RemoteCacheSync.py +46 -154
  24. edsl/data/__init__.py +3 -4
  25. edsl/data_transfer_models.py +1 -2
  26. edsl/enums.py +0 -27
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +0 -12
  29. edsl/exceptions/questions.py +6 -24
  30. edsl/exceptions/scenarios.py +0 -7
  31. edsl/inference_services/AnthropicService.py +19 -38
  32. edsl/inference_services/AwsBedrock.py +2 -0
  33. edsl/inference_services/AzureAI.py +2 -0
  34. edsl/inference_services/GoogleService.py +12 -7
  35. edsl/inference_services/InferenceServiceABC.py +85 -18
  36. edsl/inference_services/InferenceServicesCollection.py +79 -120
  37. edsl/inference_services/MistralAIService.py +3 -0
  38. edsl/inference_services/OpenAIService.py +35 -47
  39. edsl/inference_services/PerplexityService.py +3 -0
  40. edsl/inference_services/TestService.py +10 -11
  41. edsl/inference_services/TogetherAIService.py +3 -5
  42. edsl/jobs/Answers.py +14 -1
  43. edsl/jobs/Jobs.py +431 -356
  44. edsl/jobs/JobsChecks.py +10 -35
  45. edsl/jobs/JobsPrompts.py +4 -6
  46. edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
  47. edsl/jobs/buckets/BucketCollection.py +3 -44
  48. edsl/jobs/buckets/TokenBucket.py +21 -53
  49. edsl/jobs/interviews/Interview.py +408 -143
  50. edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
  51. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  52. edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
  53. edsl/jobs/tasks/TaskHistory.py +18 -38
  54. edsl/jobs/tasks/task_status_enum.py +2 -0
  55. edsl/language_models/KeyLookup.py +30 -0
  56. edsl/language_models/LanguageModel.py +236 -194
  57. edsl/language_models/ModelList.py +19 -28
  58. edsl/language_models/__init__.py +2 -1
  59. edsl/language_models/registry.py +190 -0
  60. edsl/language_models/repair.py +2 -2
  61. edsl/language_models/unused/ReplicateBase.py +83 -0
  62. edsl/language_models/utilities.py +4 -5
  63. edsl/notebooks/Notebook.py +14 -19
  64. edsl/prompts/Prompt.py +39 -29
  65. edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
  66. edsl/questions/QuestionBase.py +214 -68
  67. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
  68. edsl/questions/QuestionBasePromptsMixin.py +3 -7
  69. edsl/questions/QuestionBudget.py +1 -1
  70. edsl/questions/QuestionCheckBox.py +3 -3
  71. edsl/questions/QuestionExtract.py +7 -5
  72. edsl/questions/QuestionFreeText.py +3 -2
  73. edsl/questions/QuestionList.py +18 -10
  74. edsl/questions/QuestionMultipleChoice.py +23 -67
  75. edsl/questions/QuestionNumerical.py +4 -2
  76. edsl/questions/QuestionRank.py +17 -7
  77. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
  78. edsl/questions/SimpleAskMixin.py +3 -4
  79. edsl/questions/__init__.py +1 -2
  80. edsl/questions/derived/QuestionLinearScale.py +3 -6
  81. edsl/questions/derived/QuestionTopK.py +1 -1
  82. edsl/questions/descriptors.py +3 -17
  83. edsl/questions/question_registry.py +1 -1
  84. edsl/results/CSSParameterizer.py +1 -1
  85. edsl/results/Dataset.py +7 -170
  86. edsl/results/DatasetExportMixin.py +305 -168
  87. edsl/results/DatasetTree.py +8 -28
  88. edsl/results/Result.py +206 -298
  89. edsl/results/Results.py +131 -149
  90. edsl/results/ResultsDBMixin.py +238 -0
  91. edsl/results/ResultsExportMixin.py +0 -2
  92. edsl/results/{results_selector.py → Selector.py} +13 -23
  93. edsl/results/TableDisplay.py +171 -98
  94. edsl/results/__init__.py +1 -1
  95. edsl/scenarios/FileStore.py +239 -150
  96. edsl/scenarios/Scenario.py +193 -90
  97. edsl/scenarios/ScenarioHtmlMixin.py +3 -4
  98. edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
  99. edsl/scenarios/ScenarioList.py +244 -415
  100. edsl/scenarios/ScenarioListExportMixin.py +7 -0
  101. edsl/scenarios/ScenarioListPdfMixin.py +37 -15
  102. edsl/scenarios/__init__.py +2 -1
  103. edsl/study/ObjectEntry.py +1 -1
  104. edsl/study/SnapShot.py +1 -1
  105. edsl/study/Study.py +12 -5
  106. edsl/surveys/Rule.py +4 -5
  107. edsl/surveys/RuleCollection.py +27 -25
  108. edsl/surveys/Survey.py +791 -270
  109. edsl/surveys/SurveyCSS.py +8 -20
  110. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
  111. edsl/surveys/__init__.py +2 -4
  112. edsl/surveys/descriptors.py +2 -6
  113. edsl/surveys/instructions/ChangeInstruction.py +2 -1
  114. edsl/surveys/instructions/Instruction.py +13 -4
  115. edsl/surveys/instructions/InstructionCollection.py +6 -11
  116. edsl/templates/error_reporting/interview_details.html +1 -1
  117. edsl/templates/error_reporting/report.html +1 -1
  118. edsl/tools/plotting.py +1 -1
  119. edsl/utilities/utilities.py +23 -35
  120. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
  121. edsl-0.1.39.dev1.dist-info/RECORD +277 -0
  122. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
  123. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  124. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  125. edsl/agents/question_option_processor.py +0 -172
  126. edsl/coop/CoopFunctionsMixin.py +0 -15
  127. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  128. edsl/exceptions/inference_services.py +0 -5
  129. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  130. edsl/inference_services/AvailableModelFetcher.py +0 -215
  131. edsl/inference_services/ServiceAvailability.py +0 -135
  132. edsl/inference_services/data_structures.py +0 -134
  133. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
  134. edsl/jobs/FetchInvigilator.py +0 -47
  135. edsl/jobs/InterviewTaskManager.py +0 -98
  136. edsl/jobs/InterviewsConstructor.py +0 -50
  137. edsl/jobs/JobsComponentConstructor.py +0 -189
  138. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  139. edsl/jobs/RequestTokenEstimator.py +0 -30
  140. edsl/jobs/async_interview_runner.py +0 -138
  141. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  142. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  143. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  144. edsl/jobs/data_structures.py +0 -120
  145. edsl/jobs/decorators.py +0 -35
  146. edsl/jobs/jobs_status_enums.py +0 -9
  147. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  148. edsl/jobs/results_exceptions_handler.py +0 -98
  149. edsl/language_models/ComputeCost.py +0 -63
  150. edsl/language_models/PriceManager.py +0 -127
  151. edsl/language_models/RawResponseHandler.py +0 -106
  152. edsl/language_models/ServiceDataSources.py +0 -0
  153. edsl/language_models/key_management/KeyLookup.py +0 -63
  154. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  155. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  156. edsl/language_models/key_management/__init__.py +0 -0
  157. edsl/language_models/key_management/models.py +0 -131
  158. edsl/language_models/model.py +0 -256
  159. edsl/notebooks/NotebookToLaTeX.py +0 -142
  160. edsl/questions/ExceptionExplainer.py +0 -77
  161. edsl/questions/HTMLQuestion.py +0 -103
  162. edsl/questions/QuestionMatrix.py +0 -265
  163. edsl/questions/data_structures.py +0 -20
  164. edsl/questions/loop_processor.py +0 -149
  165. edsl/questions/response_validator_factory.py +0 -34
  166. edsl/questions/templates/matrix/__init__.py +0 -1
  167. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  168. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  169. edsl/results/MarkdownToDocx.py +0 -122
  170. edsl/results/MarkdownToPDF.py +0 -111
  171. edsl/results/TextEditor.py +0 -50
  172. edsl/results/file_exports.py +0 -252
  173. edsl/results/smart_objects.py +0 -96
  174. edsl/results/table_data_class.py +0 -12
  175. edsl/results/table_renderers.py +0 -118
  176. edsl/scenarios/ConstructDownloadLink.py +0 -109
  177. edsl/scenarios/DocumentChunker.py +0 -102
  178. edsl/scenarios/DocxScenario.py +0 -16
  179. edsl/scenarios/PdfExtractor.py +0 -40
  180. edsl/scenarios/directory_scanner.py +0 -96
  181. edsl/scenarios/file_methods.py +0 -85
  182. edsl/scenarios/handlers/__init__.py +0 -13
  183. edsl/scenarios/handlers/csv.py +0 -49
  184. edsl/scenarios/handlers/docx.py +0 -76
  185. edsl/scenarios/handlers/html.py +0 -37
  186. edsl/scenarios/handlers/json.py +0 -111
  187. edsl/scenarios/handlers/latex.py +0 -5
  188. edsl/scenarios/handlers/md.py +0 -51
  189. edsl/scenarios/handlers/pdf.py +0 -68
  190. edsl/scenarios/handlers/png.py +0 -39
  191. edsl/scenarios/handlers/pptx.py +0 -105
  192. edsl/scenarios/handlers/py.py +0 -294
  193. edsl/scenarios/handlers/sql.py +0 -313
  194. edsl/scenarios/handlers/sqlite.py +0 -149
  195. edsl/scenarios/handlers/txt.py +0 -33
  196. edsl/scenarios/scenario_selector.py +0 -156
  197. edsl/surveys/ConstructDAG.py +0 -92
  198. edsl/surveys/EditSurvey.py +0 -221
  199. edsl/surveys/InstructionHandler.py +0 -100
  200. edsl/surveys/MemoryManagement.py +0 -72
  201. edsl/surveys/RuleManager.py +0 -172
  202. edsl/surveys/Simulator.py +0 -75
  203. edsl/surveys/SurveyToApp.py +0 -141
  204. edsl/utilities/PrettyList.py +0 -56
  205. edsl/utilities/is_notebook.py +0 -18
  206. edsl/utilities/is_valid_variable_name.py +0 -11
  207. edsl/utilities/remove_edsl_version.py +0 -24
  208. edsl-0.1.39.dist-info/RECORD +0 -358
  209. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  210. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  211. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  212. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
@@ -1,103 +0,0 @@
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
@@ -1,265 +0,0 @@
1
- from __future__ import annotations
2
- from typing import Union, Optional, Dict, List, Any
3
-
4
- from pydantic import BaseModel, Field, field_validator
5
- from jinja2 import Template
6
- import random
7
- from edsl.questions.QuestionBase import QuestionBase
8
- from edsl.questions.descriptors import (
9
- QuestionOptionsDescriptor,
10
- OptionLabelDescriptor,
11
- QuestionTextDescriptor,
12
- )
13
- from edsl.questions.response_validator_abc import ResponseValidatorABC
14
- from edsl.questions.decorators import inject_exception
15
- from edsl.exceptions.questions import (
16
- QuestionAnswerValidationError,
17
- QuestionCreationValidationError,
18
- )
19
-
20
-
21
- def create_matrix_response(
22
- question_items: List[str],
23
- question_options: List[Union[int, str, float]],
24
- permissive: bool = False,
25
- ):
26
- """Create a response model for matrix questions.
27
-
28
- The response model validates that:
29
- 1. All question items are answered
30
- 2. Each answer is from the allowed options
31
- """
32
-
33
- if permissive:
34
-
35
- class MatrixResponse(BaseModel):
36
- answer: Dict[str, Any]
37
- comment: Optional[str] = None
38
- generated_tokens: Optional[Any] = None
39
-
40
- else:
41
-
42
- class MatrixResponse(BaseModel):
43
- answer: Dict[str, Union[int, str, float]] = Field(
44
- ..., description="Mapping of items to selected options"
45
- )
46
- comment: Optional[str] = None
47
- generated_tokens: Optional[Any] = None
48
-
49
- @field_validator("answer")
50
- def validate_answer(cls, v, values, **kwargs):
51
- # Check that all items have responses
52
- if not all(item in v for item in question_items):
53
- missing = set(question_items) - set(v.keys())
54
- raise ValueError(f"Missing responses for items: {missing}")
55
-
56
- # Check that all responses are valid options
57
- if not all(answer in question_options for answer in v.values()):
58
- invalid = [ans for ans in v.values() if ans not in question_options]
59
- raise ValueError(f"Invalid options selected: {invalid}")
60
- return v
61
-
62
- return MatrixResponse
63
-
64
-
65
- class MatrixResponseValidator(ResponseValidatorABC):
66
- required_params = ["question_items", "question_options", "permissive"]
67
-
68
- valid_examples = [
69
- (
70
- {"answer": {"Item1": 1, "Item2": 2}},
71
- {
72
- "question_items": ["Item1", "Item2"],
73
- "question_options": [1, 2, 3],
74
- },
75
- )
76
- ]
77
-
78
- invalid_examples = [
79
- (
80
- {"answer": {"Item1": 1}},
81
- {
82
- "question_items": ["Item1", "Item2"],
83
- "question_options": [1, 2, 3],
84
- },
85
- "Missing responses for some items",
86
- ),
87
- (
88
- {"answer": {"Item1": 4, "Item2": 5}},
89
- {
90
- "question_items": ["Item1", "Item2"],
91
- "question_options": [1, 2, 3],
92
- },
93
- "Invalid options selected",
94
- ),
95
- ]
96
-
97
- def fix(self, response, verbose=False):
98
- if verbose:
99
- print(f"Fixing matrix response: {response}")
100
-
101
- # If we have generated tokens, try to parse them
102
- if "generated_tokens" in response:
103
- try:
104
- import json
105
-
106
- fixed = json.loads(response["generated_tokens"])
107
- if isinstance(fixed, dict):
108
- # Map numeric keys to question items
109
- mapped_answer = {}
110
- for idx, item in enumerate(self.question_items):
111
- if str(idx) in fixed:
112
- mapped_answer[item] = fixed[str(idx)]
113
- if (
114
- mapped_answer
115
- ): # Only return if we successfully mapped some answers
116
- return {"answer": mapped_answer}
117
- except:
118
- pass
119
-
120
- # If answer uses numeric keys, map them to question items
121
- if "answer" in response and isinstance(response["answer"], dict):
122
- if all(str(key).isdigit() for key in response["answer"].keys()):
123
- mapped_answer = {}
124
- for idx, item in enumerate(self.question_items):
125
- if str(idx) in response["answer"]:
126
- mapped_answer[item] = response["answer"][str(idx)]
127
- if mapped_answer: # Only update if we successfully mapped some answers
128
- response["answer"] = mapped_answer
129
-
130
- return response
131
-
132
-
133
- class QuestionMatrix(QuestionBase):
134
- """A question that presents a matrix/grid where multiple items are rated using the same scale."""
135
-
136
- question_type = "matrix"
137
- question_text: str = QuestionTextDescriptor()
138
- question_items: List[str] = QuestionOptionsDescriptor()
139
- question_options: List[Union[int, str, float]] = QuestionOptionsDescriptor()
140
- option_labels: Optional[Dict[Union[int, str, float], str]] = OptionLabelDescriptor()
141
-
142
- _response_model = None
143
- response_validator_class = MatrixResponseValidator
144
-
145
- def __init__(
146
- self,
147
- question_name: str,
148
- question_text: str,
149
- question_items: List[str],
150
- question_options: List[Union[int, str, float]],
151
- option_labels: Optional[Dict[Union[int, str, float], str]] = None,
152
- include_comment: bool = True,
153
- answering_instructions: Optional[str] = None,
154
- question_presentation: Optional[str] = None,
155
- permissive: bool = False,
156
- ):
157
- """Initialize a matrix question.
158
-
159
- Args:
160
- question_name: The name of the question
161
- question_text: The text of the question
162
- question_items: List of items to be rated
163
- question_options: List of rating options
164
- option_labels: Optional mapping of options to their labels
165
- include_comment: Whether to include a comment field
166
- answering_instructions: Optional custom instructions
167
- question_presentation: Optional custom presentation
168
- permissive: Whether to strictly validate responses
169
- """
170
- self.question_name = question_name
171
-
172
- try:
173
- self.question_text = question_text
174
- except Exception as e:
175
- raise QuestionCreationValidationError(
176
- "question_text cannot be empty or too short!"
177
- ) from e
178
-
179
- self.question_items = question_items
180
- self.question_options = question_options
181
- self.option_labels = option_labels or {}
182
-
183
- self.include_comment = include_comment
184
- self.answering_instructions = answering_instructions
185
- self.question_presentation = question_presentation
186
- self.permissive = permissive
187
-
188
- def create_response_model(self):
189
- return create_matrix_response(
190
- self.question_items, self.question_options, self.permissive
191
- )
192
-
193
- @property
194
- def question_html_content(self) -> str:
195
- """Generate HTML representation of the matrix question."""
196
- template = Template(
197
- """
198
- <table class="matrix-question">
199
- <tr>
200
- <th></th>
201
- {% for option in question_options %}
202
- <th>
203
- {{ option }}
204
- {% if option in option_labels %}
205
- <br>
206
- <small>{{ option_labels[option] }}</small>
207
- {% endif %}
208
- </th>
209
- {% endfor %}
210
- </tr>
211
- {% for item in question_items %}
212
- <tr>
213
- <td>{{ item }}</td>
214
- {% for option in question_options %}
215
- <td>
216
- <input type="radio"
217
- name="{{ question_name }}_{{ item }}"
218
- value="{{ option }}"
219
- id="{{ question_name }}_{{ item }}_{{ option }}">
220
- </td>
221
- {% endfor %}
222
- </tr>
223
- {% endfor %}
224
- </table>
225
- """
226
- )
227
-
228
- return template.render(
229
- question_name=self.question_name,
230
- question_items=self.question_items,
231
- question_options=self.question_options,
232
- option_labels=self.option_labels,
233
- )
234
-
235
- @classmethod
236
- @inject_exception
237
- def example(cls) -> QuestionMatrix:
238
- """Return an example matrix question."""
239
- return cls(
240
- question_name="child_happiness",
241
- question_text="How happy would you be with different numbers of children?",
242
- question_items=[
243
- "No children",
244
- "1 child",
245
- "2 children",
246
- "3 or more children",
247
- ],
248
- question_options=[1, 2, 3, 4, 5],
249
- option_labels={1: "Very sad", 3: "Neutral", 5: "Extremely happy"},
250
- )
251
-
252
- def _simulate_answer(self) -> dict:
253
- """Simulate a random valid answer."""
254
- return {
255
- "answer": {
256
- item: random.choice(self.question_options)
257
- for item in self.question_items
258
- }
259
- }
260
-
261
-
262
- if __name__ == "__main__":
263
- import doctest
264
-
265
- doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,20 +0,0 @@
1
- from typing import Any, Optional, TypedDict
2
- from pydantic import BaseModel
3
-
4
-
5
- class RawEdslAnswerDict(TypedDict):
6
- answer: Any
7
- comment: Optional[str]
8
- generated_tokens: Optional[str]
9
-
10
-
11
- class BaseResponse(BaseModel):
12
- answer: Any
13
- comment: Optional[str] = None
14
- generated_tokens: Optional[str] = None
15
-
16
-
17
- class EdslAnswerDict(TypedDict):
18
- answer: Any
19
- comment: Optional[str]
20
- generated_tokens: Optional[str]
@@ -1,149 +0,0 @@
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
- """
@@ -1,34 +0,0 @@
1
- from edsl.questions.data_structures import BaseModel
2
- from edsl.questions.response_validator_abc import ResponseValidatorABC
3
-
4
-
5
- class ResponseValidatorFactory:
6
- """Factory class to create a response validator for a question."""
7
-
8
- def __init__(self, question):
9
- self.question = question
10
-
11
- @property
12
- def response_model(self) -> type["BaseModel"]:
13
- if self.question._response_model is not None:
14
- return self.question._response_model
15
- else:
16
- return self.question.create_response_model()
17
-
18
- @property
19
- def response_validator(self) -> "ResponseValidatorABC":
20
- """Return the response validator."""
21
- params = (
22
- {
23
- "response_model": self.question.response_model,
24
- }
25
- | {k: getattr(self.question, k) for k in self.validator_parameters}
26
- | {"exception_to_throw": getattr(self.question, "exception_to_throw", None)}
27
- | {"override_answer": getattr(self.question, "override_answer", None)}
28
- )
29
- return self.question.response_validator_class(**params)
30
-
31
- @property
32
- def validator_parameters(self) -> list[str]:
33
- """Return the parameters required for the response validator."""
34
- return self.question.response_validator_class.required_params
@@ -1 +0,0 @@
1
-
@@ -1,5 +0,0 @@
1
- Please respond with a dictionary mapping row codes to column codes. E.g., {"0": 1, "1": 3}
2
-
3
- {% if include_comment %}
4
- After the answer, you can put a comment explaining your choices on the next line.
5
- {% endif %}
@@ -1,20 +0,0 @@
1
- {{question_text}}
2
-
3
- Rows:
4
- {% for item in question_items %}
5
- {{ loop.index0 }}: {{item}}
6
- {% endfor %}
7
-
8
- Columns:
9
- {% for option in question_options %}
10
- {{ loop.index0 }}: {{option}}
11
- {%- if option in option_labels %}
12
- ({{option_labels[option]}})
13
- {%- endif %}
14
- {% endfor %}
15
-
16
-
17
- Select one column option for each row.
18
- {% if required %}
19
- All rows require a response.
20
- {% endif %}