edsl 0.1.38.dev4__py3-none-any.whl → 0.1.39__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 +197 -116
  2. edsl/__init__.py +15 -7
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +351 -147
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +101 -50
  7. edsl/agents/InvigilatorBase.py +62 -70
  8. edsl/agents/PromptConstructor.py +143 -225
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  11. edsl/agents/__init__.py +0 -1
  12. edsl/agents/prompt_helpers.py +3 -3
  13. edsl/agents/question_option_processor.py +172 -0
  14. edsl/auto/AutoStudy.py +18 -5
  15. edsl/auto/StageBase.py +53 -40
  16. edsl/auto/StageQuestions.py +2 -1
  17. edsl/auto/utilities.py +0 -6
  18. edsl/config.py +22 -2
  19. edsl/conversation/car_buying.py +2 -1
  20. edsl/coop/CoopFunctionsMixin.py +15 -0
  21. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  22. edsl/coop/PriceFetcher.py +1 -1
  23. edsl/coop/coop.py +125 -47
  24. edsl/coop/utils.py +14 -14
  25. edsl/data/Cache.py +45 -27
  26. edsl/data/CacheEntry.py +12 -15
  27. edsl/data/CacheHandler.py +31 -12
  28. edsl/data/RemoteCacheSync.py +154 -46
  29. edsl/data/__init__.py +4 -3
  30. edsl/data_transfer_models.py +2 -1
  31. edsl/enums.py +27 -0
  32. edsl/exceptions/__init__.py +50 -50
  33. edsl/exceptions/agents.py +12 -0
  34. edsl/exceptions/inference_services.py +5 -0
  35. edsl/exceptions/questions.py +24 -6
  36. edsl/exceptions/scenarios.py +7 -0
  37. edsl/inference_services/AnthropicService.py +38 -19
  38. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  39. edsl/inference_services/AvailableModelFetcher.py +215 -0
  40. edsl/inference_services/AwsBedrock.py +0 -2
  41. edsl/inference_services/AzureAI.py +0 -2
  42. edsl/inference_services/GoogleService.py +7 -12
  43. edsl/inference_services/InferenceServiceABC.py +18 -85
  44. edsl/inference_services/InferenceServicesCollection.py +120 -79
  45. edsl/inference_services/MistralAIService.py +0 -3
  46. edsl/inference_services/OpenAIService.py +47 -35
  47. edsl/inference_services/PerplexityService.py +0 -3
  48. edsl/inference_services/ServiceAvailability.py +135 -0
  49. edsl/inference_services/TestService.py +11 -10
  50. edsl/inference_services/TogetherAIService.py +5 -3
  51. edsl/inference_services/data_structures.py +134 -0
  52. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  53. edsl/jobs/Answers.py +1 -14
  54. edsl/jobs/FetchInvigilator.py +47 -0
  55. edsl/jobs/InterviewTaskManager.py +98 -0
  56. edsl/jobs/InterviewsConstructor.py +50 -0
  57. edsl/jobs/Jobs.py +356 -431
  58. edsl/jobs/JobsChecks.py +35 -10
  59. edsl/jobs/JobsComponentConstructor.py +189 -0
  60. edsl/jobs/JobsPrompts.py +6 -4
  61. edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
  62. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  63. edsl/jobs/RequestTokenEstimator.py +30 -0
  64. edsl/jobs/async_interview_runner.py +138 -0
  65. edsl/jobs/buckets/BucketCollection.py +44 -3
  66. edsl/jobs/buckets/TokenBucket.py +53 -21
  67. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  68. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  69. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  70. edsl/jobs/data_structures.py +120 -0
  71. edsl/jobs/decorators.py +35 -0
  72. edsl/jobs/interviews/Interview.py +143 -408
  73. edsl/jobs/jobs_status_enums.py +9 -0
  74. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  75. edsl/jobs/results_exceptions_handler.py +98 -0
  76. edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
  77. edsl/jobs/runners/JobsRunnerStatus.py +133 -165
  78. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  79. edsl/jobs/tasks/TaskHistory.py +38 -18
  80. edsl/jobs/tasks/task_status_enum.py +0 -2
  81. edsl/language_models/ComputeCost.py +63 -0
  82. edsl/language_models/LanguageModel.py +194 -236
  83. edsl/language_models/ModelList.py +28 -19
  84. edsl/language_models/PriceManager.py +127 -0
  85. edsl/language_models/RawResponseHandler.py +106 -0
  86. edsl/language_models/ServiceDataSources.py +0 -0
  87. edsl/language_models/__init__.py +1 -2
  88. edsl/language_models/key_management/KeyLookup.py +63 -0
  89. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  90. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  91. edsl/language_models/key_management/__init__.py +0 -0
  92. edsl/language_models/key_management/models.py +131 -0
  93. edsl/language_models/model.py +256 -0
  94. edsl/language_models/repair.py +2 -2
  95. edsl/language_models/utilities.py +5 -4
  96. edsl/notebooks/Notebook.py +19 -14
  97. edsl/notebooks/NotebookToLaTeX.py +142 -0
  98. edsl/prompts/Prompt.py +29 -39
  99. edsl/questions/ExceptionExplainer.py +77 -0
  100. edsl/questions/HTMLQuestion.py +103 -0
  101. edsl/questions/QuestionBase.py +68 -214
  102. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  103. edsl/questions/QuestionBudget.py +1 -1
  104. edsl/questions/QuestionCheckBox.py +3 -3
  105. edsl/questions/QuestionExtract.py +5 -7
  106. edsl/questions/QuestionFreeText.py +2 -3
  107. edsl/questions/QuestionList.py +10 -18
  108. edsl/questions/QuestionMatrix.py +265 -0
  109. edsl/questions/QuestionMultipleChoice.py +67 -23
  110. edsl/questions/QuestionNumerical.py +2 -4
  111. edsl/questions/QuestionRank.py +7 -17
  112. edsl/questions/SimpleAskMixin.py +4 -3
  113. edsl/questions/__init__.py +2 -1
  114. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
  115. edsl/questions/data_structures.py +20 -0
  116. edsl/questions/derived/QuestionLinearScale.py +6 -3
  117. edsl/questions/derived/QuestionTopK.py +1 -1
  118. edsl/questions/descriptors.py +17 -3
  119. edsl/questions/loop_processor.py +149 -0
  120. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
  121. edsl/questions/question_registry.py +1 -1
  122. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
  123. edsl/questions/response_validator_factory.py +34 -0
  124. edsl/questions/templates/matrix/__init__.py +1 -0
  125. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  126. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  127. edsl/results/CSSParameterizer.py +1 -1
  128. edsl/results/Dataset.py +170 -7
  129. edsl/results/DatasetExportMixin.py +168 -305
  130. edsl/results/DatasetTree.py +28 -8
  131. edsl/results/MarkdownToDocx.py +122 -0
  132. edsl/results/MarkdownToPDF.py +111 -0
  133. edsl/results/Result.py +298 -206
  134. edsl/results/Results.py +149 -131
  135. edsl/results/ResultsExportMixin.py +2 -0
  136. edsl/results/TableDisplay.py +98 -171
  137. edsl/results/TextEditor.py +50 -0
  138. edsl/results/__init__.py +1 -1
  139. edsl/results/file_exports.py +252 -0
  140. edsl/results/{Selector.py → results_selector.py} +23 -13
  141. edsl/results/smart_objects.py +96 -0
  142. edsl/results/table_data_class.py +12 -0
  143. edsl/results/table_renderers.py +118 -0
  144. edsl/scenarios/ConstructDownloadLink.py +109 -0
  145. edsl/scenarios/DocumentChunker.py +102 -0
  146. edsl/scenarios/DocxScenario.py +16 -0
  147. edsl/scenarios/FileStore.py +150 -239
  148. edsl/scenarios/PdfExtractor.py +40 -0
  149. edsl/scenarios/Scenario.py +90 -193
  150. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  151. edsl/scenarios/ScenarioList.py +415 -244
  152. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  153. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  154. edsl/scenarios/__init__.py +1 -2
  155. edsl/scenarios/directory_scanner.py +96 -0
  156. edsl/scenarios/file_methods.py +85 -0
  157. edsl/scenarios/handlers/__init__.py +13 -0
  158. edsl/scenarios/handlers/csv.py +49 -0
  159. edsl/scenarios/handlers/docx.py +76 -0
  160. edsl/scenarios/handlers/html.py +37 -0
  161. edsl/scenarios/handlers/json.py +111 -0
  162. edsl/scenarios/handlers/latex.py +5 -0
  163. edsl/scenarios/handlers/md.py +51 -0
  164. edsl/scenarios/handlers/pdf.py +68 -0
  165. edsl/scenarios/handlers/png.py +39 -0
  166. edsl/scenarios/handlers/pptx.py +105 -0
  167. edsl/scenarios/handlers/py.py +294 -0
  168. edsl/scenarios/handlers/sql.py +313 -0
  169. edsl/scenarios/handlers/sqlite.py +149 -0
  170. edsl/scenarios/handlers/txt.py +33 -0
  171. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
  172. edsl/scenarios/scenario_selector.py +156 -0
  173. edsl/study/ObjectEntry.py +1 -1
  174. edsl/study/SnapShot.py +1 -1
  175. edsl/study/Study.py +5 -12
  176. edsl/surveys/ConstructDAG.py +92 -0
  177. edsl/surveys/EditSurvey.py +221 -0
  178. edsl/surveys/InstructionHandler.py +100 -0
  179. edsl/surveys/MemoryManagement.py +72 -0
  180. edsl/surveys/Rule.py +5 -4
  181. edsl/surveys/RuleCollection.py +25 -27
  182. edsl/surveys/RuleManager.py +172 -0
  183. edsl/surveys/Simulator.py +75 -0
  184. edsl/surveys/Survey.py +270 -791
  185. edsl/surveys/SurveyCSS.py +20 -8
  186. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  187. edsl/surveys/SurveyToApp.py +141 -0
  188. edsl/surveys/__init__.py +4 -2
  189. edsl/surveys/descriptors.py +6 -2
  190. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  191. edsl/surveys/instructions/Instruction.py +4 -13
  192. edsl/surveys/instructions/InstructionCollection.py +11 -6
  193. edsl/templates/error_reporting/interview_details.html +1 -1
  194. edsl/templates/error_reporting/report.html +1 -1
  195. edsl/tools/plotting.py +1 -1
  196. edsl/utilities/PrettyList.py +56 -0
  197. edsl/utilities/is_notebook.py +18 -0
  198. edsl/utilities/is_valid_variable_name.py +11 -0
  199. edsl/utilities/remove_edsl_version.py +24 -0
  200. edsl/utilities/utilities.py +35 -23
  201. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
  202. edsl-0.1.39.dist-info/RECORD +358 -0
  203. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  204. edsl/language_models/KeyLookup.py +0 -30
  205. edsl/language_models/registry.py +0 -190
  206. edsl/language_models/unused/ReplicateBase.py +0 -83
  207. edsl/results/ResultsDBMixin.py +0 -238
  208. edsl-0.1.38.dev4.dist-info/RECORD +0 -277
  209. /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
  210. /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
  211. /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
  212. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/LICENSE +0 -0
