edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.dev2__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 (194) hide show
  1. edsl/Base.py +169 -116
  2. edsl/__init__.py +14 -6
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +358 -146
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +88 -36
  7. edsl/agents/InvigilatorBase.py +59 -70
  8. edsl/agents/PromptConstructor.py +117 -219
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionOptionProcessor.py +172 -0
  11. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  12. edsl/agents/__init__.py +0 -1
  13. edsl/agents/prompt_helpers.py +3 -3
  14. edsl/config.py +22 -2
  15. edsl/conversation/car_buying.py +2 -1
  16. edsl/coop/CoopFunctionsMixin.py +15 -0
  17. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  18. edsl/coop/PriceFetcher.py +1 -1
  19. edsl/coop/coop.py +104 -42
  20. edsl/coop/utils.py +14 -14
  21. edsl/data/Cache.py +21 -14
  22. edsl/data/CacheEntry.py +12 -15
  23. edsl/data/CacheHandler.py +33 -12
  24. edsl/data/__init__.py +4 -3
  25. edsl/data_transfer_models.py +2 -1
  26. edsl/enums.py +20 -0
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +12 -0
  29. edsl/exceptions/inference_services.py +5 -0
  30. edsl/exceptions/questions.py +24 -6
  31. edsl/exceptions/scenarios.py +7 -0
  32. edsl/inference_services/AnthropicService.py +0 -3
  33. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  34. edsl/inference_services/AvailableModelFetcher.py +209 -0
  35. edsl/inference_services/AwsBedrock.py +0 -2
  36. edsl/inference_services/AzureAI.py +0 -2
  37. edsl/inference_services/GoogleService.py +2 -11
  38. edsl/inference_services/InferenceServiceABC.py +18 -85
  39. edsl/inference_services/InferenceServicesCollection.py +105 -80
  40. edsl/inference_services/MistralAIService.py +0 -3
  41. edsl/inference_services/OpenAIService.py +1 -4
  42. edsl/inference_services/PerplexityService.py +0 -3
  43. edsl/inference_services/ServiceAvailability.py +135 -0
  44. edsl/inference_services/TestService.py +11 -8
  45. edsl/inference_services/data_structures.py +62 -0
  46. edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
  47. edsl/jobs/Answers.py +1 -14
  48. edsl/jobs/FetchInvigilator.py +40 -0
  49. edsl/jobs/InterviewTaskManager.py +98 -0
  50. edsl/jobs/InterviewsConstructor.py +48 -0
  51. edsl/jobs/Jobs.py +102 -243
  52. edsl/jobs/JobsChecks.py +35 -10
  53. edsl/jobs/JobsComponentConstructor.py +189 -0
  54. edsl/jobs/JobsPrompts.py +5 -3
  55. edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
  56. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  57. edsl/jobs/RequestTokenEstimator.py +30 -0
  58. edsl/jobs/buckets/BucketCollection.py +44 -3
  59. edsl/jobs/buckets/TokenBucket.py +53 -21
  60. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  61. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  62. edsl/jobs/decorators.py +35 -0
  63. edsl/jobs/interviews/Interview.py +77 -380
  64. edsl/jobs/jobs_status_enums.py +9 -0
  65. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  66. edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
  67. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  68. edsl/jobs/tasks/TaskHistory.py +14 -15
  69. edsl/jobs/tasks/task_status_enum.py +0 -2
  70. edsl/language_models/ComputeCost.py +63 -0
  71. edsl/language_models/LanguageModel.py +137 -234
  72. edsl/language_models/ModelList.py +11 -13
  73. edsl/language_models/PriceManager.py +127 -0
  74. edsl/language_models/RawResponseHandler.py +106 -0
  75. edsl/language_models/ServiceDataSources.py +0 -0
  76. edsl/language_models/__init__.py +0 -1
  77. edsl/language_models/key_management/KeyLookup.py +63 -0
  78. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  79. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  80. edsl/language_models/key_management/__init__.py +0 -0
  81. edsl/language_models/key_management/models.py +131 -0
  82. edsl/language_models/registry.py +49 -59
  83. edsl/language_models/repair.py +2 -2
  84. edsl/language_models/utilities.py +5 -4
  85. edsl/notebooks/Notebook.py +19 -14
  86. edsl/notebooks/NotebookToLaTeX.py +142 -0
  87. edsl/prompts/Prompt.py +29 -39
  88. edsl/questions/AnswerValidatorMixin.py +47 -2
  89. edsl/questions/ExceptionExplainer.py +77 -0
  90. edsl/questions/HTMLQuestion.py +103 -0
  91. edsl/questions/LoopProcessor.py +149 -0
  92. edsl/questions/QuestionBase.py +37 -192
  93. edsl/questions/QuestionBaseGenMixin.py +52 -48
  94. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  95. edsl/questions/QuestionCheckBox.py +1 -1
  96. edsl/questions/QuestionExtract.py +1 -1
  97. edsl/questions/QuestionFreeText.py +1 -2
  98. edsl/questions/QuestionList.py +3 -5
  99. edsl/questions/QuestionMatrix.py +265 -0
  100. edsl/questions/QuestionMultipleChoice.py +66 -22
  101. edsl/questions/QuestionNumerical.py +1 -3
  102. edsl/questions/QuestionRank.py +6 -16
  103. edsl/questions/ResponseValidatorABC.py +37 -11
  104. edsl/questions/ResponseValidatorFactory.py +28 -0
  105. edsl/questions/SimpleAskMixin.py +4 -3
  106. edsl/questions/__init__.py +1 -0
  107. edsl/questions/derived/QuestionLinearScale.py +6 -3
  108. edsl/questions/derived/QuestionTopK.py +1 -1
  109. edsl/questions/descriptors.py +17 -3
  110. edsl/questions/question_registry.py +1 -1
  111. edsl/questions/templates/matrix/__init__.py +1 -0
  112. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  113. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  114. edsl/results/CSSParameterizer.py +1 -1
  115. edsl/results/Dataset.py +170 -7
  116. edsl/results/DatasetExportMixin.py +224 -302
  117. edsl/results/DatasetTree.py +28 -8
  118. edsl/results/MarkdownToDocx.py +122 -0
  119. edsl/results/MarkdownToPDF.py +111 -0
  120. edsl/results/Result.py +192 -206
  121. edsl/results/Results.py +120 -113
  122. edsl/results/ResultsExportMixin.py +2 -0
  123. edsl/results/Selector.py +23 -13
  124. edsl/results/TableDisplay.py +98 -171
  125. edsl/results/TextEditor.py +50 -0
  126. edsl/results/__init__.py +1 -1
  127. edsl/results/smart_objects.py +96 -0
  128. edsl/results/table_data_class.py +12 -0
  129. edsl/results/table_renderers.py +118 -0
  130. edsl/scenarios/ConstructDownloadLink.py +109 -0
  131. edsl/scenarios/DirectoryScanner.py +96 -0
  132. edsl/scenarios/DocumentChunker.py +102 -0
  133. edsl/scenarios/DocxScenario.py +16 -0
  134. edsl/scenarios/FileStore.py +118 -239
  135. edsl/scenarios/PdfExtractor.py +40 -0
  136. edsl/scenarios/Scenario.py +90 -193
  137. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  138. edsl/scenarios/ScenarioJoin.py +10 -6
  139. edsl/scenarios/ScenarioList.py +383 -240
  140. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  141. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  142. edsl/scenarios/ScenarioSelector.py +156 -0
  143. edsl/scenarios/__init__.py +1 -2
  144. edsl/scenarios/file_methods.py +85 -0
  145. edsl/scenarios/handlers/__init__.py +13 -0
  146. edsl/scenarios/handlers/csv.py +38 -0
  147. edsl/scenarios/handlers/docx.py +76 -0
  148. edsl/scenarios/handlers/html.py +37 -0
  149. edsl/scenarios/handlers/json.py +111 -0
  150. edsl/scenarios/handlers/latex.py +5 -0
  151. edsl/scenarios/handlers/md.py +51 -0
  152. edsl/scenarios/handlers/pdf.py +68 -0
  153. edsl/scenarios/handlers/png.py +39 -0
  154. edsl/scenarios/handlers/pptx.py +105 -0
  155. edsl/scenarios/handlers/py.py +294 -0
  156. edsl/scenarios/handlers/sql.py +313 -0
  157. edsl/scenarios/handlers/sqlite.py +149 -0
  158. edsl/scenarios/handlers/txt.py +33 -0
  159. edsl/study/ObjectEntry.py +1 -1
  160. edsl/study/SnapShot.py +1 -1
  161. edsl/study/Study.py +5 -12
  162. edsl/surveys/ConstructDAG.py +92 -0
  163. edsl/surveys/EditSurvey.py +221 -0
  164. edsl/surveys/InstructionHandler.py +100 -0
  165. edsl/surveys/MemoryManagement.py +72 -0
  166. edsl/surveys/Rule.py +5 -4
  167. edsl/surveys/RuleCollection.py +25 -27
  168. edsl/surveys/RuleManager.py +172 -0
  169. edsl/surveys/Simulator.py +75 -0
  170. edsl/surveys/Survey.py +199 -771
  171. edsl/surveys/SurveyCSS.py +20 -8
  172. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  173. edsl/surveys/SurveyToApp.py +141 -0
  174. edsl/surveys/__init__.py +4 -2
  175. edsl/surveys/descriptors.py +6 -2
  176. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  177. edsl/surveys/instructions/Instruction.py +4 -13
  178. edsl/surveys/instructions/InstructionCollection.py +11 -6
  179. edsl/templates/error_reporting/interview_details.html +1 -1
  180. edsl/templates/error_reporting/report.html +1 -1
  181. edsl/tools/plotting.py +1 -1
  182. edsl/utilities/PrettyList.py +56 -0
  183. edsl/utilities/is_notebook.py +18 -0
  184. edsl/utilities/is_valid_variable_name.py +11 -0
  185. edsl/utilities/remove_edsl_version.py +24 -0
  186. edsl/utilities/utilities.py +35 -23
  187. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
  188. edsl-0.1.39.dev2.dist-info/RECORD +352 -0
  189. edsl/language_models/KeyLookup.py +0 -30
  190. edsl/language_models/unused/ReplicateBase.py +0 -83
  191. edsl/results/ResultsDBMixin.py +0 -238
  192. edsl-0.1.39.dev1.dist-info/RECORD +0 -277
  193. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
  194. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,172 @@
