edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev4__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/hack.py +10 -0
- 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/test_h +1 -0
- 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/gcp_bucket/example.py +50 -0
- 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.dev4.dist-info}/LICENSE +21 -21
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/METADATA +13 -11
- edsl-0.1.39.dev4.dist-info/RECORD +361 -0
- 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-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/WHEEL +0 -0
edsl/agents/prompt_helpers.py
CHANGED
@@ -1,129 +1,129 @@
|
|
1
|
-
import enum
|
2
|
-
from typing import Dict, Optional
|
3
|
-
from collections import UserList
|
4
|
-
from edsl.prompts import Prompt
|
5
|
-
|
6
|
-
|
7
|
-
class PromptComponent(enum.Enum):
|
8
|
-
AGENT_INSTRUCTIONS = "agent_instructions"
|
9
|
-
AGENT_PERSONA = "agent_persona"
|
10
|
-
QUESTION_INSTRUCTIONS = "question_instructions"
|
11
|
-
PRIOR_QUESTION_MEMORY = "prior_question_memory"
|
12
|
-
|
13
|
-
|
14
|
-
class PromptList(UserList):
|
15
|
-
separator = Prompt("
|
16
|
-
|
17
|
-
def reduce(self):
|
18
|
-
"""Reduce the list of prompts to a single prompt.
|
19
|
-
|
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
|
-
>>> p.reduce()
|
22
|
-
Prompt(text=\"""You are a happy-go lucky agent.
|
23
|
-
|
24
|
-
"""
|
25
|
-
p = self[0]
|
26
|
-
for prompt in self[1:]:
|
27
|
-
if len(prompt) > 0:
|
28
|
-
p = p + self.separator + prompt
|
29
|
-
return p
|
30
|
-
|
31
|
-
|
32
|
-
class PromptPlan:
|
33
|
-
"""A plan for constructing prompts for the LLM call.
|
34
|
-
Every prompt plan has a user prompt order and a system prompt order.
|
35
|
-
It must contain each of the values in the PromptComponent enum.
|
36
|
-
|
37
|
-
|
38
|
-
>>> p = PromptPlan(user_prompt_order=(PromptComponent.AGENT_INSTRUCTIONS, PromptComponent.AGENT_PERSONA),system_prompt_order=(PromptComponent.QUESTION_INSTRUCTIONS, PromptComponent.PRIOR_QUESTION_MEMORY))
|
39
|
-
>>> p._is_valid_plan()
|
40
|
-
True
|
41
|
-
|
42
|
-
>>> p.arrange_components(agent_instructions=1, agent_persona=2, question_instructions=3, prior_question_memory=4)
|
43
|
-
{'user_prompt': ..., 'system_prompt': ...}
|
44
|
-
|
45
|
-
>>> p = PromptPlan(user_prompt_order=("agent_instructions", ), system_prompt_order=("question_instructions", "prior_question_memory"))
|
46
|
-
Traceback (most recent call last):
|
47
|
-
...
|
48
|
-
ValueError: Invalid plan: must contain each value of PromptComponent exactly once.
|
49
|
-
|
50
|
-
"""
|
51
|
-
|
52
|
-
def __init__(
|
53
|
-
self,
|
54
|
-
user_prompt_order: Optional[tuple] = None,
|
55
|
-
system_prompt_order: Optional[tuple] = None,
|
56
|
-
):
|
57
|
-
"""Initialize the PromptPlan."""
|
58
|
-
|
59
|
-
if user_prompt_order is None:
|
60
|
-
user_prompt_order = (
|
61
|
-
PromptComponent.QUESTION_INSTRUCTIONS,
|
62
|
-
PromptComponent.PRIOR_QUESTION_MEMORY,
|
63
|
-
)
|
64
|
-
if system_prompt_order is None:
|
65
|
-
system_prompt_order = (
|
66
|
-
PromptComponent.AGENT_INSTRUCTIONS,
|
67
|
-
PromptComponent.AGENT_PERSONA,
|
68
|
-
)
|
69
|
-
|
70
|
-
# very commmon way to screw this up given how python treats single strings as iterables
|
71
|
-
if isinstance(user_prompt_order, str):
|
72
|
-
user_prompt_order = (user_prompt_order,)
|
73
|
-
|
74
|
-
if isinstance(system_prompt_order, str):
|
75
|
-
system_prompt_order = (system_prompt_order,)
|
76
|
-
|
77
|
-
if not isinstance(user_prompt_order, tuple):
|
78
|
-
raise TypeError(
|
79
|
-
f"Expected a tuple, but got {type(user_prompt_order).__name__}"
|
80
|
-
)
|
81
|
-
|
82
|
-
if not isinstance(system_prompt_order, tuple):
|
83
|
-
raise TypeError(
|
84
|
-
f"Expected a tuple, but got {type(system_prompt_order).__name__}"
|
85
|
-
)
|
86
|
-
|
87
|
-
self.user_prompt_order = self._convert_to_enum(user_prompt_order)
|
88
|
-
self.system_prompt_order = self._convert_to_enum(system_prompt_order)
|
89
|
-
if not self._is_valid_plan():
|
90
|
-
raise ValueError(
|
91
|
-
"Invalid plan: must contain each value of PromptComponent exactly once."
|
92
|
-
)
|
93
|
-
|
94
|
-
def _convert_to_enum(self, prompt_order: tuple):
|
95
|
-
"""Convert string names to PromptComponent enum values."""
|
96
|
-
return tuple(
|
97
|
-
PromptComponent(component) if isinstance(component, str) else component
|
98
|
-
for component in prompt_order
|
99
|
-
)
|
100
|
-
|
101
|
-
def _is_valid_plan(self):
|
102
|
-
"""Check if the plan is valid."""
|
103
|
-
combined = self.user_prompt_order + self.system_prompt_order
|
104
|
-
return set(combined) == set(PromptComponent)
|
105
|
-
|
106
|
-
def arrange_components(self, **kwargs) -> Dict[PromptComponent, Prompt]:
|
107
|
-
"""Arrange the components in the order specified by the plan."""
|
108
|
-
# check is valid components passed
|
109
|
-
component_strings = set([pc.value for pc in PromptComponent])
|
110
|
-
if not set(kwargs.keys()) == component_strings:
|
111
|
-
raise ValueError(
|
112
|
-
f"Invalid components passed: {set(kwargs.keys())} but expected {PromptComponent}"
|
113
|
-
)
|
114
|
-
|
115
|
-
user_prompt = PromptList(
|
116
|
-
[kwargs[component.value] for component in self.user_prompt_order]
|
117
|
-
)
|
118
|
-
system_prompt = PromptList(
|
119
|
-
[kwargs[component.value] for component in self.system_prompt_order]
|
120
|
-
)
|
121
|
-
return {"user_prompt": user_prompt, "system_prompt": system_prompt}
|
122
|
-
|
123
|
-
def get_prompts(self, **kwargs) -> Dict[str, Prompt]:
|
124
|
-
"""Get both prompts for the LLM call."""
|
125
|
-
prompts = self.arrange_components(**kwargs)
|
126
|
-
return {
|
127
|
-
"user_prompt": prompts["user_prompt"].reduce(),
|
128
|
-
"system_prompt": prompts["system_prompt"].reduce(),
|
129
|
-
}
|
1
|
+
import enum
|
2
|
+
from typing import Dict, Optional
|
3
|
+
from collections import UserList
|
4
|
+
from edsl.prompts.Prompt import Prompt
|
5
|
+
|
6
|
+
|
7
|
+
class PromptComponent(enum.Enum):
|
8
|
+
AGENT_INSTRUCTIONS = "agent_instructions"
|
9
|
+
AGENT_PERSONA = "agent_persona"
|
10
|
+
QUESTION_INSTRUCTIONS = "question_instructions"
|
11
|
+
PRIOR_QUESTION_MEMORY = "prior_question_memory"
|
12
|
+
|
13
|
+
|
14
|
+
class PromptList(UserList):
|
15
|
+
separator = Prompt("")
|
16
|
+
|
17
|
+
def reduce(self):
|
18
|
+
"""Reduce the list of prompts to a single prompt.
|
19
|
+
|
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
|
+
>>> 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}\""")
|
23
|
+
|
24
|
+
"""
|
25
|
+
p = self[0]
|
26
|
+
for prompt in self[1:]:
|
27
|
+
if len(prompt) > 0:
|
28
|
+
p = p + self.separator + prompt
|
29
|
+
return p
|
30
|
+
|
31
|
+
|
32
|
+
class PromptPlan:
|
33
|
+
"""A plan for constructing prompts for the LLM call.
|
34
|
+
Every prompt plan has a user prompt order and a system prompt order.
|
35
|
+
It must contain each of the values in the PromptComponent enum.
|
36
|
+
|
37
|
+
|
38
|
+
>>> p = PromptPlan(user_prompt_order=(PromptComponent.AGENT_INSTRUCTIONS, PromptComponent.AGENT_PERSONA),system_prompt_order=(PromptComponent.QUESTION_INSTRUCTIONS, PromptComponent.PRIOR_QUESTION_MEMORY))
|
39
|
+
>>> p._is_valid_plan()
|
40
|
+
True
|
41
|
+
|
42
|
+
>>> p.arrange_components(agent_instructions=1, agent_persona=2, question_instructions=3, prior_question_memory=4)
|
43
|
+
{'user_prompt': ..., 'system_prompt': ...}
|
44
|
+
|
45
|
+
>>> p = PromptPlan(user_prompt_order=("agent_instructions", ), system_prompt_order=("question_instructions", "prior_question_memory"))
|
46
|
+
Traceback (most recent call last):
|
47
|
+
...
|
48
|
+
ValueError: Invalid plan: must contain each value of PromptComponent exactly once.
|
49
|
+
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
user_prompt_order: Optional[tuple] = None,
|
55
|
+
system_prompt_order: Optional[tuple] = None,
|
56
|
+
):
|
57
|
+
"""Initialize the PromptPlan."""
|
58
|
+
|
59
|
+
if user_prompt_order is None:
|
60
|
+
user_prompt_order = (
|
61
|
+
PromptComponent.QUESTION_INSTRUCTIONS,
|
62
|
+
PromptComponent.PRIOR_QUESTION_MEMORY,
|
63
|
+
)
|
64
|
+
if system_prompt_order is None:
|
65
|
+
system_prompt_order = (
|
66
|
+
PromptComponent.AGENT_INSTRUCTIONS,
|
67
|
+
PromptComponent.AGENT_PERSONA,
|
68
|
+
)
|
69
|
+
|
70
|
+
# very commmon way to screw this up given how python treats single strings as iterables
|
71
|
+
if isinstance(user_prompt_order, str):
|
72
|
+
user_prompt_order = (user_prompt_order,)
|
73
|
+
|
74
|
+
if isinstance(system_prompt_order, str):
|
75
|
+
system_prompt_order = (system_prompt_order,)
|
76
|
+
|
77
|
+
if not isinstance(user_prompt_order, tuple):
|
78
|
+
raise TypeError(
|
79
|
+
f"Expected a tuple, but got {type(user_prompt_order).__name__}"
|
80
|
+
)
|
81
|
+
|
82
|
+
if not isinstance(system_prompt_order, tuple):
|
83
|
+
raise TypeError(
|
84
|
+
f"Expected a tuple, but got {type(system_prompt_order).__name__}"
|
85
|
+
)
|
86
|
+
|
87
|
+
self.user_prompt_order = self._convert_to_enum(user_prompt_order)
|
88
|
+
self.system_prompt_order = self._convert_to_enum(system_prompt_order)
|
89
|
+
if not self._is_valid_plan():
|
90
|
+
raise ValueError(
|
91
|
+
"Invalid plan: must contain each value of PromptComponent exactly once."
|
92
|
+
)
|
93
|
+
|
94
|
+
def _convert_to_enum(self, prompt_order: tuple):
|
95
|
+
"""Convert string names to PromptComponent enum values."""
|
96
|
+
return tuple(
|
97
|
+
PromptComponent(component) if isinstance(component, str) else component
|
98
|
+
for component in prompt_order
|
99
|
+
)
|
100
|
+
|
101
|
+
def _is_valid_plan(self):
|
102
|
+
"""Check if the plan is valid."""
|
103
|
+
combined = self.user_prompt_order + self.system_prompt_order
|
104
|
+
return set(combined) == set(PromptComponent)
|
105
|
+
|
106
|
+
def arrange_components(self, **kwargs) -> Dict[PromptComponent, Prompt]:
|
107
|
+
"""Arrange the components in the order specified by the plan."""
|
108
|
+
# check is valid components passed
|
109
|
+
component_strings = set([pc.value for pc in PromptComponent])
|
110
|
+
if not set(kwargs.keys()) == component_strings:
|
111
|
+
raise ValueError(
|
112
|
+
f"Invalid components passed: {set(kwargs.keys())} but expected {PromptComponent}"
|
113
|
+
)
|
114
|
+
|
115
|
+
user_prompt = PromptList(
|
116
|
+
[kwargs[component.value] for component in self.user_prompt_order]
|
117
|
+
)
|
118
|
+
system_prompt = PromptList(
|
119
|
+
[kwargs[component.value] for component in self.system_prompt_order]
|
120
|
+
)
|
121
|
+
return {"user_prompt": user_prompt, "system_prompt": system_prompt}
|
122
|
+
|
123
|
+
def get_prompts(self, **kwargs) -> Dict[str, Prompt]:
|
124
|
+
"""Get both prompts for the LLM call."""
|
125
|
+
prompts = self.arrange_components(**kwargs)
|
126
|
+
return {
|
127
|
+
"user_prompt": prompts["user_prompt"].reduce(),
|
128
|
+
"system_prompt": prompts["system_prompt"].reduce(),
|
129
|
+
}
|
@@ -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()
|