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.
- edsl/Base.py +197 -116
- edsl/__init__.py +15 -7
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +351 -147
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +101 -50
- edsl/agents/InvigilatorBase.py +62 -70
- edsl/agents/PromptConstructor.py +143 -225
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +18 -5
- edsl/auto/StageBase.py +53 -40
- edsl/auto/StageQuestions.py +2 -1
- edsl/auto/utilities.py +0 -6
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +125 -47
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +45 -27
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +31 -12
- edsl/data/RemoteCacheSync.py +154 -46
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +27 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +38 -19
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +7 -12
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +120 -79
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +47 -35
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -10
- edsl/inference_services/TogetherAIService.py +5 -3
- edsl/inference_services/data_structures.py +134 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +356 -431
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +6 -4
- edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +143 -408
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
- edsl/jobs/runners/JobsRunnerStatus.py +133 -165
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +38 -18
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +194 -236
- edsl/language_models/ModelList.py +28 -19
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +1 -2
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +68 -214
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionBudget.py +1 -1
- edsl/questions/QuestionCheckBox.py +3 -3
- edsl/questions/QuestionExtract.py +5 -7
- edsl/questions/QuestionFreeText.py +2 -3
- edsl/questions/QuestionList.py +10 -18
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +67 -23
- edsl/questions/QuestionNumerical.py +2 -4
- edsl/questions/QuestionRank.py +7 -17
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +2 -1
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
- edsl/questions/data_structures.py +20 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
- edsl/questions/question_registry.py +1 -1
- edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +168 -305
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +298 -206
- edsl/results/Results.py +149 -131
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/file_exports.py +252 -0
- edsl/results/{Selector.py → results_selector.py} +23 -13
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +150 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioList.py +415 -244
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +49 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +270 -791
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
- edsl-0.1.39.dist-info/RECORD +358 -0
- {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/registry.py +0 -190
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.38.dev4.dist-info/RECORD +0 -277
- /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
- /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
- /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
- {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
edsl/agents/prompt_helpers.py
CHANGED
@@ -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.
|
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 = "
|
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
|
-
|
188
|
+
pass
|
189
|
+
# try:
|
177
190
|
|
178
|
-
|
179
|
-
|
180
|
-
|
191
|
+
# class StageMissing(StageBase):
|
192
|
+
# def handle_data(self, data):
|
193
|
+
# return data
|
181
194
|
|
182
|
-
except NotImplementedError as e:
|
183
|
-
|
184
|
-
else:
|
185
|
-
|
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
|
-
|
190
|
-
|
202
|
+
# class StageMissingInput(StageBase):
|
203
|
+
# output = FlowDataBase
|
191
204
|
|
192
|
-
except NotImplementedError as e:
|
193
|
-
|
205
|
+
# except NotImplementedError as e:
|
206
|
+
# print(e)
|
194
207
|
|
195
|
-
else:
|
196
|
-
|
208
|
+
# else:
|
209
|
+
# raise Exception("Should have raised NotImplementedError")
|
197
210
|
|
198
|
-
@dataclass
|
199
|
-
class MockInputOutput(FlowDataBase):
|
200
|
-
|
211
|
+
# @dataclass
|
212
|
+
# class MockInputOutput(FlowDataBase):
|
213
|
+
# text: str
|
201
214
|
|
202
|
-
class StageTest(StageBase):
|
203
|
-
|
204
|
-
|
215
|
+
# class StageTest(StageBase):
|
216
|
+
# input = MockInputOutput
|
217
|
+
# output = MockInputOutput
|
205
218
|
|
206
|
-
|
207
|
-
|
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
|
-
|
218
|
-
|
229
|
+
# class BadMockInput(FlowDataBase):
|
230
|
+
# text: str
|
231
|
+
# other: str
|
219
232
|
|
220
|
-
class StageBad(StageBase):
|
221
|
-
|
222
|
-
|
233
|
+
# class StageBad(StageBase):
|
234
|
+
# input = BadMockInput
|
235
|
+
# output = BadMockInput
|
223
236
|
|
224
|
-
|
225
|
-
|
237
|
+
# def handle_data(self, data):
|
238
|
+
# return self.output(text=data["text"] + "processed")
|
226
239
|
|
227
|
-
try:
|
228
|
-
|
229
|
-
except ExceptionPipesDoNotFit as e:
|
230
|
-
|
240
|
+
# try:
|
241
|
+
# pipeline = StageTest(next_stage=StageBad(next_stage=StageTest()))
|
242
|
+
# except ExceptionPipesDoNotFit as e:
|
243
|
+
# print(e)
|
edsl/auto/StageQuestions.py
CHANGED
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__)
|
edsl/conversation/car_buying.py
CHANGED
@@ -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()
|