edsl 0.1.38.dev4__py3-none-any.whl → 0.1.39__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 (212) hide show
  1. edsl/Base.py +197 -116
  2. edsl/__init__.py +15 -7
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +351 -147
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +101 -50
  7. edsl/agents/InvigilatorBase.py +62 -70
  8. edsl/agents/PromptConstructor.py +143 -225
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  11. edsl/agents/__init__.py +0 -1
  12. edsl/agents/prompt_helpers.py +3 -3
  13. edsl/agents/question_option_processor.py +172 -0
  14. edsl/auto/AutoStudy.py +18 -5
  15. edsl/auto/StageBase.py +53 -40
  16. edsl/auto/StageQuestions.py +2 -1
  17. edsl/auto/utilities.py +0 -6
  18. edsl/config.py +22 -2
  19. edsl/conversation/car_buying.py +2 -1
  20. edsl/coop/CoopFunctionsMixin.py +15 -0
  21. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  22. edsl/coop/PriceFetcher.py +1 -1
  23. edsl/coop/coop.py +125 -47
  24. edsl/coop/utils.py +14 -14
  25. edsl/data/Cache.py +45 -27
  26. edsl/data/CacheEntry.py +12 -15
  27. edsl/data/CacheHandler.py +31 -12
  28. edsl/data/RemoteCacheSync.py +154 -46
  29. edsl/data/__init__.py +4 -3
  30. edsl/data_transfer_models.py +2 -1
  31. edsl/enums.py +27 -0
  32. edsl/exceptions/__init__.py +50 -50
  33. edsl/exceptions/agents.py +12 -0
  34. edsl/exceptions/inference_services.py +5 -0
  35. edsl/exceptions/questions.py +24 -6
  36. edsl/exceptions/scenarios.py +7 -0
  37. edsl/inference_services/AnthropicService.py +38 -19
  38. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  39. edsl/inference_services/AvailableModelFetcher.py +215 -0
  40. edsl/inference_services/AwsBedrock.py +0 -2
  41. edsl/inference_services/AzureAI.py +0 -2
  42. edsl/inference_services/GoogleService.py +7 -12
  43. edsl/inference_services/InferenceServiceABC.py +18 -85
  44. edsl/inference_services/InferenceServicesCollection.py +120 -79
  45. edsl/inference_services/MistralAIService.py +0 -3
  46. edsl/inference_services/OpenAIService.py +47 -35
  47. edsl/inference_services/PerplexityService.py +0 -3
  48. edsl/inference_services/ServiceAvailability.py +135 -0
  49. edsl/inference_services/TestService.py +11 -10
  50. edsl/inference_services/TogetherAIService.py +5 -3
  51. edsl/inference_services/data_structures.py +134 -0
  52. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  53. edsl/jobs/Answers.py +1 -14
  54. edsl/jobs/FetchInvigilator.py +47 -0
  55. edsl/jobs/InterviewTaskManager.py +98 -0
  56. edsl/jobs/InterviewsConstructor.py +50 -0
  57. edsl/jobs/Jobs.py +356 -431
  58. edsl/jobs/JobsChecks.py +35 -10
  59. edsl/jobs/JobsComponentConstructor.py +189 -0
  60. edsl/jobs/JobsPrompts.py +6 -4
  61. edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
  62. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  63. edsl/jobs/RequestTokenEstimator.py +30 -0
  64. edsl/jobs/async_interview_runner.py +138 -0
  65. edsl/jobs/buckets/BucketCollection.py +44 -3
  66. edsl/jobs/buckets/TokenBucket.py +53 -21
  67. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  68. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  69. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  70. edsl/jobs/data_structures.py +120 -0
  71. edsl/jobs/decorators.py +35 -0
  72. edsl/jobs/interviews/Interview.py +143 -408
  73. edsl/jobs/jobs_status_enums.py +9 -0
  74. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  75. edsl/jobs/results_exceptions_handler.py +98 -0
  76. edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
  77. edsl/jobs/runners/JobsRunnerStatus.py +133 -165
  78. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  79. edsl/jobs/tasks/TaskHistory.py +38 -18
  80. edsl/jobs/tasks/task_status_enum.py +0 -2
  81. edsl/language_models/ComputeCost.py +63 -0
  82. edsl/language_models/LanguageModel.py +194 -236
  83. edsl/language_models/ModelList.py +28 -19
  84. edsl/language_models/PriceManager.py +127 -0
  85. edsl/language_models/RawResponseHandler.py +106 -0
  86. edsl/language_models/ServiceDataSources.py +0 -0
  87. edsl/language_models/__init__.py +1 -2
  88. edsl/language_models/key_management/KeyLookup.py +63 -0
  89. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  90. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  91. edsl/language_models/key_management/__init__.py +0 -0
  92. edsl/language_models/key_management/models.py +131 -0
  93. edsl/language_models/model.py +256 -0
  94. edsl/language_models/repair.py +2 -2
  95. edsl/language_models/utilities.py +5 -4
  96. edsl/notebooks/Notebook.py +19 -14
  97. edsl/notebooks/NotebookToLaTeX.py +142 -0
  98. edsl/prompts/Prompt.py +29 -39
  99. edsl/questions/ExceptionExplainer.py +77 -0
  100. edsl/questions/HTMLQuestion.py +103 -0
  101. edsl/questions/QuestionBase.py +68 -214
  102. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  103. edsl/questions/QuestionBudget.py +1 -1
  104. edsl/questions/QuestionCheckBox.py +3 -3
  105. edsl/questions/QuestionExtract.py +5 -7
  106. edsl/questions/QuestionFreeText.py +2 -3
  107. edsl/questions/QuestionList.py +10 -18
  108. edsl/questions/QuestionMatrix.py +265 -0
  109. edsl/questions/QuestionMultipleChoice.py +67 -23
  110. edsl/questions/QuestionNumerical.py +2 -4
  111. edsl/questions/QuestionRank.py +7 -17
  112. edsl/questions/SimpleAskMixin.py +4 -3
  113. edsl/questions/__init__.py +2 -1
  114. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
  115. edsl/questions/data_structures.py +20 -0
  116. edsl/questions/derived/QuestionLinearScale.py +6 -3
  117. edsl/questions/derived/QuestionTopK.py +1 -1
  118. edsl/questions/descriptors.py +17 -3
  119. edsl/questions/loop_processor.py +149 -0
  120. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
  121. edsl/questions/question_registry.py +1 -1
  122. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
  123. edsl/questions/response_validator_factory.py +34 -0
  124. edsl/questions/templates/matrix/__init__.py +1 -0
  125. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  126. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  127. edsl/results/CSSParameterizer.py +1 -1
  128. edsl/results/Dataset.py +170 -7
  129. edsl/results/DatasetExportMixin.py +168 -305
  130. edsl/results/DatasetTree.py +28 -8
  131. edsl/results/MarkdownToDocx.py +122 -0
  132. edsl/results/MarkdownToPDF.py +111 -0
  133. edsl/results/Result.py +298 -206
  134. edsl/results/Results.py +149 -131
  135. edsl/results/ResultsExportMixin.py +2 -0
  136. edsl/results/TableDisplay.py +98 -171
  137. edsl/results/TextEditor.py +50 -0
  138. edsl/results/__init__.py +1 -1
  139. edsl/results/file_exports.py +252 -0
  140. edsl/results/{Selector.py → results_selector.py} +23 -13
  141. edsl/results/smart_objects.py +96 -0
  142. edsl/results/table_data_class.py +12 -0
  143. edsl/results/table_renderers.py +118 -0
  144. edsl/scenarios/ConstructDownloadLink.py +109 -0
  145. edsl/scenarios/DocumentChunker.py +102 -0
  146. edsl/scenarios/DocxScenario.py +16 -0
  147. edsl/scenarios/FileStore.py +150 -239
  148. edsl/scenarios/PdfExtractor.py +40 -0
  149. edsl/scenarios/Scenario.py +90 -193
  150. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  151. edsl/scenarios/ScenarioList.py +415 -244
  152. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  153. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  154. edsl/scenarios/__init__.py +1 -2
  155. edsl/scenarios/directory_scanner.py +96 -0
  156. edsl/scenarios/file_methods.py +85 -0
  157. edsl/scenarios/handlers/__init__.py +13 -0
  158. edsl/scenarios/handlers/csv.py +49 -0
  159. edsl/scenarios/handlers/docx.py +76 -0
  160. edsl/scenarios/handlers/html.py +37 -0
  161. edsl/scenarios/handlers/json.py +111 -0
  162. edsl/scenarios/handlers/latex.py +5 -0
  163. edsl/scenarios/handlers/md.py +51 -0
  164. edsl/scenarios/handlers/pdf.py +68 -0
  165. edsl/scenarios/handlers/png.py +39 -0
  166. edsl/scenarios/handlers/pptx.py +105 -0
  167. edsl/scenarios/handlers/py.py +294 -0
  168. edsl/scenarios/handlers/sql.py +313 -0
  169. edsl/scenarios/handlers/sqlite.py +149 -0
  170. edsl/scenarios/handlers/txt.py +33 -0
  171. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
  172. edsl/scenarios/scenario_selector.py +156 -0
  173. edsl/study/ObjectEntry.py +1 -1
  174. edsl/study/SnapShot.py +1 -1
  175. edsl/study/Study.py +5 -12
  176. edsl/surveys/ConstructDAG.py +92 -0
  177. edsl/surveys/EditSurvey.py +221 -0
  178. edsl/surveys/InstructionHandler.py +100 -0
  179. edsl/surveys/MemoryManagement.py +72 -0
  180. edsl/surveys/Rule.py +5 -4
  181. edsl/surveys/RuleCollection.py +25 -27
  182. edsl/surveys/RuleManager.py +172 -0
  183. edsl/surveys/Simulator.py +75 -0
  184. edsl/surveys/Survey.py +270 -791
  185. edsl/surveys/SurveyCSS.py +20 -8
  186. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  187. edsl/surveys/SurveyToApp.py +141 -0
  188. edsl/surveys/__init__.py +4 -2
  189. edsl/surveys/descriptors.py +6 -2
  190. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  191. edsl/surveys/instructions/Instruction.py +4 -13
  192. edsl/surveys/instructions/InstructionCollection.py +11 -6
  193. edsl/templates/error_reporting/interview_details.html +1 -1
  194. edsl/templates/error_reporting/report.html +1 -1
  195. edsl/tools/plotting.py +1 -1
  196. edsl/utilities/PrettyList.py +56 -0
  197. edsl/utilities/is_notebook.py +18 -0
  198. edsl/utilities/is_valid_variable_name.py +11 -0
  199. edsl/utilities/remove_edsl_version.py +24 -0
  200. edsl/utilities/utilities.py +35 -23
  201. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
  202. edsl-0.1.39.dist-info/RECORD +358 -0
  203. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  204. edsl/language_models/KeyLookup.py +0 -30
  205. edsl/language_models/registry.py +0 -190
  206. edsl/language_models/unused/ReplicateBase.py +0 -83
  207. edsl/results/ResultsDBMixin.py +0 -238
  208. edsl-0.1.38.dev4.dist-info/RECORD +0 -277
  209. /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
  210. /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
  211. /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
  212. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/LICENSE +0 -0
