edsl 0.1.37.dev5__py3-none-any.whl → 0.1.38__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 +63 -34
- edsl/BaseDiff.py +7 -7
- edsl/__init__.py +2 -1
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +23 -11
- edsl/agents/AgentList.py +86 -23
- edsl/agents/Invigilator.py +18 -7
- edsl/agents/InvigilatorBase.py +0 -19
- edsl/agents/PromptConstructor.py +5 -4
- edsl/auto/SurveyCreatorPipeline.py +1 -1
- edsl/auto/utilities.py +1 -1
- edsl/base/Base.py +3 -13
- edsl/config.py +8 -0
- edsl/coop/coop.py +89 -19
- edsl/data/Cache.py +45 -17
- edsl/data/CacheEntry.py +8 -3
- edsl/data/RemoteCacheSync.py +0 -19
- edsl/enums.py +2 -0
- edsl/exceptions/agents.py +4 -0
- edsl/exceptions/cache.py +5 -0
- edsl/inference_services/GoogleService.py +7 -15
- edsl/inference_services/PerplexityService.py +163 -0
- edsl/inference_services/registry.py +2 -0
- edsl/jobs/Jobs.py +110 -559
- edsl/jobs/JobsChecks.py +147 -0
- edsl/jobs/JobsPrompts.py +268 -0
- edsl/jobs/JobsRemoteInferenceHandler.py +239 -0
- edsl/jobs/buckets/TokenBucket.py +3 -0
- edsl/jobs/interviews/Interview.py +7 -7
- edsl/jobs/runners/JobsRunnerAsyncio.py +156 -28
- edsl/jobs/runners/JobsRunnerStatus.py +194 -196
- edsl/jobs/tasks/TaskHistory.py +27 -19
- edsl/language_models/LanguageModel.py +52 -90
- edsl/language_models/ModelList.py +67 -14
- edsl/language_models/registry.py +57 -4
- edsl/notebooks/Notebook.py +7 -8
- edsl/prompts/Prompt.py +8 -3
- edsl/questions/QuestionBase.py +38 -30
- edsl/questions/QuestionBaseGenMixin.py +1 -1
- edsl/questions/QuestionBasePromptsMixin.py +0 -17
- edsl/questions/QuestionExtract.py +3 -4
- edsl/questions/QuestionFunctional.py +10 -3
- edsl/questions/derived/QuestionTopK.py +2 -0
- edsl/questions/question_registry.py +36 -6
- edsl/results/CSSParameterizer.py +108 -0
- edsl/results/Dataset.py +146 -15
- edsl/results/DatasetExportMixin.py +231 -217
- edsl/results/DatasetTree.py +134 -4
- edsl/results/Result.py +31 -16
- edsl/results/Results.py +159 -65
- edsl/results/TableDisplay.py +198 -0
- edsl/results/table_display.css +78 -0
- edsl/scenarios/FileStore.py +187 -13
- edsl/scenarios/Scenario.py +73 -18
- edsl/scenarios/ScenarioJoin.py +127 -0
- edsl/scenarios/ScenarioList.py +251 -76
- edsl/surveys/MemoryPlan.py +1 -1
- edsl/surveys/Rule.py +1 -5
- edsl/surveys/RuleCollection.py +1 -1
- edsl/surveys/Survey.py +25 -19
- edsl/surveys/SurveyFlowVisualizationMixin.py +67 -9
- edsl/surveys/instructions/ChangeInstruction.py +9 -7
- edsl/surveys/instructions/Instruction.py +21 -7
- edsl/templates/error_reporting/interview_details.html +3 -3
- edsl/templates/error_reporting/interviews.html +18 -9
- edsl/{conjure → utilities}/naming_utilities.py +1 -1
- edsl/utilities/utilities.py +15 -0
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/METADATA +2 -1
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/RECORD +71 -77
- edsl/conjure/AgentConstructionMixin.py +0 -160
- edsl/conjure/Conjure.py +0 -62
- edsl/conjure/InputData.py +0 -659
- edsl/conjure/InputDataCSV.py +0 -48
- edsl/conjure/InputDataMixinQuestionStats.py +0 -182
- edsl/conjure/InputDataPyRead.py +0 -91
- edsl/conjure/InputDataSPSS.py +0 -8
- edsl/conjure/InputDataStata.py +0 -8
- edsl/conjure/QuestionOptionMixin.py +0 -76
- edsl/conjure/QuestionTypeMixin.py +0 -23
- edsl/conjure/RawQuestion.py +0 -65
- edsl/conjure/SurveyResponses.py +0 -7
- edsl/conjure/__init__.py +0 -9
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/conjure/utilities.py +0 -201
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/LICENSE +0 -0
- {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/WHEEL +0 -0
edsl/surveys/Survey.py
CHANGED
@@ -41,6 +41,8 @@ class ValidatedString(str):
|
|
41
41
|
class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
42
42
|
"""A collection of questions that supports skip logic."""
|
43
43
|
|
44
|
+
__documentation__ = """https://docs.expectedparrot.com/en/latest/surveys.html"""
|
45
|
+
|
44
46
|
questions = QuestionsDescriptor()
|
45
47
|
"""
|
46
48
|
A collection of questions that supports skip logic.
|
@@ -445,35 +447,27 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
445
447
|
"""Return a hash of the question."""
|
446
448
|
from edsl.utilities.utilities import dict_hash
|
447
449
|
|
448
|
-
return dict_hash(self.
|
450
|
+
return dict_hash(self.to_dict(add_edsl_version=False))
|
449
451
|
|
450
|
-
def
|
452
|
+
def to_dict(self, add_edsl_version=True) -> dict[str, Any]:
|
451
453
|
"""Serialize the Survey object to a dictionary.
|
452
454
|
|
453
455
|
>>> s = Survey.example()
|
454
|
-
>>> s.
|
456
|
+
>>> s.to_dict(add_edsl_version = False).keys()
|
455
457
|
dict_keys(['questions', 'memory_plan', 'rule_collection', 'question_groups'])
|
456
458
|
"""
|
457
459
|
return {
|
458
460
|
"questions": [
|
459
|
-
q.
|
461
|
+
q.to_dict(add_edsl_version=add_edsl_version)
|
462
|
+
for q in self.recombined_questions_and_instructions()
|
460
463
|
],
|
461
|
-
"memory_plan": self.memory_plan.to_dict(),
|
462
|
-
"rule_collection": self.rule_collection.to_dict(
|
464
|
+
"memory_plan": self.memory_plan.to_dict(add_edsl_version=add_edsl_version),
|
465
|
+
"rule_collection": self.rule_collection.to_dict(
|
466
|
+
add_edsl_version=add_edsl_version
|
467
|
+
),
|
463
468
|
"question_groups": self.question_groups,
|
464
469
|
}
|
465
470
|
|
466
|
-
@add_edsl_version
|
467
|
-
def to_dict(self) -> dict[str, Any]:
|
468
|
-
"""Serialize the Survey object to a dictionary.
|
469
|
-
|
470
|
-
>>> s = Survey.example()
|
471
|
-
>>> s.to_dict().keys()
|
472
|
-
dict_keys(['questions', 'memory_plan', 'rule_collection', 'question_groups', 'edsl_version', 'edsl_class_name'])
|
473
|
-
|
474
|
-
"""
|
475
|
-
return self._to_dict()
|
476
|
-
|
477
471
|
@classmethod
|
478
472
|
@remove_edsl_version
|
479
473
|
def from_dict(cls, data: dict) -> Survey:
|
@@ -1595,10 +1589,22 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
|
|
1595
1589
|
# question_names_string = ", ".join([repr(name) for name in self.question_names])
|
1596
1590
|
return f"Survey(questions=[{questions_string}], memory_plan={self.memory_plan}, rule_collection={self.rule_collection}, question_groups={self.question_groups})"
|
1597
1591
|
|
1592
|
+
def _summary(self) -> dict:
|
1593
|
+
return {
|
1594
|
+
"EDSL Class": "Survey",
|
1595
|
+
"Number of Questions": len(self),
|
1596
|
+
"Question Names": self.question_names,
|
1597
|
+
}
|
1598
|
+
|
1598
1599
|
def _repr_html_(self) -> str:
|
1599
|
-
|
1600
|
+
footer = f"<a href={self.__documentation__}>(docs)</a>"
|
1601
|
+
return str(self.summary(format="html")) + footer
|
1602
|
+
|
1603
|
+
def tree(self, node_list: Optional[List[str]] = None):
|
1604
|
+
return self.to_scenario_list().tree(node_list=node_list)
|
1600
1605
|
|
1601
|
-
|
1606
|
+
def table(self, *fields, tablefmt=None) -> Table:
|
1607
|
+
return self.to_scenario_list().to_dataset().table(*fields, tablefmt=tablefmt)
|
1602
1608
|
|
1603
1609
|
def rich_print(self) -> Table:
|
1604
1610
|
"""Print the survey in a rich format.
|
@@ -1,27 +1,85 @@
|
|
1
|
-
"""A mixin for visualizing the flow of a survey."""
|
1
|
+
"""A mixin for visualizing the flow of a survey with parameter nodes."""
|
2
2
|
|
3
3
|
from typing import Optional
|
4
4
|
from edsl.surveys.base import RulePriority, EndOfSurvey
|
5
5
|
import tempfile
|
6
|
+
import os
|
6
7
|
|
7
8
|
|
8
9
|
class SurveyFlowVisualizationMixin:
|
9
|
-
"""A mixin for visualizing the flow of a survey."""
|
10
|
+
"""A mixin for visualizing the flow of a survey with parameter visualization."""
|
10
11
|
|
11
12
|
def show_flow(self, filename: Optional[str] = None):
|
12
|
-
"""Create an image showing the flow of users through the survey."""
|
13
|
+
"""Create an image showing the flow of users through the survey and question parameters."""
|
13
14
|
# Create a graph object
|
14
15
|
import pydot
|
15
16
|
|
16
17
|
graph = pydot.Dot(graph_type="digraph")
|
17
18
|
|
18
|
-
#
|
19
|
+
# First collect all unique parameters and answer references
|
20
|
+
params_and_refs = set()
|
21
|
+
param_to_questions = {} # Keep track of which questions use each parameter
|
22
|
+
answer_refs = set() # Track answer references between questions
|
23
|
+
|
24
|
+
# First pass: collect parameters and their question associations
|
19
25
|
for index, question in enumerate(self.questions):
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
# Add the main question node
|
27
|
+
question_node = pydot.Node(
|
28
|
+
f"Q{index}", label=f"{question.question_name}", shape="ellipse"
|
29
|
+
)
|
30
|
+
graph.add_node(question_node)
|
31
|
+
|
32
|
+
if hasattr(question, "parameters"):
|
33
|
+
for param in question.parameters:
|
34
|
+
# Check if this is an answer reference (contains '.answer')
|
35
|
+
if ".answer" in param:
|
36
|
+
answer_refs.add((param.split(".")[0], index))
|
37
|
+
else:
|
38
|
+
params_and_refs.add(param)
|
39
|
+
if param not in param_to_questions:
|
40
|
+
param_to_questions[param] = []
|
41
|
+
param_to_questions[param].append(index)
|
42
|
+
|
43
|
+
# Create parameter nodes and connect them to questions
|
44
|
+
for param in params_and_refs:
|
45
|
+
param_node_name = f"param_{param}"
|
46
|
+
param_node = pydot.Node(
|
47
|
+
param_node_name,
|
48
|
+
label=f"{{{{ {param} }}}}",
|
49
|
+
shape="box",
|
50
|
+
style="filled",
|
51
|
+
fillcolor="lightgrey",
|
52
|
+
fontsize="10",
|
53
|
+
)
|
54
|
+
graph.add_node(param_node)
|
55
|
+
|
56
|
+
# Connect this parameter to all questions that use it
|
57
|
+
for q_index in param_to_questions[param]:
|
58
|
+
param_edge = pydot.Edge(
|
59
|
+
param_node_name,
|
60
|
+
f"Q{q_index}",
|
61
|
+
style="dotted",
|
62
|
+
color="grey",
|
63
|
+
arrowsize="0.5",
|
23
64
|
)
|
65
|
+
graph.add_edge(param_edge)
|
66
|
+
|
67
|
+
# Add edges for answer references
|
68
|
+
for source_q_name, target_q_index in answer_refs:
|
69
|
+
# Find the source question index by name
|
70
|
+
source_q_index = next(
|
71
|
+
i
|
72
|
+
for i, q in enumerate(self.questions)
|
73
|
+
if q.question_name == source_q_name
|
74
|
+
)
|
75
|
+
ref_edge = pydot.Edge(
|
76
|
+
f"Q{source_q_index}",
|
77
|
+
f"Q{target_q_index}",
|
78
|
+
style="dashed",
|
79
|
+
color="purple",
|
80
|
+
label="answer reference",
|
24
81
|
)
|
82
|
+
graph.add_edge(ref_edge)
|
25
83
|
|
26
84
|
# Add an "EndOfSurvey" node
|
27
85
|
graph.add_node(
|
@@ -30,7 +88,7 @@ class SurveyFlowVisualizationMixin:
|
|
30
88
|
|
31
89
|
# Add edges for normal flow through the survey
|
32
90
|
num_questions = len(self.questions)
|
33
|
-
for index in range(num_questions - 1):
|
91
|
+
for index in range(num_questions - 1):
|
34
92
|
graph.add_edge(pydot.Edge(f"Q{index}", f"Q{index+1}"))
|
35
93
|
|
36
94
|
graph.add_edge(pydot.Edge(f"Q{num_questions-1}", "EndOfSurvey"))
|
@@ -64,7 +122,7 @@ class SurveyFlowVisualizationMixin:
|
|
64
122
|
if rule.next_q != EndOfSurvey and rule.next_q < num_questions
|
65
123
|
else "EndOfSurvey"
|
66
124
|
)
|
67
|
-
if rule.before_rule:
|
125
|
+
if rule.before_rule:
|
68
126
|
edge = pydot.Edge(
|
69
127
|
source_node,
|
70
128
|
target_node,
|
@@ -24,22 +24,24 @@ class ChangeInstruction:
|
|
24
24
|
def __str__(self):
|
25
25
|
return self.text
|
26
26
|
|
27
|
-
def
|
28
|
-
|
27
|
+
def to_dict(self, add_edsl_version=True):
|
28
|
+
d = {
|
29
29
|
"keep": self.keep,
|
30
30
|
"drop": self.drop,
|
31
|
-
"edsl_class_name": "ChangeInstruction",
|
32
31
|
}
|
32
|
+
if add_edsl_version:
|
33
|
+
from edsl import __version__
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
d["edsl_version"] = __version__
|
36
|
+
d["edsl_class_name"] = "ChangeInstruction"
|
37
|
+
|
38
|
+
return d
|
37
39
|
|
38
40
|
def __hash__(self) -> int:
|
39
41
|
"""Return a hash of the question."""
|
40
42
|
from edsl.utilities.utilities import dict_hash
|
41
43
|
|
42
|
-
return dict_hash(self.
|
44
|
+
return dict_hash(self.to_dict(add_edsl_version=False))
|
43
45
|
|
44
46
|
@classmethod
|
45
47
|
@remove_edsl_version
|
@@ -18,28 +18,42 @@ class Instruction:
|
|
18
18
|
def __repr__(self):
|
19
19
|
return """Instruction(name="{}", text="{}")""".format(self.name, self.text)
|
20
20
|
|
21
|
-
def
|
22
|
-
|
21
|
+
def _repr_html_(self):
|
22
|
+
d = self.to_dict(add_edsl_version=False)
|
23
|
+
data = [[k, v] for k, v in d.items()]
|
24
|
+
from tabulate import tabulate
|
25
|
+
|
26
|
+
table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
|
27
|
+
return f"<pre>{table}</pre>"
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def example(cls) -> "Instruction":
|
31
|
+
return cls(name="example", text="This is an example instruction.")
|
32
|
+
|
33
|
+
def to_dict(self, add_edsl_version=True):
|
34
|
+
d = {
|
23
35
|
"name": self.name,
|
24
36
|
"text": self.text,
|
25
37
|
"edsl_class_name": "Instruction",
|
26
38
|
"preamble": self.preamble,
|
27
39
|
}
|
40
|
+
if add_edsl_version:
|
41
|
+
from edsl import __version__
|
42
|
+
|
43
|
+
d["edsl_version"] = __version__
|
44
|
+
d["edsl_class_name"] = "Instruction"
|
45
|
+
return d
|
28
46
|
|
29
47
|
def add_question(self, question) -> "Survey":
|
30
48
|
from edsl import Survey
|
31
49
|
|
32
50
|
return Survey([self, question])
|
33
51
|
|
34
|
-
@add_edsl_version
|
35
|
-
def to_dict(self):
|
36
|
-
return self._to_dict()
|
37
|
-
|
38
52
|
def __hash__(self) -> int:
|
39
53
|
"""Return a hash of the question."""
|
40
54
|
from edsl.utilities.utilities import dict_hash
|
41
55
|
|
42
|
-
return dict_hash(self.
|
56
|
+
return dict_hash(self.to_dict(add_edsl_version=False))
|
43
57
|
|
44
58
|
@classmethod
|
45
59
|
@remove_edsl_version
|
@@ -40,11 +40,11 @@
|
|
40
40
|
</tr>
|
41
41
|
<tr>
|
42
42
|
<td>Scenario</td>
|
43
|
-
<td>{{ interview.scenario.
|
43
|
+
<td>{{ interview.scenario.__repr__() }}</td>
|
44
44
|
</tr>
|
45
45
|
<tr>
|
46
46
|
<td>Agent</td>
|
47
|
-
<td>{{ interview.agent.
|
47
|
+
<td>{{ interview.agent.__repr__() }}</td>
|
48
48
|
</tr>
|
49
49
|
<tr>
|
50
50
|
<td>Model name</td>
|
@@ -56,7 +56,7 @@
|
|
56
56
|
</tr>
|
57
57
|
<tr>
|
58
58
|
<td>Model parameters</td>
|
59
|
-
<td>{{ interview.model.
|
59
|
+
<td>{{ interview.model.__repr__() }}</td>
|
60
60
|
</tr>
|
61
61
|
<tr>
|
62
62
|
<td>User Prompt</td>
|
@@ -1,10 +1,19 @@
|
|
1
|
+
|
2
|
+
{% if interviews|length > max_interviews %}
|
3
|
+
<h1>Only showing the first {{ max_interviews }} interviews with errors</h1>
|
4
|
+
{% else %}
|
5
|
+
<h1>Showing all interviews</h1>
|
6
|
+
{% endif %}
|
7
|
+
|
1
8
|
{% for index, interview in interviews.items() %}
|
2
|
-
{% if
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
{%
|
9
|
-
|
10
|
-
{% endfor %}
|
9
|
+
{% if index < max_interviews %}
|
10
|
+
{% if interview.exceptions != {} %}
|
11
|
+
<div class="interview">Interview: {{ index }} </div>
|
12
|
+
Model: {{ interview.model.model }}
|
13
|
+
<h1>Failing questions</h1>
|
14
|
+
{% endif %}
|
15
|
+
{% for question, exceptions in interview.exceptions.items() %}
|
16
|
+
{% include 'interview_details.html' %}
|
17
|
+
{% endfor %}
|
18
|
+
{% endif %}
|
19
|
+
{% endfor %}
|
@@ -257,7 +257,7 @@ def sanitize_string(input_string, max_length=35):
|
|
257
257
|
# print()
|
258
258
|
|
259
259
|
if __name__ == "__main__":
|
260
|
-
from edsl.conjure.InputData import InputDataABC
|
260
|
+
# from edsl.conjure.InputData import InputDataABC
|
261
261
|
import doctest
|
262
262
|
|
263
263
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
edsl/utilities/utilities.py
CHANGED
@@ -207,6 +207,21 @@ def is_notebook() -> bool:
|
|
207
207
|
return False # Probably standard Python interpreter
|
208
208
|
|
209
209
|
|
210
|
+
def file_notice(file_name):
|
211
|
+
"""Print a notice about the file being created."""
|
212
|
+
if is_notebook():
|
213
|
+
from IPython.display import HTML, display
|
214
|
+
|
215
|
+
link_text = "Download file"
|
216
|
+
display(
|
217
|
+
HTML(
|
218
|
+
f'<p>File created: {file_name}</p>.<a href="{file_name}" download>{link_text}</a>'
|
219
|
+
)
|
220
|
+
)
|
221
|
+
else:
|
222
|
+
print(f"File created: {file_name}")
|
223
|
+
|
224
|
+
|
210
225
|
class HTMLSnippet(str):
|
211
226
|
"""Create an object with html content (`value`).
|
212
227
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: edsl
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.38
|
4
4
|
Summary: Create and analyze LLM-based surveys
|
5
5
|
Home-page: https://www.expectedparrot.com/
|
6
6
|
License: MIT
|
@@ -45,6 +45,7 @@ Requires-Dist: rich (>=13.7.0,<14.0.0)
|
|
45
45
|
Requires-Dist: setuptools (<72.0)
|
46
46
|
Requires-Dist: simpleeval (>=0.9.13,<0.10.0)
|
47
47
|
Requires-Dist: sqlalchemy (>=2.0.23,<3.0.0)
|
48
|
+
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
48
49
|
Requires-Dist: tenacity (>=8.2.3,<9.0.0)
|
49
50
|
Requires-Dist: urllib3 (>=1.25.4,<1.27)
|
50
51
|
Project-URL: Documentation, https://docs.expectedparrot.com
|