edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev5__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 +413 -332
- edsl/BaseDiff.py +260 -260
- edsl/TemplateLoader.py +24 -24
- edsl/__init__.py +57 -49
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +1071 -867
- edsl/agents/AgentList.py +551 -413
- edsl/agents/Invigilator.py +284 -233
- edsl/agents/InvigilatorBase.py +257 -270
- edsl/agents/PromptConstructor.py +272 -354
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +2 -3
- edsl/agents/descriptors.py +99 -99
- edsl/agents/prompt_helpers.py +129 -129
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -117
- edsl/auto/StageBase.py +243 -230
- edsl/auto/StageGenerateSurvey.py +178 -178
- edsl/auto/StageLabelQuestions.py +125 -125
- edsl/auto/StagePersona.py +61 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
- edsl/auto/StagePersonaDimensionValues.py +74 -74
- edsl/auto/StagePersonaDimensions.py +69 -69
- edsl/auto/StageQuestions.py +74 -73
- edsl/auto/SurveyCreatorPipeline.py +21 -21
- edsl/auto/utilities.py +218 -224
- edsl/base/Base.py +279 -279
- edsl/config.py +177 -157
- edsl/conversation/Conversation.py +290 -290
- edsl/conversation/car_buying.py +59 -58
- edsl/conversation/chips.py +95 -95
- edsl/conversation/mug_negotiation.py +81 -81
- edsl/conversation/next_speaker_utilities.py +93 -93
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -54
- edsl/coop/__init__.py +2 -2
- edsl/coop/coop.py +1106 -1028
- edsl/coop/utils.py +131 -131
- edsl/data/Cache.py +573 -555
- edsl/data/CacheEntry.py +230 -233
- edsl/data/CacheHandler.py +168 -149
- edsl/data/RemoteCacheSync.py +186 -78
- edsl/data/SQLiteDict.py +292 -292
- edsl/data/__init__.py +5 -4
- edsl/data/orm.py +10 -10
- edsl/data_transfer_models.py +74 -73
- edsl/enums.py +202 -175
- edsl/exceptions/BaseException.py +21 -21
- edsl/exceptions/__init__.py +54 -54
- edsl/exceptions/agents.py +54 -42
- edsl/exceptions/cache.py +5 -5
- edsl/exceptions/configuration.py +16 -16
- edsl/exceptions/coop.py +10 -10
- edsl/exceptions/data.py +14 -14
- edsl/exceptions/general.py +34 -34
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +33 -33
- edsl/exceptions/language_models.py +63 -63
- edsl/exceptions/prompts.py +15 -15
- edsl/exceptions/questions.py +109 -91
- edsl/exceptions/results.py +29 -29
- edsl/exceptions/scenarios.py +29 -22
- edsl/exceptions/surveys.py +37 -37
- edsl/inference_services/AnthropicService.py +106 -87
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -120
- edsl/inference_services/AzureAI.py +215 -217
- edsl/inference_services/DeepInfraService.py +18 -18
- edsl/inference_services/GoogleService.py +143 -148
- edsl/inference_services/GroqService.py +20 -20
- edsl/inference_services/InferenceServiceABC.py +80 -147
- edsl/inference_services/InferenceServicesCollection.py +138 -97
- edsl/inference_services/MistralAIService.py +120 -123
- edsl/inference_services/OllamaService.py +18 -18
- edsl/inference_services/OpenAIService.py +236 -224
- edsl/inference_services/PerplexityService.py +160 -163
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -89
- edsl/inference_services/TogetherAIService.py +172 -170
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -118
- edsl/inference_services/rate_limits_cache.py +25 -25
- edsl/inference_services/registry.py +41 -41
- edsl/inference_services/write_available.py +10 -10
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +43 -56
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +823 -898
- edsl/jobs/JobsChecks.py +172 -147
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -268
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/__init__.py +1 -1
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -63
- edsl/jobs/buckets/ModelBuckets.py +65 -65
- edsl/jobs/buckets/TokenBucket.py +283 -251
- 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 +396 -661
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
- edsl/jobs/interviews/InterviewStatistic.py +63 -63
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
- edsl/jobs/interviews/InterviewStatusLog.py +92 -92
- edsl/jobs/interviews/ReportErrors.py +66 -66
- edsl/jobs/interviews/interview_status_enum.py +9 -9
- 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 +151 -466
- edsl/jobs/runners/JobsRunnerStatus.py +297 -330
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
- edsl/jobs/tasks/TaskCreators.py +64 -64
- edsl/jobs/tasks/TaskHistory.py +470 -450
- edsl/jobs/tasks/TaskStatusLog.py +23 -23
- edsl/jobs/tasks/task_status_enum.py +161 -163
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
- edsl/jobs/tokens/TokenUsage.py +34 -34
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +626 -668
- edsl/language_models/ModelList.py +164 -155
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +2 -3
- edsl/language_models/fake_openai_call.py +15 -15
- edsl/language_models/fake_openai_service.py +61 -61
- 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 +156 -156
- edsl/language_models/utilities.py +65 -64
- edsl/notebooks/Notebook.py +263 -258
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -1
- edsl/prompts/Prompt.py +352 -362
- edsl/prompts/__init__.py +2 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -664
- edsl/questions/QuestionBasePromptsMixin.py +221 -217
- edsl/questions/QuestionBudget.py +227 -227
- edsl/questions/QuestionCheckBox.py +359 -359
- edsl/questions/QuestionExtract.py +180 -182
- edsl/questions/QuestionFreeText.py +113 -114
- edsl/questions/QuestionFunctional.py +166 -166
- edsl/questions/QuestionList.py +223 -231
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +330 -286
- edsl/questions/QuestionNumerical.py +151 -153
- edsl/questions/QuestionRank.py +314 -324
- edsl/questions/Quick.py +41 -41
- edsl/questions/SimpleAskMixin.py +74 -73
- edsl/questions/__init__.py +27 -26
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
- edsl/questions/compose_questions.py +98 -98
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -21
- edsl/questions/derived/QuestionLikertFive.py +76 -76
- edsl/questions/derived/QuestionLinearScale.py +90 -87
- edsl/questions/derived/QuestionTopK.py +93 -93
- edsl/questions/derived/QuestionYesNo.py +82 -82
- edsl/questions/descriptors.py +427 -413
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
- edsl/questions/prompt_templates/question_extract.jinja +11 -11
- edsl/questions/prompt_templates/question_free_text.jinja +3 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
- edsl/questions/prompt_templates/question_list.jinja +17 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
- edsl/questions/prompt_templates/question_numerical.jinja +36 -36
- edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
- edsl/questions/question_registry.py +177 -177
- edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
- edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +12 -12
- edsl/questions/templates/budget/answering_instructions.jinja +7 -7
- edsl/questions/templates/budget/question_presentation.jinja +7 -7
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
- edsl/questions/templates/extract/answering_instructions.jinja +7 -7
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
- edsl/questions/templates/list/answering_instructions.jinja +3 -3
- edsl/questions/templates/list/question_presentation.jinja +5 -5
- 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/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
- edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
- edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
- edsl/questions/templates/numerical/question_presentation.jinja +6 -6
- edsl/questions/templates/rank/answering_instructions.jinja +11 -11
- edsl/questions/templates/rank/question_presentation.jinja +15 -15
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
- edsl/questions/templates/top_k/question_presentation.jinja +22 -22
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
- edsl/results/CSSParameterizer.py +108 -108
- edsl/results/Dataset.py +587 -424
- edsl/results/DatasetExportMixin.py +594 -731
- edsl/results/DatasetTree.py +295 -275
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +557 -465
- edsl/results/Results.py +1183 -1165
- edsl/results/ResultsExportMixin.py +45 -43
- edsl/results/ResultsGGMixin.py +121 -121
- edsl/results/TableDisplay.py +125 -198
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +2 -2
- edsl/results/file_exports.py +252 -0
- edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
- edsl/results/{Selector.py → results_selector.py} +145 -135
- edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +77 -77
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -115
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +511 -632
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +498 -601
- edsl/scenarios/ScenarioHtmlMixin.py +65 -64
- edsl/scenarios/ScenarioList.py +1458 -1287
- edsl/scenarios/ScenarioListExportMixin.py +45 -52
- edsl/scenarios/ScenarioListPdfMixin.py +239 -261
- edsl/scenarios/__init__.py +3 -4
- 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 +38 -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} +131 -127
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -1
- edsl/study/ObjectEntry.py +173 -173
- edsl/study/ProofOfWork.py +113 -113
- edsl/study/SnapShot.py +80 -80
- edsl/study/Study.py +521 -528
- edsl/study/__init__.py +4 -4
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +148 -148
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +31 -31
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +244 -244
- edsl/surveys/Rule.py +327 -326
- edsl/surveys/RuleCollection.py +385 -387
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1280 -1801
- edsl/surveys/SurveyCSS.py +273 -261
- edsl/surveys/SurveyExportMixin.py +259 -259
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
- edsl/surveys/SurveyQualtricsImport.py +284 -284
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +5 -3
- edsl/surveys/base.py +53 -53
- edsl/surveys/descriptors.py +60 -56
- edsl/surveys/instructions/ChangeInstruction.py +48 -49
- edsl/surveys/instructions/Instruction.py +56 -65
- edsl/surveys/instructions/InstructionCollection.py +82 -77
- edsl/templates/error_reporting/base.html +23 -23
- edsl/templates/error_reporting/exceptions_by_model.html +34 -34
- edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
- edsl/templates/error_reporting/exceptions_by_type.html +16 -16
- edsl/templates/error_reporting/interview_details.html +115 -115
- edsl/templates/error_reporting/interviews.html +19 -19
- edsl/templates/error_reporting/overview.html +4 -4
- edsl/templates/error_reporting/performance_plot.html +1 -1
- edsl/templates/error_reporting/report.css +73 -73
- edsl/templates/error_reporting/report.html +117 -117
- edsl/templates/error_reporting/report.js +25 -25
- edsl/tools/__init__.py +1 -1
- edsl/tools/clusters.py +192 -192
- edsl/tools/embeddings.py +27 -27
- edsl/tools/embeddings_plotting.py +118 -118
- edsl/tools/plotting.py +112 -112
- edsl/tools/summarize.py +18 -18
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +28 -28
- edsl/utilities/__init__.py +22 -22
- edsl/utilities/ast_utilities.py +25 -25
- edsl/utilities/data/Registry.py +6 -6
- edsl/utilities/data/__init__.py +1 -1
- edsl/utilities/data/scooter_results.json +1 -1
- edsl/utilities/decorators.py +77 -77
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
- edsl/utilities/interface.py +627 -627
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -263
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -28
- edsl/utilities/restricted_python.py +70 -70
- edsl/utilities/utilities.py +436 -424
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/LICENSE +21 -21
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/METADATA +13 -11
- edsl-0.1.39.dev5.dist-info/RECORD +358 -0
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.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.39.dev3.dist-info/RECORD +0 -277
edsl/surveys/MemoryPlan.py
CHANGED
@@ -1,244 +1,244 @@
|
|
1
|
-
"""A survey has a memory plan that specifies what the agent should remember when answering a question."""
|
2
|
-
|
3
|
-
from collections import UserDict, defaultdict
|
4
|
-
from typing import Optional
|
5
|
-
|
6
|
-
# from edsl.surveys.Memory import Memory
|
7
|
-
# from edsl.prompts.Prompt import Prompt
|
8
|
-
# from edsl.surveys.DAG import DAG
|
9
|
-
|
10
|
-
|
11
|
-
class MemoryPlan(UserDict):
|
12
|
-
"""A survey has a memory plan that specifies what the agent should remember when answering a question.
|
13
|
-
|
14
|
-
The basic structure of a memory plan is a dictionary of focal questions to memories.
|
15
|
-
|
16
|
-
{focal_question1: [prior_question1, prior_question2, ...], focal_question: [prior_question3]}
|
17
|
-
"""
|
18
|
-
|
19
|
-
def __init__(self, survey: Optional["Survey"] = None, data: Optional[dict] = None):
|
20
|
-
"""Initialize a memory plan.
|
21
|
-
|
22
|
-
The actual 'data' attributes of the memory plan are a dictionary of focal questions to memories.
|
23
|
-
"""
|
24
|
-
if survey is not None:
|
25
|
-
self.survey = survey
|
26
|
-
self.survey_question_names = [q.question_name for q in survey.questions]
|
27
|
-
self.question_texts = [q.question_text for q in survey.questions]
|
28
|
-
super().__init__(data or {})
|
29
|
-
|
30
|
-
@property
|
31
|
-
def name_to_text(self) -> dict:
|
32
|
-
"""Return a dictionary mapping question names to question texts."""
|
33
|
-
return dict(zip(self.survey_question_names, self.question_texts))
|
34
|
-
|
35
|
-
def add_question(self, question: "QuestionBase") -> None:
|
36
|
-
"""Add a question to the survey.
|
37
|
-
|
38
|
-
:param question: A question to add to the survey
|
39
|
-
|
40
|
-
"""
|
41
|
-
self.survey_question_names.append(question.question_name)
|
42
|
-
self.question_texts.append(question.question_text)
|
43
|
-
|
44
|
-
def _check_valid_question_name(self, question_name: str) -> None:
|
45
|
-
"""Ensure a passed question name is valid.
|
46
|
-
|
47
|
-
:param question_name: The name of the question to check.
|
48
|
-
|
49
|
-
"""
|
50
|
-
if question_name not in self.survey_question_names:
|
51
|
-
raise ValueError(
|
52
|
-
f"{question_name} is not in the survey. Current names are {self.survey_question_names}"
|
53
|
-
)
|
54
|
-
|
55
|
-
def get_memory_prompt_fragment(
|
56
|
-
self, focal_question: str, answers: dict
|
57
|
-
) -> "Prompt":
|
58
|
-
"""Generate the prompt fragment descripting that past question and answer.
|
59
|
-
|
60
|
-
:param focal_question: The current question being answered.
|
61
|
-
:param answers: A dictionary of question names to answers.
|
62
|
-
|
63
|
-
"""
|
64
|
-
from edsl.prompts.Prompt import Prompt
|
65
|
-
|
66
|
-
self._check_valid_question_name(focal_question)
|
67
|
-
|
68
|
-
if focal_question not in self:
|
69
|
-
return Prompt("")
|
70
|
-
|
71
|
-
q_and_a_pairs = [
|
72
|
-
(self.name_to_text[question_name], answers.get(question_name, None))
|
73
|
-
for question_name in self[focal_question]
|
74
|
-
]
|
75
|
-
|
76
|
-
base_prompt_text = """
|
77
|
-
Before the question you are now answering, you already answered the following question(s):
|
78
|
-
"""
|
79
|
-
|
80
|
-
def gen_line(question_text, answer):
|
81
|
-
"""Return a line of memory."""
|
82
|
-
return f"\tQuestion: {question_text}\n\tAnswer: {answer}\n"
|
83
|
-
|
84
|
-
lines = [gen_line(*pair) for pair in q_and_a_pairs]
|
85
|
-
if lines:
|
86
|
-
return Prompt(
|
87
|
-
base_prompt_text + "\n Prior questions and answers:".join(lines)
|
88
|
-
)
|
89
|
-
else:
|
90
|
-
return Prompt("")
|
91
|
-
|
92
|
-
def _check_order(self, focal_question: str, prior_question: str) -> None:
|
93
|
-
"""Ensure the prior question comes before the focal question."""
|
94
|
-
focal_index = self.survey_question_names.index(focal_question)
|
95
|
-
prior_index = self.survey_question_names.index(prior_question)
|
96
|
-
if focal_index <= prior_index:
|
97
|
-
raise ValueError(f"{prior_question} must come before {focal_question}.")
|
98
|
-
|
99
|
-
def add_single_memory(self, focal_question: str, prior_question: str) -> None:
|
100
|
-
"""Add a single memory to the memory plan.
|
101
|
-
|
102
|
-
:param focal_question: The current question being answered.
|
103
|
-
:param prior_question: The question that was answered before the focal question that should be remembered.
|
104
|
-
|
105
|
-
>>> mp = MemoryPlan.example()
|
106
|
-
>>> mp.add_single_memory("q0", "q1")
|
107
|
-
Traceback (most recent call last):
|
108
|
-
...
|
109
|
-
ValueError: q1 must come before q0.
|
110
|
-
|
111
|
-
>>> mp = MemoryPlan.example()
|
112
|
-
>>> mp.add_single_memory("q0", "crap")
|
113
|
-
Traceback (most recent call last):
|
114
|
-
...
|
115
|
-
ValueError: crap is not in the survey. Current names are ['q0', 'q1', 'q2']
|
116
|
-
|
117
|
-
>>> mp = MemoryPlan.example()
|
118
|
-
>>> mp.add_single_memory("crap", "q0")
|
119
|
-
Traceback (most recent call last):
|
120
|
-
...
|
121
|
-
ValueError: crap is not in the survey. Current names are ['q0', 'q1', 'q2']
|
122
|
-
"""
|
123
|
-
self._check_valid_question_name(focal_question)
|
124
|
-
self._check_valid_question_name(prior_question)
|
125
|
-
self._check_order(focal_question, prior_question)
|
126
|
-
from edsl.surveys.Memory import Memory
|
127
|
-
|
128
|
-
if focal_question not in self:
|
129
|
-
memory = Memory()
|
130
|
-
memory.add_prior_question(prior_question)
|
131
|
-
self[focal_question] = memory
|
132
|
-
else:
|
133
|
-
self[focal_question].add_prior_question(prior_question)
|
134
|
-
|
135
|
-
def add_memory_collection(
|
136
|
-
self, focal_question: str, prior_questions: list[str]
|
137
|
-
) -> None:
|
138
|
-
"""Add a collection of prior questions to the memory plan.
|
139
|
-
|
140
|
-
:param focal_question: The current question being answered.
|
141
|
-
:param prior_questions: A list of questions that were answered before the focal question that should be remembered.
|
142
|
-
"""
|
143
|
-
for question in prior_questions:
|
144
|
-
self.add_single_memory(focal_question, question)
|
145
|
-
|
146
|
-
def to_dict(self, add_edsl_version=True) -> dict:
|
147
|
-
"""Serialize the memory plan to a dictionary.
|
148
|
-
|
149
|
-
>>> mp = MemoryPlan.example()
|
150
|
-
>>> mp.to_dict()
|
151
|
-
{'survey_question_names': ['q0', 'q1', 'q2'], 'survey_question_texts': ['Do you like school?', 'Why not?', 'Why?'], 'data': {'q1': {'prior_questions': ['q0']}}}
|
152
|
-
"""
|
153
|
-
newdata = {}
|
154
|
-
for question_name, memory in self.items():
|
155
|
-
newdata[question_name] = memory.to_dict()
|
156
|
-
|
157
|
-
return {
|
158
|
-
"survey_question_names": self.survey_question_names,
|
159
|
-
"survey_question_texts": self.question_texts,
|
160
|
-
"data": newdata,
|
161
|
-
}
|
162
|
-
|
163
|
-
@classmethod
|
164
|
-
def from_dict(cls, data) -> "MemoryPlan":
|
165
|
-
"""Deserialize a memory plan from a dictionary."""
|
166
|
-
from edsl.surveys.Memory import Memory
|
167
|
-
|
168
|
-
newdata = {}
|
169
|
-
for question_name, memory in data["data"].items():
|
170
|
-
newdata[question_name] = Memory.from_dict(memory)
|
171
|
-
|
172
|
-
memory_plan = cls(survey=None, data=newdata)
|
173
|
-
memory_plan.survey_question_names = data["survey_question_names"]
|
174
|
-
memory_plan.question_texts = data["survey_question_texts"]
|
175
|
-
return memory_plan
|
176
|
-
|
177
|
-
def _indexify(self, d: dict):
|
178
|
-
"""Convert a dictionary of question names to a dictionary of question indices.
|
179
|
-
|
180
|
-
:param d: A dictionary of question names to indices.
|
181
|
-
"""
|
182
|
-
new_d = {}
|
183
|
-
for k, v in d.items():
|
184
|
-
key = self.survey_question_names.index(k)
|
185
|
-
new_v = set({self.survey_question_names.index(q) for q in v})
|
186
|
-
new_d[key] = new_v
|
187
|
-
return new_d
|
188
|
-
|
189
|
-
@property
|
190
|
-
def dag(self) -> "DAG":
|
191
|
-
"""Return a directed acyclic graph of the memory plan.
|
192
|
-
|
193
|
-
>>> mp = MemoryPlan.example()
|
194
|
-
>>> mp.dag
|
195
|
-
{1: {0}}
|
196
|
-
"""
|
197
|
-
from edsl.surveys.DAG import DAG
|
198
|
-
|
199
|
-
d = defaultdict(set)
|
200
|
-
for focal_question, memory in self.items():
|
201
|
-
for prior_question in memory:
|
202
|
-
d[focal_question].add(prior_question)
|
203
|
-
return DAG(self._indexify(d))
|
204
|
-
|
205
|
-
@classmethod
|
206
|
-
def example(cls):
|
207
|
-
"""Return an example memory plan."""
|
208
|
-
from edsl import Survey
|
209
|
-
|
210
|
-
mp = cls(survey=Survey.example())
|
211
|
-
mp.add_single_memory("q1", "q0")
|
212
|
-
return mp
|
213
|
-
|
214
|
-
def remove_question(self, question_name: str) -> None:
|
215
|
-
"""Remove a question from the memory plan.
|
216
|
-
|
217
|
-
:param question_name: The name of the question to remove.
|
218
|
-
"""
|
219
|
-
self._check_valid_question_name(question_name)
|
220
|
-
|
221
|
-
# Remove the question from survey_question_names and question_texts
|
222
|
-
index = self.survey_question_names.index(question_name)
|
223
|
-
self.survey_question_names.pop(index)
|
224
|
-
self.question_texts.pop(index)
|
225
|
-
|
226
|
-
# Remove the question from the memory plan if it's a focal question
|
227
|
-
self.pop(question_name, None)
|
228
|
-
|
229
|
-
# Remove the question from all memories where it appears as a prior question
|
230
|
-
for focal_question, memory in self.items():
|
231
|
-
memory.remove_prior_question(question_name)
|
232
|
-
|
233
|
-
# Update the DAG
|
234
|
-
self.dag.remove_node(index)
|
235
|
-
|
236
|
-
def remove_prior_question(self, question_name: str) -> None:
|
237
|
-
"""Remove a prior question from the memory."""
|
238
|
-
self.prior_questions = [q for q in self.prior_questions if q != question_name]
|
239
|
-
|
240
|
-
|
241
|
-
if __name__ == "__main__":
|
242
|
-
import doctest
|
243
|
-
|
244
|
-
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
1
|
+
"""A survey has a memory plan that specifies what the agent should remember when answering a question."""
|
2
|
+
|
3
|
+
from collections import UserDict, defaultdict
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
# from edsl.surveys.Memory import Memory
|
7
|
+
# from edsl.prompts.Prompt import Prompt
|
8
|
+
# from edsl.surveys.DAG import DAG
|
9
|
+
|
10
|
+
|
11
|
+
class MemoryPlan(UserDict):
|
12
|
+
"""A survey has a memory plan that specifies what the agent should remember when answering a question.
|
13
|
+
|
14
|
+
The basic structure of a memory plan is a dictionary of focal questions to memories.
|
15
|
+
|
16
|
+
{focal_question1: [prior_question1, prior_question2, ...], focal_question: [prior_question3]}
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(self, survey: Optional["Survey"] = None, data: Optional[dict] = None):
|
20
|
+
"""Initialize a memory plan.
|
21
|
+
|
22
|
+
The actual 'data' attributes of the memory plan are a dictionary of focal questions to memories.
|
23
|
+
"""
|
24
|
+
if survey is not None:
|
25
|
+
self.survey = survey
|
26
|
+
self.survey_question_names = [q.question_name for q in survey.questions]
|
27
|
+
self.question_texts = [q.question_text for q in survey.questions]
|
28
|
+
super().__init__(data or {})
|
29
|
+
|
30
|
+
@property
|
31
|
+
def name_to_text(self) -> dict:
|
32
|
+
"""Return a dictionary mapping question names to question texts."""
|
33
|
+
return dict(zip(self.survey_question_names, self.question_texts))
|
34
|
+
|
35
|
+
def add_question(self, question: "QuestionBase") -> None:
|
36
|
+
"""Add a question to the survey.
|
37
|
+
|
38
|
+
:param question: A question to add to the survey
|
39
|
+
|
40
|
+
"""
|
41
|
+
self.survey_question_names.append(question.question_name)
|
42
|
+
self.question_texts.append(question.question_text)
|
43
|
+
|
44
|
+
def _check_valid_question_name(self, question_name: str) -> None:
|
45
|
+
"""Ensure a passed question name is valid.
|
46
|
+
|
47
|
+
:param question_name: The name of the question to check.
|
48
|
+
|
49
|
+
"""
|
50
|
+
if question_name not in self.survey_question_names:
|
51
|
+
raise ValueError(
|
52
|
+
f"{question_name} is not in the survey. Current names are {self.survey_question_names}"
|
53
|
+
)
|
54
|
+
|
55
|
+
def get_memory_prompt_fragment(
|
56
|
+
self, focal_question: str, answers: dict
|
57
|
+
) -> "Prompt":
|
58
|
+
"""Generate the prompt fragment descripting that past question and answer.
|
59
|
+
|
60
|
+
:param focal_question: The current question being answered.
|
61
|
+
:param answers: A dictionary of question names to answers.
|
62
|
+
|
63
|
+
"""
|
64
|
+
from edsl.prompts.Prompt import Prompt
|
65
|
+
|
66
|
+
self._check_valid_question_name(focal_question)
|
67
|
+
|
68
|
+
if focal_question not in self:
|
69
|
+
return Prompt("")
|
70
|
+
|
71
|
+
q_and_a_pairs = [
|
72
|
+
(self.name_to_text[question_name], answers.get(question_name, None))
|
73
|
+
for question_name in self[focal_question]
|
74
|
+
]
|
75
|
+
|
76
|
+
base_prompt_text = """
|
77
|
+
Before the question you are now answering, you already answered the following question(s):
|
78
|
+
"""
|
79
|
+
|
80
|
+
def gen_line(question_text, answer):
|
81
|
+
"""Return a line of memory."""
|
82
|
+
return f"\tQuestion: {question_text}\n\tAnswer: {answer}\n"
|
83
|
+
|
84
|
+
lines = [gen_line(*pair) for pair in q_and_a_pairs]
|
85
|
+
if lines:
|
86
|
+
return Prompt(
|
87
|
+
base_prompt_text + "\n Prior questions and answers:".join(lines)
|
88
|
+
)
|
89
|
+
else:
|
90
|
+
return Prompt("")
|
91
|
+
|
92
|
+
def _check_order(self, focal_question: str, prior_question: str) -> None:
|
93
|
+
"""Ensure the prior question comes before the focal question."""
|
94
|
+
focal_index = self.survey_question_names.index(focal_question)
|
95
|
+
prior_index = self.survey_question_names.index(prior_question)
|
96
|
+
if focal_index <= prior_index:
|
97
|
+
raise ValueError(f"{prior_question} must come before {focal_question}.")
|
98
|
+
|
99
|
+
def add_single_memory(self, focal_question: str, prior_question: str) -> None:
|
100
|
+
"""Add a single memory to the memory plan.
|
101
|
+
|
102
|
+
:param focal_question: The current question being answered.
|
103
|
+
:param prior_question: The question that was answered before the focal question that should be remembered.
|
104
|
+
|
105
|
+
>>> mp = MemoryPlan.example()
|
106
|
+
>>> mp.add_single_memory("q0", "q1")
|
107
|
+
Traceback (most recent call last):
|
108
|
+
...
|
109
|
+
ValueError: q1 must come before q0.
|
110
|
+
|
111
|
+
>>> mp = MemoryPlan.example()
|
112
|
+
>>> mp.add_single_memory("q0", "crap")
|
113
|
+
Traceback (most recent call last):
|
114
|
+
...
|
115
|
+
ValueError: crap is not in the survey. Current names are ['q0', 'q1', 'q2']
|
116
|
+
|
117
|
+
>>> mp = MemoryPlan.example()
|
118
|
+
>>> mp.add_single_memory("crap", "q0")
|
119
|
+
Traceback (most recent call last):
|
120
|
+
...
|
121
|
+
ValueError: crap is not in the survey. Current names are ['q0', 'q1', 'q2']
|
122
|
+
"""
|
123
|
+
self._check_valid_question_name(focal_question)
|
124
|
+
self._check_valid_question_name(prior_question)
|
125
|
+
self._check_order(focal_question, prior_question)
|
126
|
+
from edsl.surveys.Memory import Memory
|
127
|
+
|
128
|
+
if focal_question not in self:
|
129
|
+
memory = Memory()
|
130
|
+
memory.add_prior_question(prior_question)
|
131
|
+
self[focal_question] = memory
|
132
|
+
else:
|
133
|
+
self[focal_question].add_prior_question(prior_question)
|
134
|
+
|
135
|
+
def add_memory_collection(
|
136
|
+
self, focal_question: str, prior_questions: list[str]
|
137
|
+
) -> None:
|
138
|
+
"""Add a collection of prior questions to the memory plan.
|
139
|
+
|
140
|
+
:param focal_question: The current question being answered.
|
141
|
+
:param prior_questions: A list of questions that were answered before the focal question that should be remembered.
|
142
|
+
"""
|
143
|
+
for question in prior_questions:
|
144
|
+
self.add_single_memory(focal_question, question)
|
145
|
+
|
146
|
+
def to_dict(self, add_edsl_version=True) -> dict:
|
147
|
+
"""Serialize the memory plan to a dictionary.
|
148
|
+
|
149
|
+
>>> mp = MemoryPlan.example()
|
150
|
+
>>> mp.to_dict()
|
151
|
+
{'survey_question_names': ['q0', 'q1', 'q2'], 'survey_question_texts': ['Do you like school?', 'Why not?', 'Why?'], 'data': {'q1': {'prior_questions': ['q0']}}}
|
152
|
+
"""
|
153
|
+
newdata = {}
|
154
|
+
for question_name, memory in self.items():
|
155
|
+
newdata[question_name] = memory.to_dict()
|
156
|
+
|
157
|
+
return {
|
158
|
+
"survey_question_names": self.survey_question_names,
|
159
|
+
"survey_question_texts": self.question_texts,
|
160
|
+
"data": newdata,
|
161
|
+
}
|
162
|
+
|
163
|
+
@classmethod
|
164
|
+
def from_dict(cls, data) -> "MemoryPlan":
|
165
|
+
"""Deserialize a memory plan from a dictionary."""
|
166
|
+
from edsl.surveys.Memory import Memory
|
167
|
+
|
168
|
+
newdata = {}
|
169
|
+
for question_name, memory in data["data"].items():
|
170
|
+
newdata[question_name] = Memory.from_dict(memory)
|
171
|
+
|
172
|
+
memory_plan = cls(survey=None, data=newdata)
|
173
|
+
memory_plan.survey_question_names = data["survey_question_names"]
|
174
|
+
memory_plan.question_texts = data["survey_question_texts"]
|
175
|
+
return memory_plan
|
176
|
+
|
177
|
+
def _indexify(self, d: dict):
|
178
|
+
"""Convert a dictionary of question names to a dictionary of question indices.
|
179
|
+
|
180
|
+
:param d: A dictionary of question names to indices.
|
181
|
+
"""
|
182
|
+
new_d = {}
|
183
|
+
for k, v in d.items():
|
184
|
+
key = self.survey_question_names.index(k)
|
185
|
+
new_v = set({self.survey_question_names.index(q) for q in v})
|
186
|
+
new_d[key] = new_v
|
187
|
+
return new_d
|
188
|
+
|
189
|
+
@property
|
190
|
+
def dag(self) -> "DAG":
|
191
|
+
"""Return a directed acyclic graph of the memory plan.
|
192
|
+
|
193
|
+
>>> mp = MemoryPlan.example()
|
194
|
+
>>> mp.dag
|
195
|
+
{1: {0}}
|
196
|
+
"""
|
197
|
+
from edsl.surveys.DAG import DAG
|
198
|
+
|
199
|
+
d = defaultdict(set)
|
200
|
+
for focal_question, memory in self.items():
|
201
|
+
for prior_question in memory:
|
202
|
+
d[focal_question].add(prior_question)
|
203
|
+
return DAG(self._indexify(d))
|
204
|
+
|
205
|
+
@classmethod
|
206
|
+
def example(cls):
|
207
|
+
"""Return an example memory plan."""
|
208
|
+
from edsl import Survey
|
209
|
+
|
210
|
+
mp = cls(survey=Survey.example())
|
211
|
+
mp.add_single_memory("q1", "q0")
|
212
|
+
return mp
|
213
|
+
|
214
|
+
def remove_question(self, question_name: str) -> None:
|
215
|
+
"""Remove a question from the memory plan.
|
216
|
+
|
217
|
+
:param question_name: The name of the question to remove.
|
218
|
+
"""
|
219
|
+
self._check_valid_question_name(question_name)
|
220
|
+
|
221
|
+
# Remove the question from survey_question_names and question_texts
|
222
|
+
index = self.survey_question_names.index(question_name)
|
223
|
+
self.survey_question_names.pop(index)
|
224
|
+
self.question_texts.pop(index)
|
225
|
+
|
226
|
+
# Remove the question from the memory plan if it's a focal question
|
227
|
+
self.pop(question_name, None)
|
228
|
+
|
229
|
+
# Remove the question from all memories where it appears as a prior question
|
230
|
+
for focal_question, memory in self.items():
|
231
|
+
memory.remove_prior_question(question_name)
|
232
|
+
|
233
|
+
# Update the DAG
|
234
|
+
self.dag.remove_node(index)
|
235
|
+
|
236
|
+
def remove_prior_question(self, question_name: str) -> None:
|
237
|
+
"""Remove a prior question from the memory."""
|
238
|
+
self.prior_questions = [q for q in self.prior_questions if q != question_name]
|
239
|
+
|
240
|
+
|
241
|
+
if __name__ == "__main__":
|
242
|
+
import doctest
|
243
|
+
|
244
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|