edsl 0.1.33__py3-none-any.whl → 0.1.33.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 (180) hide show
  1. edsl/Base.py +3 -9
  2. edsl/__init__.py +3 -8
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +8 -40
  5. edsl/agents/AgentList.py +0 -43
  6. edsl/agents/Invigilator.py +219 -135
  7. edsl/agents/InvigilatorBase.py +59 -148
  8. edsl/agents/{PromptConstructor.py → PromptConstructionMixin.py} +89 -138
  9. edsl/agents/__init__.py +0 -1
  10. edsl/config.py +56 -47
  11. edsl/coop/coop.py +7 -50
  12. edsl/data/Cache.py +1 -35
  13. edsl/data_transfer_models.py +38 -73
  14. edsl/enums.py +0 -4
  15. edsl/exceptions/language_models.py +1 -25
  16. edsl/exceptions/questions.py +5 -62
  17. edsl/exceptions/results.py +0 -4
  18. edsl/inference_services/AnthropicService.py +11 -13
  19. edsl/inference_services/AwsBedrock.py +17 -19
  20. edsl/inference_services/AzureAI.py +20 -37
  21. edsl/inference_services/GoogleService.py +12 -16
  22. edsl/inference_services/GroqService.py +0 -2
  23. edsl/inference_services/InferenceServiceABC.py +3 -58
  24. edsl/inference_services/OpenAIService.py +54 -48
  25. edsl/inference_services/models_available_cache.py +6 -0
  26. edsl/inference_services/registry.py +0 -6
  27. edsl/jobs/Answers.py +12 -10
  28. edsl/jobs/Jobs.py +21 -36
  29. edsl/jobs/buckets/BucketCollection.py +15 -24
  30. edsl/jobs/buckets/TokenBucket.py +14 -93
  31. edsl/jobs/interviews/Interview.py +78 -366
  32. edsl/jobs/interviews/InterviewExceptionEntry.py +19 -85
  33. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +286 -0
  34. edsl/jobs/interviews/{InterviewExceptionCollection.py → interview_exception_tracking.py} +68 -14
  35. edsl/jobs/interviews/retry_management.py +37 -0
  36. edsl/jobs/runners/JobsRunnerAsyncio.py +175 -146
  37. edsl/jobs/runners/JobsRunnerStatusMixin.py +333 -0
  38. edsl/jobs/tasks/QuestionTaskCreator.py +23 -30
  39. edsl/jobs/tasks/TaskHistory.py +213 -148
  40. edsl/language_models/LanguageModel.py +156 -261
  41. edsl/language_models/ModelList.py +2 -2
  42. edsl/language_models/RegisterLanguageModelsMeta.py +29 -14
  43. edsl/language_models/registry.py +6 -23
  44. edsl/language_models/repair.py +19 -0
  45. edsl/prompts/Prompt.py +2 -52
  46. edsl/questions/AnswerValidatorMixin.py +26 -23
  47. edsl/questions/QuestionBase.py +249 -329
  48. edsl/questions/QuestionBudget.py +41 -99
  49. edsl/questions/QuestionCheckBox.py +35 -227
  50. edsl/questions/QuestionExtract.py +27 -98
  51. edsl/questions/QuestionFreeText.py +29 -52
  52. edsl/questions/QuestionFunctional.py +0 -7
  53. edsl/questions/QuestionList.py +22 -141
  54. edsl/questions/QuestionMultipleChoice.py +65 -159
  55. edsl/questions/QuestionNumerical.py +46 -88
  56. edsl/questions/QuestionRank.py +24 -182
  57. edsl/questions/RegisterQuestionsMeta.py +12 -31
  58. edsl/questions/__init__.py +4 -3
  59. edsl/questions/derived/QuestionLikertFive.py +5 -10
  60. edsl/questions/derived/QuestionLinearScale.py +2 -15
  61. edsl/questions/derived/QuestionTopK.py +1 -10
  62. edsl/questions/derived/QuestionYesNo.py +3 -24
  63. edsl/questions/descriptors.py +7 -43
  64. edsl/questions/question_registry.py +2 -6
  65. edsl/results/Dataset.py +0 -20
  66. edsl/results/DatasetExportMixin.py +48 -46
  67. edsl/results/Result.py +5 -32
  68. edsl/results/Results.py +46 -135
  69. edsl/results/ResultsDBMixin.py +3 -3
  70. edsl/scenarios/FileStore.py +10 -71
  71. edsl/scenarios/Scenario.py +25 -96
  72. edsl/scenarios/ScenarioImageMixin.py +2 -2
  73. edsl/scenarios/ScenarioList.py +39 -361
  74. edsl/scenarios/ScenarioListExportMixin.py +0 -9
  75. edsl/scenarios/ScenarioListPdfMixin.py +4 -150
  76. edsl/study/SnapShot.py +1 -8
  77. edsl/study/Study.py +0 -32
  78. edsl/surveys/Rule.py +1 -10
  79. edsl/surveys/RuleCollection.py +5 -21
  80. edsl/surveys/Survey.py +310 -636
  81. edsl/surveys/SurveyExportMixin.py +9 -71
  82. edsl/surveys/SurveyFlowVisualizationMixin.py +1 -2
  83. edsl/surveys/SurveyQualtricsImport.py +4 -75
  84. edsl/utilities/gcp_bucket/simple_example.py +9 -0
  85. edsl/utilities/utilities.py +1 -9
  86. {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/METADATA +2 -5
  87. edsl-0.1.33.dev1.dist-info/RECORD +209 -0
  88. edsl/TemplateLoader.py +0 -24
  89. edsl/auto/AutoStudy.py +0 -117
  90. edsl/auto/StageBase.py +0 -230
  91. edsl/auto/StageGenerateSurvey.py +0 -178
  92. edsl/auto/StageLabelQuestions.py +0 -125
  93. edsl/auto/StagePersona.py +0 -61
  94. edsl/auto/StagePersonaDimensionValueRanges.py +0 -88
  95. edsl/auto/StagePersonaDimensionValues.py +0 -74
  96. edsl/auto/StagePersonaDimensions.py +0 -69
  97. edsl/auto/StageQuestions.py +0 -73
  98. edsl/auto/SurveyCreatorPipeline.py +0 -21
  99. edsl/auto/utilities.py +0 -224
  100. edsl/coop/PriceFetcher.py +0 -58
  101. edsl/inference_services/MistralAIService.py +0 -120
  102. edsl/inference_services/TestService.py +0 -80
  103. edsl/inference_services/TogetherAIService.py +0 -170
  104. edsl/jobs/FailedQuestion.py +0 -78
  105. edsl/jobs/runners/JobsRunnerStatus.py +0 -331
  106. edsl/language_models/fake_openai_call.py +0 -15
  107. edsl/language_models/fake_openai_service.py +0 -61
  108. edsl/language_models/utilities.py +0 -61
  109. edsl/questions/QuestionBaseGenMixin.py +0 -133
  110. edsl/questions/QuestionBasePromptsMixin.py +0 -266
  111. edsl/questions/Quick.py +0 -41
  112. edsl/questions/ResponseValidatorABC.py +0 -170
  113. edsl/questions/decorators.py +0 -21
  114. edsl/questions/prompt_templates/question_budget.jinja +0 -13
  115. edsl/questions/prompt_templates/question_checkbox.jinja +0 -32
  116. edsl/questions/prompt_templates/question_extract.jinja +0 -11
  117. edsl/questions/prompt_templates/question_free_text.jinja +0 -3
  118. edsl/questions/prompt_templates/question_linear_scale.jinja +0 -11
  119. edsl/questions/prompt_templates/question_list.jinja +0 -17
  120. edsl/questions/prompt_templates/question_multiple_choice.jinja +0 -33
  121. edsl/questions/prompt_templates/question_numerical.jinja +0 -37
  122. edsl/questions/templates/__init__.py +0 -0
  123. edsl/questions/templates/budget/__init__.py +0 -0
  124. edsl/questions/templates/budget/answering_instructions.jinja +0 -7
  125. edsl/questions/templates/budget/question_presentation.jinja +0 -7
  126. edsl/questions/templates/checkbox/__init__.py +0 -0
  127. edsl/questions/templates/checkbox/answering_instructions.jinja +0 -10
  128. edsl/questions/templates/checkbox/question_presentation.jinja +0 -22
  129. edsl/questions/templates/extract/__init__.py +0 -0
  130. edsl/questions/templates/extract/answering_instructions.jinja +0 -7
  131. edsl/questions/templates/extract/question_presentation.jinja +0 -1
  132. edsl/questions/templates/free_text/__init__.py +0 -0
  133. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  134. edsl/questions/templates/free_text/question_presentation.jinja +0 -1
  135. edsl/questions/templates/likert_five/__init__.py +0 -0
  136. edsl/questions/templates/likert_five/answering_instructions.jinja +0 -10
  137. edsl/questions/templates/likert_five/question_presentation.jinja +0 -12
  138. edsl/questions/templates/linear_scale/__init__.py +0 -0
  139. edsl/questions/templates/linear_scale/answering_instructions.jinja +0 -5
  140. edsl/questions/templates/linear_scale/question_presentation.jinja +0 -5
  141. edsl/questions/templates/list/__init__.py +0 -0
  142. edsl/questions/templates/list/answering_instructions.jinja +0 -4
  143. edsl/questions/templates/list/question_presentation.jinja +0 -5
  144. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  145. edsl/questions/templates/multiple_choice/answering_instructions.jinja +0 -9
  146. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  147. edsl/questions/templates/multiple_choice/question_presentation.jinja +0 -12
  148. edsl/questions/templates/numerical/__init__.py +0 -0
  149. edsl/questions/templates/numerical/answering_instructions.jinja +0 -8
  150. edsl/questions/templates/numerical/question_presentation.jinja +0 -7
  151. edsl/questions/templates/rank/__init__.py +0 -0
  152. edsl/questions/templates/rank/answering_instructions.jinja +0 -11
  153. edsl/questions/templates/rank/question_presentation.jinja +0 -15
  154. edsl/questions/templates/top_k/__init__.py +0 -0
  155. edsl/questions/templates/top_k/answering_instructions.jinja +0 -8
  156. edsl/questions/templates/top_k/question_presentation.jinja +0 -22
  157. edsl/questions/templates/yes_no/__init__.py +0 -0
  158. edsl/questions/templates/yes_no/answering_instructions.jinja +0 -6
  159. edsl/questions/templates/yes_no/question_presentation.jinja +0 -12
  160. edsl/results/DatasetTree.py +0 -145
  161. edsl/results/Selector.py +0 -118
  162. edsl/results/tree_explore.py +0 -115
  163. edsl/surveys/instructions/ChangeInstruction.py +0 -47
  164. edsl/surveys/instructions/Instruction.py +0 -34
  165. edsl/surveys/instructions/InstructionCollection.py +0 -77
  166. edsl/surveys/instructions/__init__.py +0 -0
  167. edsl/templates/error_reporting/base.html +0 -24
  168. edsl/templates/error_reporting/exceptions_by_model.html +0 -35
  169. edsl/templates/error_reporting/exceptions_by_question_name.html +0 -17
  170. edsl/templates/error_reporting/exceptions_by_type.html +0 -17
  171. edsl/templates/error_reporting/interview_details.html +0 -116
  172. edsl/templates/error_reporting/interviews.html +0 -10
  173. edsl/templates/error_reporting/overview.html +0 -5
  174. edsl/templates/error_reporting/performance_plot.html +0 -2
  175. edsl/templates/error_reporting/report.css +0 -74
  176. edsl/templates/error_reporting/report.html +0 -118
  177. edsl/templates/error_reporting/report.js +0 -25
  178. edsl-0.1.33.dist-info/RECORD +0 -295
  179. {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/LICENSE +0 -0
  180. {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/WHEEL +0 -0
@@ -3,7 +3,6 @@ from typing import Optional
3
3
 
4
4
  from edsl.exceptions import QuestionCreationValidationError
5
5
  from edsl.questions.QuestionCheckBox import QuestionCheckBox
6
- from edsl.questions.decorators import inject_exception
7
6
 
8
7
 
9
8
  class QuestionTopK(QuestionCheckBox):
@@ -18,9 +17,6 @@ class QuestionTopK(QuestionCheckBox):
18
17
  question_options: list[str],
19
18
  min_selections: int,
20
19
  max_selections: int,
21
- question_presentation: Optional[str] = None,
22
- answering_instructions: Optional[str] = None,
23
- include_comment: Optional[bool] = True,
24
20
  ):
25
21
  """Initialize the question.
26
22
 
@@ -36,9 +32,6 @@ class QuestionTopK(QuestionCheckBox):
36
32
  question_options=question_options,
37
33
  min_selections=min_selections,
38
34
  max_selections=max_selections,
39
- question_presentation=question_presentation,
40
- answering_instructions=answering_instructions,
41
- include_comment=include_comment,
42
35
  )
43
36
  if min_selections != max_selections:
44
37
  raise QuestionCreationValidationError(
@@ -53,8 +46,7 @@ class QuestionTopK(QuestionCheckBox):
53
46
  # Helpful
54
47
  ################
55
48
  @classmethod
56
- @inject_exception
57
- def example(cls, include_comment: bool = True) -> QuestionTopK:
49
+ def example(cls) -> QuestionTopK:
58
50
  """Return an example question."""
59
51
  return cls(
60
52
  question_name="two_fruits",
@@ -62,7 +54,6 @@ class QuestionTopK(QuestionCheckBox):
62
54
  question_options=["apple", "banana", "carrot", "durian"],
63
55
  min_selections=2,
64
56
  max_selections=2,
65
- include_comment=include_comment,
66
57
  )
67
58
 
68
59
 
@@ -1,10 +1,7 @@
1
1
  from __future__ import annotations
2
- from typing import Optional
3
2
  from edsl.questions.descriptors import QuestionOptionsDescriptor
4
3
  from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
5
4
 
6
- from edsl.questions.decorators import inject_exception
7
-
8
5
 
9
6
  class QuestionYesNo(QuestionMultipleChoice):
10
7
  """This question prompts the agent to respond with 'Yes' or 'No'."""
@@ -16,10 +13,7 @@ class QuestionYesNo(QuestionMultipleChoice):
16
13
  self,
17
14
  question_name: str,
18
15
  question_text: str,
19
- question_options: list[str] = ["No", "Yes"],
20
- answering_instructions: Optional[str] = None,
21
- question_presentation: Optional[str] = None,
22
- include_comment: Optional[bool] = True,
16
+ question_options: list[str] = ["Yes", "No"],
23
17
  ):
24
18
  """Instantiate a new QuestionYesNo.
25
19
 
@@ -31,10 +25,6 @@ class QuestionYesNo(QuestionMultipleChoice):
31
25
  question_name=question_name,
32
26
  question_text=question_text,
33
27
  question_options=question_options,
34
- use_code=False,
35
- answering_instructions=answering_instructions,
36
- question_presentation=question_presentation,
37
- include_comment=include_comment,
38
28
  )
39
29
  self.question_options = question_options
40
30
 
@@ -42,14 +32,9 @@ class QuestionYesNo(QuestionMultipleChoice):
42
32
  # Helpful
43
33
  ################
44
34
  @classmethod
45
- @inject_exception
46
- def example(cls, include_comment: bool = True) -> QuestionYesNo:
35
+ def example(cls) -> QuestionYesNo:
47
36
  """Return an example of a yes/no question."""
48
- return cls(
49
- question_name="is_it_equal",
50
- question_text="Is 5 + 5 equal to 11?",
51
- include_comment=include_comment,
52
- )
37
+ return cls(question_name="is_it_equal", question_text="Is 5 + 5 equal to 11?")
53
38
 
54
39
 
55
40
  def main():
@@ -74,9 +59,3 @@ def main():
74
59
  import doctest
75
60
 
76
61
  doctest.testmod(optionflags=doctest.ELLIPSIS)
77
-
78
-
79
- if __name__ == "__main__":
80
- import doctest
81
-
82
- doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -206,14 +206,12 @@ class OptionLabelDescriptor(BaseDescriptor):
206
206
 
207
207
  def validate(self, value, instance):
208
208
  """Validate the value is a string."""
209
- # key_values = [int(v) for v in value.keys()]
210
-
211
- if value and (key_values := [float(v) for v in value.keys()]) != []:
212
- if min(key_values) != min(instance.question_options):
209
+ if value is not None:
210
+ if min(value.keys()) != min(instance.question_options):
213
211
  raise QuestionCreationValidationError(
214
212
  f"First option needs a label (got {value})"
215
213
  )
216
- if max(key_values) != max(instance.question_options):
214
+ if max(value.keys()) != max(instance.question_options):
217
215
  raise QuestionCreationValidationError(
218
216
  f"Last option needs a label (got {value})"
219
217
  )
@@ -221,17 +219,12 @@ class OptionLabelDescriptor(BaseDescriptor):
221
219
  raise QuestionCreationValidationError(
222
220
  "Option labels must be strings (got {value})."
223
221
  )
224
- for key in key_values:
222
+ for key in value.keys():
225
223
  if key not in instance.question_options:
226
224
  raise QuestionCreationValidationError(
227
225
  f"Option label key ({key}) is not in question options ({instance.question_options})."
228
226
  )
229
227
 
230
- if len(value.values()) != len(set(value.values())):
231
- raise QuestionCreationValidationError(
232
- f"Option labels must be unique (got {value})."
233
- )
234
-
235
228
 
236
229
  class QuestionNameDescriptor(BaseDescriptor):
237
230
  """Validate that the `question_name` attribute is a valid variable name."""
@@ -240,15 +233,6 @@ class QuestionNameDescriptor(BaseDescriptor):
240
233
  """Validate the value is a valid variable name."""
241
234
  from edsl.utilities.utilities import is_valid_variable_name
242
235
 
243
- if "{{" in value and "}}" in value:
244
- # they're trying to use a dynamic question name - let's let this play out
245
- return None
246
-
247
- if value.endswith("_comment") or value.endswith("_generated_tokens"):
248
- raise QuestionCreationValidationError(
249
- f"`question_name` cannot end with '_comment' or '_generated_tokens - (got {value})."
250
- )
251
-
252
236
  if not is_valid_variable_name(value):
253
237
  raise QuestionCreationValidationError(
254
238
  f"`question_name` is not a valid variable name (got {value})."
@@ -295,7 +279,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
295
279
  >>> _ = q_class("dynamic_options")
296
280
  Traceback (most recent call last):
297
281
  ...
298
- edsl.exceptions.questions.QuestionCreationValidationError: ...
282
+ edsl.exceptions.questions.QuestionCreationValidationError: Dynamic question options must be of the form: '{{ question_options }}'.
299
283
  """
300
284
  if isinstance(value, str):
301
285
  # Check if the string is a dynamic question option
@@ -303,7 +287,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
303
287
  return None
304
288
  else:
305
289
  raise QuestionCreationValidationError(
306
- f"Dynamic question options must have jina2 braces - instead received: {value}."
290
+ "Dynamic question options must be of the form: '{{ question_options }}'."
307
291
  )
308
292
  if not isinstance(value, list):
309
293
  raise QuestionCreationValidationError(
@@ -372,21 +356,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
372
356
 
373
357
 
374
358
  class QuestionTextDescriptor(BaseDescriptor):
375
- """Validate that the `question_text` attribute is a string.
376
-
377
-
378
- >>> class TestQuestion:
379
- ... question_text = QuestionTextDescriptor()
380
- ... def __init__(self, question_text: str):
381
- ... self.question_text = question_text
382
-
383
- >>> _ = TestQuestion("What is the capital of France?")
384
- >>> _ = TestQuestion("What is the capital of France? {{variable}}")
385
- >>> _ = TestQuestion("What is the capital of France? {{variable name}}")
386
- Traceback (most recent call last):
387
- ...
388
- edsl.exceptions.questions.QuestionCreationValidationError: Question text contains an invalid identifier: 'variable name'
389
- """
359
+ """Validate that the `question_text` attribute is a string."""
390
360
 
391
361
  def validate(self, value, instance):
392
362
  """Validate the value is a string."""
@@ -403,12 +373,6 @@ class QuestionTextDescriptor(BaseDescriptor):
403
373
  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",
404
374
  UserWarning,
405
375
  )
406
- # iterate through all doubles braces and check if they are valid python identifiers
407
- for match in re.finditer(r"\{\{([^\{\}]+)\}\}", value):
408
- if " " in match.group(1).strip():
409
- raise QuestionCreationValidationError(
410
- f"Question text contains an invalid identifier: '{match.group(1)}'"
411
- )
412
376
 
413
377
 
414
378
  if __name__ == "__main__":
@@ -100,16 +100,12 @@ class Question(metaclass=Meta):
100
100
 
101
101
  >>> from edsl import Question
102
102
  >>> Question.available()
103
- ['checkbox', 'extract', 'free_text', 'functional', 'likert_five', 'linear_scale', 'list', 'multiple_choice', 'numerical', 'rank', 'top_k', 'yes_no']
103
+ ['budget', 'checkbox', 'extract', 'free_text', 'functional', 'likert_five', 'linear_scale', 'list', 'multiple_choice', 'numerical', 'rank', 'top_k', 'yes_no']
104
104
  """
105
- exclude = ["budget"]
106
105
  if show_class_names:
107
106
  return RegisterQuestionsMeta.question_types_to_classes()
108
107
  else:
109
- question_list = sorted(
110
- set(RegisterQuestionsMeta.question_types_to_classes().keys())
111
- )
112
- return [q for q in question_list if q not in exclude]
108
+ return sorted(set(RegisterQuestionsMeta.question_types_to_classes().keys()))
113
109
 
114
110
 
115
111
  def get_question_class(question_type):
edsl/results/Dataset.py CHANGED
@@ -8,7 +8,6 @@ from typing import Any, Union, Optional
8
8
  import numpy as np
9
9
 
10
10
  from edsl.results.ResultsExportMixin import ResultsExportMixin
11
- from edsl.results.DatasetTree import Tree
12
11
 
13
12
 
14
13
  class Dataset(UserList, ResultsExportMixin):
@@ -31,15 +30,6 @@ class Dataset(UserList, ResultsExportMixin):
31
30
  _, values = list(self.data[0].items())[0]
32
31
  return len(values)
33
32
 
34
- def keys(self):
35
- """Return the keys of the first observation in the dataset.
36
-
37
- >>> d = Dataset([{'a.b':[1,2,3,4]}])
38
- >>> d.keys()
39
- ['a.b']
40
- """
41
- return [list(o.keys())[0] for o in self]
42
-
43
33
  def __repr__(self) -> str:
44
34
  """Return a string representation of the dataset."""
45
35
  return f"Dataset({self.data})"
@@ -255,16 +245,6 @@ class Dataset(UserList, ResultsExportMixin):
255
245
 
256
246
  return Dataset(new_data)
257
247
 
258
- @property
259
- def tree(self):
260
- """Return a tree representation of the dataset.
261
-
262
- >>> d = Dataset([{'a':[1,2,3,4]}, {'b':[4,3,2,1]}])
263
- >>> d.tree.print_tree()
264
- Tree has not been constructed yet.
265
- """
266
- return Tree(self)
267
-
268
248
  @classmethod
269
249
  def example(self):
270
250
  """Return an example dataset.
@@ -4,7 +4,6 @@ import base64
4
4
  import csv
5
5
  import io
6
6
  import html
7
- from typing import Optional
8
7
 
9
8
  from typing import Literal, Optional, Union, List
10
9
 
@@ -42,7 +41,7 @@ class DatasetExportMixin:
42
41
  >>> Results.example().relevant_columns(data_type = "flimflam")
43
42
  Traceback (most recent call last):
44
43
  ...
45
- ValueError: No columns found for data type: flimflam. Available data types are: ...
44
+ ValueError: No columns found for data type: flimflam. Available data types are: ['agent', 'answer', 'comment', 'model', 'prompt', 'question_options', 'question_text', 'question_type', 'raw_model_response', 'scenario'].
46
45
  """
47
46
  columns = [list(x.keys())[0] for x in self]
48
47
  if remove_prefix:
@@ -157,13 +156,12 @@ class DatasetExportMixin:
157
156
  iframe_height: int = 200,
158
157
  iframe_width: int = 600,
159
158
  web=False,
160
- return_string: bool = False,
161
- ) -> Union[None, str, "Results"]:
159
+ ) -> None:
162
160
  """Print the results in a pretty format.
163
161
 
164
162
  :param pretty_labels: A dictionary of pretty labels for the columns.
165
163
  :param filename: The filename to save the results to.
166
- :param format: The format to print the results in. Options are 'rich', 'html', 'markdown', or 'latex'.
164
+ :param format: The format to print the results in. Options are 'rich', 'html', or 'markdown'.
167
165
  :param interactive: Whether to print the results interactively in a Jupyter notebook.
168
166
  :param split_at_dot: Whether to split the column names at the last dot w/ a newline.
169
167
  :param max_rows: The maximum number of rows to print.
@@ -172,9 +170,6 @@ class DatasetExportMixin:
172
170
  :param iframe_height: The height of the iframe.
173
171
  :param iframe_width: The width of the iframe.
174
172
  :param web: Whether to display the table in a web browser.
175
- :param return_string: Whether to return the output as a string instead of printing.
176
-
177
- :return: None if tee is False and return_string is False, the dataset if tee is True, or a string if return_string is True.
178
173
 
179
174
  Example: Print in rich format at the terminal
180
175
 
@@ -258,14 +253,11 @@ class DatasetExportMixin:
258
253
 
259
254
  >>> r.select('how_feeling').print(format='latex')
260
255
  \\begin{tabular}{l}
256
+ \\toprule
261
257
  ...
262
- \\end{tabular}
263
- <BLANKLINE>
264
258
  """
