edsl 0.1.29.dev3__py3-none-any.whl → 0.1.30__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/Base.py +18 -18
- edsl/__init__.py +23 -23
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +79 -41
- edsl/agents/AgentList.py +26 -26
- edsl/agents/Invigilator.py +19 -2
- edsl/agents/InvigilatorBase.py +15 -10
- edsl/agents/PromptConstructionMixin.py +342 -100
- edsl/agents/descriptors.py +2 -1
- edsl/base/Base.py +289 -0
- edsl/config.py +2 -1
- edsl/conjure/InputData.py +39 -8
- edsl/conversation/car_buying.py +1 -1
- edsl/coop/coop.py +187 -150
- edsl/coop/utils.py +43 -75
- edsl/data/Cache.py +41 -18
- edsl/data/CacheEntry.py +6 -7
- edsl/data/SQLiteDict.py +11 -3
- edsl/data_transfer_models.py +4 -0
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +108 -49
- edsl/jobs/buckets/ModelBuckets.py +14 -2
- edsl/jobs/buckets/TokenBucket.py +32 -5
- edsl/jobs/interviews/Interview.py +99 -79
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +19 -24
- edsl/jobs/runners/JobsRunnerAsyncio.py +16 -16
- edsl/jobs/tasks/QuestionTaskCreator.py +10 -6
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +17 -17
- edsl/language_models/ModelList.py +1 -1
- edsl/language_models/repair.py +8 -7
- edsl/notebooks/Notebook.py +47 -10
- edsl/prompts/Prompt.py +31 -19
- edsl/questions/QuestionBase.py +38 -13
- edsl/questions/QuestionBudget.py +5 -6
- edsl/questions/QuestionCheckBox.py +7 -3
- edsl/questions/QuestionExtract.py +5 -3
- edsl/questions/QuestionFreeText.py +7 -5
- edsl/questions/QuestionFunctional.py +34 -5
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +68 -12
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +46 -4
- edsl/questions/question_registry.py +20 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/DatasetExportMixin.py +570 -0
- edsl/results/Result.py +66 -70
- edsl/results/Results.py +160 -68
- edsl/results/ResultsDBMixin.py +7 -3
- edsl/results/ResultsExportMixin.py +22 -537
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +5 -5
- edsl/scenarios/FileStore.py +299 -0
- edsl/scenarios/Scenario.py +16 -24
- edsl/scenarios/ScenarioList.py +42 -17
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/Study.py +8 -16
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +88 -17
- edsl/surveys/SurveyExportMixin.py +4 -2
- edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
- edsl/tools/plotting.py +4 -2
- edsl/utilities/__init__.py +21 -21
- edsl/utilities/interface.py +66 -45
- edsl/utilities/utilities.py +11 -13
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/METADATA +11 -10
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/RECORD +74 -71
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/WHEEL +1 -1
- edsl-0.1.29.dev3.dist-info/entry_points.txt +0 -3
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/LICENSE +0 -0
@@ -7,26 +7,18 @@ import asyncio
|
|
7
7
|
import json
|
8
8
|
import time
|
9
9
|
import os
|
10
|
-
|
11
10
|
from typing import Coroutine, Any, Callable, Type, List, get_type_hints
|
12
|
-
|
13
|
-
from abc import ABC, abstractmethod, ABCMeta
|
14
|
-
|
15
|
-
from rich.table import Table
|
11
|
+
from abc import ABC, abstractmethod
|
16
12
|
|
17
13
|
from edsl.config import CONFIG
|
18
14
|
|
19
|
-
from edsl.utilities.utilities import clean_json
|
20
15
|
from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
|
21
16
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
17
|
+
|
22
18
|
from edsl.language_models.repair import repair
|
23
|
-
from edsl.exceptions.language_models import LanguageModelAttributeTypeError
|
24
19
|
from edsl.enums import InferenceServiceType
|
25
20
|
from edsl.Base import RichPrintingMixin, PersistenceMixin
|
26
|
-
from edsl.data.Cache import Cache
|
27
21
|
from edsl.enums import service_to_api_keyname
|
28
|
-
|
29
|
-
|
30
22
|
from edsl.exceptions import MissingAPIKeyError
|
31
23
|
from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
|
32
24
|
|
@@ -291,7 +283,7 @@ class LanguageModel(
|
|
291
283
|
self,
|
292
284
|
user_prompt: str,
|
293
285
|
system_prompt: str,
|
294
|
-
cache,
|
286
|
+
cache: "Cache",
|
295
287
|
iteration: int = 0,
|
296
288
|
encoded_image=None,
|
297
289
|
) -> tuple[dict, bool, str]:
|
@@ -331,12 +323,10 @@ class LanguageModel(
|
|
331
323
|
image_hash = hashlib.md5(encoded_image.encode()).hexdigest()
|
332
324
|
cache_call_params["user_prompt"] = f"{user_prompt} {image_hash}"
|
333
325
|
|
334
|
-
cached_response = cache.fetch(**cache_call_params)
|
335
|
-
|
326
|
+
cached_response, cache_key = cache.fetch(**cache_call_params)
|
336
327
|
if cached_response:
|
337
328
|
response = json.loads(cached_response)
|
338
329
|
cache_used = True
|
339
|
-
cache_key = None
|
340
330
|
else:
|
341
331
|
remote_call = hasattr(self, "remote") and self.remote
|
342
332
|
f = (
|
@@ -348,7 +338,7 @@ class LanguageModel(
|
|
348
338
|
if encoded_image:
|
349
339
|
params["encoded_image"] = encoded_image
|
350
340
|
response = await f(**params)
|
351
|
-
|
341
|
+
new_cache_key = cache.store(
|
352
342
|
user_prompt=user_prompt,
|
353
343
|
model=str(self.model),
|
354
344
|
parameters=self.parameters,
|
@@ -356,6 +346,7 @@ class LanguageModel(
|
|
356
346
|
response=response,
|
357
347
|
iteration=iteration,
|
358
348
|
)
|
349
|
+
assert new_cache_key == cache_key
|
359
350
|
cache_used = False
|
360
351
|
|
361
352
|
return response, cache_used, cache_key
|
@@ -420,7 +411,7 @@ class LanguageModel(
|
|
420
411
|
|
421
412
|
dict_response.update(
|
422
413
|
{
|
423
|
-
"
|
414
|
+
"cache_used": cache_used,
|
424
415
|
"cache_key": cache_key,
|
425
416
|
"usage": raw_response.get("usage", {}),
|
426
417
|
"raw_model_response": raw_response,
|
@@ -490,6 +481,8 @@ class LanguageModel(
|
|
490
481
|
|
491
482
|
def rich_print(self):
|
492
483
|
"""Display an object as a table."""
|
484
|
+
from rich.table import Table
|
485
|
+
|
493
486
|
table = Table(title="Language Model")
|
494
487
|
table.add_column("Attribute", style="bold")
|
495
488
|
table.add_column("Value")
|
@@ -501,7 +494,12 @@ class LanguageModel(
|
|
501
494
|
return table
|
502
495
|
|
503
496
|
@classmethod
|
504
|
-
def example(
|
497
|
+
def example(
|
498
|
+
cls,
|
499
|
+
test_model: bool = False,
|
500
|
+
canned_response: str = "Hello world",
|
501
|
+
throw_exception: bool = False,
|
502
|
+
):
|
505
503
|
"""Return a default instance of the class.
|
506
504
|
|
507
505
|
>>> from edsl.language_models import LanguageModel
|
@@ -526,6 +524,8 @@ class LanguageModel(
|
|
526
524
|
) -> dict[str, Any]:
|
527
525
|
await asyncio.sleep(0.1)
|
528
526
|
# return {"message": """{"answer": "Hello, world"}"""}
|
527
|
+
if throw_exception:
|
528
|
+
raise Exception("This is a test error")
|
529
529
|
return {"message": f'{{"answer": "{canned_response}"}}'}
|
530
530
|
|
531
531
|
def parse_response(self, raw_response: dict[str, Any]) -> str:
|
@@ -5,7 +5,7 @@ from edsl import Model
|
|
5
5
|
from edsl.language_models import LanguageModel
|
6
6
|
from edsl.Base import Base
|
7
7
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
8
|
-
from edsl.utilities import is_valid_variable_name
|
8
|
+
from edsl.utilities.utilities import is_valid_variable_name
|
9
9
|
from edsl.utilities.utilities import dict_hash
|
10
10
|
|
11
11
|
|
edsl/language_models/repair.py
CHANGED
@@ -1,18 +1,13 @@
|
|
1
1
|
import json
|
2
2
|
import asyncio
|
3
3
|
import warnings
|
4
|
-
from rich import print
|
5
|
-
from rich.console import Console
|
6
|
-
from rich.syntax import Syntax
|
7
|
-
|
8
|
-
from edsl.utilities.utilities import clean_json
|
9
|
-
|
10
|
-
from edsl.utilities.repair_functions import extract_json_from_string
|
11
4
|
|
12
5
|
|
13
6
|
async def async_repair(
|
14
7
|
bad_json, error_message="", user_prompt=None, system_prompt=None, cache=None
|
15
8
|
):
|
9
|
+
from edsl.utilities.utilities import clean_json
|
10
|
+
|
16
11
|
s = clean_json(bad_json)
|
17
12
|
|
18
13
|
try:
|
@@ -27,6 +22,8 @@ async def async_repair(
|
|
27
22
|
return valid_dict, success
|
28
23
|
|
29
24
|
try:
|
25
|
+
from edsl.utilities.repair_functions import extract_json_from_string
|
26
|
+
|
30
27
|
valid_dict = extract_json_from_string(s)
|
31
28
|
success = True
|
32
29
|
except ValueError:
|
@@ -98,6 +95,10 @@ async def async_repair(
|
|
98
95
|
except json.JSONDecodeError:
|
99
96
|
valid_dict = {}
|
100
97
|
success = False
|
98
|
+
from rich import print
|
99
|
+
from rich.console import Console
|
100
|
+
from rich.syntax import Syntax
|
101
|
+
|
101
102
|
console = Console()
|
102
103
|
error_message = (
|
103
104
|
f"All repairs. failed. LLM Model given [red]{str(bad_json)}[/red]"
|
edsl/notebooks/Notebook.py
CHANGED
@@ -1,15 +1,11 @@
|
|
1
1
|
"""A Notebook is a utility class that allows you to easily share/pull ipynbs from Coop."""
|
2
2
|
|
3
|
+
from __future__ import annotations
|
3
4
|
import json
|
4
|
-
import nbformat
|
5
|
-
from nbconvert import HTMLExporter
|
6
5
|
from typing import Dict, List, Optional
|
7
|
-
from
|
6
|
+
from uuid import uuid4
|
8
7
|
from edsl.Base import Base
|
9
|
-
from edsl.utilities.decorators import
|
10
|
-
add_edsl_version,
|
11
|
-
remove_edsl_version,
|
12
|
-
)
|
8
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
13
9
|
|
14
10
|
|
15
11
|
class Notebook(Base):
|
@@ -34,6 +30,8 @@ class Notebook(Base):
|
|
34
30
|
If no path is provided, assume this code is run in a notebook and try to load the current notebook from file.
|
35
31
|
:param name: A name for the Notebook.
|
36
32
|
"""
|
33
|
+
import nbformat
|
34
|
+
|
37
35
|
# Load current notebook path as fallback (VS Code only)
|
38
36
|
path = path or globals().get("__vsc_ipynb_file__")
|
39
37
|
if data is not None:
|
@@ -56,6 +54,37 @@ class Notebook(Base):
|
|
56
54
|
|
57
55
|
self.name = name or self.default_name
|
58
56
|
|
57
|
+
@classmethod
|
58
|
+
def from_script(cls, path: str, name: Optional[str] = None) -> "Notebook":
|
59
|
+
# Read the script file
|
60
|
+
with open(path, "r") as script_file:
|
61
|
+
script_content = script_file.read()
|
62
|
+
|
63
|
+
# Create a new Jupyter notebook
|
64
|
+
nb = nbformat.v4.new_notebook()
|
65
|
+
|
66
|
+
# Add the script content to the first cell
|
67
|
+
first_cell = nbformat.v4.new_code_cell(script_content)
|
68
|
+
nb.cells.append(first_cell)
|
69
|
+
|
70
|
+
# Create a Notebook instance with the notebook data
|
71
|
+
notebook_instance = cls(nb)
|
72
|
+
|
73
|
+
return notebook_instance
|
74
|
+
|
75
|
+
@classmethod
|
76
|
+
def from_current_script(cls) -> "Notebook":
|
77
|
+
import inspect
|
78
|
+
import os
|
79
|
+
|
80
|
+
# Get the path to the current file
|
81
|
+
current_frame = inspect.currentframe()
|
82
|
+
caller_frame = inspect.getouterframes(current_frame, 2)
|
83
|
+
current_file_path = os.path.abspath(caller_frame[1].filename)
|
84
|
+
|
85
|
+
# Use from_script to create the notebook
|
86
|
+
return cls.from_script(current_file_path)
|
87
|
+
|
59
88
|
def __eq__(self, other):
|
60
89
|
"""
|
61
90
|
Check if two Notebooks are equal.
|
@@ -103,6 +132,9 @@ class Notebook(Base):
|
|
103
132
|
"""
|
104
133
|
Return HTML representation of Notebook.
|
105
134
|
"""
|
135
|
+
from nbconvert import HTMLExporter
|
136
|
+
import nbformat
|
137
|
+
|
106
138
|
notebook = nbformat.from_dict(self.data)
|
107
139
|
html_exporter = HTMLExporter(template_name="basic")
|
108
140
|
(body, _) = html_exporter.from_notebook_node(notebook)
|
@@ -143,6 +175,8 @@ class Notebook(Base):
|
|
143
175
|
"""
|
144
176
|
Display a Notebook as a rich table.
|
145
177
|
"""
|
178
|
+
from rich.table import Table
|
179
|
+
|
146
180
|
table_data, column_names = self._table()
|
147
181
|
table = Table(title=f"{self.__class__.__name__} Attributes")
|
148
182
|
for column in column_names:
|
@@ -155,10 +189,13 @@ class Notebook(Base):
|
|
155
189
|
return table
|
156
190
|
|
157
191
|
@classmethod
|
158
|
-
def example(cls) ->
|
192
|
+
def example(cls, randomize: bool = False) -> Notebook:
|
159
193
|
"""
|
160
|
-
|
194
|
+
Returns an example Notebook instance.
|
195
|
+
|
196
|
+
:param randomize: If True, adds a random string one of the cells' output.
|
161
197
|
"""
|
198
|
+
addition = "" if not randomize else str(uuid4())
|
162
199
|
cells = [
|
163
200
|
{
|
164
201
|
"cell_type": "markdown",
|
@@ -173,7 +210,7 @@ class Notebook(Base):
|
|
173
210
|
{
|
174
211
|
"name": "stdout",
|
175
212
|
"output_type": "stream",
|
176
|
-
"text": "Hello world!\n",
|
213
|
+
"text": f"Hello world!\n{addition}",
|
177
214
|
}
|
178
215
|
],
|
179
216
|
"source": 'print("Hello world!")',
|
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
|
86
|
+
Prompt(text=\"""Hello, {{person}}How are you?\""")
|
79
87
|
|
80
88
|
>>> p + "How are you?"
|
81
|
-
Prompt(text
|
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
|
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
|
-
|
197
|
+
Prompt(text=\"""Hello, John\""")
|
190
198
|
|
191
199
|
>>> p.render({"person": "Mr. {{last_name}}", "last_name": "Horton"})
|
192
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
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()
|
edsl/questions/QuestionBase.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
"""This module contains the Question class, which is the base class for all questions in EDSL."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
|
+
import time
|
4
5
|
from abc import ABC, abstractmethod
|
5
|
-
from rich.table import Table
|
6
6
|
from typing import Any, Type, Optional, List, Callable
|
7
7
|
import copy
|
8
8
|
|
@@ -12,7 +12,7 @@ from edsl.exceptions import (
|
|
12
12
|
)
|
13
13
|
from edsl.questions.descriptors import QuestionNameDescriptor, QuestionTextDescriptor
|
14
14
|
|
15
|
-
|
15
|
+
|
16
16
|
from edsl.questions.AnswerValidatorMixin import AnswerValidatorMixin
|
17
17
|
from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
|
18
18
|
from edsl.Base import PersistenceMixin, RichPrintingMixin
|
@@ -124,6 +124,8 @@ class QuestionBase(
|
|
124
124
|
:param model: The language model to use. If None, assumes does not matter.
|
125
125
|
|
126
126
|
"""
|
127
|
+
from edsl.prompts.registry import get_classes as prompt_lookup
|
128
|
+
|
127
129
|
applicable_prompts = prompt_lookup(
|
128
130
|
component_type="question_instructions",
|
129
131
|
question_type=cls.question_type,
|
@@ -173,15 +175,16 @@ class QuestionBase(
|
|
173
175
|
def add_model_instructions(
|
174
176
|
self, *, instructions: str, model: Optional[str] = None
|
175
177
|
) -> None:
|
176
|
-
"""Add model-specific instructions for the question.
|
178
|
+
"""Add model-specific instructions for the question that override the default instructions.
|
177
179
|
|
178
180
|
:param instructions: The instructions to add. This is typically a jinja2 template.
|
179
181
|
:param model: The language model for this instruction.
|
180
182
|
|
181
183
|
>>> from edsl.questions import QuestionFreeText
|
182
184
|
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
183
|
-
>>> q.add_model_instructions(instructions = "Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
|
184
|
-
|
185
|
+
>>> q.add_model_instructions(instructions = "{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
|
186
|
+
>>> q.get_instructions(model = "gpt3")
|
187
|
+
Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
|
185
188
|
"""
|
186
189
|
from edsl import Model
|
187
190
|
|
@@ -201,6 +204,13 @@ class QuestionBase(
|
|
201
204
|
"""Get the mathcing question-answering instructions for the question.
|
202
205
|
|
203
206
|
:param model: The language model to use.
|
207
|
+
|
208
|
+
>>> from edsl import QuestionFreeText
|
209
|
+
>>> QuestionFreeText.example().get_instructions()
|
210
|
+
Prompt(text=\"""You are being asked the following question: {{question_text}}
|
211
|
+
Return a valid JSON formatted like this:
|
212
|
+
{"answer": "<put free text answer here>"}
|
213
|
+
\""")
|
204
214
|
"""
|
205
215
|
from edsl.prompts.Prompt import Prompt
|
206
216
|
|
@@ -293,7 +303,16 @@ class QuestionBase(
|
|
293
303
|
print_json(json.dumps(self.to_dict()))
|
294
304
|
|
295
305
|
def __call__(self, just_answer=True, model=None, agent=None, **kwargs):
|
296
|
-
"""Call the question.
|
306
|
+
"""Call the question.
|
307
|
+
|
308
|
+
>>> from edsl.language_models import LanguageModel
|
309
|
+
>>> m = LanguageModel.example(canned_response = "Yo, what's up?", test_model = True)
|
310
|
+
>>> from edsl import QuestionFreeText
|
311
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
312
|
+
>>> q(model = m)
|
313
|
+
"Yo, what's up?"
|
314
|
+
|
315
|
+
"""
|
297
316
|
survey = self.to_survey()
|
298
317
|
results = survey(model=model, agent=agent, **kwargs)
|
299
318
|
if just_answer:
|
@@ -304,7 +323,6 @@ class QuestionBase(
|
|
304
323
|
async def run_async(self, just_answer=True, model=None, agent=None, **kwargs):
|
305
324
|
"""Call the question."""
|
306
325
|
survey = self.to_survey()
|
307
|
-
## asyncio.run(survey.async_call());
|
308
326
|
results = await survey.run_async(model=model, agent=agent, **kwargs)
|
309
327
|
if just_answer:
|
310
328
|
return results.select(f"answer.{self.question_name}").first()
|
@@ -383,29 +401,34 @@ class QuestionBase(
|
|
383
401
|
s = Survey([self, other])
|
384
402
|
return s
|
385
403
|
|
386
|
-
def to_survey(self):
|
404
|
+
def to_survey(self) -> "Survey":
|
387
405
|
"""Turn a single question into a survey."""
|
388
406
|
from edsl.surveys.Survey import Survey
|
389
407
|
|
390
408
|
s = Survey([self])
|
391
409
|
return s
|
392
410
|
|
393
|
-
def run(self, *args, **kwargs):
|
411
|
+
def run(self, *args, **kwargs) -> "Results":
|
394
412
|
"""Turn a single question into a survey and run it."""
|
395
413
|
from edsl.surveys.Survey import Survey
|
396
414
|
|
397
415
|
s = self.to_survey()
|
398
416
|
return s.run(*args, **kwargs)
|
399
417
|
|
400
|
-
def by(self, *args):
|
401
|
-
"""Turn a single question into a survey and
|
418
|
+
def by(self, *args) -> "Jobs":
|
419
|
+
"""Turn a single question into a survey and then a Job."""
|
402
420
|
from edsl.surveys.Survey import Survey
|
403
421
|
|
404
422
|
s = Survey([self])
|
405
423
|
return s.by(*args)
|
406
424
|
|
407
|
-
def human_readable(self):
|
408
|
-
"""Print the question in a human readable format.
|
425
|
+
def human_readable(self) -> str:
|
426
|
+
"""Print the question in a human readable format.
|
427
|
+
|
428
|
+
>>> from edsl.questions import QuestionFreeText
|
429
|
+
>>> QuestionFreeText.example().human_readable()
|
430
|
+
'Question Type: free_text\\nQuestion: How are you?'
|
431
|
+
"""
|
409
432
|
lines = []
|
410
433
|
lines.append(f"Question Type: {self.question_type}")
|
411
434
|
lines.append(f"Question: {self.question_text}")
|
@@ -475,6 +498,8 @@ class QuestionBase(
|
|
475
498
|
|
476
499
|
def rich_print(self):
|
477
500
|
"""Print the question in a rich format."""
|
501
|
+
from rich.table import Table
|
502
|
+
|
478
503
|
table = Table(show_header=True, header_style="bold magenta")
|
479
504
|
table.add_column("Question Name", style="dim")
|
480
505
|
table.add_column("Question Type")
|
edsl/questions/QuestionBudget.py
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import random
|
3
|
-
import textwrap
|
4
3
|
from typing import Any, Optional, Union
|
5
4
|
from edsl.questions.QuestionBase import QuestionBase
|
6
5
|
from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
|
7
|
-
from edsl.scenarios import Scenario
|
8
|
-
from edsl.utilities import random_string
|
9
6
|
|
10
7
|
|
11
8
|
class QuestionBudget(QuestionBase):
|
@@ -46,7 +43,7 @@ class QuestionBudget(QuestionBase):
|
|
46
43
|
return answer
|
47
44
|
|
48
45
|
def _translate_answer_code_to_answer(
|
49
|
-
self, answer_codes: dict[str, int], scenario: Scenario = None
|
46
|
+
self, answer_codes: dict[str, int], scenario: "Scenario" = None
|
50
47
|
):
|
51
48
|
"""
|
52
49
|
Translate the answer codes to the actual answers.
|
@@ -63,6 +60,8 @@ class QuestionBudget(QuestionBase):
|
|
63
60
|
|
64
61
|
def _simulate_answer(self, human_readable=True):
|
65
62
|
"""Simulate a valid answer for debugging purposes (what the validator expects)."""
|
63
|
+
from edsl.utilities.utilities import random_string
|
64
|
+
|
66
65
|
if human_readable:
|
67
66
|
keys = self.question_options
|
68
67
|
else:
|
@@ -163,8 +162,8 @@ def main():
|
|
163
162
|
|
164
163
|
|
165
164
|
if __name__ == "__main__":
|
166
|
-
q = QuestionBudget.example()
|
167
|
-
results = q.run()
|
165
|
+
# q = QuestionBudget.example()
|
166
|
+
# results = q.run()
|
168
167
|
|
169
168
|
import doctest
|
170
169
|
|
@@ -9,8 +9,6 @@ from edsl.questions.descriptors import (
|
|
9
9
|
IntegerDescriptor,
|
10
10
|
QuestionOptionsDescriptor,
|
11
11
|
)
|
12
|
-
from edsl.scenarios import Scenario
|
13
|
-
from edsl.utilities import random_string
|
14
12
|
|
15
13
|
|
16
14
|
class QuestionCheckBox(QuestionBase):
|
@@ -55,13 +53,17 @@ class QuestionCheckBox(QuestionBase):
|
|
55
53
|
self._validate_answer_checkbox(answer)
|
56
54
|
return answer
|
57
55
|
|
58
|
-
def _translate_answer_code_to_answer(
|
56
|
+
def _translate_answer_code_to_answer(
|
57
|
+
self, answer_codes, scenario: "Scenario" = None
|
58
|
+
):
|
59
59
|
"""
|
60
60
|
Translate the answer code to the actual answer.
|
61
61
|
|
62
62
|
For example, for question options ["a", "b", "c"],the answer codes are 0, 1, and 2.
|
63
63
|
The LLM will respond with [0,1] and this code will translate it to ["a","b"].
|
64
64
|
"""
|
65
|
+
from edsl.scenarios.Scenario import Scenario
|
66
|
+
|
65
67
|
scenario = scenario or Scenario()
|
66
68
|
translated_options = [
|
67
69
|
Template(option).render(scenario) for option in self.question_options
|
@@ -73,6 +75,8 @@ class QuestionCheckBox(QuestionBase):
|
|
73
75
|
|
74
76
|
def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
|
75
77
|
"""Simulate a valid answer for debugging purposes."""
|
78
|
+
from edsl.utilities.utilities import random_string
|
79
|
+
|
76
80
|
min_selections = self.min_selections or 1
|
77
81
|
max_selections = self.max_selections or len(self.question_options)
|
78
82
|
num_selections = random.randint(min_selections, max_selections)
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
2
2
|
from typing import Any
|
3
3
|
from edsl.questions.QuestionBase import QuestionBase
|
4
4
|
from edsl.questions.descriptors import AnswerTemplateDescriptor
|
5
|
-
from edsl.scenarios import Scenario
|
6
|
-
from edsl.utilities import random_string
|
7
5
|
|
8
6
|
|
9
7
|
class QuestionExtract(QuestionBase):
|
@@ -44,12 +42,14 @@ class QuestionExtract(QuestionBase):
|
|
44
42
|
self._validate_answer_extract(answer)
|
45
43
|
return answer
|
46
44
|
|
47
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
45
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
48
46
|
"""Return the answer in a human-readable format."""
|
49
47
|
return answer
|
50
48
|
|
51
49
|
def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
|
52
50
|
"""Simulate a valid answer for debugging purposes."""
|
51
|
+
from edsl.utilities.utilities import random_string
|
52
|
+
|
53
53
|
return {
|
54
54
|
"answer": {key: random_string() for key in self.answer_template.keys()},
|
55
55
|
"comment": random_string(),
|
@@ -106,6 +106,8 @@ def main():
|
|
106
106
|
q.to_dict()
|
107
107
|
assert q.from_dict(q.to_dict()) == q
|
108
108
|
|
109
|
+
|
110
|
+
if __name__ == "__main__":
|
109
111
|
import doctest
|
110
112
|
|
111
113
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -1,9 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import textwrap
|
3
3
|
from typing import Any, Optional
|
4
|
+
from uuid import uuid4
|
4
5
|
from edsl.questions.QuestionBase import QuestionBase
|
5
|
-
from edsl.scenarios import Scenario
|
6
|
-
from edsl.utilities import random_string
|
7
6
|
|
8
7
|
|
9
8
|
class QuestionFreeText(QuestionBase):
|
@@ -43,12 +42,14 @@ class QuestionFreeText(QuestionBase):
|
|
43
42
|
self._validate_answer_key_value(answer, "answer", str)
|
44
43
|
return answer
|
45
44
|
|
46
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
45
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
47
46
|
"""Do nothing, because the answer is already in a human-readable format."""
|
48
47
|
return answer
|
49
48
|
|
50
49
|
def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
|
51
50
|
"""Simulate a valid answer for debugging purposes."""
|
51
|
+
from edsl.utilities.utilities import random_string
|
52
|
+
|
52
53
|
return {"answer": random_string()}
|
53
54
|
|
54
55
|
@property
|
@@ -65,9 +66,10 @@ class QuestionFreeText(QuestionBase):
|
|
65
66
|
return question_html_content
|
66
67
|
|
67
68
|
@classmethod
|
68
|
-
def example(cls) -> QuestionFreeText:
|
69
|
+
def example(cls, randomize: bool = False) -> QuestionFreeText:
|
69
70
|
"""Return an example instance of a free text question."""
|
70
|
-
|
71
|
+
addition = "" if not randomize else str(uuid4())
|
72
|
+
return cls(question_name="how_are_you", question_text=f"How are you?{addition}")
|
71
73
|
|
72
74
|
|
73
75
|
def main():
|