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.
Files changed (86) hide show
  1. edsl/Base.py +63 -34
  2. edsl/BaseDiff.py +7 -7
  3. edsl/__init__.py +2 -1
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +23 -11
  6. edsl/agents/AgentList.py +86 -23
  7. edsl/agents/Invigilator.py +18 -7
  8. edsl/agents/InvigilatorBase.py +0 -19
  9. edsl/agents/PromptConstructor.py +5 -4
  10. edsl/auto/SurveyCreatorPipeline.py +1 -1
  11. edsl/auto/utilities.py +1 -1
  12. edsl/base/Base.py +3 -13
  13. edsl/config.py +8 -0
  14. edsl/coop/coop.py +89 -19
  15. edsl/data/Cache.py +45 -17
  16. edsl/data/CacheEntry.py +8 -3
  17. edsl/data/RemoteCacheSync.py +0 -19
  18. edsl/enums.py +2 -0
  19. edsl/exceptions/agents.py +4 -0
  20. edsl/exceptions/cache.py +5 -0
  21. edsl/inference_services/GoogleService.py +7 -15
  22. edsl/inference_services/PerplexityService.py +163 -0
  23. edsl/inference_services/registry.py +2 -0
  24. edsl/jobs/Jobs.py +110 -559
  25. edsl/jobs/JobsChecks.py +147 -0
  26. edsl/jobs/JobsPrompts.py +268 -0
  27. edsl/jobs/JobsRemoteInferenceHandler.py +239 -0
  28. edsl/jobs/buckets/TokenBucket.py +3 -0
  29. edsl/jobs/interviews/Interview.py +7 -7
  30. edsl/jobs/runners/JobsRunnerAsyncio.py +156 -28
  31. edsl/jobs/runners/JobsRunnerStatus.py +194 -196
  32. edsl/jobs/tasks/TaskHistory.py +27 -19
  33. edsl/language_models/LanguageModel.py +52 -90
  34. edsl/language_models/ModelList.py +67 -14
  35. edsl/language_models/registry.py +57 -4
  36. edsl/notebooks/Notebook.py +7 -8
  37. edsl/prompts/Prompt.py +8 -3
  38. edsl/questions/QuestionBase.py +38 -30
  39. edsl/questions/QuestionBaseGenMixin.py +1 -1
  40. edsl/questions/QuestionBasePromptsMixin.py +0 -17
  41. edsl/questions/QuestionExtract.py +3 -4
  42. edsl/questions/QuestionFunctional.py +10 -3
  43. edsl/questions/derived/QuestionTopK.py +2 -0
  44. edsl/questions/question_registry.py +36 -6
  45. edsl/results/CSSParameterizer.py +108 -0
  46. edsl/results/Dataset.py +146 -15
  47. edsl/results/DatasetExportMixin.py +231 -217
  48. edsl/results/DatasetTree.py +134 -4
  49. edsl/results/Result.py +31 -16
  50. edsl/results/Results.py +159 -65
  51. edsl/results/TableDisplay.py +198 -0
  52. edsl/results/table_display.css +78 -0
  53. edsl/scenarios/FileStore.py +187 -13
  54. edsl/scenarios/Scenario.py +73 -18
  55. edsl/scenarios/ScenarioJoin.py +127 -0
  56. edsl/scenarios/ScenarioList.py +251 -76
  57. edsl/surveys/MemoryPlan.py +1 -1
  58. edsl/surveys/Rule.py +1 -5
  59. edsl/surveys/RuleCollection.py +1 -1
  60. edsl/surveys/Survey.py +25 -19
  61. edsl/surveys/SurveyFlowVisualizationMixin.py +67 -9
  62. edsl/surveys/instructions/ChangeInstruction.py +9 -7
  63. edsl/surveys/instructions/Instruction.py +21 -7
  64. edsl/templates/error_reporting/interview_details.html +3 -3
  65. edsl/templates/error_reporting/interviews.html +18 -9
  66. edsl/{conjure → utilities}/naming_utilities.py +1 -1
  67. edsl/utilities/utilities.py +15 -0
  68. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/METADATA +2 -1
  69. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/RECORD +71 -77
  70. edsl/conjure/AgentConstructionMixin.py +0 -160
  71. edsl/conjure/Conjure.py +0 -62
  72. edsl/conjure/InputData.py +0 -659
  73. edsl/conjure/InputDataCSV.py +0 -48
  74. edsl/conjure/InputDataMixinQuestionStats.py +0 -182
  75. edsl/conjure/InputDataPyRead.py +0 -91
  76. edsl/conjure/InputDataSPSS.py +0 -8
  77. edsl/conjure/InputDataStata.py +0 -8
  78. edsl/conjure/QuestionOptionMixin.py +0 -76
  79. edsl/conjure/QuestionTypeMixin.py +0 -23
  80. edsl/conjure/RawQuestion.py +0 -65
  81. edsl/conjure/SurveyResponses.py +0 -7
  82. edsl/conjure/__init__.py +0 -9
  83. edsl/conjure/examples/placeholder.txt +0 -0
  84. edsl/conjure/utilities.py +0 -201
  85. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/LICENSE +0 -0
  86. {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._to_dict())
450
+ return dict_hash(self.to_dict(add_edsl_version=False))
449
451
 
450
- def _to_dict(self) -> dict[str, Any]:
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._to_dict().keys()
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._to_dict() for q in self.recombined_questions_and_instructions()
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
- from edsl.utilities.utilities import data_to_html
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
- return data_to_html(self.to_dict())
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
- # Add nodes for each question
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
- graph.add_node(
21
- pydot.Node(
22
- f"Q{index}", label=f"{question.question_name}", shape="ellipse"
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): # From Q1 to Q3
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: # Assume skip rules have an attribute `is_skip`
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 _to_dict(self):
28
- return {
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
- @add_edsl_version
35
- def to_dict(self):
36
- return self._to_dict()
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._to_dict())
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 _to_dict(self):
22
- return {
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._to_dict())
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._repr_html_() }}</td>
43
+ <td>{{ interview.scenario.__repr__() }}</td>
44
44
  </tr>
45
45
  <tr>
46
46
  <td>Agent</td>
47
- <td>{{ interview.agent._repr_html_() }}</td>
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._repr_html_() }}</td>
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 interview.exceptions != {} %}
3
- <div class="interview">Interview: {{ index }} </div>
4
- Model: {{ interview.model.model }}
5
- <h1>Failing questions</h1>
6
- {% endif %}
7
- {% for question, exceptions in interview.exceptions.items() %}
8
- {% include 'interview_details.html' %}
9
- {% endfor %}
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)
@@ -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.37.dev5
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