265
259
  from IPython.display import HTML, display
266
260
  from edsl.utilities.utilities import is_notebook
267
- import io
268
- import sys
269
261
 
270
262
  def _determine_format(format):
271
263
  if format is None:
@@ -274,9 +266,7 @@ class DatasetExportMixin:
274
266
  else:
275
267
  format = "rich"
276
268
  if format not in ["rich", "html", "markdown", "latex"]:
277
- raise ValueError(
278
- "format must be one of 'rich', 'html', 'markdown', or 'latex'."
279
- )
269
+ raise ValueError("format must be one of 'rich', 'html', or 'markdown'.")
280
270
 
281
271
  return format
282
272
 
@@ -295,24 +285,21 @@ class DatasetExportMixin:
295
285
 
296
286
  new_data = list(_create_data())
297
287
 
298
- # Capture output if return_string is True
299
- if return_string:
300
- old_stdout = sys.stdout
301
- sys.stdout = io.StringIO()
302
-
303
- output = None
304
-
305
288
  if format == "rich":
306
289
  from edsl.utilities.interface import print_dataset_with_rich
307
290
 
308
- output = print_dataset_with_rich(
291
+ print_dataset_with_rich(
309
292
  new_data, filename=filename, split_at_dot=split_at_dot
310
293
  )
311
- elif format == "markdown":
294
+ return self if tee else None
295
+
296
+ if format == "markdown":
312
297
  from edsl.utilities.interface import print_list_of_dicts_as_markdown_table
313
298
 
314
- output = print_list_of_dicts_as_markdown_table(new_data, filename=filename)
315
- elif format == "latex":
299
+ print_list_of_dicts_as_markdown_table(new_data, filename=filename)
300
+ return self if tee else None
301
+
302
+ if format == "latex":
316
303
  df = self.to_pandas()
317
304
  df.columns = [col.replace("_", " ") for col in df.columns]
318
305
  latex_string = df.to_latex(index=False)
@@ -322,14 +309,23 @@ class DatasetExportMixin:
322
309
  f.write(latex_string)
323
310
  else:
324
311
  print(latex_string)
325
- output = latex_string
326
- elif format == "html":
312
+
313
+ return self if tee else None
314
+
315
+ if format == "html":
327
316
  from edsl.utilities.interface import print_list_of_dicts_as_html_table
328
317
 
329
318
  html_source = print_list_of_dicts_as_html_table(
330
319
  new_data, interactive=interactive
331
320
  )
332
321
 
322
+ # if download_link:
323
+ # from IPython.display import HTML, display
324
+ # csv_file = output.getvalue()
325
+ # b64 = base64.b64encode(csv_file.encode()).decode()
326
+ # download_link = f'<a href="data:file/csv;base64,{b64}" download="my_data.csv">Download CSV file</a>'
327
+ # #display(HTML(download_link))
328
+
333
329
  if iframe:
334
330
  iframe = f""""
335
331
  <iframe srcdoc="{ html.escape(html_source) }" style="width: {iframe_width}px; height: {iframe_height}px;"></iframe>
@@ -342,18 +338,7 @@ class DatasetExportMixin:
342
338
 
343
339
  view_html(html_source)
344
340
 
345
- output = html_source
346
-
347
- # Restore stdout and get captured output if return_string is True
348
- if return_string:
349
- captured_output = sys.stdout.getvalue()
350
- sys.stdout = old_stdout
351
- return captured_output or output
352
-
353
- if tee:
354
- return self
355
-
356
- return None
341
+ return self if tee else None
357
342
 
358
343
  def to_csv(
359
344
  self,
@@ -472,11 +457,7 @@ class DatasetExportMixin:
472
457
  from edsl import ScenarioList, Scenario
473
458
 
474
459
  list_of_dicts = self.to_dicts(remove_prefix=remove_prefix)
475
- scenarios = []
476
- for d in list_of_dicts:
477
- scenarios.append(Scenario(d))
478
- return ScenarioList(scenarios)
479
- # return ScenarioList([Scenario(d) for d in list_of_dicts])
460
+ return ScenarioList([Scenario(d) for d in list_of_dicts])
480
461
 
481
462
  def to_agent_list(self, remove_prefix: bool = True):
482
463
  """Convert the results to a list of dictionaries, one per agent.
@@ -520,7 +501,7 @@ class DatasetExportMixin:
520
501
 
521
502
  return list_of_dicts
522
503
 
523
- def to_list(self, flatten=False, remove_none=False, unzipped=False) -> list[list]:
504
+ def to_list(self, flatten=False, remove_none=False) -> list[list]:
524
505
  """Convert the results to a list of lists.
525
506
 
526
507
  :param flatten: Whether to flatten the list of lists.
@@ -615,6 +596,27 @@ class DatasetExportMixin:
615
596
  if return_link:
616
597
  return filename
617
598
 
599
+ def to_docx(self, filename: Optional[str] = None, separator: str = "\n"):
600
+ """Export the results to a Word document.
601
+
602
+ :param filename: The filename to save the Word document to.
603
+
604
+
605
+ """
606
+ from docx import Document
607
+
608
+ doc = Document()
609
+ for entry in self:
610
+ key, values = list(entry.items())[0]
611
+ doc.add_paragraph(key)
612
+ line = separator.join(values)
613
+ doc.add_paragraph(line)
614
+
615
+ if filename is not None:
616
+ doc.save(filename)
617
+ else:
618
+ return doc
619
+
618
620
  def tally(
619
621
  self, *fields: Optional[str], top_n: Optional[int] = None, output="Dataset"
620
622
  ) -> Union[dict, "Dataset"]:
edsl/results/Result.py CHANGED
@@ -53,8 +53,8 @@ class Result(Base, UserDict):
53
53
 
54
54
  >>> import warnings
55
55
  >>> warnings.simplefilter("ignore", UserWarning)
56
- >>> Result.example().answer == {'how_feeling_yesterday': 'Great', 'how_feeling': 'OK'}
57
- True
56
+ >>> Result.example().answer
57
+ {'how_feeling': 'OK', 'how_feeling_comment': 'This is a real survey response from a human.', 'how_feeling_yesterday': 'Great', 'how_feeling_yesterday_comment': 'This is a real survey response from a human.'}
58
58
 
59
59
  Its main data is an Agent, a Scenario, a Model, an Iteration, and an Answer.
60
60
  These are stored both in the UserDict and as attributes.
@@ -73,8 +73,6 @@ class Result(Base, UserDict):
73
73
  raw_model_response=None,
74
74
  survey: Optional["Survey"] = None,
75
75
  question_to_attributes: Optional[dict] = None,
76
- generated_tokens: Optional[dict] = None,
77
- comments_dict: Optional[dict] = None,
78
76
  ):
79
77
  """Initialize a Result object.
80
78
 
@@ -115,7 +113,6 @@ class Result(Base, UserDict):
115
113
  "prompt": prompt or {},
116
114
  "raw_model_response": raw_model_response or {},
117
115
  "question_to_attributes": question_to_attributes,
118
- "generated_tokens": generated_tokens or {},
119
116
  }