@@ -0,0 +1,137 @@
1
+ from jinja2 import Environment, meta
2
+ from typing import Any, Set, TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from edsl.agents.PromptConstructor import PromptConstructor
6
+ from edsl.scenarios.Scenario import Scenario
7
+
8
+
9
+ class QuestionTemplateReplacementsBuilder:
10
+ def __init__(self, prompt_constructor: "PromptConstructor"):
11
+ self.prompt_constructor = prompt_constructor
12
+
13
+ def question_file_keys(self):
14
+ question_text = self.prompt_constructor.question.question_text
15
+ file_keys = self._find_file_keys(self.prompt_constructor.scenario)
16
+ return self._extract_file_keys_from_question_text(question_text, file_keys)
17
+
18
+ def scenario_file_keys(self):
19
+ return self._find_file_keys(self.prompt_constructor.scenario)
20
+
21
+ def get_jinja2_variables(template_str: str) -> Set[str]:
22
+ """
23
+ Extracts all variable names from a Jinja2 template using Jinja2's built-in parsing.
24
+
25
+ Args:
26
+ template_str (str): The Jinja2 template string
27
+
28
+ Returns:
29
+ Set[str]: A set of variable names found in the template
30
+ """
31
+ env = Environment()
32
+ ast = env.parse(template_str)
33
+ return meta.find_undeclared_variables(ast)
34
+
35
+ @staticmethod
36
+ def _find_file_keys(scenario: "Scenario") -> list:
37
+ """We need to find all the keys in the scenario that refer to FileStore objects.
38
+ These will be used to append to the prompt a list of files that are part of the scenario.
39
+
40
+ >>> from edsl import Scenario
41
+ >>> from edsl.scenarios.FileStore import FileStore
42
+ >>> import tempfile
43
+ >>> with tempfile.NamedTemporaryFile() as f:
44
+ ... _ = f.write(b"Hello, world!")
45
+ ... _ = f.seek(0)
46
+ ... fs = FileStore(f.name)
47
+ ... scenario = Scenario({"fs_file": fs, 'a': 1})
48
+ ... QuestionTemplateReplacementsBuilder._find_file_keys(scenario)
49
+ ['fs_file']
50
+ """
51
+ from edsl.scenarios.FileStore import FileStore
52
+
53
+ file_entries = []
54
+ for key, value in scenario.items():
55
+ if isinstance(value, FileStore):
56
+ file_entries.append(key)
57
+ return file_entries
58
+
59
+ @staticmethod
60
+ def _extract_file_keys_from_question_text(
61
+ question_text: str, scenario_file_keys: list
62
+ ) -> list:
63
+ """
64
+ Extracts the file keys from a question text.
65
+
66
+ >>> from edsl import Scenario
67
+ >>> from edsl.scenarios.FileStore import FileStore
68
+ >>> import tempfile
69
+ >>> with tempfile.NamedTemporaryFile() as f:
70
+ ... _ = f.write(b"Hello, world!")
71
+ ... _ = f.seek(0)
72
+ ... fs = FileStore(f.name)
73
+ ... scenario = Scenario({"fs_file": fs, 'a': 1})
74
+ ... QuestionTemplateReplacementsBuilder._extract_file_keys_from_question_text("{{ fs_file }}", ['fs_file'])
75
+ ['fs_file']
76
+ """
77
+ variables = QuestionTemplateReplacementsBuilder.get_jinja2_variables(
78
+ question_text
79
+ )
80
+ question_file_keys = []
81
+ for var in variables:
82
+ if var in scenario_file_keys:
83
+ question_file_keys.append(var)
84
+ return question_file_keys
85
+
86
+ def _scenario_replacements(self) -> dict[str, Any]:
87
+ # File references dictionary
88
+ file_refs = {key: f"<see file {key}>" for key in self.scenario_file_keys()}
89
+
90
+ # Scenario items excluding file keys
91
+ scenario_items = {
92
+ k: v
93
+ for k, v in self.prompt_constructor.scenario.items()
94
+ if k not in self.scenario_file_keys()
95
+ }
96
+ return {**file_refs, **scenario_items}
97
+
98
+ @staticmethod
99
+ def _question_data_replacements(
100
+ question: dict, question_data: dict
101
+ ) -> dict[str, Any]:
102
+ """Builds a dictionary of replacement values for rendering a prompt by combining multiple data sources.
103
+
104
+ >>> from edsl import QuestionMultipleChoice
105
+ >>> q = QuestionMultipleChoice(question_text="Do you like school?", question_name = "q0", question_options = ["yes", "no"])
106
+ >>> QuestionTemplateReplacementsBuilder._question_data_replacements(q, q.data)
107
+ {'use_code': False, 'include_comment': True, 'question_name': 'q0', 'question_text': 'Do you like school?', 'question_options': ['yes', 'no']}
108
+
109
+ """
110
+ question_settings = {
111
+ "use_code": getattr(question, "_use_code", True),
112
+ "include_comment": getattr(question, "_include_comment", False),
113
+ }
114
+ return {**question_settings, **question_data}
115
+
116
+ def build_replacement_dict(self, question_data: dict) -> dict[str, Any]:
117
+ """Builds a dictionary of replacement values for rendering a prompt by combining multiple data sources."""
118
+ rpl = {}
119
+ rpl["scenario"] = self._scenario_replacements()
120
+ rpl["question"] = self._question_data_replacements(
121
+ self.prompt_constructor.question, question_data
122
+ )
123
+ rpl["prior_answers"] = self.prompt_constructor.prior_answers_dict()
124
+ rpl["agent"] = {"agent": self.prompt_constructor.agent}
125
+
126
+ # Combine all dictionaries using dict.update() for clarity
127
+ replacement_dict = {}
128
+ for r in rpl.values():
129
+ replacement_dict.update(r)
130
+
131
+ return replacement_dict
132
+
133
+
134
+ if __name__ == "__main__":
135
+ import doctest
136
+
137
+ doctest.testmod()
edsl/agents/__init__.py CHANGED
@@ -1,3 +1,2 @@
1
1
  from edsl.agents.Agent import Agent