1
+ from typing import Union, TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from edsl.questions.QuestionBase import QuestionBase
5
+
6
+ from edsl.surveys.Rule import Rule
7
+ from .base import RulePriority, EndOfSurvey
8
+ from edsl.exceptions.surveys import SurveyError, SurveyCreationError
9
+
10
+
11
+ class ValidatedString(str):
12
+ def __new__(cls, content):
13
+ if "<>" in content:
14
+ raise SurveyCreationError(
15
+ "The expression contains '<>', which is not allowed. You probably mean '!='."
16
+ )
17
+ return super().__new__(cls, content)
18
+
19
+
20
+ class RuleManager:
21
+ def __init__(self, survey):
22
+ self.survey = survey
23
+
24
+ def _get_question_index(
25
+ self, q: Union["QuestionBase", str, EndOfSurvey.__class__]
26
+ ) -> Union[int, EndOfSurvey.__class__]:
27
+ """Return the index of the question or EndOfSurvey object.
28
+
29
+ :param q: The question or question name to get the index of.
30
+
31
+ It can handle it if the user passes in the question name, the question object, or the EndOfSurvey object.
32
+
33
+ >>> from edsl.questions import QuestionFreeText
34
+ >>> from edsl import Survey
35
+ >>> s = Survey.example()
36
+ >>> s._get_question_index("q0")
37
+ 0
38
+
39
+ This doesnt' work with questions that don't exist:
40
+
41
+ >>> s._get_question_index("poop")
42
+ Traceback (most recent call last):
43
+ ...
44
+ edsl.exceptions.surveys.SurveyError: Question name poop not found in survey. The current question names are {'q0': 0, 'q1': 1, 'q2': 2}.
45
+ ...
46
+ """
47
+ if q == EndOfSurvey:
48
+ return EndOfSurvey
49
+ else:
50
+ question_name = q if isinstance(q, str) else q.question_name
51
+ if question_name not in self.survey.question_name_to_index:
52
+ raise SurveyError(
53
+ f"""Question name {question_name} not found in survey. The current question names are {self.survey.question_name_to_index}."""
54
+ )
55
+ return self.survey.question_name_to_index[question_name]
56
+
57
+ def _get_new_rule_priority(
58
+ self, question_index: int, before_rule: bool = False
59
+ ) -> int:
60
+ """Return the priority for the new rule.
61
+
62
+ :param question_index: The index of the question to add the rule to.
63
+ :param before_rule: Whether the rule is evaluated before the question is answered.
64
+
65
+ >>> from edsl import Survey
66
+ >>> s = Survey.example()
67
+ >>> RuleManager(s)._get_new_rule_priority(0)
68
+ 1
69
+ """
70
+ current_priorities = [
71
+ rule.priority
72
+ for rule in self.survey.rule_collection.applicable_rules(
73
+ question_index, before_rule
74
+ )
75
+ ]
76
+ if len(current_priorities) == 0:
77
+ return RulePriority.DEFAULT.value + 1
78
+
79
+ max_priority = max(current_priorities)
80
+ # newer rules take priority over older rules
81
+ new_priority = (
82
+ RulePriority.DEFAULT.value
83
+ if len(current_priorities) == 0
84
+ else max_priority + 1
85
+ )
86
+ return new_priority
87
+
88
+ def add_rule(
89
+ self,
90
+ question: Union["QuestionBase", str],
91
+ expression: str,
92
+ next_question: Union["QuestionBase", str, int],
93
+ before_rule: bool = False,
94
+ ) -> "Survey":
95
+ """
96
+ Add a rule to a Question of the Survey with the appropriate priority.
97
+
98
+ :param question: The question to add the rule to.
99
+ :param expression: The expression to evaluate.
100
+ :param next_question: The next question to go to if the rule is true.
101
+ :param before_rule: Whether the rule is evaluated before the question is answered.
102
+
103
+
104
+ - The last rule added for the question will have the highest priority.
105
+ - If there are no rules, the rule added gets priority -1.
106
+ """
107
+ question_index = self.survey._get_question_index(question) # Fix
108
+
109
+ # Might not have the name of the next question yet
110
+ if isinstance(next_question, int):
111
+ next_question_index = next_question
112
+ else:
113
+ next_question_index = self._get_question_index(next_question)
114
+
115
+ new_priority = self._get_new_rule_priority(question_index, before_rule) # fix
116
+
117
+ self.survey.rule_collection.add_rule(
118
+ Rule(
119
+ current_q=question_index,
120
+ expression=expression,
121
+ next_q=next_question_index,
122
+ question_name_to_index=self.survey.question_name_to_index,
123
+ priority=new_priority,
124
+ before_rule=before_rule,
125
+ )
126
+ )
127
+
128
+ return self.survey
129
+
130
+ def add_stop_rule(
131
+ self, question: Union["QuestionBase", str], expression: str
132
+ ) -> "Survey":
133
+ """Add a rule that stops the survey.
134
+ The rule is evaluated *after* the question is answered. If the rule is true, the survey ends.
135
+
136
+ :param question: The question to add the stop rule to.
137
+ :param expression: The expression to evaluate.
138
+
139
+ If this rule is true, the survey ends.
140
+
141
+ Here, answering "yes" to q0 ends the survey:
142
+
143
+ >>> from edsl import Survey
144
+ >>> s = Survey.example().add_stop_rule("q0", "q0 == 'yes'")
145
+ >>> s.next_question("q0", {"q0": "yes"})
146
+ EndOfSurvey
147
+
148
+ By comparison, answering "no" to q0 does not end the survey:
149
+
150
+ >>> s.next_question("q0", {"q0": "no"}).question_name
151
+ 'q1'
152
+
153
+ >>> s.add_stop_rule("q0", "q1 <> 'yes'")
154
+ Traceback (most recent call last):
155
+ ...
156
+ edsl.exceptions.surveys.SurveyCreationError: The expression contains '<>', which is not allowed. You probably mean '!='.
157
+ ...
158
+ """
159
+ expression = ValidatedString(expression)
160
+ prior_question_appears = False
161
+ for prior_question in self.survey.questions:
162
+ if prior_question.question_name in expression:
163
+ prior_question_appears = True
164
+
165
+ if not prior_question_appears:
166
+ import warnings
167
+
168
+ warnings.warn(
169
+ f"The expression {expression} does not contain any prior question names. This is probably a mistake."
170
+ )
171
+ self.survey.add_rule(question, expression, EndOfSurvey)
172
+ return self.survey
@@ -0,0 +1,75 @@
1
+ from typing import Callable
2
+
3
+
4
+ class Simulator:
5
+ def __init__(self, survey):
6
+ self.survey = survey
7
+
8
+ @classmethod
9
+ def random_survey(cls):
10
+ """Create a random survey."""
11
+ from edsl.questions import QuestionMultipleChoice, QuestionFreeText
12
+ from random import choice
13
+ from edsl.surveys.Survey import Survey
14
+
15
+ num_questions = 10
16
+ questions = []
17
+ for i in range(num_questions):
18
+ if choice([True, False]):
19
+ q = QuestionMultipleChoice(
20
+ question_text="nothing",
21
+ question_name="q_" + str(i),
22
+ question_options=list(range(3)),
23
+ )
24
+ questions.append(q)
25
+ else:
26
+ questions.append(
27
+ QuestionFreeText(
28
+ question_text="nothing", question_name="q_" + str(i)
29
+ )
30
+ )
31
+ s = Survey(questions)
32
+ start_index = choice(range(num_questions - 1))
33
+ end_index = choice(range(start_index + 1, 10))
34
+ s = s.add_rule(f"q_{start_index}", "True", f"q_{end_index}")
35
+ question_to_delete = choice(range(num_questions))
36
+ s.delete_question(f"q_{question_to_delete}")
37
+ return s
38
+
39
+ def simulate(self) -> dict:
40
+ """Simulate the survey and return the answers."""
41
+ i = self.survey.gen_path_through_survey()
42
+ q = next(i)
43
+ num_passes = 0
44
+ while True:
45
+ num_passes += 1
46
+ try:
47
+ answer = q._simulate_answer()
48
+ q = i.send({q.question_name: answer["answer"]})
49
+ except StopIteration:
50
+ break
51
+
52
+ if num_passes > 100:
53
+ print("Too many passes.")
54
+ raise Exception("Too many passes.")
55
+ return self.survey.answers
56
+
57
+ def create_agent(self) -> "Agent":
58
+ """Create an agent from the simulated answers."""
59
+ answers_dict = self.survey.simulate()
60
+ from edsl.agents.Agent import Agent
61
+
62
+ def construct_answer_dict_function(traits: dict) -> Callable:
63
+ def func(self, question: "QuestionBase", scenario=None):
64
+ return traits.get(question.question_name, None)
65
+
66
+ return func
67
+
68
+ return Agent(traits=answers_dict).add_direct_question_answering_method(
69
+ construct_answer_dict_function(answers_dict)
70
+ )
71
+
72
+ def simulate_results(self) -> "Results":
73
+ """Simulate the survey and return the results."""
74
+ a = self.create_agent()
75
+ return self.survey.by([a]).run()