edsl/results/Results.py CHANGED
@@ -9,13 +9,9 @@ import random
9
9
  from collections import UserList, defaultdict
10
10
  from typing import Optional, Callable, Any, Type, Union, List, TYPE_CHECKING
11
11
 
12
- if TYPE_CHECKING:
13
- from edsl import Survey, Cache, AgentList, ModelList, ScenarioList
14
- from edsl.results.Result import Result
15
- from edsl.jobs.tasks.TaskHistory import TaskHistory
16
-
17
- from simpleeval import EvalWithCompoundTypes
12
+ from bisect import bisect_left
18
13
 
14
+ from edsl.Base import Base
19
15
  from edsl.exceptions.results import (
20
16
  ResultsError,
21
17
  ResultsBadMutationstringError,
@@ -26,25 +22,27 @@ from edsl.exceptions.results import (
26
22
  ResultsDeserializationError,
27
23
  )
28
24
 
25
+ if TYPE_CHECKING:
26
+ from edsl.surveys.Survey import Survey
27
+ from edsl.data.Cache import Cache
28
+ from edsl.agents.AgentList import AgentList
29
+ from edsl.language_models.model import Model
30
+ from edsl.scenarios.ScenarioList import ScenarioList
31
+ from edsl.results.Result import Result
32
+ from edsl.jobs.tasks.TaskHistory import TaskHistory
33
+ from edsl.language_models.ModelList import ModelList
34
+ from simpleeval import EvalWithCompoundTypes
35
+
29
36
  from edsl.results.ResultsExportMixin import ResultsExportMixin
30
- from edsl.results.ResultsToolsMixin import ResultsToolsMixin
31
- from edsl.results.ResultsDBMixin import ResultsDBMixin
32
37
  from edsl.results.ResultsGGMixin import ResultsGGMixin
33
- from edsl.results.ResultsFetchMixin import ResultsFetchMixin
34
-
35
- from edsl.utilities.decorators import remove_edsl_version
36
- from edsl.utilities.utilities import dict_hash
37
-
38
-
39
- from edsl.Base import Base
38
+ from edsl.results.results_fetch_mixin import ResultsFetchMixin
39
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
40
40
 
41
41
 
42
42
  class Mixins(
43
43
  ResultsExportMixin,
44
- ResultsDBMixin,
45
44
  ResultsFetchMixin,
46
45
  ResultsGGMixin,
47
- ResultsToolsMixin,
48
46
  ):
49
47
  def long(self):
50
48
  return self.table().long()
@@ -91,6 +89,7 @@ class Results(UserList, Mixins, Base):
91
89
  "question_type",
92
90
  "comment",
93
91
  "generated_tokens",
92
+ "cache_used",
94
93
  ]