2
2
  from edsl.agents.AgentList import AgentList
3
- from edsl.agents.InvigilatorBase import InvigilatorBase
@@ -1,7 +1,7 @@
1
1
  import enum
2
2
  from typing import Dict, Optional
3
3
  from collections import UserList
4
- from edsl.prompts import Prompt
4
+ from edsl.prompts.Prompt import Prompt
5
5
 
6
6
 
7
7
  class PromptComponent(enum.Enum):
@@ -12,14 +12,14 @@ class PromptComponent(enum.Enum):
12
12
 
13
13
 
14
14
  class PromptList(UserList):
15
- separator = Prompt(" ")
15
+ separator = Prompt("")
16
16
 
17
17
  def reduce(self):
18
18
  """Reduce the list of prompts to a single prompt.
19
19
 
20
20
  >>> p = PromptList([Prompt("You are a happy-go lucky agent."), Prompt("You are an agent with the following persona: {'age': 22, 'hair': 'brown', 'height': 5.5}")])
21
21
  >>> p.reduce()
22
- Prompt(text=\"""You are a happy-go lucky agent. You are an agent with the following persona: {'age': 22, 'hair': 'brown', 'height': 5.5}\""")
22
+ Prompt(text=\"""You are a happy-go lucky agent.You are an agent with the following persona: {'age': 22, 'hair': 'brown', 'height': 5.5}\""")
23
23
 
24
24
  """
