edsl 0.1.27.dev2__py3-none-any.whl → 0.1.29__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 (119) hide show
  1. edsl/Base.py +107 -30
  2. edsl/BaseDiff.py +260 -0
  3. edsl/__init__.py +25 -21
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +103 -46
  6. edsl/agents/AgentList.py +97 -13
  7. edsl/agents/Invigilator.py +23 -10
  8. edsl/agents/InvigilatorBase.py +19 -14
  9. edsl/agents/PromptConstructionMixin.py +342 -100
  10. edsl/agents/descriptors.py +5 -2
  11. edsl/base/Base.py +289 -0
  12. edsl/config.py +2 -1
  13. edsl/conjure/AgentConstructionMixin.py +152 -0
  14. edsl/conjure/Conjure.py +56 -0
  15. edsl/conjure/InputData.py +659 -0
  16. edsl/conjure/InputDataCSV.py +48 -0
  17. edsl/conjure/InputDataMixinQuestionStats.py +182 -0
  18. edsl/conjure/InputDataPyRead.py +91 -0
  19. edsl/conjure/InputDataSPSS.py +8 -0
  20. edsl/conjure/InputDataStata.py +8 -0
  21. edsl/conjure/QuestionOptionMixin.py +76 -0
  22. edsl/conjure/QuestionTypeMixin.py +23 -0
  23. edsl/conjure/RawQuestion.py +65 -0
  24. edsl/conjure/SurveyResponses.py +7 -0
  25. edsl/conjure/__init__.py +9 -4
  26. edsl/conjure/examples/placeholder.txt +0 -0
  27. edsl/conjure/naming_utilities.py +263 -0
  28. edsl/conjure/utilities.py +165 -28
  29. edsl/conversation/Conversation.py +238 -0
  30. edsl/conversation/car_buying.py +58 -0
  31. edsl/conversation/mug_negotiation.py +81 -0
  32. edsl/conversation/next_speaker_utilities.py +93 -0
  33. edsl/coop/coop.py +337 -121
  34. edsl/coop/utils.py +56 -70
  35. edsl/data/Cache.py +74 -22
  36. edsl/data/CacheHandler.py +10 -9
  37. edsl/data/SQLiteDict.py +11 -3
  38. edsl/inference_services/AnthropicService.py +1 -0
  39. edsl/inference_services/DeepInfraService.py +20 -13
  40. edsl/inference_services/GoogleService.py +7 -1
  41. edsl/inference_services/InferenceServicesCollection.py +33 -7
  42. edsl/inference_services/OpenAIService.py +17 -10
  43. edsl/inference_services/models_available_cache.py +69 -0
  44. edsl/inference_services/rate_limits_cache.py +25 -0
  45. edsl/inference_services/write_available.py +10 -0
  46. edsl/jobs/Answers.py +15 -1
  47. edsl/jobs/Jobs.py +322 -73
  48. edsl/jobs/buckets/BucketCollection.py +9 -3
  49. edsl/jobs/buckets/ModelBuckets.py +4 -2
  50. edsl/jobs/buckets/TokenBucket.py +1 -2
  51. edsl/jobs/interviews/Interview.py +7 -10
  52. edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
  53. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +39 -20
  54. edsl/jobs/interviews/retry_management.py +4 -4
  55. edsl/jobs/runners/JobsRunnerAsyncio.py +103 -65
  56. edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
  57. edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
  58. edsl/jobs/tasks/TaskHistory.py +4 -3
  59. edsl/language_models/LanguageModel.py +42 -55
  60. edsl/language_models/ModelList.py +96 -0
  61. edsl/language_models/registry.py +14 -0
  62. edsl/language_models/repair.py +97 -25
  63. edsl/notebooks/Notebook.py +157 -32
  64. edsl/prompts/Prompt.py +31 -19
  65. edsl/questions/QuestionBase.py +145 -23
  66. edsl/questions/QuestionBudget.py +5 -6
  67. edsl/questions/QuestionCheckBox.py +7 -3
  68. edsl/questions/QuestionExtract.py +5 -3
  69. edsl/questions/QuestionFreeText.py +3 -3
  70. edsl/questions/QuestionFunctional.py +0 -3
  71. edsl/questions/QuestionList.py +3 -4
  72. edsl/questions/QuestionMultipleChoice.py +16 -8
  73. edsl/questions/QuestionNumerical.py +4 -3
  74. edsl/questions/QuestionRank.py +5 -3
  75. edsl/questions/__init__.py +4 -3
  76. edsl/questions/descriptors.py +9 -4
  77. edsl/questions/question_registry.py +27 -31
  78. edsl/questions/settings.py +1 -1
  79. edsl/results/Dataset.py +31 -0
  80. edsl/results/DatasetExportMixin.py +493 -0
  81. edsl/results/Result.py +42 -82
  82. edsl/results/Results.py +178 -66
  83. edsl/results/ResultsDBMixin.py +10 -9
  84. edsl/results/ResultsExportMixin.py +23 -507
  85. edsl/results/ResultsGGMixin.py +3 -3
  86. edsl/results/ResultsToolsMixin.py +9 -9
  87. edsl/scenarios/FileStore.py +140 -0
  88. edsl/scenarios/Scenario.py +59 -6
  89. edsl/scenarios/ScenarioList.py +138 -52
  90. edsl/scenarios/ScenarioListExportMixin.py +32 -0
  91. edsl/scenarios/ScenarioListPdfMixin.py +2 -1
  92. edsl/scenarios/__init__.py +1 -0
  93. edsl/study/ObjectEntry.py +173 -0
  94. edsl/study/ProofOfWork.py +113 -0
  95. edsl/study/SnapShot.py +73 -0
  96. edsl/study/Study.py +498 -0
  97. edsl/study/__init__.py +4 -0
  98. edsl/surveys/MemoryPlan.py +11 -4
  99. edsl/surveys/Survey.py +124 -37
  100. edsl/surveys/SurveyExportMixin.py +25 -5
  101. edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
  102. edsl/tools/plotting.py +4 -2
  103. edsl/utilities/__init__.py +21 -20
  104. edsl/utilities/gcp_bucket/__init__.py +0 -0
  105. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  106. edsl/utilities/gcp_bucket/simple_example.py +9 -0
  107. edsl/utilities/interface.py +90 -73
  108. edsl/utilities/repair_functions.py +28 -0
  109. edsl/utilities/utilities.py +59 -6
  110. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/METADATA +42 -15
  111. edsl-0.1.29.dist-info/RECORD +203 -0
  112. edsl/conjure/RawResponseColumn.py +0 -327
  113. edsl/conjure/SurveyBuilder.py +0 -308
  114. edsl/conjure/SurveyBuilderCSV.py +0 -78
  115. edsl/conjure/SurveyBuilderSPSS.py +0 -118
  116. edsl/data/RemoteDict.py +0 -103
  117. edsl-0.1.27.dev2.dist-info/RECORD +0 -172
  118. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/LICENSE +0 -0
  119. {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/WHEEL +0 -0
@@ -1,7 +1,9 @@
1
- """A Notebook is ...."""
1
+ """A Notebook is a utility class that allows you to easily share/pull ipynbs from Coop."""
2
2
 
3
+ import json
3
4
  from typing import Dict, List, Optional
4
- from rich.table import Table
5
+
6
+
5
7
  from edsl.Base import Base
6
8
  from edsl.utilities.decorators import (
7
9
  add_edsl_version,
@@ -14,43 +16,91 @@ class Notebook(Base):
14
16
  A Notebook is a utility class that allows you to easily share/pull ipynbs from Coop.
15
17
  """
16
18
 
17
- def __init__(self, data: Optional[Dict] = None, path: Optional[str] = None):
19
+ default_name = "notebook"
20
+
21
+ def __init__(
22
+ self,
23
+ data: Optional[Dict] = None,
24
+ path: Optional[str] = None,
25
+ name: Optional[str] = None,
26
+ ):
18
27
  """
19
28
  Initialize a new Notebook.
20
- - if a path is provided, try to load the notebook from that path.
21
- - if no path is provided, assume this code is run in a notebook and try to load the current notebook.
29
+
30
+ :param data: A dictionary representing the notebook data.
31
+ This dictionary must conform to the official Jupyter Notebook format, as defined by nbformat.
32
+ :param path: A filepath from which to load the notebook.
33
+ If no path is provided, assume this code is run in a notebook and try to load the current notebook from file.
34
+ :param name: A name for the Notebook.
22
35
  """
36
+ import nbformat
37
+
38
+ # Load current notebook path as fallback (VS Code only)
39
+ path = path or globals().get("__vsc_ipynb_file__")
23
40
  if data is not None:
41
+ nbformat.validate(data)
24
42
  self.data = data
25
43
  elif path is not None:
26
- # TO BE IMPLEMENTED
27
- # store in this var the data from the notebook
28
- self.data = {"some": "data"}
44
+ with open(path, mode="r", encoding="utf-8") as f:
45
+ data = nbformat.read(f, as_version=4)
46
+ self.data = json.loads(json.dumps(data))
29
47
  else:
30
- # TO BE IMPLEMENTED
31
- # 1. Check you're in a notebook ...
32
- # 2. get its info and store it in self.data
33
- self.data = {"some": "data"}
48
+ # TODO: Support for IDEs other than VSCode
49
+ raise NotImplementedError(
50
+ "Cannot create a notebook from within itself in this development environment"
51
+ )
34
52
 
35
- # deprioritize - perhaps add sanity check function
53
+ # TODO: perhaps add sanity check function
36
54
  # 1. could check if the notebook is a valid notebook
37
55
  # 2. could check notebook uses EDSL
38
56
  # ....
39
57
 
58
+ self.name = name or self.default_name
59
+
60
+ @classmethod
61
+ def from_script(cls, path: str, name: Optional[str] = None) -> "Notebook":
62
+ # Read the script file
63
+ with open(path, "r") as script_file:
64
+ script_content = script_file.read()
65
+
66
+ # Create a new Jupyter notebook
67
+ nb = nbformat.v4.new_notebook()
68
+
69
+ # Add the script content to the first cell
70
+ first_cell = nbformat.v4.new_code_cell(script_content)
71
+ nb.cells.append(first_cell)
72
+
73
+ # Create a Notebook instance with the notebook data
74
+ notebook_instance = cls(nb)
75
+
76
+ return notebook_instance
77
+
78
+ @classmethod
79
+ def from_current_script(cls) -> "Notebook":
80
+ import inspect
81
+ import os
82
+
83
+ # Get the path to the current file
84
+ current_frame = inspect.currentframe()
85
+ caller_frame = inspect.getouterframes(current_frame, 2)
86
+ current_file_path = os.path.abspath(caller_frame[1].filename)
87
+
88
+ # Use from_script to create the notebook
89
+ return cls.from_script(current_file_path)
90
+
40
91
  def __eq__(self, other):
41
92
  """
42
93
  Check if two Notebooks are equal.
94
+ This only checks the notebook data.
43
95
  """
44
96
  return self.data == other.data
45
97
 
46
98
  @add_edsl_version
47
99
  def to_dict(self) -> dict:
48
100
  """
49
- Convert to a dictionary.
50
- AF: here you will create a dict from which self.from_dict can recreate the object.
51
- AF: the decorator will add the edsl_version to the dict.
101
+ Convert a Notebook to a dictionary.
52
102
  """
53
- return {"data": self.data}
103
+ return {"name": self.name, "data": self.data}
54
104
 
55
105
  @classmethod
56
106
  @remove_edsl_version
@@ -58,12 +108,17 @@ class Notebook(Base):
58
108
  """
59
109
  Convert a dictionary representation of a Notebook to a Notebook object.
60
110
  """
61
- return cls(data=d["data"])
111
+ return cls(data=d["data"], name=d["name"])
112
+
113
+ def to_file(self, path: str):
114
+ """
115
+ Save the notebook at the specified filepath.
116
+ """
117
+ nbformat.write(nbformat.from_dict(self.data), fp=path)
62
118
 
63
119
  def print(self):
64
120
  """
65
121
  Print the notebook.
66
- AF: not sure how this should behave for a notebook
67
122
  """
68
123
  from rich import print_json
69
124
  import json
@@ -72,45 +127,115 @@ class Notebook(Base):
72
127
 
73
128
  def __repr__(self):
74
129
  """
75
- AF: not sure how this should behave for a notebook
130
+ Return representation of Notebook.
76
131
  """
77
- return f"Notebook({self.to_dict()})"
132
+ return f'Notebook(data={self.data}, name="""{self.name}""")'
78
133
 
79
134
  def _repr_html_(self):
80
135
  """
81
- AF: not sure how this should behave for a notebook
136
+ Return HTML representation of Notebook.
137
+ """
138
+ from nbconvert import HTMLExporter
139
+ import nbformat
140
+
141
+ notebook = nbformat.from_dict(self.data)
142
+ html_exporter = HTMLExporter(template_name="basic")
143
+ (body, _) = html_exporter.from_notebook_node(notebook)
144
+ return body
145
+
146
+ def _table(self) -> tuple[dict, list]:
147
+ """
148
+ Prepare generic table data.
82
149
  """
83
- from edsl.utilities.utilities import data_to_html
150
+ table_data = []
151
+
152
+ notebook_preview = ""
153
+ for cell in self.data["cells"]:
154
+ if "source" in cell:
155
+ notebook_preview += f"{cell['source']}\n"
156
+ if len(notebook_preview) > 1000:
157
+ notebook_preview = f"{notebook_preview[:1000]} [...]"
158
+ break
159
+ notebook_preview = notebook_preview.rstrip()
160
+
161
+ table_data.append(
162
+ {
163
+ "Attribute": "name",
164
+ "Value": repr(self.name),
165
+ }
166
+ )
167
+ table_data.append(
168
+ {
169
+ "Attribute": "notebook_preview",
170
+ "Value": notebook_preview,
171
+ }
172
+ )
84
173
 
85
- return data_to_html(self.to_dict())
174
+ column_names = ["Attribute", "Value"]
175
+ return table_data, column_names
86
176
 
87
177
  def rich_print(self) -> "Table":
88
178
  """
89
- AF: not sure how we should implement this for a notebook
179
+ Display a Notebook as a rich table.
90
180
  """
91
- pass
181
+ from rich.table import Table
182
+
183
+ table_data, column_names = self._table()
184
+ table = Table(title=f"{self.__class__.__name__} Attributes")
185
+ for column in column_names:
186
+ table.add_column(column, style="bold")
187
+
188
+ for row in table_data:
189
+ row_data = [row[column] for column in column_names]
190
+ table.add_row(*row_data)
191
+
192
+ return table
92
193
 
93
194
  @classmethod
94
195
  def example(cls) -> "Notebook":
95
196
  """
96
197
  Return an example Notebook.
97
- AF: add a simple custom example here
98
198
  """
99
- return cls(data={"some": "data"})
199
+ cells = [
200
+ {
201
+ "cell_type": "markdown",
202
+ "metadata": dict(),
203
+ "source": "# Test notebook",
204
+ },
205
+ {
206
+ "cell_type": "code",
207
+ "execution_count": 1,
208
+ "metadata": dict(),
209
+ "outputs": [
210
+ {
211
+ "name": "stdout",
212
+ "output_type": "stream",
213
+ "text": "Hello world!\n",
214
+ }
215
+ ],
216
+ "source": 'print("Hello world!")',
217
+ },
218
+ ]
219
+ data = {
220
+ "metadata": dict(),
221
+ "nbformat": 4,
222
+ "nbformat_minor": 4,
223
+ "cells": cells,
224
+ }
225
+ return cls(data=data)
100
226
 
101
227
  def code(self) -> List[str]:
102
228
  """
103
229
  Return the code that could be used to create this Notebook.
104
- AF: Again, not sure
105
230
  """
106
231
  lines = []
107
- lines.append("from edsl.notebooks import Notebook")
108
- lines.append(f"s = Notebook({self.data})")
232
+ lines.append("from edsl import Notebook")
233
+ lines.append(f'nb = Notebook(data={self.data}, name="""{self.name}""")')
109
234
  return lines
110
235
 
111
236
 
112
237
  if __name__ == "__main__":
113
- from edsl.notebooks import Notebook
238
+ from edsl import Notebook
114
239
 
115
240
  notebook = Notebook.example()
116
241
  assert notebook == notebook.from_dict(notebook.to_dict())
edsl/prompts/Prompt.py CHANGED
@@ -1,12 +1,16 @@
1
- """Class for creating prompts to be used in a survey."""
2
-
3
1
  from __future__ import annotations
4
2
  from typing import Optional
5
3
  from abc import ABC
6
4
  from typing import Any, List
7
5
 
8
6
  from rich.table import Table
9
- from jinja2 import Template, Environment, meta, TemplateSyntaxError
7
+ from jinja2 import Template, Environment, meta, TemplateSyntaxError, Undefined
8
+
9
+
10
+ class PreserveUndefined(Undefined):
11
+ def __str__(self):
12
+ return "{{ " + self._undefined_name + " }}"
13
+
10
14
 
11
15
  from edsl.exceptions.prompts import TemplateRenderError
12
16
  from edsl.prompts.prompt_config import (
@@ -35,6 +39,10 @@ class PromptBase(
35
39
 
36
40
  return data_to_html(self.to_dict())
37
41
 
42
+ def __len__(self):
43
+ """Return the length of the prompt text."""
44
+ return len(self.text)
45
+
38
46
  @classmethod
39
47
  def prompt_attributes(cls) -> List[str]:
40
48
  """Return the prompt class attributes."""
@@ -75,10 +83,10 @@ class PromptBase(
75
83
  >>> p = Prompt("Hello, {{person}}")
76
84
  >>> p2 = Prompt("How are you?")
77
85
  >>> p + p2
78
- Prompt(text='Hello, {{person}}How are you?')
86
+ Prompt(text=\"""Hello, {{person}}How are you?\""")
79
87
 
80
88
  >>> p + "How are you?"
81
- Prompt(text='Hello, {{person}}How are you?')
89
+ Prompt(text=\"""Hello, {{person}}How are you?\""")
82
90
  """
83
91
  if isinstance(other_prompt, str):
84
92
  return self.__class__(self.text + other_prompt)
@@ -114,7 +122,7 @@ class PromptBase(
114
122
  Example:
115
123
  >>> p = Prompt("Hello, {{person}}")
116
124
  >>> p
117
- Prompt(text='Hello, {{person}}')
125
+ Prompt(text=\"""Hello, {{person}}\""")
118
126
  """
119
127
  return f'Prompt(text="""{self.text}""")'
120
128
 
@@ -137,7 +145,7 @@ class PromptBase(
137
145
  :param template: The template to find the variables in.
138
146
 
139
147
  """
140
- env = Environment()
148
+ env = Environment(undefined=PreserveUndefined)
141
149
  ast = env.parse(template)
142
150
  return list(meta.find_undeclared_variables(ast))
143
151
 
@@ -186,13 +194,16 @@ class PromptBase(
186
194
 
187
195
  >>> p = Prompt("Hello, {{person}}")
188
196
  >>> p.render({"person": "John"})
189
- 'Hello, John'
197
+ Prompt(text=\"""Hello, John\""")
190
198
 
191
199
  >>> p.render({"person": "Mr. {{last_name}}", "last_name": "Horton"})
192
- 'Hello, Mr. Horton'
200
+ Prompt(text=\"""Hello, Mr. Horton\""")
193
201
 
194
202
  >>> p.render({"person": "Mr. {{last_name}}", "last_name": "Ho{{letter}}ton"}, max_nesting = 1)
195
- 'Hello, Mr. Horton'
203
+ Prompt(text=\"""Hello, Mr. Ho{{ letter }}ton\""")
204
+
205
+ >>> p.render({"person": "Mr. {{last_name}}"})
206
+ Prompt(text=\"""Hello, Mr. {{ last_name }}\""")
196
207
  """
197
208
  new_text = self._render(
198
209
  self.text, primary_replacement, **additional_replacements
@@ -216,12 +227,13 @@ class PromptBase(
216
227
  >>> codebook = {"age": "Age"}
217
228
  >>> p = Prompt("You are an agent named {{ name }}. {{ codebook['age']}}: {{ age }}")
218
229
  >>> p.render({"name": "John", "age": 44}, codebook=codebook)
219
- 'You are an agent named John. Age: 44'
230
+ Prompt(text=\"""You are an agent named John. Age: 44\""")
220
231
  """
232
+ env = Environment(undefined=PreserveUndefined)
221
233
  try:
222
234
  previous_text = None
223
235
  for _ in range(MAX_NESTING):
224
- rendered_text = Template(text).render(
236
+ rendered_text = env.from_string(text).render(
225
237
  primary_replacement, **additional_replacements
226
238
  )
227
239
  if rendered_text == previous_text:
@@ -258,7 +270,7 @@ class PromptBase(
258
270
  >>> p = Prompt("Hello, {{person}}")
259
271
  >>> p2 = Prompt.from_dict(p.to_dict())
260
272
  >>> p2
261
- Prompt(text='Hello, {{person}}')
273
+ Prompt(text=\"""Hello, {{person}}\""")
262
274
 
263
275
  """
264
276
  class_name = data["class_name"]
@@ -290,6 +302,12 @@ class Prompt(PromptBase):
290
302
  component_type = ComponentTypes.GENERIC
291
303
 
292
304
 
305
+ if __name__ == "__main__":
306
+ print("Running doctests...")
307
+ import doctest
308
+
309
+ doctest.testmod()
310
+
293
311
  from edsl.prompts.library.question_multiple_choice import *
294
312
  from edsl.prompts.library.agent_instructions import *
295
313
  from edsl.prompts.library.agent_persona import *
@@ -302,9 +320,3 @@ from edsl.prompts.library.question_numerical import *
302
320
  from edsl.prompts.library.question_rank import *
303
321
  from edsl.prompts.library.question_extract import *
304
322
  from edsl.prompts.library.question_list import *
305
-
306
-
307
- if __name__ == "__main__":
308
- import doctest
309
-
310
- doctest.testmod()