95
94
 
96
95
  def __init__(
@@ -129,22 +128,43 @@ class Results(UserList, Mixins, Base):
129
128
  def _summary(self) -> dict:
130
129
  import reprlib
131
130
 
132
- # import yaml
133
-
134
131
  d = {
135
- "EDSL Class": "Results",
136
- # "docs_url": self.__documentation__,
137
- "# of agents": len(set(self.agents)),
138
- "# of distinct models": len(set(self.models)),
139
- "# of observations": len(self),
140
- "# Scenarios": len(set(self.scenarios)),
141
- "Survey Length (# questions)": len(self.survey),
132
+ "observations": len(self),
133
+ "agents": len(set(self.agents)),
134
+ "models": len(set(self.models)),
135
+ "scenarios": len(set(self.scenarios)),
136
+ "questions": len(self.survey),
142
137
  "Survey question names": reprlib.repr(self.survey.question_names),
143
- "Object hash": hash(self),
144
138
  }
145
139
  return d
146
140
 
147
- def compute_job_cost(self, include_cached_responses_in_cost=False) -> float:
141
+ def insert(self, item):
142
+ item_order = getattr(item, "order", None)
143
+ if item_order is not None:
144
+ # Get list of orders, putting None at the end
145
+ orders = [getattr(x, "order", None) for x in self]
146
+ # Filter to just the non-None orders for bisect
147
+ sorted_orders = [x for x in orders if x is not None]
148
+ if sorted_orders:
149
+ index = bisect_left(sorted_orders, item_order)
150
+ # Account for any None values before this position
151
+ index += orders[:index].count(None)
152
+ else:
153
+ # If no sorted items yet, insert before any unordered items
154
+ index = 0
155
+ self.data.insert(index, item)
156
+ else:
157
+ # No order - append to end
158
+ self.data.append(item)
159
+
160
+ def append(self, item):
161
+ self.insert(item)
162
+
163
+ def extend(self, other):
164
+ for item in other:
165
+ self.insert(item)
166
+
167
+ def compute_job_cost(self, include_cached_responses_in_cost: bool = False) -> float:
148
168
  """
149
169
  Computes the cost of a completed job in USD.
150
170
  """
@@ -258,24 +278,6 @@ class Results(UserList, Mixins, Base):
258
278
 
259
279
  raise TypeError("Invalid argument type")
260
280
 
261
- def _update_results(self) -> None:
262
- from edsl import Agent, Scenario
263
- from edsl.language_models import LanguageModel
264
- from edsl.results import Result
265
-
266
- if self._job_uuid and len(self.data) < self._total_results:
267
- results = [
268
- Result(
269
- agent=Agent.from_dict(json.loads(r.agent)),
270
- scenario=Scenario.from_dict(json.loads(r.scenario)),
271
- model=LanguageModel.from_dict(json.loads(r.model)),
272
- iteration=1,
273
- answer=json.loads(r.answer),
274
- )
275
- for r in CRUD.read_results(self._job_uuid)
276
- ]
277
- self.data = results
278
-
279
281
  def __add__(self, other: Results) -> Results:
280
282
  """Add two Results objects together.
281
283
  They must have the same survey and created columns.
@@ -303,13 +305,10 @@ class Results(UserList, Mixins, Base):
303
305
  )
304
306
 
305
307
  def __repr__(self) -> str:
306
- import reprlib
307
-
308
- return f"Results(data = {reprlib.repr(self.data)}, survey = {repr(self.survey)}, created_columns = {self.created_columns})"
308
+ return f"Results(data = {self.data}, survey = {repr(self.survey)}, created_columns = {self.created_columns})"
309
309
 
310
310
  def table(
311
311
  self,
312
- # selector_string: Optional[str] = "*.*",
313
312
  *fields,
314
313
  tablefmt: Optional[str] = None,
315
314
  pretty_labels: Optional[dict] = None,
@@ -345,28 +344,14 @@ class Results(UserList, Mixins, Base):
345
344
  print_parameters=print_parameters,
346
345
  )
347
346
  )
348
- # return (
349
- # self.select(f"{selector_string}")
350
- # .to_scenario_list()
351
- # .table(*fields, tablefmt=tablefmt)
352
- # )
353
-
354
- def _repr_html_(self) -> str:
355
- d = self._summary()
356
- from edsl import Scenario
357
-
358
- footer = f"<a href={self.__documentation__}>(docs)</a>"
359
-
360
- s = Scenario(d)
361
- td = s.to_dataset().table(tablefmt="html")
362
- return td._repr_html_() + footer
363
347
 
364
348
  def to_dict(
365
349
  self,
366
- sort=False,
367
- add_edsl_version=False,
368
- include_cache=False,
369
- include_task_history=False,
350
+ sort: bool = False,
351
+ add_edsl_version: bool = False,
352
+ include_cache: bool = False,
353
+ include_task_history: bool = False,
354
+ include_cache_info: bool = True,
370
355
  ) -> dict[str, Any]:
371
356
  from edsl.data.Cache import Cache
372
357
 
@@ -377,7 +362,11 @@ class Results(UserList, Mixins, Base):
377
362
 
378
363
  d = {
379
364
  "data": [
380
- result.to_dict(add_edsl_version=add_edsl_version) for result in data
365
+ result.to_dict(
366
+ add_edsl_version=add_edsl_version,
367
+ include_cache_info=include_cache_info,
368
+ )
369
+ for result in data
381
370
  ],
382
371
  "survey": self.survey.to_dict(add_edsl_version=add_edsl_version),
383
372
  "created_columns": self.created_columns,
@@ -404,7 +393,7 @@ class Results(UserList, Mixins, Base):
404
393
 
405
394
  return d
406
395
 
407
- def compare(self, other_results):
396
+ def compare(self, other_results: Results) -> dict:
408
397
  """
409
398
  Compare two Results objects and return the differences.
410
399
  """
@@ -422,11 +411,15 @@ class Results(UserList, Mixins, Base):
422
411
  }
423
412
 
424
413
  @property
425
- def has_unfixed_exceptions(self):
414
+ def has_unfixed_exceptions(self) -> bool:
426
415
  return self.task_history.has_unfixed_exceptions
427
416
 
428
417
  def __hash__(self) -> int:
429
- return dict_hash(self.to_dict(sort=True, add_edsl_version=False))
418
+ from edsl.utilities.utilities import dict_hash
419
+
420
+ return dict_hash(
421
+ self.to_dict(sort=True, add_edsl_version=False, include_cache_info=False)
422
+ )
430
423
 
431
424
  @property
432
425
  def hashes(self) -> set:
@@ -472,32 +465,35 @@ class Results(UserList, Mixins, Base):
472
465
  >>> r == r2
473
466
  True
474
467
  """
475
- from edsl import Survey, Cache
468
+ from edsl.surveys.Survey import Survey
469
+ from edsl.data.Cache import Cache
476
470
  from edsl.results.Result import Result
477
471
  from edsl.jobs.tasks.TaskHistory import TaskHistory
472
+ from edsl.agents.Agent import Agent
473
+
474
+ survey = Survey.from_dict(data["survey"])
475
+ results_data = [Result.from_dict(r) for r in data["data"]]
476
+ created_columns = data.get("created_columns", None)
477
+ cache = Cache.from_dict(data.get("cache")) if "cache" in data else Cache()
478
+ task_history = (
479
+ TaskHistory.from_dict(data.get("task_history"))
480
+ if "task_history" in data
481
+ else TaskHistory(interviews=[])
482
+ )
483
+ params = {
484
+ "survey": survey,
485
+ "data": results_data,
486
+ "created_columns": created_columns,
487
+ "cache": cache,
488
+ "task_history": task_history,
489
+ }
478
490
 
479
491
  try:
480
- results = cls(
481
- survey=Survey.from_dict(data["survey"]),
482
- data=[Result.from_dict(r) for r in data["data"]],
483
- created_columns=data.get("created_columns", None),
484
- cache=(
485
- Cache.from_dict(data.get("cache")) if "cache" in data else Cache()
486
- ),
487
- task_history=(
488
- TaskHistory.from_dict(data.get("task_history"))
489
- if "task_history" in data
490
- else TaskHistory(interviews=[])
491
- ),
492
- )
492
+ results = cls(**params)
493
493
  except Exception as e:
494
494
  raise ResultsDeserializationError(f"Error in Results.from_dict: {e}")
495
495
  return results
496
496
 
497
- ######################
498
- ## Convenience methods
499
- ## & Report methods
500
- ######################
501
497
  @property
502
498
  def _key_to_data_type(self) -> dict[str, str]:
503
499
  """
@@ -544,10 +540,12 @@ class Results(UserList, Mixins, Base):
544
540
 
545
541
  >>> r = Results.example()
546
542
  >>> r.columns
547
- ['agent.agent_instruction', ...]
543
+ ['agent.agent_index', ...]
548
544
  """
549
545
  column_names = [f"{v}.{k}" for k, v in self._key_to_data_type.items()]
550
- return sorted(column_names)
546
+ from edsl.utilities.PrettyList import PrettyList
547
+
548
+ return PrettyList(sorted(column_names))
551
549
 
552
550
  @property
553
551
  def answer_keys(self) -> dict[str, str]:
@@ -567,7 +565,7 @@ class Results(UserList, Mixins, Base):
567
565
  answer_keys = self._data_type_to_keys["answer"]
568
566
  answer_keys = {k for k in answer_keys if "_comment" not in k}
569
567
  questions_text = [
570
- self.survey.get_question(k).question_text for k in answer_keys
568
+ self.survey._get_question_by_name(k).question_text for k in answer_keys
571
569
  ]
572
570
  short_question_text = [shorten_string(q, 80) for q in questions_text]
573
571
  initial_dict = dict(zip(answer_keys, short_question_text))
@@ -584,7 +582,7 @@ class Results(UserList, Mixins, Base):
584
582
  >>> r.agents
585
583
  AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'}), Agent(traits = {'status': 'Sad'})])
586
584
  """
587
- from edsl import AgentList
585
+ from edsl.agents.AgentList import AgentList
588
586
 
589
587
  return AgentList([r.agent for r in self.data])
590
588
 
@@ -598,10 +596,13 @@ class Results(UserList, Mixins, Base):
598
596
  >>> r.models[0]
599
597
  Model(model_name = ...)
600
598
  """
601
- from edsl import ModelList
599
+ from edsl.language_models.ModelList import ModelList
602
600
 
603
601
  return ModelList([r.model for r in self.data])
604
602
 
603
+ def __eq__(self, other):
604
+ return hash(self) == hash(other)
605
+
605
606
  @property
606
607
  def scenarios(self) -> ScenarioList:
607
608
  """Return a list of all of the scenarios in the Results.
@@ -610,9 +611,9 @@ class Results(UserList, Mixins, Base):
610
611
 
611
612
  >>> r = Results.example()
612
613
  >>> r.scenarios
613
- ScenarioList([Scenario({'period': 'morning'}), Scenario({'period': 'afternoon'}), Scenario({'period': 'morning'}), Scenario({'period': 'afternoon'})])
614
+ ScenarioList([Scenario({'period': 'morning', 'scenario_index': 0}), Scenario({'period': 'afternoon', 'scenario_index': 1}), Scenario({'period': 'morning', 'scenario_index': 0}), Scenario({'period': 'afternoon', 'scenario_index': 1})])
614
615
  """
615
- from edsl import ScenarioList
616
+ from edsl.scenarios.ScenarioList import ScenarioList
616
617
 
617
618
  return ScenarioList([r.scenario for r in self.data])
618
619
 
@@ -624,7 +625,7 @@ class Results(UserList, Mixins, Base):
624
625
 
625
626
  >>> r = Results.example()
626
627
  >>> r.agent_keys
627
- ['agent_instruction', 'agent_name', 'status']
628
+ ['agent_index', 'agent_instruction', 'agent_name', 'status']
628
629
  """
629
630
  return sorted(self._data_type_to_keys["agent"])
630
631
 
@@ -634,7 +635,7 @@ class Results(UserList, Mixins, Base):
634
635
 
635
636
  >>> r = Results.example()
636
637
  >>> r.model_keys
637
- ['frequency_penalty', 'logprobs', 'max_tokens', 'model', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
638
+ ['frequency_penalty', 'logprobs', 'max_tokens', 'model', 'model_index', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
638
639
  """
639
640
  return sorted(self._data_type_to_keys["model"])
640
641
 
@@ -644,7 +645,7 @@ class Results(UserList, Mixins, Base):
644
645
 
645
646
  >>> r = Results.example()
646
647
  >>> r.scenario_keys
647
- ['period']
648
+ ['period', 'scenario_index']
648
649
  """
649
650
  return sorted(self._data_type_to_keys["scenario"])
650
651
 
@@ -670,7 +671,7 @@ class Results(UserList, Mixins, Base):
670
671
 
671
672
  >>> r = Results.example()
672
673
  >>> r.all_keys
673
- ['agent_instruction', 'agent_name', 'frequency_penalty', 'how_feeling', 'how_feeling_yesterday', 'logprobs', 'max_tokens', 'model', 'period', 'presence_penalty', 'status', 'temperature', 'top_logprobs', 'top_p']
674
+ ['agent_index', ...]
674
675
  """
675
676
  answer_keys = set(self.answer_keys)
676
677
  all_keys = (
@@ -691,13 +692,19 @@ class Results(UserList, Mixins, Base):
691
692
  """
692
693
  return self.data[0]
693
694
 
694
- def answer_truncate(self, column: str, top_n=5, new_var_name=None) -> Results:
695
+ def answer_truncate(
696
+ self, column: str, top_n: int = 5, new_var_name: str = None
697
+ ) -> Results:
695
698
  """Create a new variable that truncates the answers to the top_n.
696
699
 
697
700
  :param column: The column to truncate.
698
701
  :param top_n: The number of top answers to keep.
699
702
  :param new_var_name: The name of the new variable. If None, it is the original name + '_truncated'.
700
703
 
704
+ Example:
705
+ >>> r = Results.example()
706
+ >>> r.answer_truncate('how_feeling', top_n = 2).select('how_feeling', 'how_feeling_truncated')
707
+ Dataset([{'answer.how_feeling': ['OK', 'Great', 'Terrible', 'OK']}, {'answer.how_feeling_truncated': ['Other', 'Other', 'Other', 'Other']}])
701
708
 
702
709
 
703
710
  """
@@ -777,7 +784,7 @@ class Results(UserList, Mixins, Base):
777
784
  @staticmethod
778
785
  def _create_evaluator(
779
786
  result: Result, functions_dict: Optional[dict] = None
780
- ) -> EvalWithCompoundTypes:
787
+ ) -> "EvalWithCompoundTypes":
781
788
  """Create an evaluator for the expression.
782
789
 
783
790
  >>> from unittest.mock import Mock
@@ -800,6 +807,8 @@ class Results(UserList, Mixins, Base):
800
807
  ...
801
808
  simpleeval.NameNotDefined: 'how_feeling' is not defined for expression 'how_feeling== 'OK''
802
809
  """
810
+ from simpleeval import EvalWithCompoundTypes
811
+
803
812
  if functions_dict is None:
804
813
  functions_dict = {}
805
814
  evaluator = EvalWithCompoundTypes(
@@ -858,6 +867,26 @@ class Results(UserList, Mixins, Base):
858
867
  created_columns=self.created_columns + [var_name],
859
868
  )
860
869
 
870
+ def add_column(self, column_name: str, values: list) -> Results:
871
+ """Adds columns to Results
872
+
873
+ >>> r = Results.example()
874
+ >>> r.add_column('a', [1,2,3, 4]).select('a')
875
+ Dataset([{'answer.a': [1, 2, 3, 4]}])
876
+ """
877
+
878
+ assert len(values) == len(
879
+ self.data
880
+ ), "The number of values must match the number of results."
881
+ new_results = self.data.copy()
882
+ for i, result in enumerate(new_results):
883
+ result["answer"][column_name] = values[i]
884
+ return Results(
885
+ survey=self.survey,
886
+ data=new_results,
887
+ created_columns=self.created_columns + [column_name],
888
+ )
889
+
861
890
  def rename(self, old_name: str, new_name: str) -> Results:
862
891
  """Rename an answer column in a Results object.
863
892
 
@@ -896,7 +925,7 @@ class Results(UserList, Mixins, Base):
896
925
  n: Optional[int] = None,
897
926
  frac: Optional[float] = None,
898
927
  with_replacement: bool = True,
899
- seed: Optional[str] = "edsl",
928
+ seed: Optional[str] = None,
900
929
  ) -> Results:
901
930
  """Sample the results.
902
931
 
@@ -911,7 +940,7 @@ class Results(UserList, Mixins, Base):
911
940
  >>> len(r.sample(2))
912
941
  2
913
942
  """
914
- if seed != "edsl":
943
+ if seed:
915
944
  random.seed(seed)
916
945
 
917
946
  if n is None and frac is None:
@@ -949,7 +978,7 @@ class Results(UserList, Mixins, Base):
949
978
  Dataset([{'answer.how_feeling_yesterday': ['Great', 'Good', 'OK', 'Terrible']}])
950
979
  """
951
980
 
952
- from edsl.results.Selector import Selector
981
+ from edsl.results.results_selector import Selector
953
982
 
954
983
  if len(self) == 0:
955
984
  raise Exception("No data to select from---the Results object is empty.")
@@ -964,6 +993,7 @@ class Results(UserList, Mixins, Base):
964
993
  return selector.select(*columns)
965
994
 
966
995
  def sort_by(self, *columns: str, reverse: bool = False) -> Results:
996
+ """Sort the results by one or more columns."""
967
997
  import warnings
968
998
 
969
999
  warnings.warn(
@@ -972,6 +1002,7 @@ class Results(UserList, Mixins, Base):
972
1002
  return self.order_by(*columns, reverse=reverse)
973
1003
 
974
1004
  def _parse_column(self, column: str) -> tuple[str, str]:
1005
+ """Parse a column name into a data type and key."""
975
1006
  if "." in column:
976
1007
  return column.split(".")
977
1008
  return self._key_to_data_type[column], column
@@ -987,20 +1018,12 @@ class Results(UserList, Mixins, Base):
987
1018
  Example:
988
1019
 
989
1020
  >>> r = Results.example()
990
- >>> r.sort_by('how_feeling', reverse=False).select('how_feeling').print()
991
- answer.how_feeling
992
- --------------------
993
- Great
994
- OK
995
- OK
996
- Terrible
997
- >>> r.sort_by('how_feeling', reverse=True).select('how_feeling').print()
998
- answer.how_feeling
999
- --------------------
1000
- Terrible
1001
- OK
1002
- OK
1003
- Great
1021
+ >>> r.sort_by('how_feeling', reverse=False).select('how_feeling')
1022
+ Dataset([{'answer.how_feeling': ['Great', 'OK', 'OK', 'Terrible']}])
1023
+
1024
+ >>> r.sort_by('how_feeling', reverse=True).select('how_feeling')
1025
+ Dataset([{'answer.how_feeling': ['Terrible', 'OK', 'OK', 'Great']}])
1026
+
1004
1027
  """
1005
1028
 
1006
1029
  def to_numeric_if_possible(v):
@@ -1032,24 +1055,19 @@ class Results(UserList, Mixins, Base):
1032
1055
  Example usage: Create an example `Results` instance and apply filters to it:
1033
1056
 
1034
1057
  >>> r = Results.example()
1035
- >>> r.filter("how_feeling == 'Great'").select('how_feeling').print()
1036
- answer.how_feeling
1037
- --------------------
1038
- Great
1058
+ >>> r.filter("how_feeling == 'Great'").select('how_feeling')
1059
+ Dataset([{'answer.how_feeling': ['Great']}])
1039
1060
 
1040
1061
  Example usage: Using an OR operator in the filter expression.
1041
1062
 
1042
- >>> r = Results.example().filter("how_feeling = 'Great'").select('how_feeling').print()
1063
+ >>> r = Results.example().filter("how_feeling = 'Great'").select('how_feeling')
1043
1064
  Traceback (most recent call last):
1044
1065
  ...
1045
1066
  edsl.exceptions.results.ResultsFilterError: You must use '==' instead of '=' in the filter expression.
1046
1067
  ...
1047
1068
 
1048
- >>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling').print()
1049
- answer.how_feeling
1050
- --------------------
1051
- Great
1052
- Terrible
1069
+ >>> r.filter("how_feeling == 'Great' or how_feeling == 'Terrible'").select('how_feeling')
1070
+ Dataset([{'answer.how_feeling': ['Great', 'Terrible']}])
1053
1071
  """
1054
1072
 
1055
1073
  def has_single_equals(string):
@@ -14,6 +14,8 @@ def to_dataset(func):
14
14
  """Return the function with the Results object converted to a Dataset object."""
15
15
  if self.__class__.__name__ == "Results":
16
16
  return func(self.select(), *args, **kwargs)
17
+ elif self.__class__.__name__ == "AgentList":
18
+ return func(self.to_dataset(), *args, **kwargs)
17
19
  else:
18
20
  return func(self, *args, **kwargs)
19
21