120
117
  super().__init__(**data)
121
118
  # but also store the data as attributes
@@ -128,8 +125,6 @@ class Result(Base, UserDict):
128
125
  self.raw_model_response = raw_model_response or {}
129
126
  self.survey = survey
130
127
  self.question_to_attributes = question_to_attributes
131
- self.generated_tokens = generated_tokens
132
- self.comments_dict = comments_dict or {}
133
128
 
134
129
  self._combined_dict = None
135
130
  self._problem_keys = None
@@ -145,7 +140,7 @@ class Result(Base, UserDict):
145
140
  else:
146
141
  agent_name = self.agent.name
147
142
 
148
- # comments_dict = {k: v for k, v in self.answer.items() if k.endswith("_comment")}
143
+ comments_dict = {k: v for k, v in self.answer.items() if k.endswith("_comment")}
149
144
  question_text_dict = {}
150
145
  question_options_dict = {}
151
146
  question_type_dict = {}
@@ -172,12 +167,11 @@ class Result(Base, UserDict):
172
167
  "answer": self.answer,
173
168
  "prompt": self.prompt,
174
169
  "raw_model_response": self.raw_model_response,
175
- "iteration": {"iteration": self.iteration},
170
+ # "iteration": {"iteration": self.iteration},
176
171
  "question_text": question_text_dict,