25
25
  p = self[0]
@@ -0,0 +1,172 @@
1
+ from jinja2 import Environment, meta
2
+ from typing import List, Optional, Union
3
+
4
+
5
+ class QuestionOptionProcessor:
6
+ """
7
+ Class that manages the processing of question options.
8
+ These can be provided directly, as a template string, or fetched from prior answers or the scenario.
9
+ """
10
+
11
+ def __init__(self, prompt_constructor):
12
+ self.prompt_constructor = prompt_constructor
13
+
14
+ @staticmethod
15
+ def _get_default_options() -> list:
16
+ """Return default placeholder options."""
17
+ return [f"<< Option {i} - Placeholder >>" for i in range(1, 4)]
18
+
19
+ @staticmethod
20
+ def _parse_template_variable(template_str: str) -> str:
21
+ """
22
+ Extract the variable name from a template string.
23
+
24
+ Args:
25
+ template_str (str): Jinja template string
26
+
27
+ Returns:
28
+ str: Name of the first undefined variable in the template
29
+
30
+ >>> QuestionOptionProcessor._parse_template_variable("Here are some {{ options }}")
31
+ 'options'
32
+ >>> QuestionOptionProcessor._parse_template_variable("Here are some {{ options }} and {{ other }}")
33
+ Traceback (most recent call last):
34
+ ...
35
+ ValueError: Multiple variables found in template string
36
+ >>> QuestionOptionProcessor._parse_template_variable("Here are some")
37
+ Traceback (most recent call last):
38
+ ...
39
+ ValueError: No variables found in template string
40
+ """
41
+ env = Environment()
42
+ parsed_content = env.parse(template_str)
43
+ undeclared_variables = list(meta.find_undeclared_variables(parsed_content))
44
+ if not undeclared_variables:
45
+ raise ValueError("No variables found in template string")
46
+ if len(undeclared_variables) > 1:
47
+ raise ValueError("Multiple variables found in template string")
48
+ return undeclared_variables[0]
49
+
50
+ @staticmethod
51
+ def _get_options_from_scenario(
52
+ scenario: dict, option_key: str
53
+ ) -> Union[list, None]:
54
+ """
55
+ Try to get options from scenario data.
56
+
57
+ >>> from edsl import Scenario
58
+ >>> scenario = Scenario({"options": ["Option 1", "Option 2"]})
59
+ >>> QuestionOptionProcessor._get_options_from_scenario(scenario, "options")
60
+ ['Option 1', 'Option 2']
61
+
62
+
63
+ Returns:
64
+ list | None: List of options if found in scenario, None otherwise
65
+ """
66
+ scenario_options = scenario.get(option_key)
67
+ return scenario_options if isinstance(scenario_options, list) else None
68
+
69
+ @staticmethod
70
+ def _get_options_from_prior_answers(
71
+ prior_answers: dict, option_key: str
72
+ ) -> Union[list, None]:
73
+ """
74
+ Try to get options from prior answers.
75
+
76
+ prior_answers (dict): Dictionary of prior answers
77
+ option_key (str): Key to look up in prior answers
78
+
79
+ >>> from edsl import QuestionList as Q
80
+ >>> q = Q.example()
81
+ >>> q.answer = ["Option 1", "Option 2"]
82
+ >>> prior_answers = {"options": q}
83
+ >>> QuestionOptionProcessor._get_options_from_prior_answers(prior_answers, "options")
84
+ ['Option 1', 'Option 2']
85
+ >>> QuestionOptionProcessor._get_options_from_prior_answers(prior_answers, "wrong_key") is None
86
+ True
87
+
88
+ Returns:
89
+ list | None: List of options if found in prior answers, None otherwise
90
+ """
91
+ prior_answer = prior_answers.get(option_key)
92
+ if prior_answer and hasattr(prior_answer, "answer"):
93
+ if isinstance(prior_answer.answer, list):
94
+ return prior_answer.answer
95
+ return None
96
+
97
+ def get_question_options(self, question_data: dict) -> list:
98
+ """
99
+ Extract and process question options from question data.
100
+
101
+ Args:
102
+ question_data (dict): Dictionary containing question configuration
103
+
104
+ Returns:
105
+ list: List of question options. Returns default placeholders if no valid options found.
106
+
107
+ >>> class MockPromptConstructor:
108
+ ... pass
109
+ >>> mpc = MockPromptConstructor()
110
+ >>> from edsl import Scenario
111
+ >>> mpc.scenario = Scenario({"options": ["Option 1", "Option 2"]})
112
+ >>> processor = QuestionOptionProcessor(mpc)
113
+
114
+ The basic case where options are directly provided:
115
+
116
+ >>> question_data = {"question_options": ["Option 1", "Option 2"]}
117
+ >>> processor.get_question_options(question_data)
118
+ ['Option 1', 'Option 2']
119
+
120
+ The case where options are provided as a template string:
121
+
122
+ >>> question_data = {"question_options": "{{ options }}"}
123
+ >>> processor.get_question_options(question_data)
124
+ ['Option 1', 'Option 2']
125
+
126
+ The case where there is a templace string but it's in the prior answers:
127
+
128
+ >>> class MockQuestion:
129
+ ... pass
130
+ >>> q0 = MockQuestion()
131
+ >>> q0.answer = ["Option 1", "Option 2"]
132
+ >>> mpc.prior_answers_dict = lambda: {'q0': q0}
133
+ >>> processor = QuestionOptionProcessor(mpc)
134
+ >>> question_data = {"question_options": "{{ q0 }}"}
135
+ >>> processor.get_question_options(question_data)
136
+ ['Option 1', 'Option 2']
137
+
138
+ The case we're no options are found:
139
+ >>> processor.get_question_options({"question_options": "{{ poop }}"})
140
+ ['<< Option 1 - Placeholder >>', '<< Option 2 - Placeholder >>', '<< Option 3 - Placeholder >>']
141
+
142
+ """
143
+ options_entry = question_data.get("question_options")
144
+
145
+ # If not a template string, return as is or default
146
+ if not isinstance(options_entry, str):
147
+ return options_entry if options_entry else self._get_default_options()
148
+
149
+ # Parse template to get variable name
150
+ option_key = self._parse_template_variable(options_entry)
151
+
152
+ # Try getting options from scenario
153
+ scenario_options = self._get_options_from_scenario(
154
+ self.prompt_constructor.scenario, option_key
155
+ )
156
+ if scenario_options:
157
+ return scenario_options
158
+
159
+ # Try getting options from prior answers
160
+ prior_answer_options = self._get_options_from_prior_answers(
161
+ self.prompt_constructor.prior_answers_dict(), option_key
162
+ )
163
+ if prior_answer_options:
164
+ return prior_answer_options
165
+
166
+ return self._get_default_options()
167
+
168
+
169
+ if __name__ == "__main__":
170
+ import doctest
171
+
172
+ doctest.testmod()
edsl/auto/AutoStudy.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from typing import Optional, TYPE_CHECKING
2
2
 