177
172
  "question_options": question_options_dict,
178
173
  "question_type": question_type_dict,
179
- "comment": self.comments_dict,
180
- "generated_tokens": self.generated_tokens,
174
+ "comment": comments_dict,
181
175
  }
182
176
 
183
177
  def check_expression(self, expression) -> None:
@@ -266,26 +260,6 @@ class Result(Base, UserDict):
266
260
  for key, value in subdict.items():
267
261
  yield (index, data_type, key, str(value))
268
262
 
269
- def leaves(self):
270
- leaves = []
271
- for question_name, answer in self.answer.items():
272
- if not question_name.endswith("_comment"):
273
- leaves.append(
274
- {
275
- "question": f"({question_name}): "
276
- + str(
277
- self.question_to_attributes[question_name]["question_text"]
278
- ),
279
- "answer": answer,
280
- "comment": self.answer.get(question_name + "_comment", ""),
281
- "scenario": repr(self.scenario),
282
- "agent": repr(self.agent),
283
- "model": repr(self.model),
284
- "iteration": self.iteration,
285
- }
286
- )
287
- return leaves
288
-
289
263
  ###############
290
264
  # Useful
291
265
  ###############
@@ -367,7 +341,6 @@ class Result(Base, UserDict):
367
341
  "raw_model_response", {"raw_model_response": "No raw model response"}
368
342
  ),
369
343
  question_to_attributes=json_dict.get("question_to_attributes", None),
370
- generated_tokens=json_dict.get("generated_tokens", {}),
371
344
  )
372
345
  return result
373
346