3
3
  from edsl import Model
4
4
  from edsl.auto.StageQuestions import StageQuestions
@@ -11,10 +11,12 @@ from edsl.auto.StagePersonaDimensionValueRanges import (
11
11
  from edsl.auto.StageLabelQuestions import StageLabelQuestions
12
12
  from edsl.auto.StageGenerateSurvey import StageGenerateSurvey
13
13
 
14
- # from edsl.auto.StageBase import gen_pipeline
15
-
16
14
  from edsl.auto.utilities import agent_generator, create_agents, gen_pipeline
17
15
 
16
+ if TYPE_CHECKING:
17
+ from edsl.surveys.Survey import Survey
18
+ from edsl.agents.AgentList import AgentList
19
+
18
20
 
19
21
  class AutoStudy:
20
22
  def __init__(
@@ -24,8 +26,10 @@ class AutoStudy:
24
26
  model: Optional["Model"] = None,
25
27
  survey: Optional["Survey"] = None,
26
28
  agent_list: Optional["AgentList"] = None,
27
- default_num_agents=11,
29
+ default_num_agents: int = 11,
28
30
  ):
31
+ """AutoStudy class for generating surveys and agents."""
32
+
29
33
  self.overall_question = overall_question
30
34
  self.population = population
31
35
  self._survey = survey
@@ -36,6 +40,15 @@ class AutoStudy:
36
40
  self.default_num_agents = default_num_agents
37
41
  self.model = model or Model()
38
42
 
43
+ def to_dict(self):
44
+ return {
45
+ "overall_question": self.overall_question,
46
+ "population": self.population,
47
+ "survey": self.survey.to_dict(),
48
+ "persona_mapping": self.persona_mapping.to_dict(),
49
+ "results": self.results.to_dict(),
50
+ }
51
+
39
52
  @property
40
53
  def survey(self):
41
54
  if self._survey is None:
@@ -111,7 +124,7 @@ class AutoStudy:
111
124
 
112
125
 
113
126
  if __name__ == "__main__":
114
- overall_question = "Should online platforms be regulated with respect to selling electric scooters?"
127
+ overall_question = "I have an open source Python library for working with LLMs. What are some ways we can market this to others?"
115
128
  auto_study = AutoStudy(overall_question, population="US Adults")
116
129
 
117
130
  results = auto_study.results
edsl/auto/StageBase.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ import json
2
3
  from typing import Dict, List, Any, TypeVar, Generator, Dict, Callable
3
4
  from dataclasses import dataclass, field, KW_ONLY, fields, asdict
4
5
  import textwrap
@@ -35,6 +36,13 @@ class FlowDataBase:
35
36
  sent_to_stage_name: str = field(default_factory=str)
36
37
  came_from_stage_name: str = field(default_factory=str)
37
38
 
39
+ def to_dict(self):
40
+ return asdict(self)
41
+
42
+ @classmethod
43
+ def from_dict(cls, data: dict):
44
+ return cls(**data)
45
+
38
46
  def __getitem__(self, key):
39
47
  """Allows dictionary-style getting."""
40
48
  return getattr(self, key)
@@ -126,6 +134,10 @@ class StageBase(ABC):
126
134
  else:
127
135
  self.next_stage = None
128
136
 
137
+ @classmethod
138
+ def function_parameters(self):
139
+ return fields(self.input)
140
+
129
141
  @classmethod
130
142
  def func(cls, **kwargs):
131
143
  "This provides a shortcut for running a stage by passing keyword arguments to the input function."
@@ -173,58 +185,59 @@ class StageBase(ABC):
173
185
 
174
186
 
175
187
  if __name__ == "__main__":
176
- try:
188
+ pass
189
+ # try:
177
190
 
178
- class StageMissing(StageBase):
179
- def handle_data(self, data):
180
- return data
191
+ # class StageMissing(StageBase):
192
+ # def handle_data(self, data):
193
+ # return data
181
194
 
182
- except NotImplementedError as e:
183
- print(e)
184
- else:
185
- raise Exception("Should have raised NotImplementedError")
195
+ # except NotImplementedError as e:
196
+ # print(e)
197
+ # else:
198
+ # raise Exception("Should have raised NotImplementedError")
186
199
 
187
- try:
200
+ # try:
188
201
 
189
- class StageMissingInput(StageBase):
190
- output = FlowDataBase
202
+ # class StageMissingInput(StageBase):
203
+ # output = FlowDataBase
191
204
 
192
- except NotImplementedError as e:
193
- print(e)
205
+ # except NotImplementedError as e:
206
+ # print(e)
194
207
 
195
- else:
196
- raise Exception("Should have raised NotImplementedError")
208
+ # else:
209
+ # raise Exception("Should have raised NotImplementedError")
197
210
 
198
- @dataclass
199
- class MockInputOutput(FlowDataBase):
200
- text: str
211
+ # @dataclass
212
+ # class MockInputOutput(FlowDataBase):
213
+ # text: str
201
214
 
202
- class StageTest(StageBase):
203
- input = MockInputOutput
204
- output = MockInputOutput
215
+ # class StageTest(StageBase):
216
+ # input = MockInputOutput
217
+ # output = MockInputOutput
205
218
 
206
- def handle_data(self, data):
207
- return self.output(text=data["text"] + "processed")
219
+ # def handle_data(self, data):
220
+ # return self.output(text=data["text"] + "processed")
208
221
 
209
- result = StageTest().process(MockInputOutput(text="Hello world!"))
210
- print(result.text)
222
+ # result = StageTest().process(MockInputOutput(text="Hello world!"))
223
+ # print(result.text)
211
224
 
212
- pipeline = StageTest(next_stage=StageTest(next_stage=StageTest()))
213
- result = pipeline.process(MockInputOutput(text="Hello world!"))
214
- print(result.text)
225
+ # pipeline = StageTest(next_stage=StageTest(next_stage=StageTest()))
226
+ # result = pipeline.process(MockInputOutput(text="Hello world!"))
227
+ # print(result.text)
215
228
 
216
- class BadMockInput(FlowDataBase):
217
- text: str
218
- other: str
229
+ # class BadMockInput(FlowDataBase):
230
+ # text: str
231
+ # other: str
219
232
 
220
- class StageBad(StageBase):
221
- input = BadMockInput
222
- output = BadMockInput
233
+ # class StageBad(StageBase):
234
+ # input = BadMockInput
235
+ # output = BadMockInput
223
236
 
224
- def handle_data(self, data):
225
- return self.output(text=data["text"] + "processed")
237
+ # def handle_data(self, data):
238
+ # return self.output(text=data["text"] + "processed")
226
239
 
227
- try:
228
- pipeline = StageTest(next_stage=StageBad(next_stage=StageTest()))
229
- except ExceptionPipesDoNotFit as e:
230
- print(e)
240
+ # try:
241
+ # pipeline = StageTest(next_stage=StageBad(next_stage=StageTest()))
242
+ # except ExceptionPipesDoNotFit as e:
243
+ # print(e)
@@ -68,6 +68,7 @@ if __name__ == "__main__":
68
68
  population="Consumers",
69
69
  )
70
70
  )
71
- StageQuestions.func(
71
+
72
+ results = StageQuestions.func(
72
73
  overall_question="Why aren't my students studying more?", population="Tech"
73
74
  )
edsl/auto/utilities.py CHANGED
@@ -88,12 +88,6 @@ def agent_eligibility(
88
88
  q_eligibility(model=model, questions=questions, persona=persona, cache=cache)
89
89
  == "Yes"
90
90
  )
91
- # results = (
92
- # q.by(model)
93
- # .by(Scenario({"questions": questions, "persona": persona}))
94
- # .run(cache=cache)
95
- # )
96
- # return results.select("eligibility").first() == "Yes"
97
91
 
98
92
 
99
93
  def gen_agent_traits(dimension_dict: dict, seed_value: Optional[str] = None):
edsl/config.py CHANGED
@@ -1,12 +1,16 @@
1
1
  """This module provides a Config class that loads environment variables from a .env file and sets them as class attributes."""
2
2
 
3
3
  import os
4
+ import platformdirs
4
5
  from dotenv import load_dotenv, find_dotenv
5
- from edsl.exceptions import (
6
+ from edsl.exceptions.configuration import (
6
7
  InvalidEnvironmentVariableError,
7
8
  MissingEnvironmentVariableError,
8
9
  )
9
10
 
11
+ cache_dir = platformdirs.user_cache_dir("edsl")
12
+ os.makedirs(cache_dir, exist_ok=True)
13
+
10
14
  # valid values for EDSL_RUN_MODE
11
15
  EDSL_RUN_MODES = [
12
16
  "development",
@@ -34,7 +38,8 @@ CONFIG_MAP = {
34
38
  "info": "This config var determines the maximum number of seconds to wait before retrying a failed API call.",
35
39
  },
36
40
  "EDSL_DATABASE_PATH": {
37
- "default": f"sqlite:///{os.path.join(os.getcwd(), '.edsl_cache/data.db')}",
41
+ # "default": f"sqlite:///{os.path.join(os.getcwd(), '.edsl_cache/data.db')}",
42
+ "default": f"sqlite:///{os.path.join(platformdirs.user_cache_dir('edsl'), 'lm_model_calls.db')}",
38
43
  "info": "This config var determines the path to the cache file.",
39
44
  },
40
45
  "EDSL_DEFAULT_MODEL": {
@@ -69,6 +74,10 @@ CONFIG_MAP = {
69
74
  "default": "False",
70
75
  "info": "This config var determines whether to open the exception report URL in the browser",
71
76
  },
77
+ "EDSL_REMOTE_TOKEN_BUCKET_URL": {
78
+ "default": "None",
79
+ "info": "This config var holds the URL of the remote token bucket server.",
80
+ },
72
81
  }
73
82
 
74
83
 
@@ -81,6 +90,9 @@ class Config:
81
90
  self._load_dotenv()
82
91
  self._set_env_vars()
83
92
 
93
+ def show_path_to_dot_env(self):
94
+ print(find_dotenv(usecwd=True))
95
+
84
96
  def _set_run_mode(self) -> None:
85
97
  """
86
98
  Sets EDSL_RUN_MODE as a class attribute.
@@ -144,6 +156,14 @@ class Config:
144
156
  raise MissingEnvironmentVariableError(f"{env_var} is not set. {info}")
145
157
  return self.__dict__.get(env_var)
146
158
 
159
+ def __iter__(self):
160
+ """Iterate over the environment variables."""
161
+ return iter(self.__dict__)
162
+
163
+ def items(self):
164
+ """Iterate over the environment variables and their values."""
165
+ return self.__dict__.items()
166
+
147
167
  def show(self) -> str:
148
168
  """Print the currently set environment vars."""
149
169
  max_env_var_length = max(len(env_var) for env_var in self.__dict__)
@@ -29,7 +29,8 @@ a3 = Agent(
29
29
  c1 = Conversation(agent_list=AgentList([a1, a3, a2]), max_turns=5, verbose=True)
30
30
  c2 = Conversation(agent_list=AgentList([a1, a2]), max_turns=5, verbose=True)
31
31
 
32
- c = Cache.load("car_talk.json.gz")
32
+ # c = Cache.load("car_talk.json.gz")
33
+ c = Cache()
33
34
  # breakpoint()
34
35
  combo = ConversationList([c1, c2], cache=c)
35
36
  combo.run()
@@ -0,0 +1,15 @@
1
+ class CoopFunctionsMixin:
2
+ def better_names(self, existing_names):
3
+ from edsl import QuestionList, Scenario
4
+
5
+ s = Scenario({"existing_names": existing_names})
6
+ q = QuestionList(
7
+ question_text="""The following colum names are already in use: {{ existing_names }}
8
+ Please provide new names for the columns.
9
+ They should be short, one or two words, and unique. They should be valid Python idenifiers.
10
+ No spaces - use underscores instead.
11
+ """,
12
+ question_name="better_names",
13
+ )
14
+ results = q.by(s).run(verbose=False)
15
+ return results.select("answer.better_names").first()